1
2
3
4
5 package multipart
6
7 import (
8 "bytes"
9 "crypto/rand"
10 "errors"
11 "fmt"
12 "io"
13 "net/textproto"
14 "sort"
15 "strings"
16 )
17
18
19 type Writer struct {
20 w io.Writer
21 boundary string
22 lastpart *part
23 }
24
25
26
27 func NewWriter(w io.Writer) *Writer {
28 return &Writer{
29 w: w,
30 boundary: randomBoundary(),
31 }
32 }
33
34
35 func (w *Writer) Boundary() string {
36 return w.boundary
37 }
38
39
40
41
42
43
44
45 func (w *Writer) SetBoundary(boundary string) error {
46 if w.lastpart != nil {
47 return errors.New("mime: SetBoundary called after write")
48 }
49
50 if len(boundary) < 1 || len(boundary) > 70 {
51 return errors.New("mime: invalid boundary length")
52 }
53 end := len(boundary) - 1
54 for i, b := range boundary {
55 if 'A' <= b && b <= 'Z' || 'a' <= b && b <= 'z' || '0' <= b && b <= '9' {
56 continue
57 }
58 switch b {
59 case '\'', '(', ')', '+', '_', ',', '-', '.', '/', ':', '=', '?':
60 continue
61 case ' ':
62 if i != end {
63 continue
64 }
65 }
66 return errors.New("mime: invalid boundary character")
67 }
68 w.boundary = boundary
69 return nil
70 }
71
72
73
74 func (w *Writer) FormDataContentType() string {
75 b := w.boundary
76
77
78 if strings.ContainsAny(b, `()<>@,;:\"/[]?= `) {
79 b = `"` + b + `"`
80 }
81 return "multipart/form-data; boundary=" + b
82 }
83
84 func randomBoundary() string {
85 var buf [30]byte
86 _, err := io.ReadFull(rand.Reader, buf[:])
87 if err != nil {
88 panic(err)
89 }
90 return fmt.Sprintf("%x", buf[:])
91 }
92
93
94
95
96
97 func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
98 if w.lastpart != nil {
99 if err := w.lastpart.close(); err != nil {
100 return nil, err
101 }
102 }
103 var b bytes.Buffer
104 if w.lastpart != nil {
105 fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary)
106 } else {
107 fmt.Fprintf(&b, "--%s\r\n", w.boundary)
108 }
109
110 keys := make([]string, 0, len(header))
111 for k := range header {
112 keys = append(keys, k)
113 }
114 sort.Strings(keys)
115 for _, k := range keys {
116 for _, v := range header[k] {
117 fmt.Fprintf(&b, "%s: %s\r\n", k, v)
118 }
119 }
120 fmt.Fprintf(&b, "\r\n")
121 _, err := io.Copy(w.w, &b)
122 if err != nil {
123 return nil, err
124 }
125 p := &part{
126 mw: w,
127 }
128 w.lastpart = p
129 return p, nil
130 }
131
132 var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
133
134 func escapeQuotes(s string) string {
135 return quoteEscaper.Replace(s)
136 }
137
138
139
140 func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
141 h := make(textproto.MIMEHeader)
142 h.Set("Content-Disposition",
143 fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
144 escapeQuotes(fieldname), escapeQuotes(filename)))
145 h.Set("Content-Type", "application/octet-stream")
146 return w.CreatePart(h)
147 }
148
149
150
151 func (w *Writer) CreateFormField(fieldname string) (io.Writer, error) {
152 h := make(textproto.MIMEHeader)
153 h.Set("Content-Disposition",
154 fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
155 return w.CreatePart(h)
156 }
157
158
159 func (w *Writer) WriteField(fieldname, value string) error {
160 p, err := w.CreateFormField(fieldname)
161 if err != nil {
162 return err
163 }
164 _, err = p.Write([]byte(value))
165 return err
166 }
167
168
169
170 func (w *Writer) Close() error {
171 if w.lastpart != nil {
172 if err := w.lastpart.close(); err != nil {
173 return err
174 }
175 w.lastpart = nil
176 }
177 _, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
178 return err
179 }
180
181 type part struct {
182 mw *Writer
183 closed bool
184 we error
185 }
186
187 func (p *part) close() error {
188 p.closed = true
189 return p.we
190 }
191
192 func (p *part) Write(d []byte) (n int, err error) {
193 if p.closed {
194 return 0, errors.New("multipart: can't write to finished part")
195 }
196 n, err = p.mw.w.Write(d)
197 if err != nil {
198 p.we = err
199 }
200 return
201 }
202
View as plain text