Merge pull request 'feature/soap-parser' (#2) from feature/soap-parser into main
Reviewed-on: https://gargoton.petite-maison-orange.fr/eric/pmomusic/pulls/2
This commit is contained in:
9
.pmomusic.yml
Normal file
9
.pmomusic.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
devices:
|
||||||
|
mediarenderer:
|
||||||
|
fakerenderer:
|
||||||
|
udn: d7eaad15-7d21-4411-926a-bc1eea0713db
|
||||||
|
mediaserver:
|
||||||
|
qobuz:
|
||||||
|
udn: 28963b75-4c5f-4da7-b10e-ffafd
|
||||||
|
host:
|
||||||
|
http_port: "8080"
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"makefile.configureOnOpen": false
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.SetLevel(log.DebugLevel) // → niveau debug
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(
|
ctx, stop := signal.NotifyContext(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
|
|||||||
BIN
db/chroma.sqlite3
Normal file
BIN
db/chroma.sqlite3
Normal file
Binary file not shown.
89
soap/buildsoap.go
Normal file
89
soap/buildsoap.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package soap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----- Générateurs -----
|
||||||
|
|
||||||
|
// BuildUPnPResponse construit une réponse SOAP avec <ActionNameResponse>
|
||||||
|
func BuildUPnPResponse(serviceURN, action string, values map[string]string) ([]byte, error) {
|
||||||
|
env := &Envelope{
|
||||||
|
XMLName: xml.Name{Local: "s:Envelope"},
|
||||||
|
Body: Body{
|
||||||
|
Content: buildActionResponse(serviceURN, action, values),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return marshalSOAP(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildSOAPFault construit un Fault SOAP standard
|
||||||
|
func BuildSOAPFault(code, description, detail string) ([]byte, error) {
|
||||||
|
env := &Envelope{
|
||||||
|
XMLName: xml.Name{Local: "s:Envelope"},
|
||||||
|
Body: Body{
|
||||||
|
Content: buildFault(code, description, detail),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return marshalSOAP(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Internes -----
|
||||||
|
|
||||||
|
func buildActionResponse(serviceURN, action string, values map[string]string) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(fmt.Sprintf(`<u:%sResponse xmlns:u="%s">`, action, serviceURN))
|
||||||
|
for k, v := range values {
|
||||||
|
buf.WriteString(fmt.Sprintf("<%s>%s</%s>", k, xmlEscape(v), k))
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf(`</u:%sResponse>`, action))
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFault(code, description, detail string) []byte {
|
||||||
|
return []byte(fmt.Sprintf(`
|
||||||
|
<Fault>
|
||||||
|
<faultcode>%s</faultcode>
|
||||||
|
<faultstring>%s</faultstring>
|
||||||
|
<detail>%s</detail>
|
||||||
|
</Fault>`, xmlEscape(code), xmlEscape(description), xmlEscape(detail)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalSOAP(env *Envelope) ([]byte, error) {
|
||||||
|
type soapEnvelope struct {
|
||||||
|
XMLName xml.Name `xml:"s:Envelope"`
|
||||||
|
SoapNS string `xml:"xmlns:s,attr"`
|
||||||
|
EncNS string `xml:"s:encodingStyle,attr"`
|
||||||
|
Body struct {
|
||||||
|
XMLName xml.Name `xml:"s:Body"`
|
||||||
|
Content string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := soapEnvelope{
|
||||||
|
SoapNS: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
EncNS: "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
}
|
||||||
|
tmp.Body.Content = string(env.Body.Content)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
|
||||||
|
enc := xml.NewEncoder(&buf)
|
||||||
|
enc.Indent("", " ")
|
||||||
|
if err := enc.Encode(tmp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
enc.Flush()
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// xmlEscape échappe manuellement les caractères dangereux
|
||||||
|
func xmlEscape(s string) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
xml.EscapeText(&buf, []byte(s))
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
@@ -3,84 +3,211 @@ package soap
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"gargoton.petite-maison-orange.fr/eric/pmomusic/didl"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ----- SOAP envelope -----
|
||||||
|
|
||||||
type Envelope struct {
|
type Envelope struct {
|
||||||
XMLName xml.Name `xml:"Envelope"`
|
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||||
|
Header *Header `xml:"Header"`
|
||||||
Body Body `xml:"Body"`
|
Body Body `xml:"Body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Body struct {
|
type Header struct {
|
||||||
Content []byte `xml:",innerxml"` // <- capture tout le contenu du Body sous forme de XML brut
|
Content []byte `xml:",innerxml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Content []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- UPnP request/response -----
|
||||||
|
|
||||||
|
type ActionRequest struct {
|
||||||
|
Name string
|
||||||
|
Args map[string]interface{}
|
||||||
|
RawXML []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionResponse struct {
|
||||||
|
Name string
|
||||||
|
Values map[string]string
|
||||||
|
RawXML []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fault struct {
|
||||||
|
Code string
|
||||||
|
Description string
|
||||||
|
Detail string
|
||||||
|
RawXML []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Utils -----
|
||||||
|
|
||||||
func prettyPrintXML(raw string) string {
|
func prettyPrintXML(raw string) string {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
decoder := xml.NewDecoder(bytes.NewReader([]byte(raw)))
|
dec := xml.NewDecoder(bytes.NewReader([]byte(raw)))
|
||||||
encoder := xml.NewEncoder(&out)
|
enc := xml.NewEncoder(&out)
|
||||||
encoder.Indent("", " ") // définit l'indentation
|
enc.Indent("", " ")
|
||||||
for {
|
for {
|
||||||
t, err := decoder.Token()
|
t, err := dec.Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := encoder.EncodeToken(t); err != nil {
|
if err := enc.EncodeToken(t); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
encoder.Flush()
|
enc.Flush()
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseSOAPGeneric(body []byte) {
|
// ----- Parseurs -----
|
||||||
|
|
||||||
|
func ParseSOAPEnvelope(body []byte) (*Envelope, error) {
|
||||||
var env Envelope
|
var env Envelope
|
||||||
if err := xml.Unmarshal(body, &env); err != nil {
|
if err := xml.Unmarshal(body, &env); err != nil {
|
||||||
log.Warnf("❌ Failed to unmarshal SOAP Envelope: %v", err)
|
return nil, fmt.Errorf("unmarshal SOAP Envelope: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return &env, nil
|
||||||
|
}
|
||||||
|
|
||||||
decoder := xml.NewDecoder(bytes.NewReader(env.Body.Content))
|
// ParamDecoder permet de transformer les valeurs des paramètres et éventuellement renommer le paramètre
|
||||||
|
type ParamDecoder func(action, param, value string) (newParam string, out interface{}, err error)
|
||||||
|
|
||||||
|
// ParseUPnPAction extrait l’action et ses arguments à partir d’un Body SOAP.
|
||||||
|
// Si decoder != nil, il est appelé pour chaque paramètre.
|
||||||
|
func ParseUPnPAction(env *Envelope, decoder ParamDecoder) (*ActionRequest, error) {
|
||||||
|
dec := xml.NewDecoder(bytes.NewReader(env.Body.Content))
|
||||||
var currentAction string
|
var currentAction string
|
||||||
args := make(map[string]interface{})
|
args := make(map[string]interface{})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
tok, err := decoder.Token()
|
tok, err := dec.Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
if err == io.EOF {
|
||||||
log.Warnf("❌ SOAP parse error: %v", err)
|
break
|
||||||
}
|
}
|
||||||
break
|
return nil, fmt.Errorf("SOAP parse error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := tok.(type) {
|
switch t := tok.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
if currentAction == "" {
|
if currentAction == "" {
|
||||||
currentAction = t.Name.Local // nom de l'action
|
currentAction = t.Name.Local
|
||||||
} else {
|
} else {
|
||||||
var value string
|
var value string
|
||||||
decoder.DecodeElement(&value, &t)
|
if err := dec.DecodeElement(&value, &t); err != nil {
|
||||||
|
return nil, fmt.Errorf("decode param %s: %w", t.Name.Local, err)
|
||||||
var ival interface{}
|
|
||||||
ival, err = didl.Parse(value)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
ival = ival.(*didl.DIDLLite).ToMarkdown()
|
|
||||||
} else {
|
|
||||||
ival = prettyPrintXML(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args[t.Name.Local] = ival
|
paramName := t.Name.Local
|
||||||
|
var paramValue interface{} = value
|
||||||
|
|
||||||
|
if decoder != nil {
|
||||||
|
if newName, out, err := decoder(currentAction, paramName, value); err == nil {
|
||||||
|
paramName = newName
|
||||||
|
paramValue = out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args[paramName] = paramValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("📡 SOAP Action: %s", currentAction)
|
return &ActionRequest{
|
||||||
for k, v := range args {
|
Name: currentAction,
|
||||||
log.Infof(" %s = %v", k, v)
|
Args: args,
|
||||||
|
RawXML: env.Body.Content,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response (renderer -> contrôleur)
|
||||||
|
func ParseUPnPResponse(env *Envelope) (*ActionResponse, *Fault, error) {
|
||||||
|
dec := xml.NewDecoder(bytes.NewReader(env.Body.Content))
|
||||||
|
var respName string
|
||||||
|
values := make(map[string]string)
|
||||||
|
|
||||||
|
for {
|
||||||
|
tok, err := dec.Token()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("SOAP parse error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := tok.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
if t.Name.Local == "Fault" {
|
||||||
|
// Parse fautes SOAP
|
||||||
|
var f struct {
|
||||||
|
Code string `xml:"faultcode"`
|
||||||
|
Desc string `xml:"faultstring"`
|
||||||
|
Detail string `xml:"detail"`
|
||||||
|
}
|
||||||
|
if err := dec.DecodeElement(&f, &t); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("decode Fault: %w", err)
|
||||||
|
}
|
||||||
|
return nil, &Fault{
|
||||||
|
Code: f.Code,
|
||||||
|
Description: f.Desc,
|
||||||
|
Detail: f.Detail,
|
||||||
|
RawXML: env.Body.Content,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if respName == "" {
|
||||||
|
respName = t.Name.Local
|
||||||
|
} else {
|
||||||
|
var value string
|
||||||
|
if err := dec.DecodeElement(&value, &t); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("decode response param %s: %w", t.Name.Local, err)
|
||||||
|
}
|
||||||
|
values[t.Name.Local] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if respName == "" {
|
||||||
|
return nil, nil, fmt.Errorf("no response or fault in SOAP body")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ActionResponse{respName, values, env.Body.Content}, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Exemple -----
|
||||||
|
|
||||||
|
func ParseSOAPGeneric(body []byte, decoder ParamDecoder) {
|
||||||
|
env, err := ParseSOAPEnvelope(body)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("❌ %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Essayer d'abord comme requête
|
||||||
|
if req, err := ParseUPnPAction(env, decoder); err == nil && req.Name != "" {
|
||||||
|
log.Infof("📡 SOAP Request Action: %s", req.Name)
|
||||||
|
for k, v := range req.Args {
|
||||||
|
log.Infof(" %s = %v", k, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon comme réponse
|
||||||
|
if resp, fault, err := ParseUPnPResponse(env); err == nil {
|
||||||
|
if resp != nil {
|
||||||
|
log.Infof("📡 SOAP Response: %s", resp.Name)
|
||||||
|
for k, v := range resp.Values {
|
||||||
|
log.Infof(" %s = %v", k, v)
|
||||||
|
}
|
||||||
|
} else if fault != nil {
|
||||||
|
log.Warnf("❌ SOAP Fault: %s - %s (detail: %s)", fault.Code, fault.Description, fault.Detail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
package avtransport
|
package avtransport
|
||||||
|
|
||||||
import sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
|
import (
|
||||||
|
"gargoton.petite-maison-orange.fr/eric/pmomusic/didl"
|
||||||
|
sv "gargoton.petite-maison-orange.fr/eric/pmomusic/upnp/devices/services/statevariables"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func _AVTransportURIMetaDataParser(value string) (interface{}, error) {
|
||||||
|
log.Warnf("[avtransport] Parsing AVTransport)")
|
||||||
|
didl, err := didl.Parse(value)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return didl, nil
|
||||||
|
}
|
||||||
|
|
||||||
var AVTransportURIMetaData = func() *sv.StateVariable {
|
var AVTransportURIMetaData = func() *sv.StateVariable {
|
||||||
|
|
||||||
ts := sv.StateType_String.NewStateValue("AVTransportURIMetaData")
|
ts := sv.StateType_String.NewStateValue("AVTransportURIMetaData")
|
||||||
|
ts.SetValueParser(_AVTransportURIMetaDataParser)
|
||||||
|
|
||||||
return ts
|
return ts
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ func (a *ActionInstance) TypeID() string {
|
|||||||
return "ActionInstance"
|
return "ActionInstance"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ActionInstance) Arguments(name string) (*Argument, bool) {
|
||||||
|
arg, ok := a.arguments[name]
|
||||||
|
return arg, ok
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ActionInstance) ToXMLElement() *etree.Element {
|
func (a *ActionInstance) ToXMLElement() *etree.Element {
|
||||||
elem := etree.NewElement("action")
|
elem := etree.NewElement("action")
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package statevariables
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -88,6 +89,17 @@ func (instance *StateVarInstance) AllowedValues() []interface{} {
|
|||||||
return instance.allowedValues
|
return instance.allowedValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (instance *StateVarInstance) HasParser() bool {
|
||||||
|
return instance.parse != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (instance *StateVarInstance) ParseValue(value string) (interface{}, error) {
|
||||||
|
if instance.parse == nil {
|
||||||
|
return value, errors.New("Not parsed value")
|
||||||
|
}
|
||||||
|
return instance.parse(value)
|
||||||
|
}
|
||||||
|
|
||||||
// IsValueInRange checks if a value falls within the defined range.
|
// IsValueInRange checks if a value falls within the defined range.
|
||||||
// Always returns true if no range is set.
|
// Always returns true if no range is set.
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,10 @@ func (state *StateVariable) Minimum() interface{} {
|
|||||||
return state.valueRange.min
|
return state.valueRange.min
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (state *StateVariable) SetValueParser(parser StringValueParser) {
|
||||||
|
state.parse = parser
|
||||||
|
}
|
||||||
|
|
||||||
// SetRange defines the inclusive value range [min, max].
|
// SetRange defines the inclusive value range [min, max].
|
||||||
// Validates and casts values to the state variable's type.
|
// Validates and casts values to the state variable's type.
|
||||||
//
|
//
|
||||||
|
|||||||
5
upnp/interface.go
Normal file
5
upnp/interface.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package upnp
|
||||||
|
|
||||||
|
type Markdownable interface {
|
||||||
|
ToMarkdown() string
|
||||||
|
}
|
||||||
@@ -147,9 +147,6 @@ func (svc *ServiceInstance) EventSubHandler() func(w http.ResponseWriter, r *htt
|
|||||||
func (svc *ServiceInstance) ControlHandler() func(w http.ResponseWriter, r *http.Request) {
|
func (svc *ServiceInstance) ControlHandler() func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Infof("📡 Control request for %s:%s", svc.device.Name(), svc.Name())
|
log.Infof("📡 Control request for %s:%s", svc.device.Name(), svc.Name())
|
||||||
log.Infof("➡️ Method: %s URL: %s", r.Method, r.URL.Path)
|
|
||||||
log.Infof("Header SOAPACTION: %s", r.Header.Get("SOAPACTION"))
|
|
||||||
log.Infof("Header Content-Type: %s", r.Header.Get("Content-Type"))
|
|
||||||
|
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,14 +155,85 @@ func (svc *ServiceInstance) ControlHandler() func(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
soap.ParseSOAPGeneric(body)
|
// Callback générique pour décoder chaque paramètre
|
||||||
|
decoder := func(action, param, value string) (string, interface{}, error) {
|
||||||
|
// 1️⃣ Chercher si le param correspond à une StateVarInstance
|
||||||
|
|
||||||
// Réponse minimale SOAP
|
sv, ok := svc.statevariables[param]
|
||||||
|
log.Warnf("Look for a variable named : %s -> %v", param, ok)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
if sv.HasParser() {
|
||||||
|
v, err := sv.ParseValue(value)
|
||||||
|
if err != nil {
|
||||||
|
return param, value, err
|
||||||
|
}
|
||||||
|
return param, v, err
|
||||||
|
}
|
||||||
|
v, err := sv.Cast(value)
|
||||||
|
if err != nil {
|
||||||
|
return param, value, err
|
||||||
|
}
|
||||||
|
return param, v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Chercher si le param correspond à une ActionInstance et appliquer un parseur associé
|
||||||
|
act, ok := svc.actions[action]
|
||||||
|
log.Debugf("Look for an action named : %s -> %v", action, ok)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
if argument, ok := act.Arguments(param); ok {
|
||||||
|
sv_name := argument.StateVariable().Name()
|
||||||
|
sv := svc.statevariables[sv_name]
|
||||||
|
v, err := sv.ParseValue(value)
|
||||||
|
|
||||||
|
log.Debugf("It corresponds to variable : %s with a parser %v", sv_name, sv.HasParser())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return param, value, err
|
||||||
|
}
|
||||||
|
return param, v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Sinon, valeur brute
|
||||||
|
return param, value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
env, err := soap.ParseSOAPEnvelope(body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("❌ Failed to parse SOAP enveloppe: %v", err)
|
||||||
|
soapResp, _ := soap.BuildSOAPFault("s:Client", "Invalid Args", err.Error())
|
||||||
|
w.Header().Set("Content-Type", `text/xml; charset="utf-8"`)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write(soapResp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := soap.ParseUPnPAction(env, decoder)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("❌ Failed to parse SOAP Action: %v", err)
|
||||||
|
soapResp, _ := soap.BuildSOAPFault("s:Client", "Invalid Args", err.Error())
|
||||||
|
w.Header().Set("Content-Type", `text/xml; charset="utf-8"`)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write(soapResp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("➡️ SOAP Action: %s", req.Name)
|
||||||
|
for k, v := range req.Args {
|
||||||
|
if mi, ok := v.(Markdownable); ok {
|
||||||
|
v = mi.ToMarkdown()
|
||||||
|
}
|
||||||
|
log.Infof(" %s:%s = %v", req.Name, k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ici tu peux appeler l'action correspondante sur svc.actions[req.Name] et récupérer le résultat
|
||||||
|
// Exemple de réponse minimale :
|
||||||
|
resp, _ := soap.BuildUPnPResponse(svc.ServiceType(), req.Name, map[string]string{})
|
||||||
w.Header().Set("Content-Type", `text/xml; charset="utf-8"`)
|
w.Header().Set("Content-Type", `text/xml; charset="utf-8"`)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, _ = w.Write([]byte(`<?xml version="1.0"?>
|
_, _ = w.Write(resp)
|
||||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
|
||||||
<s:Body/>
|
|
||||||
</s:Envelope>`))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user