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:
Mechiel Lukkien
2023-12-05 21:13:57 +01:00
parent fcaa504878
commit 72ac1fde29
51 changed files with 696 additions and 568 deletions

27
message/decode.go Normal file
View 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
View 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}), "а")
}

View File

@ -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.

View File

@ -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
}
}

View File

@ -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)