1package message
2
3import (
4 "strings"
5)
6
7// ThreadSubject returns the base subject to use for matching against other
8// messages, to see if they belong to the same thread. A matching subject is
9// always required to match to an existing thread, both if
10// References/In-Reply-To header(s) are present, and if not.
11//
12// isResponse indicates if this message is a response, such as a reply or a
13// forward.
14//
15// Subject should already be q/b-word-decoded.
16//
17// If allowNull is true, base subjects with a \0 can be returned. If not set,
18// an empty string is returned if a base subject would have a \0.
19func ThreadSubject(subject string, allowNull bool) (threadSubject string, isResponse bool) {
20 subject = strings.ToLower(subject)
21
22 // ../rfc/5256:101, Step 1.
23 var s string
24 for _, c := range subject {
25 if c == '\r' {
26 continue
27 } else if c == ' ' || c == '\n' || c == '\t' {
28 if !strings.HasSuffix(s, " ") {
29 s += " "
30 }
31 } else {
32 s += string(c)
33 }
34 }
35
36 // ../rfc/5256:107 ../rfc/5256:811, removing mailing list tag "[...]" and reply/forward "re"/"fwd" prefix.
37 removeBlob := func(s string) string {
38 for i, c := range s {
39 if i == 0 {
40 if c != '[' {
41 return s
42 }
43 } else if c == '[' {
44 return s
45 } else if c == ']' {
46 s = s[i+1:] // Past [...].
47 s = strings.TrimRight(s, " \t") // *WSP
48 return s
49 }
50 }
51 return s
52 }
53 // ../rfc/5256:107 ../rfc/5256:811
54 removeLeader := func(s string) string {
55 if strings.HasPrefix(s, " ") || strings.HasPrefix(s, "\t") {
56 s = s[1:] // WSP
57 }
58
59 orig := s
60
61 // Remove zero or more subj-blob
62 for {
63 prevs := s
64 s = removeBlob(s)
65 if prevs == s {
66 break
67 }
68 }
69
70 if strings.HasPrefix(s, "re") {
71 s = s[2:]
72 } else if strings.HasPrefix(s, "fwd") {
73 s = s[3:]
74 } else if strings.HasPrefix(s, "fw") {
75 s = s[2:]
76 } else {
77 return orig
78 }
79 s = strings.TrimLeft(s, " \t") // *WSP
80 s = removeBlob(s)
81 if !strings.HasPrefix(s, ":") {
82 return orig
83 }
84 s = s[1:]
85 isResponse = true
86 return s
87 }
88
89 for {
90 // ../rfc/5256:104 ../rfc/5256:817, remove trailing "(fwd)" or WSP, Step 2.
91 for {
92 prevs := s
93 s = strings.TrimRight(s, " \t")
94 if strings.HasSuffix(s, "(fwd)") {
95 s = strings.TrimSuffix(s, "(fwd)")
96 isResponse = true
97 }
98 if s == prevs {
99 break
100 }
101 }
102
103 for {
104 prevs := s
105 s = removeLeader(s) // Step 3.
106 if ns := removeBlob(s); ns != "" {
107 s = ns // Step 4.
108 }
109 // Step 5, ../rfc/5256:123
110 if s == prevs {
111 break
112 }
113 }
114
115 // Step 6. ../rfc/5256:128 ../rfc/5256:805
116 if strings.HasPrefix(s, "[fwd:") && strings.HasSuffix(s, "]") {
117 s = s[len("[fwd:") : len(s)-1]
118 isResponse = true
119 continue // From step 2 again.
120 }
121 break
122 }
123 if !allowNull && strings.ContainsRune(s, 0) {
124 s = ""
125 }
126 return s, isResponse
127}
128