Fix not handling invalid RCPT TO addresses.
[mailpopbox.git] / pop3 / conn_test.go
1 package pop3
2
3 import (
4 "fmt"
5 "io"
6 "io/ioutil"
7 "net"
8 "net/textproto"
9 "path/filepath"
10 "reflect"
11 "runtime"
12 "strings"
13 "testing"
14
15 "github.com/uber-go/zap"
16 )
17
18 func _fl(depth int) string {
19 _, file, line, _ := runtime.Caller(depth + 1)
20 return fmt.Sprintf("[%s:%d]", filepath.Base(file), line)
21 }
22
23 func ok(t testing.TB, err error) {
24 if err != nil {
25 t.Errorf("%s unexpected error: %v", _fl(1), err)
26 }
27 }
28
29 func responseOK(t testing.TB, conn *textproto.Conn) string {
30 line, err := conn.ReadLine()
31 if err != nil {
32 t.Errorf("%s responseOK: %v", _fl(1), err)
33 }
34 if !strings.HasPrefix(line, "+OK") {
35 t.Errorf("%s expected +OK, got %q", _fl(1), line)
36 }
37 return line
38 }
39
40 func responseERR(t testing.TB, conn *textproto.Conn) string {
41 line, err := conn.ReadLine()
42 if err != nil {
43 t.Errorf("%s responseERR: %v", _fl(1), err)
44 }
45 if !strings.HasPrefix(line, "-ERR") {
46 t.Errorf("%s expected -ERR, got %q", _fl(1), line)
47 }
48 return line
49 }
50
51 func runServer(t *testing.T, po PostOffice) net.Listener {
52 l, err := net.Listen("tcp", "localhost:0")
53 if err != nil {
54 t.Fatal(err)
55 return nil
56 }
57
58 go func() {
59 for {
60 conn, err := l.Accept()
61 if err != nil {
62 return
63 }
64 go AcceptConnection(conn, po, zap.New(zap.NullEncoder()))
65 }
66 }()
67 return l
68 }
69
70 type testServer struct {
71 user, pass string
72 mb testMailbox
73 }
74
75 func (s *testServer) Name() string {
76 return "Test-Server"
77 }
78
79 func (s *testServer) OpenMailbox(user, pass string) (Mailbox, error) {
80 if s.user == user && s.pass == pass {
81 return &s.mb, nil
82 }
83 return nil, fmt.Errorf("bad username/pass")
84 }
85
86 type testMailbox struct {
87 msgs map[int]*testMessage
88 }
89
90 func (mb *testMailbox) ListMessages() ([]Message, error) {
91 msgs := make([]Message, 0, len(mb.msgs))
92 for i, _ := range mb.msgs {
93 msgs = append(msgs, mb.msgs[i])
94 }
95 return msgs, nil
96 }
97
98 func (mb *testMailbox) GetMessage(id int) Message {
99 if msg, ok := mb.msgs[id]; ok {
100 return msg
101 }
102 return nil
103 }
104
105 func (mb *testMailbox) Retrieve(msg Message) (io.ReadCloser, error) {
106 r := strings.NewReader(msg.(*testMessage).body)
107 return ioutil.NopCloser(r), nil
108 }
109
110 func (mb *testMailbox) Delete(msg Message) error {
111 msg.(*testMessage).deleted = true
112 return nil
113 }
114
115 func (mb *testMailbox) Close() error {
116 return nil
117 }
118
119 func (mb *testMailbox) Reset() {
120 for _, msg := range mb.msgs {
121 msg.deleted = false
122 }
123 }
124
125 type testMessage struct {
126 id int
127 size int
128 deleted bool
129 body string
130 }
131
132 func (m *testMessage) ID() int {
133 return m.id
134 }
135 func (m *testMessage) Size() int {
136 return m.size
137 }
138 func (m *testMessage) Deleted() bool {
139 return m.deleted
140 }
141
142 func newTestServer() *testServer {
143 return &testServer{
144 user: "u",
145 pass: "p",
146 mb: testMailbox{
147 msgs: make(map[int]*testMessage),
148 },
149 }
150 }
151
152 // RFC 1939 ยง 10
153 func TestExampleSession(t *testing.T) {
154 s := newTestServer()
155 l := runServer(t, s)
156 defer l.Close()
157
158 s.mb.msgs[1] = &testMessage{1, 120, false, ""}
159 s.mb.msgs[2] = &testMessage{2, 200, false, ""}
160
161 conn, err := textproto.Dial(l.Addr().Network(), l.Addr().String())
162 ok(t, err)
163
164 line := responseOK(t, conn)
165 if !strings.Contains(line, s.Name()) {
166 t.Errorf("POP greeting did not include server name, got %q", line)
167 }
168
169 ok(t, conn.PrintfLine("USER u"))
170 responseOK(t, conn)
171
172 ok(t, conn.PrintfLine("PASS p"))
173 responseOK(t, conn)
174
175 ok(t, conn.PrintfLine("STAT"))
176 line = responseOK(t, conn)
177 expected := "+OK 2 320"
178 if line != expected {
179 t.Errorf("STAT expected %q, got %q", expected, line)
180 }
181
182 ok(t, conn.PrintfLine("LIST"))
183 responseOK(t, conn)
184 lines, err := conn.ReadDotLines()
185 ok(t, err)
186 if len(lines) != 2 {
187 t.Errorf("LIST expected 2 lines, got %d", len(lines))
188 }
189 expected = "1 120"
190 if lines[0] != expected {
191 t.Errorf("LIST line 0 expected %q, got %q", expected, lines[0])
192 }
193 expected = "2 200"
194 if lines[1] != expected {
195 t.Errorf("LIST line 1 expected %q, got %q", expected, lines[1])
196 }
197
198 ok(t, conn.PrintfLine("QUIT"))
199 responseOK(t, conn)
200 }
201
202 type requestResponse struct {
203 command string
204 expecter func(testing.TB, *textproto.Conn) string
205 }
206
207 func expectOKResponse(predicate func(string) bool) func(testing.TB, *textproto.Conn) string {
208 return func(t testing.TB, conn *textproto.Conn) string {
209 line := responseOK(t, conn)
210 if !predicate(line) {
211 t.Errorf("%s Predicate failed, got %q", _fl(1), line)
212 }
213 return line
214 }
215 }
216
217 func clientServerTest(t *testing.T, s *testServer, sequence []requestResponse) {
218 l := runServer(t, s)
219 defer l.Close()
220
221 conn, err := textproto.Dial(l.Addr().Network(), l.Addr().String())
222 ok(t, err)
223
224 responseOK(t, conn)
225
226 for _, pair := range sequence {
227 ok(t, conn.PrintfLine(pair.command))
228 pair.expecter(t, conn)
229 if t.Failed() {
230 t.Logf("command %q", pair.command)
231 }
232 }
233 }
234
235 func TestAuthStates(t *testing.T) {
236 clientServerTest(t, newTestServer(), []requestResponse{
237 {"STAT", responseERR},
238 {"NOOP", responseOK},
239 {"USER bad", responseOK},
240 {"PASS bad", responseERR},
241 {"LIST", responseERR},
242 {"USER u", responseOK},
243 {"PASS bad", responseERR},
244 {"STAT", responseERR},
245 {"PASS p", responseOK},
246 {"QUIT", responseOK},
247 })
248 }
249
250 func TestDeleted(t *testing.T) {
251 s := newTestServer()
252 s.mb.msgs[1] = &testMessage{1, 999, false, ""}
253 s.mb.msgs[2] = &testMessage{2, 10, false, ""}
254
255 clientServerTest(t, s, []requestResponse{
256 {"USER u", responseOK},
257 {"PASS p", responseOK},
258 {"STAT", expectOKResponse(func(s string) bool {
259 return s == "+OK 2 1009"
260 })},
261 {"DELE 1", responseOK},
262 {"RETR 1", responseERR},
263 {"DELE 1", responseERR},
264 {"STAT", expectOKResponse(func(s string) bool {
265 return s == "+OK 1 10"
266 })},
267 {"RSET", responseOK},
268 {"STAT", expectOKResponse(func(s string) bool {
269 return s == "+OK 2 1009"
270 })},
271 {"QUIT", responseOK},
272 })
273 }
274
275 func TestCaseSensitivty(t *testing.T) {
276 s := newTestServer()
277 s.mb.msgs[999] = &testMessage{999, 1, false, "a"}
278
279 clientServerTest(t, s, []requestResponse{
280 {"user u", responseOK},
281 {"PasS p", responseOK},
282 {"sTaT", responseOK},
283 {"retr 1", responseERR},
284 {"dele 999", responseOK},
285 {"QUIT", responseOK},
286 })
287 }
288
289 func TestRetr(t *testing.T) {
290 s := newTestServer()
291 s.mb.msgs[1] = &testMessage{1, 5, false, "hello"}
292 s.mb.msgs[2] = &testMessage{2, 69, false, "this\r\nis a\r\n.\r\ntest"}
293
294 clientServerTest(t, s, []requestResponse{
295 {"USER u", responseOK},
296 {"PASS p", responseOK},
297 {"STAT", responseOK},
298 {"RETR 1", func(t testing.TB, tp *textproto.Conn) string {
299 responseOK(t, tp)
300 resp, err := tp.ReadDotLines()
301 if err != nil {
302 t.Error(err)
303 return ""
304 }
305
306 expected := []string{"hello"}
307 if !reflect.DeepEqual(resp, expected) {
308 t.Errorf("Expected %v, got %v", expected, resp)
309 }
310
311 return ""
312 }},
313 {"RETR 2", func(t testing.TB, tp *textproto.Conn) string {
314 responseOK(t, tp)
315 resp, err := tp.ReadDotLines()
316 if err != nil {
317 t.Error(err)
318 return ""
319 }
320
321 expected := []string{"this", "is a", ".", "test"}
322 if !reflect.DeepEqual(resp, expected) {
323 t.Errorf("Expected %v, got %v", expected, resp)
324 }
325
326 return ""
327 }},
328 {"QUIT", responseOK},
329 })
330 }