1
0
mirror of https://github.com/golang/go synced 2024-10-05 04:31:22 -06:00
go/src/pkg/mime/multipart/quotedprintable.go
Brad Fitzpatrick 24555c7b8c mime/multipart: don't strip leading space/tab in quoted-printable decoding
Late bug fix, but this is arguably a regression from Go 1.0,
since we added this transparent decoding since then. Without
this fix, Go 1.0 users could decode this correctly, but Go 1.1
users would not be able to.

The newly added test is from the RFC itself.

The updated tests had the wrong "want" values before. They
were there to test \r\n vs \n equivalence (which is
unchanged), not leading whitespace.

The skipWhite decoder struct field was added in the battles of
Issue 4771 in revision b3bb265bfecf. It was just a wrong
strategy, from an earlier round of attempts in
https://golang.org/cl/7300092/

Update #4771
Fixes #5295

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/8536045
2013-04-17 20:04:58 -07:00

119 lines
2.7 KiB
Go

// Copyright 2012 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.
// The file define a quoted-printable decoder, as specified in RFC 2045.
// Deviations:
// 1. in addition to "=\r\n", "=\n" is also treated as soft line break.
// 2. it will pass through a '\r' or '\n' not preceded by '=', consistent
// with other broken QP encoders & decoders.
package multipart
import (
"bufio"
"bytes"
"fmt"
"io"
)
type qpReader struct {
br *bufio.Reader
rerr error // last read error
line []byte // to be consumed before more of br
}
func newQuotedPrintableReader(r io.Reader) io.Reader {
return &qpReader{
br: bufio.NewReader(r),
}
}
func fromHex(b byte) (byte, error) {
switch {
case b >= '0' && b <= '9':
return b - '0', nil
case b >= 'A' && b <= 'F':
return b - 'A' + 10, nil
}
return 0, fmt.Errorf("multipart: invalid quoted-printable hex byte 0x%02x", b)
}
func (q *qpReader) readHexByte(v []byte) (b byte, err error) {
if len(v) < 2 {
return 0, io.ErrUnexpectedEOF
}
var hb, lb byte
if hb, err = fromHex(v[0]); err != nil {
return 0, err
}
if lb, err = fromHex(v[1]); err != nil {
return 0, err
}
return hb<<4 | lb, nil
}
func isQPDiscardWhitespace(r rune) bool {
switch r {
case '\n', '\r', ' ', '\t':
return true
}
return false
}
var (
crlf = []byte("\r\n")
lf = []byte("\n")
softSuffix = []byte("=")
)
func (q *qpReader) Read(p []byte) (n int, err error) {
for len(p) > 0 {
if len(q.line) == 0 {
if q.rerr != nil {
return n, q.rerr
}
q.line, q.rerr = q.br.ReadSlice('\n')
// Does the line end in CRLF instead of just LF?
hasLF := bytes.HasSuffix(q.line, lf)
hasCR := bytes.HasSuffix(q.line, crlf)
wholeLine := q.line
q.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace)
if bytes.HasSuffix(q.line, softSuffix) {
rightStripped := wholeLine[len(q.line):]
q.line = q.line[:len(q.line)-1]
if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) {
q.rerr = fmt.Errorf("multipart: invalid bytes after =: %q", rightStripped)
}
} else if hasLF {
if hasCR {
q.line = append(q.line, '\r', '\n')
} else {
q.line = append(q.line, '\n')
}
}
continue
}
b := q.line[0]
switch {
case b == '=':
b, err = q.readHexByte(q.line[1:])
if err != nil {
return n, err
}
q.line = q.line[2:] // 2 of the 3; other 1 is done below
case b == '\t' || b == '\r' || b == '\n':
break
case b < ' ' || b > '~':
return n, fmt.Errorf("multipart: invalid unescaped byte 0x%02x in quoted-printable body", b)
}
p[0] = b
p = p[1:]
q.line = q.line[1:]
n++
}
return n, nil
}