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  }