1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
package mailstuff
import (
"fmt"
"net/mail"
"regexp"
"strings"
)
type Set[T comparable] map[T]struct{}
func (s Set[T]) Insert(val T) {
s[val] = struct{}{}
}
func mapHas[K comparable, V any](m map[K]V, k K) bool {
_, ok := m[k]
return ok
}
func (s Set[T]) Has(val T) bool {
return mapHas(s, val)
}
func (s Set[T]) PickOne() T {
for v := range s {
return v
}
var zero T
return zero
}
type MessageID string
type ThreadedMessage struct {
*mail.Message
Parent *ThreadedMessage
Children Set[*ThreadedMessage]
}
var reReplyID = regexp.MustCompile("<[^> \t\r\n]+>")
func rfc2822parse(msg *mail.Message) *jwzMessage {
// TODO: This is bad, and needs a real implementation.
ret := &jwzMessage{
Subject: msg.Header.Get("Subject"),
ID: jwzID(msg.Header.Get("Message-ID")),
}
refIDs := strings.Fields(msg.Header.Get("References"))
strings.Fields(msg.Header.Get("References"))
if replyID := reReplyID.FindString(msg.Header.Get("In-Reply-To")); replyID != "" {
refIDs = append(refIDs, replyID)
}
ret.References = make([]jwzID, len(refIDs))
for i := range refIDs {
ret.References[i] = jwzID(refIDs[i])
}
return ret
}
func ThreadMessages(msgs []*mail.Message) (Set[*ThreadedMessage], map[MessageID]*ThreadedMessage) {
jwzMsgs := make(map[jwzID]*jwzMessage, len(msgs))
retMsgs := make(map[jwzID]*ThreadedMessage, len(msgs))
bogusCnt := 0
for _, msg := range msgs {
jwzMsg := rfc2822parse(msg)
// RFC 5256:
//
// If a message does not contain a Message-ID header
// line, or the Message-ID header line does not
// contain a valid Message ID, then assign a unique
// Message ID to this message.
//
// If two or more messages have the same Message ID,
// then only use that Message ID in the first (lowest
// sequence number) message, and assign a unique
// Message ID to each of the subsequent messages with
// a duplicate of that Message ID.
for jwzMsg.ID == "" || mapHas(jwzMsgs, jwzMsg.ID) {
jwzMsg.ID = jwzID(fmt.Sprintf("bogus.%d", bogusCnt))
bogusCnt++
}
jwzMsgs[jwzMsg.ID] = jwzMsg
retMsgs[jwzMsg.ID] = &ThreadedMessage{
Message: msg,
}
}
jwzThreads := jwzThreadMessages(jwzMsgs)
var convertMessage func(*jwzContainer) *ThreadedMessage
convertMessage = func(in *jwzContainer) *ThreadedMessage {
var out *ThreadedMessage
if in.Message == nil {
out = new(ThreadedMessage)
} else {
out = retMsgs[in.Message.ID]
}
out.Children = make(Set[*ThreadedMessage], len(in.Children))
for inChild := range in.Children {
outChild := convertMessage(inChild)
out.Children.Insert(outChild)
outChild.Parent = out
}
return out
}
retThreads := make(Set[*ThreadedMessage], len(jwzThreads))
for inThread := range jwzThreads {
retThreads.Insert(convertMessage(inThread))
}
return retThreads, retMsgs
}
|