Source file src/pkg/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 "net/textproto"
14 "strings"
15 )
16
17 // A Writer generates multipart messages.
18 type Writer struct {
19 w io.Writer
20 boundary string
21 lastpart *part
22 }
23
24 // NewWriter returns a new multipart Writer with a random boundary,
25 // writing to w.
26 func NewWriter(w io.Writer) *Writer {
27 return &Writer{
28 w: w,
29 boundary: randomBoundary(),
30 }
31 }
32
33 // Boundary returns the Writer's randomly selected boundary string.
34 func (w *Writer) Boundary() string {
35 return w.boundary
36 }
37
38 // FormDataContentType returns the Content-Type for an HTTP
39 // multipart/form-data with this Writer's Boundary.
40 func (w *Writer) FormDataContentType() string {
41 return "multipart/form-data; boundary=" + w.boundary
42 }
43
44 func randomBoundary() string {
45 var buf [30]byte
46 _, err := io.ReadFull(rand.Reader, buf[:])
47 if err != nil {
48 panic(err)
49 }
50 return fmt.Sprintf("%x", buf[:])
51 }
52
53 // CreatePart creates a new multipart section with the provided
54 // header. The body of the part should be written to the returned
55 // Writer. After calling CreatePart, any previous part may no longer
56 // be written to.
57 func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
58 if w.lastpart != nil {
59 if err := w.lastpart.close(); err != nil {
60 return nil, err
61 }
62 }
63 var b bytes.Buffer
64 if w.lastpart != nil {
65 fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary)
66 } else {
67 fmt.Fprintf(&b, "--%s\r\n", w.boundary)
68 }
69 // TODO(bradfitz): move this to textproto.MimeHeader.Write(w), have it sort
70 // and clean, like http.Header.Write(w) does.
71 for k, vv := range header {
72 for _, v := range vv {
73 fmt.Fprintf(&b, "%s: %s\r\n", k, v)
74 }
75 }
76 fmt.Fprintf(&b, "\r\n")
77 _, err := io.Copy(w.w, &b)
78 if err != nil {
79 return nil, err
80 }
81 p := &part{
82 mw: w,
83 }
84 w.lastpart = p
85 return p, nil
86 }
87
88 var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
89
90 func escapeQuotes(s string) string {
91 return quoteEscaper.Replace(s)
92 }
93
94 // CreateFormFile is a convenience wrapper around CreatePart. It creates
95 // a new form-data header with the provided field name and file name.
96 func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
97 h := make(textproto.MIMEHeader)
98 h.Set("Content-Disposition",
99 fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
100 escapeQuotes(fieldname), escapeQuotes(filename)))
101 h.Set("Content-Type", "application/octet-stream")
102 return w.CreatePart(h)
103 }
104
105 // CreateFormField calls CreatePart with a header using the
106 // given field name.
107 func (w *Writer) CreateFormField(fieldname string) (io.Writer, error) {
108 h := make(textproto.MIMEHeader)
109 h.Set("Content-Disposition",
110 fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
111 return w.CreatePart(h)
112 }
113
114 // WriteField calls CreateFormField and then writes the given value.
115 func (w *Writer) WriteField(fieldname, value string) error {
116 p, err := w.CreateFormField(fieldname)
117 if err != nil {
118 return err
119 }
120 _, err = p.Write([]byte(value))
121 return err
122 }
123
124 // Close finishes the multipart message and writes the trailing
125 // boundary end line to the output.
126 func (w *Writer) Close() error {
127 if w.lastpart != nil {
128 if err := w.lastpart.close(); err != nil {
129 return err
130 }
131 w.lastpart = nil
132 }
133 _, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
134 return err
135 }
136
137 type part struct {
138 mw *Writer
139 closed bool
140 we error // last error that occurred writing
141 }
142
143 func (p *part) close() error {
144 p.closed = true
145 return p.we
146 }
147
148 func (p *part) Write(d []byte) (n int, err error) {
149 if p.closed {
150 return 0, errors.New("multipart: can't write to finished part")
151 }
152 n, err = p.mw.w.Write(d)
153 if err != nil {
154 p.we = err
155 }
156 return
157 }