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 }