github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/email/lore/parse.go (about) 1 // Copyright 2023 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package lore 5 6 import ( 7 "regexp" 8 "sort" 9 "strings" 10 11 "github.com/google/syzkaller/dashboard/dashapi" 12 "github.com/google/syzkaller/pkg/email" 13 ) 14 15 type Thread struct { 16 Subject string 17 MessageID string 18 Type dashapi.DiscussionType 19 BugIDs []string 20 Messages []*email.Email 21 } 22 23 // Threads extracts individual threads from a list of emails. 24 func Threads(emails []*email.Email) []*Thread { 25 ctx := &parseCtx{ 26 messages: map[string]*email.Email{}, 27 next: map[*email.Email][]*email.Email{}, 28 } 29 for _, email := range emails { 30 ctx.record(email) 31 } 32 ctx.process() 33 return ctx.threads 34 } 35 36 // DiscussionType extracts the specific discussion type from an email. 37 func DiscussionType(msg *email.Email) dashapi.DiscussionType { 38 discType := dashapi.DiscussionMention 39 if msg.OwnEmail { 40 discType = dashapi.DiscussionReport 41 } 42 // This is very crude, but should work for now. 43 if patchSubjectRe.MatchString(strings.ToLower(msg.Subject)) { 44 discType = dashapi.DiscussionPatch 45 } else if strings.Contains(msg.Subject, "Monthly") { 46 discType = dashapi.DiscussionReminder 47 } 48 return discType 49 } 50 51 var patchSubjectRe = regexp.MustCompile(`\[(?:(?:rfc|resend)\s+)*patch`) 52 53 type parseCtx struct { 54 threads []*Thread 55 messages map[string]*email.Email 56 next map[*email.Email][]*email.Email 57 } 58 59 func (c *parseCtx) record(msg *email.Email) { 60 c.messages[msg.MessageID] = msg 61 } 62 63 func (c *parseCtx) process() { 64 // List messages for which we dont't have ancestors. 65 nodes := []*email.Email{} 66 for _, msg := range c.messages { 67 if msg.InReplyTo == "" || c.messages[msg.InReplyTo] == nil { 68 nodes = append(nodes, msg) 69 } else { 70 parent := c.messages[msg.InReplyTo] 71 c.next[parent] = append(c.next[parent], msg) 72 } 73 } 74 // Iterate starting from these tree nodes. 75 for _, node := range nodes { 76 c.visit(node, nil) 77 } 78 // Collect BugIDs. 79 for _, thread := range c.threads { 80 unique := map[string]struct{}{} 81 for _, msg := range thread.Messages { 82 for _, id := range msg.BugIDs { 83 unique[id] = struct{}{} 84 } 85 } 86 var ids []string 87 for id := range unique { 88 ids = append(ids, id) 89 } 90 sort.Strings(ids) 91 thread.BugIDs = ids 92 } 93 } 94 95 func (c *parseCtx) visit(msg *email.Email, thread *Thread) { 96 var oldInfo *email.OldThreadInfo 97 if thread != nil { 98 oldInfo = &email.OldThreadInfo{ 99 ThreadType: thread.Type, 100 } 101 } 102 msgType := DiscussionType(msg) 103 switch email.NewMessageAction(msg, msgType, oldInfo) { 104 case email.ActionIgnore: 105 thread = nil 106 case email.ActionAppend: 107 thread.Messages = append(thread.Messages, msg) 108 case email.ActionNewThread: 109 thread = &Thread{ 110 MessageID: msg.MessageID, 111 Subject: msg.Subject, 112 Type: msgType, 113 Messages: []*email.Email{msg}, 114 } 115 c.threads = append(c.threads, thread) 116 } 117 for _, nextMsg := range c.next[msg] { 118 c.visit(nextMsg, thread) 119 } 120 }