Files
pmomusic/soap/parseSoap.go

214 lines
4.7 KiB
Go
Raw Normal View History

2025-09-07 07:59:26 +02:00
package soap
import (
"bytes"
"encoding/xml"
"fmt"
2025-09-07 07:59:26 +02:00
"io"
log "github.com/sirupsen/logrus"
)
// ----- SOAP envelope -----
2025-09-07 07:59:26 +02:00
type Envelope struct {
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"`
}
type Header struct {
Content []byte `xml:",innerxml"`
}
2025-09-07 07:59:26 +02:00
type Body struct {
Content []byte `xml:",innerxml"`
2025-09-07 07:59:26 +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
dec := xml.NewDecoder(bytes.NewReader([]byte(raw)))
enc := xml.NewEncoder(&out)
enc.Indent("", " ")
2025-09-07 07:59:26 +02:00
for {
t, err := dec.Token()
2025-09-07 07:59:26 +02:00
if err != nil {
break
}
if err := enc.EncodeToken(t); err != nil {
2025-09-07 07:59:26 +02:00
break
}
}
enc.Flush()
2025-09-07 07:59:26 +02:00
return out.String()
}
// ----- 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 {
return nil, fmt.Errorf("unmarshal SOAP Envelope: %w", err)
2025-09-07 07:59:26 +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
// ParseUPnPAction extrait laction et ses arguments à partir dun 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 {
tok, err := dec.Token()
2025-09-07 07:59:26 +02:00
if err != nil {
if err == io.EOF {
break
2025-09-07 07:59:26 +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 == "" {
currentAction = t.Name.Local
2025-09-07 07:59:26 +02:00
} else {
var value string
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
paramName := t.Name.Local
var paramValue interface{} = value
2025-09-07 07:59:26 +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
}
args[paramName] = paramValue
}
}
}
2025-09-07 07:59:26 +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
}
return nil, nil, fmt.Errorf("SOAP parse error: %w", err)
2025-09-07 07:59:26 +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
}
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
}
}