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  }