diff --git a/encoding.go b/encoding.go index ca4869b..a503276 100644 --- a/encoding.go +++ b/encoding.go @@ -1,14 +1,13 @@ package message import ( + "bytes" "encoding/base64" "errors" "fmt" "io" "mime/quotedprintable" "strings" - - "github.com/emersion/go-textwrapper" ) type UnknownEncodingError struct { @@ -57,9 +56,9 @@ func encodingWriter(enc string, w io.Writer) (io.WriteCloser, error) { case "quoted-printable": wc = quotedprintable.NewWriter(w) case "base64": - wc = base64.NewEncoder(base64.StdEncoding, textwrapper.NewRFC822(w)) + wc = base64.NewEncoder(base64.StdEncoding, &lineWrapper{w: w, maxLineLen: 76}) case "7bit", "8bit": - wc = nopCloser{textwrapper.New(w, "\r\n", 1000)} + wc = nopCloser{&lineWrapper{w: w, maxLineLen: 998}} case "binary", "": wc = nopCloser{w} default: @@ -86,3 +85,67 @@ func (r *whitespaceReplacingReader) Read(p []byte) (int, error) { return n, err } + +type lineWrapper struct { + w io.Writer + maxLineLen int + + curLineLen int + cr bool +} + +func (w *lineWrapper) Write(b []byte) (int, error) { + var written int + for len(b) > 0 { + var l []byte + l, b = cutLine(b, w.maxLineLen-w.curLineLen) + + lf := bytes.HasSuffix(l, []byte("\n")) + l = bytes.TrimSuffix(l, []byte("\n")) + + n, err := w.w.Write(l) + if err != nil { + return written, err + } + written += n + + cr := bytes.HasSuffix(l, []byte("\r")) + if len(l) == 0 { + cr = w.cr + } + + if !lf && len(b) == 0 { + w.curLineLen += len(l) + w.cr = cr + break + } + w.curLineLen = 0 + + ending := []byte("\r\n") + if cr { + ending = []byte("\n") + } + _, err = w.w.Write(ending) + if err != nil { + return written, err + } + w.cr = false + } + + return written, nil +} + +func cutLine(b []byte, max int) ([]byte, []byte) { + for i := 0; i < len(b); i++ { + if b[i] == '\r' && i == max { + continue + } + if b[i] == '\n' { + return b[:i+1], b[i+1:] + } + if i >= max { + return b[:i], b[i:] + } + } + return b, nil +} diff --git a/encoding_test.go b/encoding_test.go index 92ab453..153147f 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -71,3 +71,99 @@ func TestEncode(t *testing.T) { } } } + +var lineWrapperTests = []struct { + name string + in, out string +}{ + { + name: "empty", + in: "", + out: "", + }, + { + name: "oneLine", + in: "ab", + out: "ab", + }, + { + name: "oneLineMax", + in: "abc", + out: "abc", + }, + { + name: "twoLines", + in: "abcde", + out: "abc\r\nde", + }, + { + name: "twoLinesMax", + in: "abcdef", + out: "abc\r\ndef", + }, + { + name: "threeLines", + in: "abcdefhi", + out: "abc\r\ndef\r\nhi", + }, + { + name: "wrappedMiss", + in: "abcd\nef", + out: "abc\r\nd\r\nef", + }, + { + name: "wrappedLF", + in: "abc\ndef\nhij", + out: "abc\r\ndef\r\nhij", + }, + { + name: "wrappedCRLF", + in: "abc\r\ndef\r\nhij", + out: "abc\r\ndef\r\nhij", + }, + { + name: "trailingCRLF", + in: "a\r\n", + out: "a\r\n", + }, + { + name: "cr", + in: "\r\r\r\r\r", + out: "\r\r\r\r\n\r", + }, +} + +func TestLineWrapper(t *testing.T) { + for _, tc := range lineWrapperTests { + t.Run(tc.name, func(t *testing.T) { + var sb strings.Builder + w := &lineWrapper{w: &sb, maxLineLen: 3} + if _, err := io.WriteString(w, tc.in); err != nil { + t.Fatalf("WriteString() = %v", err) + } + if s := sb.String(); s != tc.out { + t.Errorf("got %q, want %q", s, tc.out) + } + }) + + t.Run(tc.name+"/bytePerByte", func(t *testing.T) { + var sb strings.Builder + w := &lineWrapper{w: &sb, maxLineLen: 3} + if err := writeStringBytePerByte(w, tc.in); err != nil { + t.Fatalf("writeStringBytePerByte() = %v", err) + } + if s := sb.String(); s != tc.out { + t.Errorf("got %q, want %q", s, tc.out) + } + }) + } +} + +func writeStringBytePerByte(w io.Writer, s string) error { + for i := 0; i < len(s); i++ { + if _, err := w.Write([]byte{s[i]}); err != nil { + return err + } + } + return nil +} diff --git a/go.mod b/go.mod index 4491717..c524b2f 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,4 @@ module github.com/emersion/go-message go 1.14 -require ( - github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 - golang.org/x/text v0.14.0 -) +require golang.org/x/text v0.14.0 diff --git a/go.sum b/go.sum index bb451ed..4303ed2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY= -github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=