mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 18:24:35 +03:00
expose fewer internals in packages, for easier software reuse
- prometheus is now behind an interface, they aren't dependencies for the reusable components anymore. - some dependencies have been inverted: instead of packages importing a main package to get configuration, the main package now sets configuration in these packages. that means fewer internals are pulled in. - some functions now have new parameters for values that were retrieved from package "mox-".
This commit is contained in:
27
message/decode.go
Normal file
27
message/decode.go
Normal file
@ -0,0 +1,27 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/encoding/ianaindex"
|
||||
)
|
||||
|
||||
// DecodeReader returns a reader that reads from r, decoding as charset. If
|
||||
// charset is empty, us-ascii, utf-8 or unknown, the original reader is
|
||||
// returned and no decoding takes place.
|
||||
func DecodeReader(charset string, r io.Reader) io.Reader {
|
||||
switch strings.ToLower(charset) {
|
||||
case "", "us-ascii", "utf-8":
|
||||
return r
|
||||
}
|
||||
enc, _ := ianaindex.MIME.Encoding(charset)
|
||||
if enc == nil {
|
||||
enc, _ = ianaindex.IANA.Encoding(charset)
|
||||
}
|
||||
// todo: ianaindex doesn't know all encodings, e.g. gb2312. should we transform them, with which code?
|
||||
if enc == nil {
|
||||
return r
|
||||
}
|
||||
return enc.NewDecoder().Reader(r)
|
||||
}
|
24
message/decode_test.go
Normal file
24
message/decode_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeReader(t *testing.T) {
|
||||
check := func(charset, input, output string) {
|
||||
t.Helper()
|
||||
buf, err := io.ReadAll(DecodeReader(charset, strings.NewReader(input)))
|
||||
tcheck(t, err, "decode")
|
||||
if string(buf) != output {
|
||||
t.Fatalf("decoding %q with charset %q, got %q, expected %q", input, charset, buf, output)
|
||||
}
|
||||
}
|
||||
|
||||
check("", "☺", "☺") // No decoding.
|
||||
check("us-ascii", "☺", "☺") // No decoding.
|
||||
check("utf-8", "☺", "☺")
|
||||
check("iso-8859-1", string([]byte{0xa9}), "©")
|
||||
check("iso-8859-5", string([]byte{0xd0}), "а")
|
||||
}
|
@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mjl-/mox/moxvar"
|
||||
"github.com/mjl-/mox/smtp"
|
||||
)
|
||||
|
||||
@ -29,7 +28,7 @@ func MessageIDCanonical(s string) (string, bool, error) {
|
||||
// Seen in practice: Message-ID: <valid@valid.example> (added by postmaster@some.example)
|
||||
// Doesn't seem valid, but we allow it.
|
||||
s, rem, have := strings.Cut(s, ">")
|
||||
if !have || (rem != "" && (moxvar.Pedantic || !strings.HasPrefix(rem, " "))) {
|
||||
if !have || (rem != "" && (Pedantic || !strings.HasPrefix(rem, " "))) {
|
||||
return "", false, fmt.Errorf("%w: missing >", errBadMessageID)
|
||||
}
|
||||
// We canonicalize the Message-ID: lower-case, no unneeded quoting.
|
||||
|
@ -25,11 +25,12 @@ import (
|
||||
"golang.org/x/text/encoding/ianaindex"
|
||||
|
||||
"github.com/mjl-/mox/mlog"
|
||||
"github.com/mjl-/mox/moxio"
|
||||
"github.com/mjl-/mox/moxvar"
|
||||
"github.com/mjl-/mox/smtp"
|
||||
)
|
||||
|
||||
// Pedantic enables stricter parsing.
|
||||
var Pedantic bool
|
||||
|
||||
var (
|
||||
ErrBadContentType = errors.New("bad content-type")
|
||||
)
|
||||
@ -207,7 +208,7 @@ func (p *Part) Walk(elog *slog.Logger, parent *Part) error {
|
||||
// If this is a DSN and we are not in pedantic mode, accept unexpected end of
|
||||
// message. This is quite common because MTA's sometimes just truncate the original
|
||||
// message in a place that makes the message invalid.
|
||||
if errors.Is(err, errUnexpectedEOF) && !moxvar.Pedantic && parent != nil && len(parent.Parts) >= 3 && p == &parent.Parts[2] && parent.MediaType == "MULTIPART" && parent.MediaSubType == "REPORT" {
|
||||
if errors.Is(err, errUnexpectedEOF) && !Pedantic && parent != nil && len(parent.Parts) >= 3 && p == &parent.Parts[2] && parent.MediaType == "MULTIPART" && parent.MediaSubType == "REPORT" {
|
||||
mp, err = fallbackPart(mp, br, int64(len(buf)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing invalid embedded message: %w", err)
|
||||
@ -305,7 +306,7 @@ func newPart(log mlog.Log, strict bool, r io.ReaderAt, offset int64, parent *Par
|
||||
ct := p.header.Get("Content-Type")
|
||||
mt, params, err := mime.ParseMediaType(ct)
|
||||
if err != nil && ct != "" {
|
||||
if moxvar.Pedantic || strict {
|
||||
if Pedantic || strict {
|
||||
return p, fmt.Errorf("%w: %s: %q", ErrBadContentType, err, ct)
|
||||
}
|
||||
|
||||
@ -337,7 +338,7 @@ func newPart(log mlog.Log, strict bool, r io.ReaderAt, offset int64, parent *Par
|
||||
} else if mt != "" {
|
||||
t := strings.SplitN(strings.ToUpper(mt), "/", 2)
|
||||
if len(t) != 2 {
|
||||
if moxvar.Pedantic || strict {
|
||||
if Pedantic || strict {
|
||||
return p, fmt.Errorf("bad content-type: %q (content-type %q)", mt, ct)
|
||||
}
|
||||
log.Debug("malformed media-type, ignoring and continuing", slog.String("type", mt))
|
||||
@ -584,7 +585,7 @@ func (p *Part) Reader() io.Reader {
|
||||
// already). For unknown or missing character sets/encodings, the original reader
|
||||
// is returned.
|
||||
func (p *Part) ReaderUTF8OrBinary() io.Reader {
|
||||
return moxio.DecodeReader(p.ContentTypeParams["charset"], p.Reader())
|
||||
return DecodeReader(p.ContentTypeParams["charset"], p.Reader())
|
||||
}
|
||||
|
||||
func (p *Part) bodyReader(r io.Reader) io.Reader {
|
||||
@ -689,7 +690,7 @@ func (r *crlfReader) Read(buf []byte) (int, error) {
|
||||
if b == '\n' && !r.prevcr {
|
||||
err = errBareLF
|
||||
break
|
||||
} else if b != '\n' && r.prevcr && (r.strict || moxvar.Pedantic) {
|
||||
} else if b != '\n' && r.prevcr && (r.strict || Pedantic) {
|
||||
err = errBareCR
|
||||
break
|
||||
}
|
||||
@ -719,7 +720,7 @@ type bufAt struct {
|
||||
const maxLineLength = 8 * 1024
|
||||
|
||||
func (b *bufAt) maxLineLength() int {
|
||||
if b.strict || moxvar.Pedantic {
|
||||
if b.strict || Pedantic {
|
||||
return 1000
|
||||
}
|
||||
return maxLineLength
|
||||
@ -777,7 +778,7 @@ func (b *bufAt) line(consume, requirecrlf bool) (buf []byte, crlf bool, err erro
|
||||
}
|
||||
i++
|
||||
if i >= b.nbuf || b.buf[i] != '\n' {
|
||||
if b.strict || moxvar.Pedantic {
|
||||
if b.strict || Pedantic {
|
||||
return nil, false, errBareCR
|
||||
}
|
||||
continue
|
||||
@ -833,7 +834,7 @@ func (r *offsetReader) Read(buf []byte) (int, error) {
|
||||
if n > 0 {
|
||||
r.offset += int64(n)
|
||||
max := maxLineLength
|
||||
if r.strict || moxvar.Pedantic {
|
||||
if r.strict || Pedantic {
|
||||
max = 1000
|
||||
}
|
||||
|
||||
@ -844,7 +845,7 @@ func (r *offsetReader) Read(buf []byte) (int, error) {
|
||||
if err == nil || err == io.EOF {
|
||||
if c == '\n' && !r.prevcr {
|
||||
err = errBareLF
|
||||
} else if c != '\n' && r.prevcr && (r.strict || moxvar.Pedantic) {
|
||||
} else if c != '\n' && r.prevcr && (r.strict || Pedantic) {
|
||||
err = errBareCR
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/mjl-/mox/mlog"
|
||||
"github.com/mjl-/mox/moxvar"
|
||||
)
|
||||
|
||||
var pkglog = mlog.New("message", nil)
|
||||
@ -54,7 +53,7 @@ func TestBadContentType(t *testing.T) {
|
||||
expBody := "test"
|
||||
|
||||
// Pedantic is like strict.
|
||||
moxvar.Pedantic = true
|
||||
Pedantic = true
|
||||
s := "content-type: text/html;;\r\n\r\ntest"
|
||||
p, err := EnsurePart(pkglog.Logger, false, strings.NewReader(s), int64(len(s)))
|
||||
tfail(t, err, ErrBadContentType)
|
||||
@ -63,7 +62,7 @@ func TestBadContentType(t *testing.T) {
|
||||
tcompare(t, string(buf), expBody)
|
||||
tcompare(t, p.MediaType, "APPLICATION")
|
||||
tcompare(t, p.MediaSubType, "OCTET-STREAM")
|
||||
moxvar.Pedantic = false
|
||||
Pedantic = false
|
||||
|
||||
// Strict
|
||||
s = "content-type: text/html;;\r\n\r\ntest"
|
||||
@ -111,12 +110,12 @@ func TestBareCR(t *testing.T) {
|
||||
expBody := "bare\rcr\r\n"
|
||||
|
||||
// Pedantic is like strict.
|
||||
moxvar.Pedantic = true
|
||||
Pedantic = true
|
||||
p, err := EnsurePart(pkglog.Logger, false, strings.NewReader(s), int64(len(s)))
|
||||
tfail(t, err, errBareCR)
|
||||
_, err = io.ReadAll(p.Reader())
|
||||
tfail(t, err, errBareCR)
|
||||
moxvar.Pedantic = false
|
||||
Pedantic = false
|
||||
|
||||
// Strict.
|
||||
p, err = EnsurePart(pkglog.Logger, true, strings.NewReader(s), int64(len(s)))
|
||||
@ -291,12 +290,12 @@ func TestBareCrLf(t *testing.T) {
|
||||
err = parse(false, "\r\ntest\ntest\r\n")
|
||||
tfail(t, err, errBareLF)
|
||||
|
||||
moxvar.Pedantic = true
|
||||
Pedantic = true
|
||||
err = parse(false, "subject: test\rtest\r\n")
|
||||
tfail(t, err, errBareCR)
|
||||
err = parse(false, "\r\ntest\rtest\r\n")
|
||||
tfail(t, err, errBareCR)
|
||||
moxvar.Pedantic = false
|
||||
Pedantic = false
|
||||
|
||||
err = parse(true, "subject: test\rtest\r\n")
|
||||
tfail(t, err, errBareCR)
|
||||
|
Reference in New Issue
Block a user