github.com/Kolosok86/http@v0.1.2/internal/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  	"sort"
    14  	"strings"
    15  
    16  	"github.com/Kolosok86/http/textproto"
    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  	keys := make([]string, 0, len(header))
   112  	for k := range header {
   113  		keys = append(keys, k)
   114  	}
   115  	sort.Strings(keys)
   116  	for _, k := range keys {
   117  		for _, v := range header[k] {
   118  			fmt.Fprintf(&b, "%s: %s\r\n", k, v)
   119  		}
   120  	}
   121  	fmt.Fprintf(&b, "\r\n")
   122  	_, err := io.Copy(w.w, &b)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	p := &part{
   127  		mw: w,
   128  	}
   129  	w.lastpart = p
   130  	return p, nil
   131  }
   132  
   133  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
   134  
   135  func escapeQuotes(s string) string {
   136  	return quoteEscaper.Replace(s)
   137  }
   138  
   139  // CreateFormFile is a convenience wrapper around CreatePart. It creates
   140  // a new form-data header with the provided field name and file name.
   141  func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
   142  	h := make(textproto.MIMEHeader)
   143  	h.Set("Content-Disposition",
   144  		fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
   145  			escapeQuotes(fieldname), escapeQuotes(filename)))
   146  	h.Set("Content-Type", "application/octet-stream")
   147  	return w.CreatePart(h)
   148  }
   149  
   150  // CreateFormField calls CreatePart with a header using the
   151  // given field name.
   152  func (w *Writer) CreateFormField(fieldname string) (io.Writer, error) {
   153  	h := make(textproto.MIMEHeader)
   154  	h.Set("Content-Disposition",
   155  		fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
   156  	return w.CreatePart(h)
   157  }
   158  
   159  // WriteField calls CreateFormField and then writes the given value.
   160  func (w *Writer) WriteField(fieldname, value string) error {
   161  	p, err := w.CreateFormField(fieldname)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	_, err = p.Write([]byte(value))
   166  	return err
   167  }
   168  
   169  // Close finishes the multipart message and writes the trailing
   170  // boundary end line to the output.
   171  func (w *Writer) Close() error {
   172  	if w.lastpart != nil {
   173  		if err := w.lastpart.close(); err != nil {
   174  			return err
   175  		}
   176  		w.lastpart = nil
   177  	}
   178  	_, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
   179  	return err
   180  }
   181  
   182  type part struct {
   183  	mw     *Writer
   184  	closed bool
   185  	we     error // last error that occurred writing
   186  }
   187  
   188  func (p *part) close() error {
   189  	p.closed = true
   190  	return p.we
   191  }
   192  
   193  func (p *part) Write(d []byte) (n int, err error) {
   194  	if p.closed {
   195  		return 0, errors.New("multipart: can't write to finished part")
   196  	}
   197  	n, err = p.mw.w.Write(d)
   198  	if err != nil {
   199  		p.we = err
   200  	}
   201  	return
   202  }