Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

With this PR gowsdl will be enabled to support SOAP MTOM #201

Merged
merged 2 commits into from
Apr 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
GHACCOUNT := hooklift
NAME := gowsdl
VERSION := v0.2.1
VERSION := v0.5.0

include common.mk

Expand Down
150 changes: 150 additions & 0 deletions soap/MMAEncoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package soap

import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"mime"
"mime/multipart"
"net/textproto"
"strings"
)

const mmaContentType string = `multipart/related; start="<soaprequest@gowsdl.lib>"; type="text/xml"; boundary="%s"`

type mmaEncoder struct {
writer *multipart.Writer
attachments []MIMEMultipartAttachment
}

type mmaDecoder struct {
reader *multipart.Reader
}

func newMmaEncoder(w io.Writer, attachments []MIMEMultipartAttachment) *mmaEncoder {
return &mmaEncoder{
writer: multipart.NewWriter(w),
attachments: attachments,
}
}

func newMmaDecoder(r io.Reader, boundary string) *mmaDecoder {
return &mmaDecoder{
reader: multipart.NewReader(r, boundary),
}
}

func (e *mmaEncoder) Encode(v interface{}) error {
var err error
var soapPartWriter io.Writer

// 1. write SOAP envelope part
headers := make(textproto.MIMEHeader)
headers.Set("Content-Type", `text/xml;charset=UTF-8`)
headers.Set("Content-Transfer-Encoding", "8bit")
headers.Set("Content-ID", "<soaprequest@gowsdl.lib>")
if soapPartWriter, err = e.writer.CreatePart(headers); err != nil {
return err
}
xmlEncoder := xml.NewEncoder(soapPartWriter)
if err := xmlEncoder.Encode(v); err != nil {
return err
}

// 2. write attachments parts
for _, attachment := range e.attachments {
attHeader := make(textproto.MIMEHeader)
attHeader.Set("Content-Type", fmt.Sprintf("application/octet-stream; name=%s", attachment.Name))
attHeader.Set("Content-Transfer-Encoding", "binary")
attHeader.Set("Content-ID", fmt.Sprintf("<%s>", attachment.Name))
attHeader.Set("Content-Disposition",
fmt.Sprintf("attachment; name=\"%s\"; filename=\"%s\"", attachment.Name, attachment.Name))
var attachmentPartWriter io.Writer
attachmentPartWriter, err := e.writer.CreatePart(attHeader)
if err != nil {
return err
}
_, err = io.Copy(attachmentPartWriter, bytes.NewReader(attachment.Data))
if err != nil {
return err
}
}

return nil
}

func (e *mmaEncoder) Flush() error {
return e.writer.Close()
}

func (e *mmaEncoder) Boundary() string {
return e.writer.Boundary()
}

func getMmaHeader(contentType string) (string, error) {
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
return "", err
}

if strings.HasPrefix(mediaType, "multipart/") {
boundary, ok := params["boundary"]
if !ok || boundary == "" {
return "", fmt.Errorf("invalid multipart boundary: %s", boundary)
}

startInfo, ok := params["start"]
if !ok || startInfo != "<soaprequest@gowsdl.lib>" {
return "", fmt.Errorf(`expected param start="<soaprequest@gowsdl.lib>", got %s`, startInfo)
}
return boundary, nil
}

return "", nil
}

func (d *mmaDecoder) Decode(v interface{}) error {
soapEnvResp := v.(*SOAPEnvelopeResponse)
attachments := make([]MIMEMultipartAttachment, 0)
for {
p, err := d.reader.NextPart()
if err != nil {
if err == io.EOF {
break
}
return err
}
contentType := p.Header.Get("Content-Type")
if contentType == "text/xml;charset=UTF-8" {
// decode SOAP part
err := xml.NewDecoder(p).Decode(v)
if err != nil {
return err
}
} else {
// decode attachment parts
contentID := p.Header.Get("Content-Id")
if contentID == "" {
return errors.New("Invalid multipart content ID")
}
content, err := ioutil.ReadAll(p)
if err != nil {
return err
}

contentID = strings.Trim(contentID, "<>")
attachments = append(attachments, MIMEMultipartAttachment{
Name: contentID,
Data: content,
})
}
}
if len(attachments) > 0 {
soapEnvResp.Attachments = attachments
}

return nil
}
18 changes: 16 additions & 2 deletions soap/MTOMEncoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type xopPlaceholder struct {
Href string `xml:"href,attr"`
}

// NewBinary allocate a new Binary backed by the given byte slice
// NewBinary allocate a new Binary backed by the given byte slice, an auto-generated packageID and no MTOM-usage
func NewBinary(v []byte) *Binary {
return &Binary{&v, "application/octet-stream", "", false}
}
Expand All @@ -41,6 +41,18 @@ func (b *Binary) Bytes() []byte {
return *b.content
}

// SetUseMTOM activates the XOP transformation of binaries in MTOM requests
func (b *Binary) SetUseMTOM(useMTOM bool) *Binary {
b.useMTOM = useMTOM
return b
}

// SetPackageID sets and overrides the default auto-generated package ID to be used for the multipart binary
func (b *Binary) SetPackageID(packageID string) *Binary {
b.packageID = packageID
return b
}

// SetContentType sets the content type the content will be transmitted as multipart
func (b *Binary) SetContentType(contentType string) *Binary {
b.contentType = contentType
Expand All @@ -55,7 +67,9 @@ func (b *Binary) ContentType() string {
// MarshalXML implements the xml.Marshaler interface to encode a Binary to XML
func (b *Binary) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if b.useMTOM {
b.packageID = fmt.Sprintf("%d", rand.Int())
if b.packageID == "" {
b.packageID = fmt.Sprintf("%d", rand.Int())
}
return enc.EncodeElement(struct {
Include *xopPlaceholder `xml:"http://www.w3.org/2004/08/xop/include Include"`
}{
Expand Down
Loading