-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparser.go
More file actions
111 lines (95 loc) · 2.62 KB
/
parser.go
File metadata and controls
111 lines (95 loc) · 2.62 KB
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
package bounce_parser
import (
"errors"
"io"
"strings"
"github.com/emersion/go-message/mail"
)
func Parse(subject string, mr *mail.Reader) (BounceInfo, error) {
if contains(subject, HardBounceSubjectList) {
return BounceInfo{Type: BounceHard, Reason: "Mailbox unreachable", Subject: subject, Mailbox: extractMailbox(mr)}, nil
}
for {
p, err := mr.NextPart()
if err != nil {
break
}
switch p.Header.(type) {
case *mail.InlineHeader, *mail.Header:
body, err := readBody(p.Body)
if err != nil {
return BounceInfo{}, err
}
if contains(body, HardBounceBodyList) {
return BounceInfo{Type: BounceHard, Reason: "Hard bounce body", Subject: subject, Mailbox: extractMailbox(mr)}, nil
}
if contains(body, SoftBounceBodyList) {
return BounceInfo{Type: BounceSoft, Reason: "Soft bounce body", Subject: subject, Mailbox: extractMailbox(mr)}, nil
}
}
}
return BounceInfo{Type: BounceNone, Subject: subject}, nil
}
// contains checks if the string contains any of the patterns
func contains(s string, patterns []string) bool {
s = strings.ToLower(s)
for _, p := range patterns {
if strings.Contains(s, p) {
return true
}
}
return false
}
// readBody read body
func readBody(r io.Reader) (string, error) {
buf := new(strings.Builder)
_, err := io.Copy(buf, r)
if err != nil {
return "", errors.New(err.Error() + ": " + buf.String())
}
return strings.ToLower(buf.String()), nil
}
func extractMailbox(mr *mail.Reader) string {
// Global header for gmail X-Failed-Recipients
if failed := mr.Header.Get("X-Failed-Recipients"); failed != "" {
return strings.TrimSpace(failed)
}
// for other (Delivery-Status, RFC822)
for {
p, err := mr.NextPart()
if err != nil {
break
}
contentType := strings.ToLower(p.Header.Get("Content-Type"))
// message/delivery-status
if strings.HasPrefix(contentType, "message/delivery-status") {
body, err := readBody(p.Body)
if err != nil {
continue
}
lines := strings.Split(body, "\n")
for _, line := range lines {
lowerLine := strings.ToLower(line)
if strings.HasPrefix(lowerLine, "final-recipient:") ||
strings.HasPrefix(lowerLine, "original-recipient:") ||
strings.HasPrefix(lowerLine, "x-failed-recipients:") {
parts := strings.Split(line, ";")
if len(parts) > 1 {
return strings.TrimSpace(parts[len(parts)-1])
}
}
}
}
// message/rfc822
if strings.HasPrefix(contentType, "message/rfc822") {
subReader, err := mail.CreateReader(p.Body)
if err != nil {
continue
}
if to := subReader.Header.Get("To"); to != "" {
return strings.TrimSpace(to)
}
}
}
return ""
}