Source file src/mime/multipart/writer.go

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

View as plain text