send: ensure crlf line endings

RFC 5322 states that messages should only use CRLF line endings. Here
are two important excerpts:

> 2.1. General Description
>
>  Messages are divided into lines of characters.  A line is a series of
>  characters that is delimited with the two characters carriage-return
>  and line-feed; that is, the carriage return (CR) character (ASCII
>  value 13) followed immediately by the line feed (LF) character (ASCII
>  value 10).  (The carriage return/line feed pair is usually written in
>  this document as "CRLF".)

> 2.3.  Body
>
>  The body of a message is simply lines of US-ASCII characters.  The
>  only two limitations on the body are as follows:
>
>  o  CR and LF MUST only occur together as CRLF; they MUST NOT appear
>     independently in the body.
>  o  Lines of characters in the body MUST be limited to 998 characters,
>     and SHOULD be limited to 78 characters, excluding the CRLF.

Most MTA are tolerant to invalid messages but some others are more
pedantic. Ensure we only send valid line endings.

Link: https://www.rfc-editor.org/rfc/rfc5322.html#section-2.1
Link: https://www.rfc-editor.org/rfc/rfc5322.html#section-2.3
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Matěj Cepl <mcepl@cepl.eu>
This commit is contained in:
Robin Jarry
2025-01-07 00:32:48 +01:00
parent 7356be2be5
commit cb1c4c9c62

View File

@@ -1,6 +1,8 @@
package send
import (
"bufio"
"bytes"
"fmt"
"io"
"net/url"
@@ -23,14 +25,45 @@ func NewSender(
return nil, err
}
var w io.WriteCloser
switch protocol {
case "smtp", "smtp+insecure", "smtps":
return newSmtpSender(protocol, auth, uri, domain, from, rcpts)
w, err = newSmtpSender(protocol, auth, uri, domain, from, rcpts)
case "jmap":
return newJmapSender(worker, from, rcpts, copyTo)
w, err = newJmapSender(worker, from, rcpts, copyTo)
case "":
return newSendmailSender(uri, rcpts)
w, err = newSendmailSender(uri, rcpts)
default:
return nil, fmt.Errorf("unsupported protocol %s", protocol)
err = fmt.Errorf("unsupported protocol %s", protocol)
}
if err != nil {
return nil, err
}
return &crlfWriter{w: w}, nil
}
type crlfWriter struct {
w io.WriteCloser
buf bytes.Buffer
}
func (w *crlfWriter) Write(p []byte) (int, error) {
return w.buf.Write(p)
}
func (w *crlfWriter) Close() error {
defer w.w.Close() // ensure closed even on error
scan := bufio.NewScanner(&w.buf)
for scan.Scan() {
if _, err := w.w.Write(append(scan.Bytes(), '\r', '\n')); err != nil {
return nil
}
}
if scan.Err() != nil {
return scan.Err()
}
return w.w.Close()
}