Home | History | Annotate | Download | only in multipart
      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 }
    186