...
Run Format

Source file src/mime/multipart/writer.go

Documentation: mime/multipart

  // Copyright 2011 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  package multipart
  
  import (
  	"bytes"
  	"crypto/rand"
  	"errors"
  	"fmt"
  	"io"
  	"net/textproto"
  	"sort"
  	"strings"
  )
  
  // A Writer generates multipart messages.
  type Writer struct {
  	w        io.Writer
  	boundary string
  	lastpart *part
  }
  
  // NewWriter returns a new multipart Writer with a random boundary,
  // writing to w.
  func NewWriter(w io.Writer) *Writer {
  	return &Writer{
  		w:        w,
  		boundary: randomBoundary(),
  	}
  }
  
  // Boundary returns the Writer's boundary.
  func (w *Writer) Boundary() string {
  	return w.boundary
  }
  
  // SetBoundary overrides the Writer's default randomly-generated
  // boundary separator with an explicit value.
  //
  // SetBoundary must be called before any parts are created, may only
  // contain certain ASCII characters, and must be non-empty and
  // at most 70 bytes long.
  func (w *Writer) SetBoundary(boundary string) error {
  	if w.lastpart != nil {
  		return errors.New("mime: SetBoundary called after write")
  	}
  	// rfc2046#section-5.1.1
  	if len(boundary) < 1 || len(boundary) > 70 {
  		return errors.New("mime: invalid boundary length")
  	}
  	end := len(boundary) - 1
  	for i, b := range boundary {
  		if 'A' <= b && b <= 'Z' || 'a' <= b && b <= 'z' || '0' <= b && b <= '9' {
  			continue
  		}
  		switch b {
  		case '\'', '(', ')', '+', '_', ',', '-', '.', '/', ':', '=', '?':
  			continue
  		case ' ':
  			if i != end {
  				continue
  			}
  		}
  		return errors.New("mime: invalid boundary character")
  	}
  	w.boundary = boundary
  	return nil
  }
  
  // FormDataContentType returns the Content-Type for an HTTP
  // multipart/form-data with this Writer's Boundary.
  func (w *Writer) FormDataContentType() string {
  	return "multipart/form-data; boundary=" + w.boundary
  }
  
  func randomBoundary() string {
  	var buf [30]byte
  	_, err := io.ReadFull(rand.Reader, buf[:])
  	if err != nil {
  		panic(err)
  	}
  	return fmt.Sprintf("%x", buf[:])
  }
  
  // CreatePart creates a new multipart section with the provided
  // header. The body of the part should be written to the returned
  // Writer. After calling CreatePart, any previous part may no longer
  // be written to.
  func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
  	if w.lastpart != nil {
  		if err := w.lastpart.close(); err != nil {
  			return nil, err
  		}
  	}
  	var b bytes.Buffer
  	if w.lastpart != nil {
  		fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary)
  	} else {
  		fmt.Fprintf(&b, "--%s\r\n", w.boundary)
  	}
  
  	keys := make([]string, 0, len(header))
  	for k := range header {
  		keys = append(keys, k)
  	}
  	sort.Strings(keys)
  	for _, k := range keys {
  		for _, v := range header[k] {
  			fmt.Fprintf(&b, "%s: %s\r\n", k, v)
  		}
  	}
  	fmt.Fprintf(&b, "\r\n")
  	_, err := io.Copy(w.w, &b)
  	if err != nil {
  		return nil, err
  	}
  	p := &part{
  		mw: w,
  	}
  	w.lastpart = p
  	return p, nil
  }
  
  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
  
  func escapeQuotes(s string) string {
  	return quoteEscaper.Replace(s)
  }
  
  // CreateFormFile is a convenience wrapper around CreatePart. It creates
  // a new form-data header with the provided field name and file name.
  func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
  	h := make(textproto.MIMEHeader)
  	h.Set("Content-Disposition",
  		fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
  			escapeQuotes(fieldname), escapeQuotes(filename)))
  	h.Set("Content-Type", "application/octet-stream")
  	return w.CreatePart(h)
  }
  
  // CreateFormField calls CreatePart with a header using the
  // given field name.
  func (w *Writer) CreateFormField(fieldname string) (io.Writer, error) {
  	h := make(textproto.MIMEHeader)
  	h.Set("Content-Disposition",
  		fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
  	return w.CreatePart(h)
  }
  
  // WriteField calls CreateFormField and then writes the given value.
  func (w *Writer) WriteField(fieldname, value string) error {
  	p, err := w.CreateFormField(fieldname)
  	if err != nil {
  		return err
  	}
  	_, err = p.Write([]byte(value))
  	return err
  }
  
  // Close finishes the multipart message and writes the trailing
  // boundary end line to the output.
  func (w *Writer) Close() error {
  	if w.lastpart != nil {
  		if err := w.lastpart.close(); err != nil {
  			return err
  		}
  		w.lastpart = nil
  	}
  	_, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
  	return err
  }
  
  type part struct {
  	mw     *Writer
  	closed bool
  	we     error // last error that occurred writing
  }
  
  func (p *part) close() error {
  	p.closed = true
  	return p.we
  }
  
  func (p *part) Write(d []byte) (n int, err error) {
  	if p.closed {
  		return 0, errors.New("multipart: can't write to finished part")
  	}
  	n, err = p.mw.w.Write(d)
  	if err != nil {
  		p.we = err
  	}
  	return
  }
  

View as plain text