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