Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ _testmain.go
*.exe
*.test
*.prof

.idea
22 changes: 14 additions & 8 deletions encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,25 @@ func IsUnknownEncoding(err error) bool {
}

func encodingReader(enc string, r io.Reader) (io.Reader, error) {
var dec io.Reader
switch strings.ToLower(enc) {
case "7bit", "8bit", "binary", "":
return r, nil
}

bufReader := NewBufferingReader(r)
var decoder io.Reader

switch strings.ToLower(enc) {
case "quoted-printable":
dec = quotedprintable.NewReader(r)
decoder = quotedprintable.NewReader(bufReader)
case "base64":
wrapped := &whitespaceReplacingReader{wrapped: r}
dec = base64.NewDecoder(base64.StdEncoding, wrapped)
case "7bit", "8bit", "binary", "":
dec = r
wrapped := &whitespaceReplacingReader{wrapped: bufReader}
decoder = base64.NewDecoder(base64.StdEncoding, wrapped)
default:
return nil, fmt.Errorf("unhandled encoding %q", enc)
return bufReader, nil
}
return dec, nil

return NewRecoverableReader(decoder, bufReader), nil
}

type nopCloser struct {
Expand Down
4 changes: 2 additions & 2 deletions encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ func TestDecode(t *testing.T) {

func TestDecode_error(t *testing.T) {
_, err := encodingReader("idontexist", nil)
if err == nil {
t.Errorf("Expected an error when creating decoder for invalid encoding")
if err != nil {
t.Errorf("Error not expected, but got: %v", err)
}
}

Expand Down
101 changes: 101 additions & 0 deletions reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package message

import (
"bytes"
"io"
)

// BufferingReader reads from an underlying reader and stores the data in a buffer
// This allows the data to be re-read even after the original reader is consumed
type BufferingReader struct {
r io.Reader // The underlying reader
buffer bytes.Buffer // Buffer to store all read data
readEOF bool // Whether we've reached EOF on the underlying reader
}

// NewBufferingReader creates a reader that buffers all data it reads
func NewBufferingReader(r io.Reader) *BufferingReader {
return &BufferingReader{
r: r,
buffer: bytes.Buffer{},
readEOF: false,
}
}

// Read implements the io.Reader interface
func (br *BufferingReader) Read(p []byte) (n int, err error) {
// If we've read to the end of the buffer and reached EOF, return EOF
if br.readEOF && br.buffer.Len() == 0 {
return 0, io.EOF
}

n, err = br.r.Read(p)

// If we got data, add it to the buffer
if n > 0 {
br.buffer.Write(p[:n])
}

// If we reached EOF, mark it
if err == io.EOF {
br.readEOF = true
}

return n, err
}

// GetReader returns a new reader for the buffered data
func (br *BufferingReader) GetReader() io.Reader {
data := make([]byte, br.buffer.Len())
copy(data, br.buffer.Bytes())

return bytes.NewReader(data)
}

// RecoverableReader tries to read with a decoder first, falling back to raw content if needed
type RecoverableReader struct {
decoder io.Reader // The decoder (e.g., quoted-printable, base64)
bufReader *BufferingReader // The buffering reader for raw content
usingDecoder bool // Whether we're currently using the decoder
readSome bool // Whether we've read anything successfully from the decoder
}

// NewRecoverableReader creates a reader that tries to decode but falls back to raw content if needed
func NewRecoverableReader(decoder io.Reader, bufReader *BufferingReader) *RecoverableReader {
return &RecoverableReader{
decoder: decoder,
bufReader: bufReader,
usingDecoder: true,
readSome: false,
}
}

// Read implements the io.Reader interface
func (rr *RecoverableReader) Read(p []byte) (n int, err error) {
// Try the decoder first if we're still using it
if rr.usingDecoder {
n, err = rr.decoder.Read(p)

if n > 0 {
rr.readSome = true
}

// If no error or just EOF after reading some data, return
if err == nil || (err == io.EOF && rr.readSome) {
return n, err
}

// If decoder failed (error other than EOF, or EOF without reading anything),
// switch to raw reader (fallback)
rr.usingDecoder = false

// Create a new reader from the buffered content
// Replace the decoder with the raw reader
// Try reading from the raw reader
rawReader := rr.bufReader.GetReader()
rr.decoder = rawReader
return rr.decoder.Read(p)
}

return rr.decoder.Read(p)
}