1package message
2
3import (
4 "fmt"
5 "io"
6 "log/slog"
7 "net/textproto"
8
9 "github.com/mjl-/mox/dns"
10 "github.com/mjl-/mox/mlog"
11 "github.com/mjl-/mox/smtp"
12)
13
14// From extracts the address in the From-header.
15//
16// An RFC5322 message must have a From header.
17// In theory, multiple addresses may be present. In practice zero or multiple
18// From headers may be present. From returns an error if there is not exactly
19// one address. This address can be used for evaluating a DMARC policy against
20// SPF and DKIM results.
21func From(elog *slog.Logger, strict bool, r io.ReaderAt, p *Part) (raddr smtp.Address, envelope *Envelope, header textproto.MIMEHeader, rerr error) {
22 log := mlog.New("message", elog)
23
24 // ../rfc/7489:1243
25
26 // todo: only allow utf8 if enabled in session/message?
27
28 var err error
29 if p == nil {
30 var pp Part
31 pp, err = Parse(log.Logger, strict, r)
32 if err != nil {
33 // todo: should we continue with p, perhaps headers can be parsed?
34 return raddr, nil, nil, fmt.Errorf("parsing message: %v", err)
35 }
36 p = &pp
37 }
38 header, err = p.Header()
39 if err != nil {
40 return raddr, nil, nil, fmt.Errorf("parsing message header: %v", err)
41 }
42 from := p.Envelope.From
43 if len(from) != 1 {
44 return raddr, nil, nil, fmt.Errorf("from header has %d addresses, need exactly 1 address", len(from))
45 }
46 d, err := dns.ParseDomain(from[0].Host)
47 if err != nil {
48 return raddr, nil, nil, fmt.Errorf("bad domain in from address: %v", err)
49 }
50 lp, err := smtp.ParseLocalpart(from[0].User)
51 if err != nil {
52 return raddr, nil, nil, fmt.Errorf("parsing localpart in from address: %v", err)
53 }
54 addr := smtp.NewAddress(lp, d)
55 return addr, p.Envelope, textproto.MIMEHeader(header), nil
56}
57