Skip to content

Commit df38069

Browse files
buchanaeianlancetaylor
authored andcommitted
mime/multipart: add Part.NextRawPart to avoid QP decoding
NextPart has automatic handling of quoted-printable encoding, which is sometimes undesirable. NextRawPart adds a method for reading a part while bypassing such automatic handling. Fixes #29090 Change-Id: I6a042a4077c64091efa3f5dbecce0d9a34ac7065 Reviewed-on: https://go-review.googlesource.com/c/go/+/152877 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent a08cb9f commit df38069

File tree

2 files changed

+87
-11
lines changed

2 files changed

+87
-11
lines changed

src/mime/multipart/multipart.go

+27-11
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ type Part struct {
3636
// The headers of the body, if any, with the keys canonicalized
3737
// in the same fashion that the Go http.Request headers are.
3838
// For example, "foo-bar" changes case to "Foo-Bar"
39-
//
40-
// As a special case, if the "Content-Transfer-Encoding" header
41-
// has a value of "quoted-printable", that header is instead
42-
// hidden from this map and the body is transparently decoded
43-
// during Read calls.
4439
Header textproto.MIMEHeader
4540

4641
mr *Reader
@@ -126,7 +121,7 @@ func (r *stickyErrorReader) Read(p []byte) (n int, _ error) {
126121
return n, r.err
127122
}
128123

129-
func newPart(mr *Reader) (*Part, error) {
124+
func newPart(mr *Reader, rawPart bool) (*Part, error) {
130125
bp := &Part{
131126
Header: make(map[string][]string),
132127
mr: mr,
@@ -135,10 +130,14 @@ func newPart(mr *Reader) (*Part, error) {
135130
return nil, err
136131
}
137132
bp.r = partReader{bp}
138-
const cte = "Content-Transfer-Encoding"
139-
if strings.EqualFold(bp.Header.Get(cte), "quoted-printable") {
140-
bp.Header.Del(cte)
141-
bp.r = quotedprintable.NewReader(bp.r)
133+
134+
// rawPart is used to switch between Part.NextPart and Part.NextRawPart.
135+
if !rawPart {
136+
const cte = "Content-Transfer-Encoding"
137+
if strings.EqualFold(bp.Header.Get(cte), "quoted-printable") {
138+
bp.Header.Del(cte)
139+
bp.r = quotedprintable.NewReader(bp.r)
140+
}
142141
}
143142
return bp, nil
144143
}
@@ -300,7 +299,24 @@ type Reader struct {
300299

301300
// NextPart returns the next part in the multipart or an error.
302301
// When there are no more parts, the error io.EOF is returned.
302+
//
303+
// As a special case, if the "Content-Transfer-Encoding" header
304+
// has a value of "quoted-printable", that header is instead
305+
// hidden and the body is transparently decoded during Read calls.
303306
func (r *Reader) NextPart() (*Part, error) {
307+
return r.nextPart(false)
308+
}
309+
310+
// NextRawPart returns the next part in the multipart or an error.
311+
// When there are no more parts, the error io.EOF is returned.
312+
//
313+
// Unlike NextPart, it does not have special handling for
314+
// "Content-Transfer-Encoding: quoted-printable".
315+
func (r *Reader) NextRawPart() (*Part, error) {
316+
return r.nextPart(true)
317+
}
318+
319+
func (r *Reader) nextPart(rawPart bool) (*Part, error) {
304320
if r.currentPart != nil {
305321
r.currentPart.Close()
306322
}
@@ -325,7 +341,7 @@ func (r *Reader) NextPart() (*Part, error) {
325341

326342
if r.isBoundaryDelimiterLine(line) {
327343
r.partsRead++
328-
bp, err := newPart(r)
344+
bp, err := newPart(r, rawPart)
329345
if err != nil {
330346
return nil, err
331347
}

src/mime/multipart/multipart_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,66 @@ func testQuotedPrintableEncoding(t *testing.T, cte string) {
449449
}
450450
}
451451

452+
func TestRawPart(t *testing.T) {
453+
// https://github.com/golang/go/issues/29090
454+
455+
body := strings.Replace(`--0016e68ee29c5d515f04cedf6733
456+
Content-Type: text/plain; charset="utf-8"
457+
Content-Transfer-Encoding: quoted-printable
458+
459+
<div dir=3D"ltr">Hello World.</div>
460+
--0016e68ee29c5d515f04cedf6733
461+
Content-Type: text/plain; charset="utf-8"
462+
Content-Transfer-Encoding: quoted-printable
463+
464+
<div dir=3D"ltr">Hello World.</div>
465+
--0016e68ee29c5d515f04cedf6733--`, "\n", "\r\n", -1)
466+
467+
r := NewReader(strings.NewReader(body), "0016e68ee29c5d515f04cedf6733")
468+
469+
// This part is expected to be raw, bypassing the automatic handling
470+
// of quoted-printable.
471+
part, err := r.NextRawPart()
472+
if err != nil {
473+
t.Fatal(err)
474+
}
475+
if _, ok := part.Header["Content-Transfer-Encoding"]; !ok {
476+
t.Errorf("missing Content-Transfer-Encoding")
477+
}
478+
var buf bytes.Buffer
479+
_, err = io.Copy(&buf, part)
480+
if err != nil {
481+
t.Error(err)
482+
}
483+
got := buf.String()
484+
// Data is still quoted-printable.
485+
want := `<div dir=3D"ltr">Hello World.</div>`
486+
if got != want {
487+
t.Errorf("wrong part value:\n got: %q\nwant: %q", got, want)
488+
}
489+
490+
// This part is expected to have automatic decoding of quoted-printable.
491+
part, err = r.NextPart()
492+
if err != nil {
493+
t.Fatal(err)
494+
}
495+
if te, ok := part.Header["Content-Transfer-Encoding"]; ok {
496+
t.Errorf("unexpected Content-Transfer-Encoding of %q", te)
497+
}
498+
499+
buf.Reset()
500+
_, err = io.Copy(&buf, part)
501+
if err != nil {
502+
t.Error(err)
503+
}
504+
got = buf.String()
505+
// QP data has been decoded.
506+
want = `<div dir="ltr">Hello World.</div>`
507+
if got != want {
508+
t.Errorf("wrong part value:\n got: %q\nwant: %q", got, want)
509+
}
510+
}
511+
452512
// Test parsing an image attachment from gmail, which previously failed.
453513
func TestNested(t *testing.T) {
454514
// nested-mime is the body part of a multipart/mixed email

0 commit comments

Comments
 (0)