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