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  }