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