package quotedprintable
import (
"bufio"
"bytes"
"fmt"
"io"
)
type Reader struct {
br *bufio .Reader
rerr error
line []byte
}
func NewReader (r io .Reader ) *Reader {
return &Reader {
br : bufio .NewReader (r ),
}
}
func fromHex (b byte ) (byte , error ) {
switch {
case b >= '0' && b <= '9' :
return b - '0' , nil
case b >= 'A' && b <= 'F' :
return b - 'A' + 10 , nil
case b >= 'a' && b <= 'f' :
return b - 'a' + 10 , nil
}
return 0 , fmt .Errorf ("quotedprintable: invalid hex byte 0x%02x" , b )
}
func readHexByte (v []byte ) (b byte , err error ) {
if len (v ) < 2 {
return 0 , io .ErrUnexpectedEOF
}
var hb , lb byte
if hb , err = fromHex (v [0 ]); err != nil {
return 0 , err
}
if lb , err = fromHex (v [1 ]); err != nil {
return 0 , err
}
return hb <<4 | lb , nil
}
func isQPDiscardWhitespace (r rune ) bool {
switch r {
case '\n' , '\r' , ' ' , '\t' :
return true
}
return false
}
var (
crlf = []byte ("\r\n" )
lf = []byte ("\n" )
softSuffix = []byte ("=" )
)
func (r *Reader ) Read (p []byte ) (n int , err error ) {
for len (p ) > 0 {
if len (r .line ) == 0 {
if r .rerr != nil {
return n , r .rerr
}
r .line , r .rerr = r .br .ReadSlice ('\n' )
hasLF := bytes .HasSuffix (r .line , lf )
hasCR := bytes .HasSuffix (r .line , crlf )
wholeLine := r .line
r .line = bytes .TrimRightFunc (wholeLine , isQPDiscardWhitespace )
if bytes .HasSuffix (r .line , softSuffix ) {
rightStripped := wholeLine [len (r .line ):]
r .line = r .line [:len (r .line )-1 ]
if !bytes .HasPrefix (rightStripped , lf ) && !bytes .HasPrefix (rightStripped , crlf ) &&
!(len (rightStripped ) == 0 && len (r .line ) > 0 && r .rerr == io .EOF ) {
r .rerr = fmt .Errorf ("quotedprintable: invalid bytes after =: %q" , rightStripped )
}
} else if hasLF {
if hasCR {
r .line = append (r .line , '\r' , '\n' )
} else {
r .line = append (r .line , '\n' )
}
}
continue
}
b := r .line [0 ]
switch {
case b == '=' :
b , err = readHexByte (r .line [1 :])
if err != nil {
if len (r .line ) >= 2 && r .line [1 ] != '\r' && r .line [1 ] != '\n' {
b = '='
break
}
return n , err
}
r .line = r .line [2 :]
case b == '\t' || b == '\r' || b == '\n' :
break
case b >= 0x80 :
break
case b < ' ' || b > '~' :
return n , fmt .Errorf ("quotedprintable: invalid unescaped byte 0x%02x in body" , b )
}
p [0 ] = b
p = p [1 :]
r .line = r .line [1 :]
n ++
}
return n , nil
}