10 "github.com/mjl-/mox/dns"
11 "github.com/mjl-/mox/mlog"
12 "github.com/mjl-/mox/smtp"
16 errFromHeaders = errors.New("missing or duplicate from header")
17 errFromAddresses = errors.New("from header does not have exactly one address")
20// From extracts the address in the From-header.
22// An RFC5322 message must have a From header.
23// In theory, multiple addresses may be present. In practice zero or multiple
24// From headers may be present. From returns an error if there is not exactly
25// one From header with exactly one address. This address can be used for
26// evaluating a DMARC policy against SPF and DKIM results.
27func From(elog *slog.Logger, strict bool, r io.ReaderAt, p *Part) (raddr smtp.Address, envelope *Envelope, header textproto.MIMEHeader, rerr error) {
28 log := mlog.New("message", elog)
32 // todo: only allow utf8 if enabled in session/message?
37 pp, err = Parse(log.Logger, strict, r)
39 // todo: should we continue with p, perhaps headers can be parsed?
40 return raddr, nil, nil, fmt.Errorf("parsing message: %v", err)
44 header, err = p.Header()
46 return raddr, nil, nil, fmt.Errorf("parsing message header: %v", err)
49 // Multiple From headers are only allowed by the obsolete syntax, which leaves
51 // We would use the first, while DKIM signs the physically last instance
53 // covered by the signature need not be the same. Reject instead of picking.
54 if l := header.Values("From"); len(l) != 1 {
55 return raddr, nil, nil, fmt.Errorf("%w: %d headers", errFromHeaders, len(l))
57 from := p.Envelope.From
59 return raddr, nil, nil, fmt.Errorf("%w: %d addresses", errFromAddresses, len(from))
61 d, err := dns.ParseDomain(from[0].Host)
63 return raddr, nil, nil, fmt.Errorf("bad domain in from address: %v", err)
65 lp, err := smtp.ParseLocalpart(from[0].User)
67 return raddr, nil, nil, fmt.Errorf("parsing localpart in from address: %v", err)
69 addr := smtp.NewAddress(lp, d)
70 return addr, p.Envelope, textproto.MIMEHeader(header), nil