github.com/q45/go@v0.0.0-20151101211701-a4fb8c13db3f/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 non-empty and
    43  // at most 69 bytes long.
    44  func (w *Writer) SetBoundary(boundary string) error {
    45  	if w.lastpart != nil {
    46  		return errors.New("mime: SetBoundary called after write")
    47  	}
    48  	// rfc2046#section-5.1.1
    49  	if len(boundary) < 1 || len(boundary) > 69 {
    50  		return errors.New("mime: invalid boundary length")
    51  	}
    52  	for _, b := range boundary {
    53  		if 'A' <= b && b <= 'Z' || 'a' <= b && b <= 'z' || '0' <= b && b <= '9' {
    54  			continue
    55  		}
    56  		switch b {
    57  		case '\'', '(', ')', '+', '_', ',', '-', '.', '/', ':', '=', '?':
    58  			continue
    59  		}
    60  		return errors.New("mime: invalid boundary character")
    61  	}
    62  	w.boundary = boundary
    63  	return nil
    64  }
    65  
    66  // FormDataContentType returns the Content-Type for an HTTP
    67  // multipart/form-data with this Writer's Boundary.
    68  func (w *Writer) FormDataContentType() string {
    69  	return "multipart/form-data; boundary=" + w.boundary
    70  }
    71  
    72  func randomBoundary() string {
    73  	var buf [30]byte
    74  	_, err := io.ReadFull(rand.Reader, buf[:])
    75  	if err != nil {
    76  		panic(err)
    77  	}
    78  	return fmt.Sprintf("%x", buf[:])
    79  }
    80  
    81  // CreatePart creates a new multipart section with the provided
    82  // header. The body of the part should be written to the returned
    83  // Writer. After calling CreatePart, any previous part may no longer
    84  // be written to.
    85  func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
    86  	if w.lastpart != nil {
    87  		if err := w.lastpart.close(); err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  	var b bytes.Buffer
    92  	if w.lastpart != nil {
    93  		fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary)
    94  	} else {
    95  		fmt.Fprintf(&b, "--%s\r\n", w.boundary)
    96  	}
    97  	// TODO(bradfitz): move this to textproto.MimeHeader.Write(w), have it sort
    98  	// and clean, like http.Header.Write(w) does.
    99  	for k, vv := range header {
   100  		for _, v := range vv {
   101  			fmt.Fprintf(&b, "%s: %s\r\n", k, v)
   102  		}
   103  	}
   104  	fmt.Fprintf(&b, "\r\n")
   105  	_, err := io.Copy(w.w, &b)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	p := &part{
   110  		mw: w,
   111  	}
   112  	w.lastpart = p
   113  	return p, nil
   114  }
   115  
   116  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
   117  
   118  func escapeQuotes(s string) string {
   119  	return quoteEscaper.Replace(s)
   120  }
   121  
   122  // CreateFormFile is a convenience wrapper around CreatePart. It creates
   123  // a new form-data header with the provided field name and file name.
   124  func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
   125  	h := make(textproto.MIMEHeader)
   126  	h.Set("Content-Disposition",
   127  		fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
   128  			escapeQuotes(fieldname), escapeQuotes(filename)))
   129  	h.Set("Content-Type", "application/octet-stream")
   130  	return w.CreatePart(h)
   131  }
   132  
   133  // CreateFormField calls CreatePart with a header using the
   134  // given field name.
   135  func (w *Writer) CreateFormField(fieldname string) (io.Writer, error) {
   136  	h := make(textproto.MIMEHeader)
   137  	h.Set("Content-Disposition",
   138  		fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
   139  	return w.CreatePart(h)
   140  }
   141  
   142  // WriteField calls CreateFormField and then writes the given value.
   143  func (w *Writer) WriteField(fieldname, value string) error {
   144  	p, err := w.CreateFormField(fieldname)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	_, err = p.Write([]byte(value))
   149  	return err
   150  }
   151  
   152  // Close finishes the multipart message and writes the trailing
   153  // boundary end line to the output.
   154  func (w *Writer) Close() error {
   155  	if w.lastpart != nil {
   156  		if err := w.lastpart.close(); err != nil {
   157  			return err
   158  		}
   159  		w.lastpart = nil
   160  	}
   161  	_, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
   162  	return err
   163  }
   164  
   165  type part struct {
   166  	mw     *Writer
   167  	closed bool
   168  	we     error // last error that occurred writing
   169  }
   170  
   171  func (p *part) close() error {
   172  	p.closed = true
   173  	return p.we
   174  }
   175  
   176  func (p *part) Write(d []byte) (n int, err error) {
   177  	if p.closed {
   178  		return 0, errors.New("multipart: can't write to finished part")
   179  	}
   180  	n, err = p.mw.w.Write(d)
   181  	if err != nil {
   182  		p.we = err
   183  	}
   184  	return
   185  }