2025-09-07 07:59:26 +02:00
|
|
|
|
package soap
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"encoding/xml"
|
2025-09-07 11:45:43 +02:00
|
|
|
|
"fmt"
|
2025-09-07 07:59:26 +02:00
|
|
|
|
"io"
|
|
|
|
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
// ----- SOAP envelope -----
|
|
|
|
|
|
|
2025-09-07 07:59:26 +02:00
|
|
|
|
type Envelope struct {
|
2025-09-07 11:45:43 +02:00
|
|
|
|
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
|
|
|
|
|
Header *Header `xml:"Header"`
|
2025-09-07 07:59:26 +02:00
|
|
|
|
Body Body `xml:"Body"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
type Header struct {
|
|
|
|
|
|
Content []byte `xml:",innerxml"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-07 07:59:26 +02:00
|
|
|
|
type Body struct {
|
2025-09-07 11:45:43 +02:00
|
|
|
|
Content []byte `xml:",innerxml"`
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
// ----- 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 -----
|
|
|
|
|
|
|
2025-09-07 07:59:26 +02:00
|
|
|
|
func prettyPrintXML(raw string) string {
|
|
|
|
|
|
var out bytes.Buffer
|
2025-09-07 11:45:43 +02:00
|
|
|
|
dec := xml.NewDecoder(bytes.NewReader([]byte(raw)))
|
|
|
|
|
|
enc := xml.NewEncoder(&out)
|
|
|
|
|
|
enc.Indent("", " ")
|
2025-09-07 07:59:26 +02:00
|
|
|
|
for {
|
2025-09-07 11:45:43 +02:00
|
|
|
|
t, err := dec.Token()
|
2025-09-07 07:59:26 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2025-09-07 11:45:43 +02:00
|
|
|
|
if err := enc.EncodeToken(t); err != nil {
|
2025-09-07 07:59:26 +02:00
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-07 11:45:43 +02:00
|
|
|
|
enc.Flush()
|
2025-09-07 07:59:26 +02:00
|
|
|
|
return out.String()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
// ----- Parseurs -----
|
|
|
|
|
|
|
|
|
|
|
|
func ParseSOAPEnvelope(body []byte) (*Envelope, error) {
|
2025-09-07 07:59:26 +02:00
|
|
|
|
var env Envelope
|
|
|
|
|
|
if err := xml.Unmarshal(body, &env); err != nil {
|
2025-09-07 11:45:43 +02:00
|
|
|
|
return nil, fmt.Errorf("unmarshal SOAP Envelope: %w", err)
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
2025-09-07 11:45:43 +02:00
|
|
|
|
return &env, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
2025-09-07 07:59:26 +02:00
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
// 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))
|
2025-09-07 07:59:26 +02:00
|
|
|
|
var currentAction string
|
|
|
|
|
|
args := make(map[string]interface{})
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
2025-09-07 11:45:43 +02:00
|
|
|
|
tok, err := dec.Token()
|
2025-09-07 07:59:26 +02:00
|
|
|
|
if err != nil {
|
2025-09-07 11:45:43 +02:00
|
|
|
|
if err == io.EOF {
|
|
|
|
|
|
break
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
2025-09-07 11:45:43 +02:00
|
|
|
|
return nil, fmt.Errorf("SOAP parse error: %w", err)
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch t := tok.(type) {
|
|
|
|
|
|
case xml.StartElement:
|
|
|
|
|
|
if currentAction == "" {
|
2025-09-07 11:45:43 +02:00
|
|
|
|
currentAction = t.Name.Local
|
2025-09-07 07:59:26 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
var value string
|
2025-09-07 11:45:43 +02:00
|
|
|
|
if err := dec.DecodeElement(&value, &t); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("decode param %s: %w", t.Name.Local, err)
|
|
|
|
|
|
}
|
2025-09-07 07:59:26 +02:00
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
paramName := t.Name.Local
|
|
|
|
|
|
var paramValue interface{} = value
|
2025-09-07 07:59:26 +02:00
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
if decoder != nil {
|
|
|
|
|
|
if newName, out, err := decoder(currentAction, paramName, value); err == nil {
|
|
|
|
|
|
paramName = newName
|
|
|
|
|
|
paramValue = out
|
|
|
|
|
|
}
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
args[paramName] = paramValue
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-07 07:59:26 +02:00
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
return &ActionRequest{
|
|
|
|
|
|
Name: currentAction,
|
|
|
|
|
|
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
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
2025-09-07 11:45:43 +02:00
|
|
|
|
return nil, nil, fmt.Errorf("SOAP parse error: %w", err)
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
2025-09-07 11:45:43 +02:00
|
|
|
|
|
|
|
|
|
|
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")
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-07 11:45:43 +02:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
2025-09-07 07:59:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|