github.com/bgentry/go@v0.0.0-20150121062915-6cf5a733d54d/src/mime/multipart/writer.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package multipart
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/rand"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net/textproto"
    14  	"strings"
    15  )
    16  
    17  // A Writer generates multipart messages.
    18  type Writer struct {
    19  	w        io.Writer
    20  	boundary string
    21  	lastpart *part
    22  }
    23  
    24  // NewWriter returns a new multipart Writer with a random boundary,
    25  // writing to w.
    26  func NewWriter(w io.Writer) *Writer {
    27  	return &Writer{
    28  		w:        w,
    29  		boundary: randomBoundary(),
    30  	}
    31  }
    32  
    33  // Boundary returns the Writer's boundary.
    34  func (w *Writer) Boundary() string {
    35  	return w.boundary
    36  }
    37  
    38  // SetBoundary overrides the Writer's default randomly-generated
    39  // boundary separator with an explicit value.
    40  //
    41  // SetBoundary must be called before any parts are created, may only
    42  // contain certain ASCII characters, and must be 1-69 bytes long.
    43  func (w *Writer) SetBoundary(boundary string) error {
    44  	if w.lastpart != nil {
    45  		return errors.New("mime: SetBoundary called after write")
    46  	}
    47  	// rfc2046#section-5.1.1
    48  	if len(boundary) < 1 || len(boundary) > 69 {
    49  		return errors.New("mime: invalid boundary length")
    50  	}
    51  	for _, b := range boundary {
    52  		if 'A' <= b && b <= 'Z' || 'a' <= b && b <= 'z' || '0' <= b && b <= '9' {
    53  			continue
    54  		}
    55  		switch b {
    56  		case '\'', '(', ')', '+', '_', ',', '-', '.', '/', ':', '=', '?':
    57  			continue
    58  		}
    59  		return errors.New("mime: invalid boundary character")
    60  	}
    61  	w.boundary = boundary
    62  	return nil
    63  }
    64  
    65  // FormDataContentType returns the Content-Type for an HTTP
    66  // multipart/form-data with this Writer's Boundary.
    67  func (w *Writer) FormDataContentType() string {
    68  	return "multipart/form-data; boundary=" + w.boundary
    69  }
    70  
    71  func randomBoundary() string {
    72  	var buf [30]byte
    73  	_, err := io.ReadFull(rand.Reader, buf[:])
    74  	if err != nil {
    75  		panic(err)
    76  	}
    77  	return fmt.Sprintf("%x", buf[:])
    78  }
    79  
    80  // CreatePart creates a new multipart section with the provided
    81  // header. The body of the part should be written to the returned
    82  // Writer. After calling CreatePart, any previous part may no longer
    83  // be written to.
    84  func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
    85  	if w.lastpart != nil {
    86  		if err := w.lastpart.close(); err != nil {
    87  			return nil, err
    88  		}
    89  	}
    90  	var b bytes.Buffer
    91  	if w.lastpart != nil {
    92  		fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary)
    93  	} else {
    94  		fmt.Fprintf(&b, "--%s\r\n", w.boundary)
    95  	}
    96  	// TODO(bradfitz): move this to textproto.MimeHeader.Write(w), have it sort
    97  	// and clean, like http.Header.Write(w) does.
    98  	for k, vv := range header {
    99  		for _, v := range vv {
   100  			fmt.Fprintf(&b, "%s: %s\r\n", k, v)
   101  		}
   102  	}
   103  	fmt.Fprintf(&b, "\r\n")
   104  	_, err := io.Copy(w.w, &b)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	p := &part{
   109  		mw: w,
   110  	}
   111  	w.lastpart = p
   112  	return p, nil
   113  }
   114  
   115  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
   116  
   117  func escapeQuotes(s string) string {
   118  	return quoteEscaper.Replace(s)
   119  }
   120  
   121  // CreateFormFile is a convenience wrapper around CreatePart. It creates
   122  // a new form-data header with the provided field name and file name.
   123  func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
   124  	h := make(textproto.MIMEHeader)
   125  	h.Set("Content-Disposition",
   126  		fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
   127  			escapeQuotes(fieldname), escapeQuotes(filename)))
   128  	h.Set("Content-Type", "application/octet-stream")
   129  	return w.CreatePart(h)
   130  }
   131  
   132  // CreateFormField calls CreatePart with a header using the
   133  // given field name.
   134  func (w *Writer) CreateFormField(fieldname string) (io.Writer, error) {
   135  	h := make(textproto.MIMEHeader)
   136  	h.Set("Content-Disposition",
   137  		fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
   138  	return w.CreatePart(h)
   139  }
   140  
   141  // WriteField calls CreateFormField and then writes the given value.
   142  func (w *Writer) WriteField(fieldname, value string) error {
   143  	p, err := w.CreateFormField(fieldname)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	_, err = p.Write([]byte(value))
   148  	return err
   149  }
   150  
   151  // Close finishes the multipart message and writes the trailing
   152  // boundary end line to the output.
   153  func (w *Writer) Close() error {
   154  	if w.lastpart != nil {
   155  		if err := w.lastpart.close(); err != nil {
   156  			return err
   157  		}
   158  		w.lastpart = nil
   159  	}
   160  	_, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
   161  	return err
   162  }
   163  
   164  type part struct {
   165  	mw     *Writer
   166  	closed bool
   167  	we     error // last error that occurred writing
   168  }
   169  
   170  func (p *part) close() error {
   171  	p.closed = true
   172  	return p.we
   173  }
   174  
   175  func (p *part) Write(d []byte) (n int, err error) {
   176  	if p.closed {
   177  		return 0, errors.New("multipart: can't write to finished part")
   178  	}
   179  	n, err = p.mw.w.Write(d)
   180  	if err != nil {
   181  		p.we = err
   182  	}
   183  	return
   184  }