diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 |
commit | 28592ee1ea1f5cdffcf85472f9de0285d928cf12 (patch) | |
tree | 32944e18b23f7fe4a0818a694aa2a6dfb1835463 /src/pkg/mime/multipart | |
parent | e836bee4716dc0d4d913537ad3ad1925a7ac32d0 (diff) | |
download | golang-upstream/59.tar.gz |
Imported Upstream version 59upstream/59
Diffstat (limited to 'src/pkg/mime/multipart')
-rw-r--r-- | src/pkg/mime/multipart/formdata.go | 2 | ||||
-rw-r--r-- | src/pkg/mime/multipart/formdata_test.go | 2 | ||||
-rw-r--r-- | src/pkg/mime/multipart/multipart.go | 68 | ||||
-rw-r--r-- | src/pkg/mime/multipart/multipart_test.go | 34 | ||||
-rw-r--r-- | src/pkg/mime/multipart/writer.go | 6 | ||||
-rw-r--r-- | src/pkg/mime/multipart/writer_test.go | 7 |
6 files changed, 71 insertions, 48 deletions
diff --git a/src/pkg/mime/multipart/formdata.go b/src/pkg/mime/multipart/formdata.go index 5f3286565..91404d6f4 100644 --- a/src/pkg/mime/multipart/formdata.go +++ b/src/pkg/mime/multipart/formdata.go @@ -19,7 +19,7 @@ import ( // a Content-Disposition of "form-data". // It stores up to maxMemory bytes of the file parts in memory // and the remainder on disk in temporary files. -func (r *multiReader) ReadForm(maxMemory int64) (f *Form, err os.Error) { +func (r *Reader) ReadForm(maxMemory int64) (f *Form, err os.Error) { form := &Form{make(map[string][]string), make(map[string][]*FileHeader)} defer func() { if err != nil { diff --git a/src/pkg/mime/multipart/formdata_test.go b/src/pkg/mime/multipart/formdata_test.go index 9424c3778..4bc464931 100644 --- a/src/pkg/mime/multipart/formdata_test.go +++ b/src/pkg/mime/multipart/formdata_test.go @@ -31,10 +31,12 @@ func TestReadForm(t *testing.T) { if _, ok := fd.(*os.File); ok { t.Error("file is *os.File, should not be") } + fd.Close() fd = testFile(t, f.File["fileb"][0], "fileb.txt", filebContents) if _, ok := fd.(*os.File); !ok { t.Errorf("file has unexpected underlying type %T", fd) } + fd.Close() } func testFile(t *testing.T, fh *FileHeader, efn, econtent string) File { diff --git a/src/pkg/mime/multipart/multipart.go b/src/pkg/mime/multipart/multipart.go index 9affa1126..4711fd78b 100644 --- a/src/pkg/mime/multipart/multipart.go +++ b/src/pkg/mime/multipart/multipart.go @@ -24,25 +24,15 @@ import ( "regexp" ) +// TODO(bradfitz): inline these once the compiler can inline them in +// read-only situation (such as bytes.HasSuffix) +var lf = []byte("\n") +var crlf = []byte("\r\n") + var headerRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z0-9\\-]+): *([^\r\n]+)") var emptyParams = make(map[string]string) -// Reader is an iterator over parts in a MIME multipart body. -// Reader's underlying parser consumes its input as needed. Seeking -// isn't supported. -type Reader interface { - // NextPart returns the next part in the multipart or an error. - // When there are no more parts, the error os.EOF is returned. - NextPart() (*Part, os.Error) - - // ReadForm parses an entire multipart message whose parts have - // a Content-Disposition of "form-data". - // It stores up to maxMemory bytes of the file parts in memory - // and the remainder on disk in temporary files. - ReadForm(maxMemory int64) (*Form, os.Error) -} - // A Part represents a single part in a multipart body. type Part struct { // The headers of the body, if any, with the keys canonicalized @@ -51,7 +41,7 @@ type Part struct { Header textproto.MIMEHeader buffer *bytes.Buffer - mr *multiReader + mr *Reader disposition string dispositionParams map[string]string @@ -91,20 +81,19 @@ func (p *Part) parseContentDisposition() { // NewReader creates a new multipart Reader reading from r using the // given MIME boundary. -func NewReader(reader io.Reader, boundary string) Reader { +func NewReader(reader io.Reader, boundary string) *Reader { b := []byte("\r\n--" + boundary + "--") - return &multiReader{ + return &Reader{ bufReader: bufio.NewReader(reader), + nl: b[:2], nlDashBoundary: b[:len(b)-2], dashBoundaryDash: b[2:], dashBoundary: b[2 : len(b)-2], } } -// Implementation .... - -func newPart(mr *multiReader) (*Part, os.Error) { +func newPart(mr *Reader) (*Part, os.Error) { bp := &Part{ Header: make(map[string][]string), mr: mr, @@ -188,16 +177,21 @@ func (bp *Part) Close() os.Error { return nil } -type multiReader struct { +// Reader is an iterator over parts in a MIME multipart body. +// Reader's underlying parser consumes its input as needed. Seeking +// isn't supported. +type Reader struct { bufReader *bufio.Reader currentPart *Part partsRead int - nlDashBoundary, dashBoundaryDash, dashBoundary []byte + nl, nlDashBoundary, dashBoundaryDash, dashBoundary []byte } -func (mr *multiReader) NextPart() (*Part, os.Error) { +// NextPart returns the next part in the multipart or an error. +// When there are no more parts, the error os.EOF is returned. +func (mr *Reader) NextPart() (*Part, os.Error) { if mr.currentPart != nil { mr.currentPart.Close() } @@ -233,11 +227,11 @@ func (mr *multiReader) NextPart() (*Part, os.Error) { continue } - if bytes.Equal(line, []byte("\r\n")) { - // Consume the "\r\n" separator between the - // body of the previous part and the boundary - // line we now expect will follow. (either a - // new part or the end boundary) + // Consume the "\n" or "\r\n" separator between the + // body of the previous part and the boundary line we + // now expect will follow. (either a new part or the + // end boundary) + if bytes.Equal(line, mr.nl) { expectNewPart = true continue } @@ -247,7 +241,7 @@ func (mr *multiReader) NextPart() (*Part, os.Error) { panic("unreachable") } -func (mr *multiReader) isBoundaryDelimiterLine(line []byte) bool { +func (mr *Reader) isBoundaryDelimiterLine(line []byte) bool { // http://tools.ietf.org/html/rfc2046#section-5.1 // The boundary delimiter line is then defined as a line // consisting entirely of two hyphen characters ("-", @@ -257,13 +251,17 @@ func (mr *multiReader) isBoundaryDelimiterLine(line []byte) bool { if !bytes.HasPrefix(line, mr.dashBoundary) { return false } - if bytes.HasSuffix(line, []byte("\r\n")) { - return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-2]) + if bytes.HasSuffix(line, mr.nl) { + return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-len(mr.nl)]) } // Violate the spec and also support newlines without the // carriage return... - if bytes.HasSuffix(line, []byte("\n")) { - return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-1]) + if mr.partsRead == 0 && bytes.HasSuffix(line, lf) { + if onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-1]) { + mr.nl = mr.nl[1:] + mr.nlDashBoundary = mr.nlDashBoundary[1:] + return true + } } return false } @@ -280,5 +278,5 @@ func onlyHorizontalWhitespace(s []byte) bool { func hasPrefixThenNewline(s, prefix []byte) bool { return bytes.HasPrefix(s, prefix) && (len(s) == len(prefix)+1 && s[len(s)-1] == '\n' || - len(s) == len(prefix)+2 && bytes.HasSuffix(s, []byte("\r\n"))) + len(s) == len(prefix)+2 && bytes.HasSuffix(s, crlf)) } diff --git a/src/pkg/mime/multipart/multipart_test.go b/src/pkg/mime/multipart/multipart_test.go index 4ec3d30bd..1357466ac 100644 --- a/src/pkg/mime/multipart/multipart_test.go +++ b/src/pkg/mime/multipart/multipart_test.go @@ -25,7 +25,7 @@ func TestHorizontalWhitespace(t *testing.T) { } func TestBoundaryLine(t *testing.T) { - mr := NewReader(strings.NewReader(""), "myBoundary").(*multiReader) + mr := NewReader(strings.NewReader(""), "myBoundary") if !mr.isBoundaryDelimiterLine([]byte("--myBoundary\r\n")) { t.Error("expected") } @@ -81,7 +81,7 @@ func TestNameAccessors(t *testing.T) { var longLine = strings.Repeat("\n\n\r\r\r\n\r\000", (1<<20)/8) -func testMultipartBody() string { +func testMultipartBody(sep string) string { testBody := ` This is a multi-part message. This line is ignored. --MyBoundary @@ -112,21 +112,26 @@ never read data useless trailer ` - testBody = strings.Replace(testBody, "\n", "\r\n", -1) + testBody = strings.Replace(testBody, "\n", sep, -1) return strings.Replace(testBody, "[longline]", longLine, 1) } func TestMultipart(t *testing.T) { - bodyReader := strings.NewReader(testMultipartBody()) - testMultipart(t, bodyReader) + bodyReader := strings.NewReader(testMultipartBody("\r\n")) + testMultipart(t, bodyReader, false) +} + +func TestMultipartOnlyNewlines(t *testing.T) { + bodyReader := strings.NewReader(testMultipartBody("\n")) + testMultipart(t, bodyReader, true) } func TestMultipartSlowInput(t *testing.T) { - bodyReader := strings.NewReader(testMultipartBody()) - testMultipart(t, &slowReader{bodyReader}) + bodyReader := strings.NewReader(testMultipartBody("\r\n")) + testMultipart(t, &slowReader{bodyReader}, false) } -func testMultipart(t *testing.T, r io.Reader) { +func testMultipart(t *testing.T, r io.Reader, onlyNewlines bool) { reader := NewReader(r, "MyBoundary") buf := new(bytes.Buffer) @@ -149,8 +154,15 @@ func testMultipart(t *testing.T, r io.Reader) { if _, err := io.Copy(buf, part); err != nil { t.Errorf("part 1 copy: %v", err) } - expectEq(t, "My value\r\nThe end.", - buf.String(), "Value of first part") + + adjustNewlines := func(s string) string { + if onlyNewlines { + return strings.Replace(s, "\r\n", "\n", -1) + } + return s + } + + expectEq(t, adjustNewlines("My value\r\nThe end."), buf.String(), "Value of first part") // Part2 part, err = reader.NextPart() @@ -187,7 +199,7 @@ func testMultipart(t *testing.T, r io.Reader) { if _, err := io.Copy(buf, part); err != nil { t.Errorf("part 3 copy: %v", err) } - expectEq(t, "Line 1\r\nLine 2\r\nLine 3 ends in a newline, but just one.\r\n", + expectEq(t, adjustNewlines("Line 1\r\nLine 2\r\nLine 3 ends in a newline, but just one.\r\n"), buf.String(), "body of part 3") // Part4 diff --git a/src/pkg/mime/multipart/writer.go b/src/pkg/mime/multipart/writer.go index b436dd012..97a8897b2 100644 --- a/src/pkg/mime/multipart/writer.go +++ b/src/pkg/mime/multipart/writer.go @@ -61,7 +61,11 @@ func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, os.Error) { } } var b bytes.Buffer - fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary) + if w.lastpart != nil { + fmt.Fprintf(&b, "\r\n--%s\r\n", w.boundary) + } else { + fmt.Fprintf(&b, "--%s\r\n", w.boundary) + } // TODO(bradfitz): move this to textproto.MimeHeader.Write(w), have it sort // and clean, like http.Header.Write(w) does. for k, vv := range header { diff --git a/src/pkg/mime/multipart/writer_test.go b/src/pkg/mime/multipart/writer_test.go index e6a04c388..494e936c4 100644 --- a/src/pkg/mime/multipart/writer_test.go +++ b/src/pkg/mime/multipart/writer_test.go @@ -30,6 +30,13 @@ func TestWriter(t *testing.T) { if err != nil { t.Fatalf("Close: %v", err) } + s := b.String() + if len(s) == 0 { + t.Fatal("String: unexpected empty result") + } + if s[0] == '\r' || s[0] == '\n' { + t.Fatal("String: unexpected newline") + } } r := NewReader(&b, w.Boundary()) |