6 "github.com/mjl-/mox/smtp"
9// ReferencedIDs returns the Message-IDs referenced from the References header(s),
10// with a fallback to the In-Reply-To header(s). The ids are canonicalized for
11// thread-matching, like with MessageIDCanonical. Empty message-id's are skipped.
12func ReferencedIDs(references []string, inReplyTo []string) ([]string, error) {
13 var refids []string // In thread-canonical form.
15 // parse and add 0 or 1 reference, returning the remaining refs string for a next attempt.
16 parse1 := func(refs string, one bool) string {
17 refs = strings.TrimLeft(refs, " \t\r\n")
18 if !strings.HasPrefix(refs, "<") {
19 // To make progress, we skip to next space or >.
20 i := strings.IndexAny(refs, " >")
27 // Look for the ending > or next <. If < is before >, this entry is truncated.
28 i := strings.IndexAny(refs, "<>")
33 // Truncated entry, we ignore it.
36 ref := strings.ToLower(refs[:i])
37 // Some MUAs wrap References line in the middle of message-id's, and others
38 // recombine them. Take out bare WSP in message-id's.
39 ref = strings.ReplaceAll(ref, " ", "")
40 ref = strings.ReplaceAll(ref, "\t", "")
42 // Canonicalize the quotedness of the message-id.
43 addr, err := smtp.ParseAddress(ref)
45 // Leave the hostname form intact.
46 t := strings.Split(ref, "@")
47 ref = addr.Localpart.String() + "@" + t[len(t)-1]
49 // log.Errorx("assigning threads: bad reference in references header, using raw value", err, mlog.Field("msgid", mid), mlog.Field("reference", ref))
51 refids = append(refids, ref)
56 // References is the modern way (for a long time already) to reference ancestors.
57 // The direct parent is typically at the end of the list.
58 for _, refs := range references {
60 refs = parse1(refs, false)
63 // We only look at the In-Reply-To header if we didn't find any References.
65 for _, s := range inReplyTo {