// Definition for the SOAP structure required for UPnP's SOAP usage.

package soap

import (
	
	
	
	
	
	
	
	
	
)

const (
	soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
	soapPrefix        = xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
	soapSuffix        = `</s:Body></s:Envelope>`
)

type SOAPClient struct {
	EndpointURL url.URL
	HTTPClient  http.Client
}

func ( url.URL) *SOAPClient {
	return &SOAPClient{
		EndpointURL: ,
	}
}

// PerformSOAPAction makes a SOAP request, with the given action.
// inAction and outAction must both be pointers to structs with string fields
// only.
func ( *SOAPClient) ( context.Context, ,  string,  interface{},  interface{}) error {
	,  := encodeRequestAction(, , )
	if  != nil {
		return 
	}

	 := &http.Request{
		Method: "POST",
		URL:    &.EndpointURL,
		Header: http.Header{
			"SOAPACTION":   []string{`"` +  + "#" +  + `"`},
			"CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
		},
		Body: ioutil.NopCloser(bytes.NewBuffer()),
		// Set ContentLength to avoid chunked encoding - some servers might not support it.
		ContentLength: int64(len()),
	}
	 = .WithContext()
	,  := .HTTPClient.Do()
	if  != nil {
		return fmt.Errorf("goupnp: error performing SOAP HTTP request: %v", )
	}
	defer .Body.Close()
	if .StatusCode != 200 && .ContentLength == 0 {
		return fmt.Errorf("goupnp: SOAP request got HTTP %s", .Status)
	}

	 := newSOAPEnvelope()
	 := xml.NewDecoder(.Body)
	if  := .Decode();  != nil {
		return fmt.Errorf("goupnp: error decoding response body: %v", )
	}

	if .Body.Fault != nil {
		return .Body.Fault
	} else if .StatusCode != 200 {
		return fmt.Errorf("goupnp: SOAP request got HTTP %s", .Status)
	}

	if  != nil {
		if  := xml.Unmarshal(.Body.RawAction, );  != nil {
			return fmt.Errorf("goupnp: error unmarshalling out action: %v, %v", , .Body.RawAction)
		}
	}

	return nil
}

// PerformAction is the legacy version of PerformActionCtx, which uses
// context.Background.
func ( *SOAPClient) (,  string,  interface{},  interface{}) error {
	return .PerformActionCtx(context.Background(), , , , )
}

// newSOAPAction creates a soapEnvelope with the given action and arguments.
func newSOAPEnvelope() *soapEnvelope {
	return &soapEnvelope{
		EncodingStyle: soapEncodingStyle,
	}
}

// encodeRequestAction is a hacky way to create an encoded SOAP envelope
// containing the given action. Experiments with one router have shown that it
// 500s for requests where the outer default xmlns is set to the SOAP
// namespace, and then reassigning the default namespace within that to the
// service namespace. Hand-coding the outer XML to work-around this.
func encodeRequestAction(,  string,  interface{}) ([]byte, error) {
	 := new(bytes.Buffer)
	.WriteString(soapPrefix)
	.WriteString(`<u:`)
	xml.EscapeText(, []byte())
	.WriteString(` xmlns:u="`)
	xml.EscapeText(, []byte())
	.WriteString(`">`)
	if  != nil {
		if  := encodeRequestArgs(, );  != nil {
			return nil, 
		}
	}
	.WriteString(`</u:`)
	xml.EscapeText(, []byte())
	.WriteString(`>`)
	.WriteString(soapSuffix)
	return .Bytes(), nil
}

func encodeRequestArgs( *bytes.Buffer,  interface{}) error {
	 := reflect.Indirect(reflect.ValueOf())
	if .Kind() != reflect.Struct {
		return fmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", .Type())
	}
	 := xml.NewEncoder()
	 := .NumField()
	 := .Type()
	for  := 0;  < ; ++ {
		 := .Field()
		 := .Name
		if  := .Tag.Get("soap");  != "" {
			 = 
		}
		 := .Field()
		if .Kind() != reflect.String {
			return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", , .Type())
		}
		 := xml.StartElement{Name: xml.Name{Space: "", Local: }, Attr: nil}
		if  := .EncodeToken();  != nil {
			return fmt.Errorf("goupnp: error encoding start element for SOAP arg %q: %v", , )
		}
		if  := .Flush();  != nil {
			return fmt.Errorf("goupnp: error flushing start element for SOAP arg %q: %v", , )
		}
		if ,  := .Write([]byte(escapeXMLText(.Interface().(string))));  != nil {
			return fmt.Errorf("goupnp: error writing value for SOAP arg %q: %v", , )
		}
		if  := .EncodeToken(.End());  != nil {
			return fmt.Errorf("goupnp: error encoding end element for SOAP arg %q: %v", , )
		}
	}
	.Flush()
	return nil
}

var xmlCharRx = regexp.MustCompile("[<>&]")

// escapeXMLText is used by generated code to escape text in XML, but only
// escaping the characters `<`, `>`, and `&`.
//
// This is provided in order to work around SOAP server implementations that
// fail to decode XML correctly, specifically failing to decode `"`, `'`. Note
// that this can only be safely used for injecting into XML text, but not into
// attributes or other contexts.
func escapeXMLText( string) string {
	return xmlCharRx.ReplaceAllStringFunc(, replaceEntity)
}

func replaceEntity( string) string {
	switch  {
	case "<":
		return "&lt;"
	case ">":
		return "&gt;"
	case "&":
		return "&amp;"
	}
	return 
}

type soapEnvelope struct {
	XMLName       xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
	EncodingStyle string   `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
	Body          soapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
}

type soapBody struct {
	Fault     *SOAPFaultError `xml:"Fault"`
	RawAction []byte          `xml:",innerxml"`
}

// SOAPFaultError implements error, and contains SOAP fault information.
type SOAPFaultError struct {
	FaultCode   string `xml:"faultcode"`
	FaultString string `xml:"faultstring"`
	Detail      struct {
		UPnPError struct {
			Errorcode        int    `xml:"errorCode"`
			ErrorDescription string `xml:"errorDescription"`
		} `xml:"UPnPError"`
		Raw []byte `xml:",innerxml"`
	} `xml:"detail"`
}

func ( *SOAPFaultError) () string {
	return fmt.Sprintf("SOAP fault. Code: %s | Explanation: %s | Detail: %s",
		.FaultCode, .FaultString, string(.Detail.Raw))
}