Skip to content

Commit

Permalink
Merge pull request #1 from supplyon/feature/attachment_support
Browse files Browse the repository at this point in the history
Add support for soap calls with file attachments
  • Loading branch information
ThomasObenaus authored Apr 9, 2021
2 parents 0a3b7af + 37c7531 commit d04c094
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 21 deletions.
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="<[email protected]>"; 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", "<[email protected]>")
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 != "<[email protected]>" {
return "", fmt.Errorf(`expected param start="<[email protected]>", 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

0 comments on commit d04c094

Please sign in to comment.