package smtpsrv import ( "bufio" "bytes" "io" "strings" "testing" ) func TestReadUntilDot(t *testing.T) { cases := []struct { input string max int64 want string wantErr error }{ // EOF before any input -> unexpected EOF. {"", 0, "", io.ErrUnexpectedEOF}, {"", 1, "", io.ErrUnexpectedEOF}, // EOF after exceeding max -> unexpected EOF. {"abcdef", 2, "ab", io.ErrUnexpectedEOF}, // \n at the beginning of the buffer are just as invalid, and the // error takes precedence over the unexpected EOF. {"\n", 0, "", errInvalidLineEnding}, {"\n", 1, "", errInvalidLineEnding}, {"\n", 2, "", errInvalidLineEnding}, {"\n\r\n.\r\n", 10, "", errInvalidLineEnding}, // \r and then EOF -> unexpected EOF, because we never had a chance to // assess if the line ending is valid or not. {"\r", 2, "", io.ErrUnexpectedEOF}, // Lonely \r -> invalid line ending. {"abc\rdef", 10, "abc", errInvalidLineEnding}, {"abc\r\rdef", 10, "abc", errInvalidLineEnding}, // Lonely \n -> invalid line ending. {"abc\ndef", 10, "abc", errInvalidLineEnding}, // Various valid cases. {"abc\r\n.\r\n", 10, "abc\n", nil}, {"\r\n.\r\n", 10, "\n", nil}, // Start with the final dot - the smallest "message" (empty). {".\r\n", 10, "", nil}, // Max bytes reached -> message too large. {"abc\r\n.\r\n", 5, "abc\n", errMessageTooLarge}, {"abcdefg\r\n.\r\n", 5, "abcde", errMessageTooLarge}, {"ab\r\ncdefg\r\n.\r\n", 5, "ab\ncd", errMessageTooLarge}, // Dot-stuffing. // https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2 {"abc\r\n.def\r\n.\r\n", 20, "abc\ndef\n", nil}, {"abc\r\n..def\r\n.\r\n", 20, "abc\n.def\n", nil}, {"abc\r\n..\r\n.\r\n", 20, "abc\n.\n", nil}, {".x\r\n.\r\n", 20, "x\n", nil}, {"..\r\n.\r\n", 20, ".\n", nil}, } for i, c := range cases { r := bufio.NewReader(strings.NewReader(c.input)) got, err := readUntilDot(r, c.max) if err != c.wantErr { t.Errorf("case %d %q: got error %v, want %v", i, c.input, err, c.wantErr) } if !bytes.Equal(got, []byte(c.want)) { t.Errorf("case %d %q: got %q, want %q", i, c.input, got, c.want) } } } type badBuffer bytes.Buffer func (b *badBuffer) Read(p []byte) (int, error) { // Return an arbitrary non-EOF error for testing. return 0, io.ErrNoProgress } func TestReadUntilDotReadError(t *testing.T) { r := bufio.NewReader(&badBuffer{}) _, err := readUntilDot(r, 10) if err != io.ErrNoProgress { t.Errorf("got error %v, want %v", err, io.ErrNoProgress) } }