github.com/bgentry/go@v0.0.0-20150121062915-6cf5a733d54d/src/mime/internal/quotedprintable/quotedprintable.go (about) 1 // Copyright 2012 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 // The file define a quoted-printable decoder, as specified in RFC 2045. 6 // Deviations: 7 // 1. in addition to "=\r\n", "=\n" is also treated as soft line break. 8 // 2. it will pass through a '\r' or '\n' not preceded by '=', consistent 9 // with other broken QP encoders & decoders. 10 11 package quotedprintable 12 13 import ( 14 "bufio" 15 "bytes" 16 "fmt" 17 "io" 18 ) 19 20 type qpReader struct { 21 br *bufio.Reader 22 rerr error // last read error 23 line []byte // to be consumed before more of br 24 } 25 26 func NewReader(r io.Reader) io.Reader { 27 return &qpReader{ 28 br: bufio.NewReader(r), 29 } 30 } 31 32 func fromHex(b byte) (byte, error) { 33 switch { 34 case b >= '0' && b <= '9': 35 return b - '0', nil 36 case b >= 'A' && b <= 'F': 37 return b - 'A' + 10, nil 38 } 39 return 0, fmt.Errorf("multipart: invalid quoted-printable hex byte 0x%02x", b) 40 } 41 42 func (q *qpReader) readHexByte(v []byte) (b byte, err error) { 43 if len(v) < 2 { 44 return 0, io.ErrUnexpectedEOF 45 } 46 var hb, lb byte 47 if hb, err = fromHex(v[0]); err != nil { 48 return 0, err 49 } 50 if lb, err = fromHex(v[1]); err != nil { 51 return 0, err 52 } 53 return hb<<4 | lb, nil 54 } 55 56 func isQPDiscardWhitespace(r rune) bool { 57 switch r { 58 case '\n', '\r', ' ', '\t': 59 return true 60 } 61 return false 62 } 63 64 var ( 65 crlf = []byte("\r\n") 66 lf = []byte("\n") 67 softSuffix = []byte("=") 68 ) 69 70 func (q *qpReader) Read(p []byte) (n int, err error) { 71 for len(p) > 0 { 72 if len(q.line) == 0 { 73 if q.rerr != nil { 74 return n, q.rerr 75 } 76 q.line, q.rerr = q.br.ReadSlice('\n') 77 78 // Does the line end in CRLF instead of just LF? 79 hasLF := bytes.HasSuffix(q.line, lf) 80 hasCR := bytes.HasSuffix(q.line, crlf) 81 wholeLine := q.line 82 q.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace) 83 if bytes.HasSuffix(q.line, softSuffix) { 84 rightStripped := wholeLine[len(q.line):] 85 q.line = q.line[:len(q.line)-1] 86 if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) { 87 q.rerr = fmt.Errorf("multipart: invalid bytes after =: %q", rightStripped) 88 } 89 } else if hasLF { 90 if hasCR { 91 q.line = append(q.line, '\r', '\n') 92 } else { 93 q.line = append(q.line, '\n') 94 } 95 } 96 continue 97 } 98 b := q.line[0] 99 100 switch { 101 case b == '=': 102 b, err = q.readHexByte(q.line[1:]) 103 if err != nil { 104 return n, err 105 } 106 q.line = q.line[2:] // 2 of the 3; other 1 is done below 107 case b == '\t' || b == '\r' || b == '\n': 108 break 109 case b < ' ' || b > '~': 110 return n, fmt.Errorf("multipart: invalid unescaped byte 0x%02x in quoted-printable body", b) 111 } 112 p[0] = b 113 p = p[1:] 114 q.line = q.line[1:] 115 n++ 116 } 117 return n, nil 118 }