summaryrefslogtreecommitdiff
path: root/libgo/go/mime
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/mime')
-rw-r--r--libgo/go/mime/grammar.go4
-rw-r--r--libgo/go/mime/mediatype.go15
-rw-r--r--libgo/go/mime/mediatype_test.go3
-rw-r--r--libgo/go/mime/multipart/formdata.go2
-rw-r--r--libgo/go/mime/multipart/formdata_test.go4
-rw-r--r--libgo/go/mime/multipart/multipart.go90
-rw-r--r--libgo/go/mime/multipart/multipart_test.go97
-rw-r--r--libgo/go/mime/multipart/writer.go157
-rw-r--r--libgo/go/mime/multipart/writer_test.go78
-rw-r--r--libgo/go/mime/type.go2
10 files changed, 369 insertions, 83 deletions
diff --git a/libgo/go/mime/grammar.go b/libgo/go/mime/grammar.go
index e60cbb8df74..6e319ff8be8 100644
--- a/libgo/go/mime/grammar.go
+++ b/libgo/go/mime/grammar.go
@@ -9,13 +9,13 @@ import (
)
// isTSpecial returns true if rune is in 'tspecials' as defined by RFC
-// 1531 and RFC 2045.
+// 1521 and RFC 2045.
func isTSpecial(rune int) bool {
return strings.IndexRune(`()<>@,;:\"/[]?=`, rune) != -1
}
// IsTokenChar returns true if rune is in 'token' as defined by RFC
-// 1531 and RFC 2045.
+// 1521 and RFC 2045.
func IsTokenChar(rune int) bool {
// token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
// or tspecials>
diff --git a/libgo/go/mime/mediatype.go b/libgo/go/mime/mediatype.go
index f28ff3e9681..40c735c5baa 100644
--- a/libgo/go/mime/mediatype.go
+++ b/libgo/go/mime/mediatype.go
@@ -31,11 +31,13 @@ func validMediaTypeOrDisposition(s string) bool {
}
// ParseMediaType parses a media type value and any optional
-// parameters, per RFC 1531. Media types are the values in
-// Content-Type and Content-Disposition headers (RFC 2183). On
-// success, ParseMediaType returns the media type converted to
-// lowercase and trimmed of white space and a non-nil params. On
-// error, it returns an empty string and a nil params.
+// parameters, per RFC 1521. Media types are the values in
+// Content-Type and Content-Disposition headers (RFC 2183).
+// On success, ParseMediaType returns the media type converted
+// to lowercase and trimmed of white space. The returned params
+// is always a non-nil map. Params maps from the lowercase
+// attribute to the attribute value with its case preserved.
+// On error, it returns an empty string and a nil params.
func ParseMediaType(v string) (mediatype string, params map[string]string) {
i := strings.Index(v, ";")
if i == -1 {
@@ -132,7 +134,7 @@ func ParseMediaType(v string) (mediatype string, params map[string]string) {
}
func decode2231Enc(v string) string {
- sv := strings.Split(v, "'", 3)
+ sv := strings.SplitN(v, "'", 3)
if len(sv) != 3 {
return ""
}
@@ -211,6 +213,7 @@ func consumeMediaParam(v string) (param, value, rest string) {
rest = rest[1:] // consume semicolon
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
param, rest = consumeToken(rest)
+ param = strings.ToLower(param)
if param == "" {
return "", "", v
}
diff --git a/libgo/go/mime/mediatype_test.go b/libgo/go/mime/mediatype_test.go
index 454ddd03778..93264bd09a1 100644
--- a/libgo/go/mime/mediatype_test.go
+++ b/libgo/go/mime/mediatype_test.go
@@ -60,6 +60,7 @@ func TestConsumeMediaParam(t *testing.T) {
{" ; foo=bar", "foo", "bar", ""},
{"; foo=bar", "foo", "bar", ""},
{";foo=bar", "foo", "bar", ""},
+ {";FOO=bar", "foo", "bar", ""},
{`;foo="bar"`, "foo", "bar", ""},
{`;foo="bar"; `, "foo", "bar", "; "},
{`;foo="bar"; foo=baz`, "foo", "bar", "; foo=baz"},
@@ -127,7 +128,7 @@ func TestParseMediaType(t *testing.T) {
`URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"`,
"message/external-body",
m("access-type", "URL",
- "URL", "ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar")},
+ "url", "ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar")},
{`application/x-stuff; ` +
`title*0*=us-ascii'en'This%20is%20even%20more%20; ` +
diff --git a/libgo/go/mime/multipart/formdata.go b/libgo/go/mime/multipart/formdata.go
index 5f328656590..91404d6f41c 100644
--- a/libgo/go/mime/multipart/formdata.go
+++ b/libgo/go/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/libgo/go/mime/multipart/formdata_test.go b/libgo/go/mime/multipart/formdata_test.go
index b56e2a430e0..4bc4649317a 100644
--- a/libgo/go/mime/multipart/formdata_test.go
+++ b/libgo/go/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.Error("file has unexpected underlying type %T", fd)
+ 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/libgo/go/mime/multipart/multipart.go b/libgo/go/mime/multipart/multipart.go
index 9affa112611..2533bd337dc 100644
--- a/libgo/go/mime/multipart/multipart.go
+++ b/libgo/go/mime/multipart/multipart.go
@@ -21,28 +21,15 @@ import (
"mime"
"net/textproto"
"os"
- "regexp"
)
-var headerRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z0-9\\-]+): *([^\r\n]+)")
+// 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 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 +38,7 @@ type Part struct {
Header textproto.MIMEHeader
buffer *bytes.Buffer
- mr *multiReader
+ mr *Reader
disposition string
dispositionParams map[string]string
@@ -71,7 +58,6 @@ func (p *Part) FormName() string {
return p.dispositionParams["name"]
}
-
// FileName returns the filename parameter of the Part's
// Content-Disposition header.
func (p *Part) FileName() string {
@@ -91,20 +77,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,
@@ -117,22 +102,12 @@ func newPart(mr *multiReader) (*Part, os.Error) {
}
func (bp *Part) populateHeaders() os.Error {
- for {
- lineBytes, err := bp.mr.bufReader.ReadSlice('\n')
- if err != nil {
- return err
- }
- line := string(lineBytes)
- if line == "\n" || line == "\r\n" {
- return nil
- }
- if matches := headerRegexp.FindStringSubmatch(line); len(matches) == 3 {
- bp.Header.Add(matches[1], matches[2])
- continue
- }
- return os.NewError("Unexpected header line found parsing multipart body")
+ r := textproto.NewReader(bp.mr.bufReader)
+ header, err := r.ReadMIMEHeader()
+ if err == nil {
+ bp.Header = header
}
- panic("unreachable")
+ return err
}
// Read reads the body of a part, after its headers and before the
@@ -188,16 +163,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 +213,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 +227,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 +237,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 +264,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/libgo/go/mime/multipart/multipart_test.go b/libgo/go/mime/multipart/multipart_test.go
index 8222fbd8a4d..38079e53a19 100644
--- a/libgo/go/mime/multipart/multipart_test.go
+++ b/libgo/go/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)
@@ -136,21 +141,28 @@ func testMultipart(t *testing.T, r io.Reader) {
t.Error("Expected part1")
return
}
- if part.Header.Get("Header1") != "value1" {
- t.Error("Expected Header1: value")
+ if x := part.Header.Get("Header1"); x != "value1" {
+ t.Errorf("part.Header.Get(%q) = %q, want %q", "Header1", x, "value1")
}
- if part.Header.Get("foo-bar") != "baz" {
- t.Error("Expected foo-bar: baz")
+ if x := part.Header.Get("foo-bar"); x != "baz" {
+ t.Errorf("part.Header.Get(%q) = %q, want %q", "foo-bar", x, "baz")
}
- if part.Header.Get("Foo-Bar") != "baz" {
- t.Error("Expected Foo-Bar: baz")
+ if x := part.Header.Get("Foo-Bar"); x != "baz" {
+ t.Errorf("part.Header.Get(%q) = %q, want %q", "Foo-Bar", x, "baz")
}
buf.Reset()
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
@@ -203,7 +215,7 @@ func testMultipart(t *testing.T, r io.Reader) {
t.Error("Didn't expect a fifth part.")
}
if err != os.EOF {
- t.Errorf("On fifth part expected os.EOF; got %v", err)
+ t.Errorf("On fifth part expected os.EOF; got %v", err)
}
}
@@ -307,6 +319,29 @@ Oh no, premature EOF!
}
}
+func TestZeroLengthBody(t *testing.T) {
+ testBody := strings.Replace(`
+This is a multi-part message. This line is ignored.
+--MyBoundary
+foo: bar
+
+
+--MyBoundary--
+`, "\n", "\r\n", -1)
+ r := NewReader(strings.NewReader(testBody), "MyBoundary")
+ part, err := r.NextPart()
+ if err != nil {
+ t.Fatalf("didn't get a part")
+ }
+ n, err := io.Copy(ioutil.Discard, part)
+ if err != nil {
+ t.Errorf("error reading part: %v", err)
+ }
+ if n != 0 {
+ t.Errorf("read %d bytes; expected 0", n)
+ }
+}
+
type slowReader struct {
r io.Reader
}
@@ -317,3 +352,29 @@ func (s *slowReader) Read(p []byte) (int, os.Error) {
}
return s.r.Read(p[:1])
}
+
+func TestLineContinuation(t *testing.T) {
+ // This body, extracted from an email, contains headers that span multiple
+ // lines.
+
+ // TODO: The original mail ended with a double-newline before the
+ // final delimiter; this was manually edited to use a CRLF.
+ testBody :=
+ "\n--Apple-Mail-2-292336769\nContent-Transfer-Encoding: 7bit\nContent-Type: text/plain;\n\tcharset=US-ASCII;\n\tdelsp=yes;\n\tformat=flowed\n\nI'm finding the same thing happening on my system (10.4.1).\n\n\n--Apple-Mail-2-292336769\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/html;\n\tcharset=ISO-8859-1\n\n<HTML><BODY>I'm finding the same thing =\nhappening on my system (10.4.1).=A0 But I built it with XCode =\n2.0.</BODY></=\nHTML>=\n\r\n--Apple-Mail-2-292336769--\n"
+
+ r := NewReader(strings.NewReader(testBody), "Apple-Mail-2-292336769")
+
+ for i := 0; i < 2; i++ {
+ part, err := r.NextPart()
+ if err != nil {
+ t.Fatalf("didn't get a part")
+ }
+ n, err := io.Copy(ioutil.Discard, part)
+ if err != nil {
+ t.Errorf("error reading part: %v", err)
+ }
+ if n <= 0 {
+ t.Errorf("read %d bytes; expected >0", n)
+ }
+ }
+}
diff --git a/libgo/go/mime/multipart/writer.go b/libgo/go/mime/multipart/writer.go
new file mode 100644
index 00000000000..97a8897b299
--- /dev/null
+++ b/libgo/go/mime/multipart/writer.go
@@ -0,0 +1,157 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package multipart
+
+import (
+ "bytes"
+ "crypto/rand"
+ "fmt"
+ "io"
+ "net/textproto"
+ "os"
+ "strings"
+)
+
+// A Writer generates multipart messages.
+type Writer struct {
+ w io.Writer
+ boundary string
+ lastpart *part
+}
+
+// NewWriter returns a new multipart Writer with a random boundary,
+// writing to w.
+func NewWriter(w io.Writer) *Writer {
+ return &Writer{
+ w: w,
+ boundary: randomBoundary(),
+ }
+}
+
+// Boundary returns the Writer's randomly selected boundary string.
+func (w *Writer) Boundary() string {
+ return w.boundary
+}
+
+// FormDataContentType returns the Content-Type for an HTTP
+// multipart/form-data with this Writer's Boundary.
+func (w *Writer) FormDataContentType() string {
+ return "multipart/form-data; boundary=" + w.boundary
+}
+
+func randomBoundary() string {
+ var buf [30]byte
+ _, err := io.ReadFull(rand.Reader, buf[:])
+ if err != nil {
+ panic(err)
+ }
+ return fmt.Sprintf("%x", buf[:])
+}
+
+// CreatePart creates a new multipart section with the provided
+// header. The body of the part should be written to the returned
+// Writer. After calling CreatePart, any previous part may no longer
+// be written to.
+func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, os.Error) {
+ if w.lastpart != nil {
+ if err := w.lastpart.close(); err != nil {
+ return nil, err
+ }
+ }
+ var b bytes.Buffer
+ 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 {
+ for _, v := range vv {
+ fmt.Fprintf(&b, "%s: %s\r\n", k, v)
+ }
+ }
+ fmt.Fprintf(&b, "\r\n")
+ _, err := io.Copy(w.w, &b)
+ if err != nil {
+ return nil, err
+ }
+ p := &part{
+ mw: w,
+ }
+ w.lastpart = p
+ return p, nil
+}
+
+func escapeQuotes(s string) string {
+ s = strings.Replace(s, "\\", "\\\\", -1)
+ s = strings.Replace(s, "\"", "\\\"", -1)
+ return s
+}
+
+// CreateFormFile is a convenience wrapper around CreatePart. It creates
+// a new form-data header with the provided field name and file name.
+func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, os.Error) {
+ h := make(textproto.MIMEHeader)
+ h.Set("Content-Disposition",
+ fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
+ escapeQuotes(fieldname), escapeQuotes(filename)))
+ h.Set("Content-Type", "application/octet-stream")
+ return w.CreatePart(h)
+}
+
+// CreateFormField calls CreatePart with a header using the
+// given field name.
+func (w *Writer) CreateFormField(fieldname string) (io.Writer, os.Error) {
+ h := make(textproto.MIMEHeader)
+ h.Set("Content-Disposition",
+ fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
+ return w.CreatePart(h)
+}
+
+// WriteField calls CreateFormField and then writes the given value.
+func (w *Writer) WriteField(fieldname, value string) os.Error {
+ p, err := w.CreateFormField(fieldname)
+ if err != nil {
+ return err
+ }
+ _, err = p.Write([]byte(value))
+ return err
+}
+
+// Close finishes the multipart message and writes the trailing
+// boundary end line to the output.
+func (w *Writer) Close() os.Error {
+ if w.lastpart != nil {
+ if err := w.lastpart.close(); err != nil {
+ return err
+ }
+ w.lastpart = nil
+ }
+ _, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary)
+ return err
+}
+
+type part struct {
+ mw *Writer
+ closed bool
+ we os.Error // last error that occurred writing
+}
+
+func (p *part) close() os.Error {
+ p.closed = true
+ return p.we
+}
+
+func (p *part) Write(d []byte) (n int, err os.Error) {
+ if p.closed {
+ return 0, os.NewError("multipart: can't write to finished part")
+ }
+ n, err = p.mw.w.Write(d)
+ if err != nil {
+ p.we = err
+ }
+ return
+}
diff --git a/libgo/go/mime/multipart/writer_test.go b/libgo/go/mime/multipart/writer_test.go
new file mode 100644
index 00000000000..494e936c4c6
--- /dev/null
+++ b/libgo/go/mime/multipart/writer_test.go
@@ -0,0 +1,78 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package multipart
+
+import (
+ "bytes"
+ "io/ioutil"
+ "testing"
+)
+
+func TestWriter(t *testing.T) {
+ fileContents := []byte("my file contents")
+
+ var b bytes.Buffer
+ w := NewWriter(&b)
+ {
+ part, err := w.CreateFormFile("myfile", "my-file.txt")
+ if err != nil {
+ t.Fatalf("CreateFormFile: %v", err)
+ }
+ part.Write(fileContents)
+ err = w.WriteField("key", "val")
+ if err != nil {
+ t.Fatalf("WriteField: %v", err)
+ }
+ part.Write([]byte("val"))
+ err = w.Close()
+ 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())
+
+ part, err := r.NextPart()
+ if err != nil {
+ t.Fatalf("part 1: %v", err)
+ }
+ if g, e := part.FormName(), "myfile"; g != e {
+ t.Errorf("part 1: want form name %q, got %q", e, g)
+ }
+ slurp, err := ioutil.ReadAll(part)
+ if err != nil {
+ t.Fatalf("part 1: ReadAll: %v", err)
+ }
+ if e, g := string(fileContents), string(slurp); e != g {
+ t.Errorf("part 1: want contents %q, got %q", e, g)
+ }
+
+ part, err = r.NextPart()
+ if err != nil {
+ t.Fatalf("part 2: %v", err)
+ }
+ if g, e := part.FormName(), "key"; g != e {
+ t.Errorf("part 2: want form name %q, got %q", e, g)
+ }
+ slurp, err = ioutil.ReadAll(part)
+ if err != nil {
+ t.Fatalf("part 2: ReadAll: %v", err)
+ }
+ if e, g := "val", string(slurp); e != g {
+ t.Errorf("part 2: want contents %q, got %q", e, g)
+ }
+
+ part, err = r.NextPart()
+ if part != nil || err == nil {
+ t.Fatalf("expected end of parts; got %v, %v", part, err)
+ }
+}
diff --git a/libgo/go/mime/type.go b/libgo/go/mime/type.go
index 8c43b81b0c5..8ecfe9a37b1 100644
--- a/libgo/go/mime/type.go
+++ b/libgo/go/mime/type.go
@@ -19,7 +19,7 @@ var typeFiles = []string{
}
var mimeTypes = map[string]string{
- ".css": "text/css",
+ ".css": "text/css; charset=utf-8",
".gif": "image/gif",
".htm": "text/html; charset=utf-8",
".html": "text/html; charset=utf-8",