blob: 2a4828359bc1bb4b80813f8b696c933f16488a57 [file] [log] [blame]
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file defines quoted-printable decoders and encoders, as specified in RFC
// 2045.
// Deviations:
// 1. in addition to "=\r\n", "=\n" is also treated as soft line break.
// 2. it will pass through a '\r' or '\n' not preceded by '=', consistent
// with other broken QP encoders and decoders.
// Package quotedprintable implements quoted-printable and message header
// encoding as specified by RFC 2045 and RFC 2047.
package quotedprintable
import (
"bufio"
"bytes"
"fmt"
"io"
"gopkg.in/alexcesaro/quotedprintable.v2/internal"
)
// Encode encodes src into at most MaxEncodedLen(len(src)) bytes to dst,
// returning the actual number of bytes written to dst.
func Encode(dst, src []byte) int {
n := 0
for i, c := range src {
switch {
case c != '=' && (c >= '!' && c <= '~' || c == '\n' || c == '\r'):
dst[n] = c
n++
case c == ' ' || c == '\t':
if isLastChar(i, src) {
internal.EncodeByte(dst[n:], c)
n += 3
} else {
dst[n] = c
n++
}
default:
internal.EncodeByte(dst[n:], c)
n += 3
}
}
return n
}
// isLastChar returns true if byte i is the last character of the line.
func isLastChar(i int, src []byte) bool {
return i == len(src)-1 ||
(i < len(src)-1 && src[i+1] == '\n') ||
(i < len(src)-2 && src[i+1] == '\r' && src[i+2] == '\n')
}
// EncodeToString returns the quoted-printable encoding of src.
func EncodeToString(src []byte) string {
dbuf := make([]byte, MaxEncodedLen(len(src)))
n := Encode(dbuf, src)
return string(dbuf[:n])
}
// MaxEncodedLen returns the maximum length of an encoding of n source bytes.
func MaxEncodedLen(n int) int { return 3 * n }
// NewEncoder returns a new quoted-printable stream encoder. Data written to the
// returned writer will be encoded and then written to w.
func NewEncoder(w io.Writer) io.Writer {
return &encoder{w}
}
type encoder struct {
w io.Writer
}
func (e *encoder) Write(p []byte) (int, error) {
dbuf := make([]byte, MaxEncodedLen(len(p)))
n := Encode(dbuf, p)
n, err := e.w.Write(dbuf[:n])
if err != nil {
nn := 0
for i := 0; i < n; i++ {
if dbuf[i] == '=' {
if i+2 >= n {
break
}
i += 2
}
nn++
}
return nn, err
}
return len(p), nil
}
// Decode decodes src into at most MaxDecodedLen(len(src)) bytes to dst,
// returning the actual number of bytes written to dst.
func Decode(dst, src []byte) (n int, err error) {
var eol, trimLen, eolLen int
for i := 0; i < len(src); i++ {
if i == eol {
eol = bytes.IndexByte(src[i:], '\n') + i + 1
if eol == i {
eol = len(src)
eolLen = 0
} else if eol-2 >= i && src[eol-2] == '\r' {
eolLen = 2
} else {
eolLen = 1
}
// Count the number of bytes to trim
trimLen = 0
for {
if trimLen == eol-eolLen-i {
break
}
switch src[eol-eolLen-trimLen-1] {
case '\n', '\r', ' ', '\t':
trimLen++
continue
case '=':
if trimLen > 0 {
trimLen += eolLen + 1
eolLen = 0
err = fmt.Errorf("quotedprintable: invalid bytes after =: %q", src[eol-trimLen+1:eol])
} else {
trimLen = eolLen + 1
eolLen = 0
}
}
break
}
}
// Skip trimmable bytes
if trimLen > 0 && i == eol-trimLen-eolLen {
if err != nil {
return n, err
}
i += trimLen - 1
continue
}
switch c := src[i]; {
case c == '=':
if i+2 >= len(src) {
return n, io.ErrUnexpectedEOF
}
b, convErr := internal.ReadHexByte(src[i+1:])
if convErr != nil {
return n, convErr
}
dst[n] = b
n++
i += 2
case (c >= ' ' && c <= '~') || c == '\n' || c == '\r' || c == '\t':
dst[n] = c
n++
default:
return n, fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in quoted-printable body", c)
}
}
return n, nil
}
// MaxDecodedLen returns the maximum length of a decoding of n source bytes.
func MaxDecodedLen(n int) int { return n }
// DecodeString returns the bytes represented by the quoted-printable string s.
func DecodeString(s string) ([]byte, error) {
dbuf := make([]byte, MaxDecodedLen(len(s)))
n, err := Decode(dbuf, []byte(s))
return dbuf[:n], err
}
// NewDecoder returns a new quoted-printable stream decoder.
func NewDecoder(r io.Reader) io.Reader {
return &qpReader{br: bufio.NewReader(r)}
}
type qpReader struct {
br *bufio.Reader
line []byte
eof bool
err error
}
func (q *qpReader) Read(p []byte) (int, error) {
n := 0
for n < len(p) {
if len(q.line) == 0 {
if q.err != nil {
return n, q.err
} else if q.eof {
return n, io.EOF
}
q.line, q.err = q.br.ReadSlice('\n')
if q.err == io.EOF {
q.eof = true
} else if q.err != nil {
return n, q.err
}
var nn int
nn, q.err = Decode(q.line, q.line)
q.line = q.line[:nn]
}
nn := copy(p[n:], q.line)
n += nn
q.line = q.line[nn:]
}
return n, nil
}