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

View as plain text