13 "github.com/mjl-/mox/dns"
14 "github.com/mjl-/mox/message"
15 "github.com/mjl-/mox/mlog"
16 "github.com/mjl-/mox/smtp"
19var pkglog = mlog.New("dsn", nil)
21func xparseDomain(s string) dns.Domain {
22 d, err := dns.ParseDomain(s)
24 panic(fmt.Sprintf("parsing domain %q: %v", s, err))
29func xparseIPDomain(s string) dns.IPDomain {
30 return dns.IPDomain{Domain: xparseDomain(s)}
33func tparseMessage(t *testing.T, data []byte, nparts int) (*Message, *message.Part) {
35 m, p, err := Parse(pkglog.Logger, bytes.NewReader(data))
37 t.Fatalf("parsing dsn: %v", err)
39 if len(p.Parts) != nparts {
40 t.Fatalf("got %d parts, expected %d", len(p.Parts), nparts)
45func tcheckType(t *testing.T, p *message.Part, mt, mst, cte string) {
47 if !strings.EqualFold(p.MediaType, mt) {
48 t.Fatalf("got mediatype %q, expected %q", p.MediaType, mt)
50 if !strings.EqualFold(p.MediaSubType, mst) {
51 t.Fatalf("got mediasubtype %q, expected %q", p.MediaSubType, mst)
53 if !strings.EqualFold(p.ContentTransferEncoding, cte) {
54 t.Fatalf("got content-transfer-encoding %q, expected %q", p.ContentTransferEncoding, cte)
58func tcompare(t *testing.T, got, exp any) {
60 if !reflect.DeepEqual(got, exp) {
61 t.Fatalf("got %#v, expected %#v", got, exp)
65func tcompareReader(t *testing.T, r io.Reader, exp []byte) {
67 buf, err := io.ReadAll(r)
69 t.Fatalf("data read, got %q, expected %q", buf, exp)
73func TestDSN(t *testing.T) {
74 log := mlog.New("dsn", nil)
78 // An ascii-only message.
82 From: smtp.Path{Localpart: "postmaster", IPDomain: xparseIPDomain("mox.example")},
83 To: smtp.Path{Localpart: "mjl", IPDomain: xparseIPDomain("remote.example")},
85 MessageID: "test@localhost",
86 TextBody: "delivery failure\n",
88 ReportingMTA: "mox.example",
89 ReceivedFromMTA: smtp.Ehlo{Name: xparseIPDomain("relay.example"), ConnIP: net.ParseIP("10.10.10.10")},
91 FutureReleaseRequest: "for;123",
93 Recipients: []Recipient{
95 FinalRecipient: smtp.Path{Localpart: "mjl", IPDomain: xparseIPDomain("remote.example")},
102 Original: []byte("Subject: test\r\n"),
104 msgbuf, err := m.Compose(log, false)
106 t.Fatalf("composing dsn: %v", err)
109 pmsg, part := tparseMessage(t, msgbuf, 3)
110 tcheckType(t, part, "multipart", "report", "")
111 tcheckType(t, &part.Parts[0], "text", "plain", "7bit")
112 tcheckType(t, &part.Parts[1], "message", "delivery-status", "7bit")
113 tcheckType(t, &part.Parts[2], "text", "rfc822-headers", "7bit")
114 tcompare(t, part.Parts[2].ContentTypeParams["charset"], "")
115 tcompareReader(t, part.Parts[2].Reader(), m.Original)
116 tcompare(t, pmsg.Recipients[0].FinalRecipient, m.Recipients[0].FinalRecipient)
117 // todo: test more fields
119 msgbufutf8, err := m.Compose(log, true)
121 t.Fatalf("composing dsn with utf-8: %v", err)
123 pmsg, part = tparseMessage(t, msgbufutf8, 3)
124 tcheckType(t, part, "multipart", "report", "")
125 tcheckType(t, &part.Parts[0], "text", "plain", "7bit")
126 tcheckType(t, &part.Parts[1], "message", "delivery-status", "7bit")
127 tcheckType(t, &part.Parts[2], "text", "rfc822-headers", "7bit")
128 tcompare(t, part.Parts[2].ContentTypeParams["charset"], "")
129 tcompareReader(t, part.Parts[2].Reader(), m.Original)
130 tcompare(t, pmsg.Recipients[0].FinalRecipient, m.Recipients[0].FinalRecipient)
136 From: smtp.Path{Localpart: "postmæster", IPDomain: xparseIPDomain("møx.example")},
137 To: smtp.Path{Localpart: "møx", IPDomain: xparseIPDomain("remøte.example")},
139 MessageID: "test@localhost",
140 TextBody: "delivery failure¿\n",
142 ReportingMTA: "mox.example",
143 ReceivedFromMTA: smtp.Ehlo{Name: xparseIPDomain("reläy.example"), ConnIP: net.ParseIP("10.10.10.10")},
146 Recipients: []Recipient{
149 FinalRecipient: smtp.Path{Localpart: "møx", IPDomain: xparseIPDomain("remøte.example")},
151 LastAttemptDate: now,
155 Original: []byte("Subject: tést\r\n"),
157 msgbuf, err = m.Compose(log, false)
159 t.Fatalf("composing utf-8 dsn without utf-8 support: %v", err)
161 pmsg, part = tparseMessage(t, msgbuf, 3)
162 tcheckType(t, part, "multipart", "report", "")
163 tcheckType(t, &part.Parts[0], "text", "plain", "7bit")
164 tcheckType(t, &part.Parts[1], "message", "delivery-status", "7bit")
165 tcheckType(t, &part.Parts[2], "text", "rfc822-headers", "base64")
166 tcompare(t, part.Parts[2].ContentTypeParams["charset"], "utf-8")
167 tcompareReader(t, part.Parts[2].Reader(), m.Original)
168 tcompare(t, pmsg.Recipients[0].FinalRecipient, m.Recipients[0].FinalRecipient)
170 msgbufutf8, err = m.Compose(log, true)
172 t.Fatalf("composing utf-8 dsn with utf-8 support: %v", err)
174 pmsg, part = tparseMessage(t, msgbufutf8, 3)
175 tcheckType(t, part, "multipart", "report", "")
176 tcheckType(t, &part.Parts[0], "text", "plain", "8bit")
177 tcheckType(t, &part.Parts[1], "message", "global-delivery-status", "8bit")
178 tcheckType(t, &part.Parts[2], "message", "global-headers", "8bit")
179 tcompare(t, part.Parts[2].ContentTypeParams["charset"], "")
180 tcompareReader(t, part.Parts[2].Reader(), m.Original)
181 tcompare(t, pmsg.Recipients[0].FinalRecipient, m.Recipients[0].FinalRecipient)
183 // Now a message without 3rd multipart.
185 msgbufutf8, err = m.Compose(log, true)
187 t.Fatalf("composing utf-8 dsn with utf-8 support: %v", err)
189 pmsg, part = tparseMessage(t, msgbufutf8, 2)
190 tcheckType(t, part, "multipart", "report", "")
191 tcheckType(t, &part.Parts[0], "text", "plain", "8bit")
192 tcheckType(t, &part.Parts[1], "message", "global-delivery-status", "8bit")
193 tcompare(t, pmsg.Recipients[0].FinalRecipient, m.Recipients[0].FinalRecipient)