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