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
115
116
117
118
119
120
|
// Copyright 2023 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package lore
import (
"regexp"
"sort"
"strings"
"github.com/google/syzkaller/dashboard/dashapi"
"github.com/google/syzkaller/pkg/email"
)
type Thread struct {
Subject string
MessageID string
Type dashapi.DiscussionType
BugIDs []string
Messages []*email.Email
}
// Threads extracts individual threads from a list of emails.
func Threads(emails []*email.Email) []*Thread {
ctx := &parseCtx{
messages: map[string]*email.Email{},
next: map[*email.Email][]*email.Email{},
}
for _, email := range emails {
ctx.record(email)
}
ctx.process()
return ctx.threads
}
// DiscussionType extracts the specific discussion type from an email.
func DiscussionType(msg *email.Email) dashapi.DiscussionType {
discType := dashapi.DiscussionMention
if msg.OwnEmail {
discType = dashapi.DiscussionReport
}
// This is very crude, but should work for now.
if patchSubjectRe.MatchString(strings.ToLower(msg.Subject)) {
discType = dashapi.DiscussionPatch
} else if strings.Contains(msg.Subject, "Monthly") {
discType = dashapi.DiscussionReminder
}
return discType
}
var patchSubjectRe = regexp.MustCompile(`\[(?:(?:rfc|resend)\s+)*patch`)
type parseCtx struct {
threads []*Thread
messages map[string]*email.Email
next map[*email.Email][]*email.Email
}
func (c *parseCtx) record(msg *email.Email) {
c.messages[msg.MessageID] = msg
}
func (c *parseCtx) process() {
// List messages for which we dont't have ancestors.
nodes := []*email.Email{}
for _, msg := range c.messages {
if msg.InReplyTo == "" || c.messages[msg.InReplyTo] == nil {
nodes = append(nodes, msg)
} else {
parent := c.messages[msg.InReplyTo]
c.next[parent] = append(c.next[parent], msg)
}
}
// Iterate starting from these tree nodes.
for _, node := range nodes {
c.visit(node, nil)
}
// Collect BugIDs.
for _, thread := range c.threads {
unique := map[string]struct{}{}
for _, msg := range thread.Messages {
for _, id := range msg.BugIDs {
unique[id] = struct{}{}
}
}
var ids []string
for id := range unique {
ids = append(ids, id)
}
sort.Strings(ids)
thread.BugIDs = ids
}
}
func (c *parseCtx) visit(msg *email.Email, thread *Thread) {
var oldInfo *email.OldThreadInfo
if thread != nil {
oldInfo = &email.OldThreadInfo{
ThreadType: thread.Type,
}
}
msgType := DiscussionType(msg)
switch email.NewMessageAction(msg, msgType, oldInfo) {
case email.ActionIgnore:
thread = nil
case email.ActionAppend:
thread.Messages = append(thread.Messages, msg)
case email.ActionNewThread:
thread = &Thread{
MessageID: msg.MessageID,
Subject: msg.Subject,
Type: msgType,
Messages: []*email.Email{msg},
}
c.threads = append(c.threads, thread)
}
for _, nextMsg := range c.next[msg] {
c.visit(nextMsg, thread)
}
}
|