1
2
3
4
5 package textproto
6
7 import (
8 "bufio"
9 "bytes"
10 "io"
11 "reflect"
12 "strings"
13 "testing"
14 )
15
16 func reader(s string) *Reader {
17 return NewReader(bufio.NewReader(strings.NewReader(s)))
18 }
19
20 func TestReadLine(t *testing.T) {
21 r := reader("line1\nline2\n")
22 s, err := r.ReadLine()
23 if s != "line1" || err != nil {
24 t.Fatalf("Line 1: %s, %v", s, err)
25 }
26 s, err = r.ReadLine()
27 if s != "line2" || err != nil {
28 t.Fatalf("Line 2: %s, %v", s, err)
29 }
30 s, err = r.ReadLine()
31 if s != "" || err != io.EOF {
32 t.Fatalf("EOF: %s, %v", s, err)
33 }
34 }
35
36 func TestReadContinuedLine(t *testing.T) {
37 r := reader("line1\nline\n 2\nline3\n")
38 s, err := r.ReadContinuedLine()
39 if s != "line1" || err != nil {
40 t.Fatalf("Line 1: %s, %v", s, err)
41 }
42 s, err = r.ReadContinuedLine()
43 if s != "line 2" || err != nil {
44 t.Fatalf("Line 2: %s, %v", s, err)
45 }
46 s, err = r.ReadContinuedLine()
47 if s != "line3" || err != nil {
48 t.Fatalf("Line 3: %s, %v", s, err)
49 }
50 s, err = r.ReadContinuedLine()
51 if s != "" || err != io.EOF {
52 t.Fatalf("EOF: %s, %v", s, err)
53 }
54 }
55
56 func TestReadCodeLine(t *testing.T) {
57 r := reader("123 hi\n234 bye\n345 no way\n")
58 code, msg, err := r.ReadCodeLine(0)
59 if code != 123 || msg != "hi" || err != nil {
60 t.Fatalf("Line 1: %d, %s, %v", code, msg, err)
61 }
62 code, msg, err = r.ReadCodeLine(23)
63 if code != 234 || msg != "bye" || err != nil {
64 t.Fatalf("Line 2: %d, %s, %v", code, msg, err)
65 }
66 code, msg, err = r.ReadCodeLine(346)
67 if code != 345 || msg != "no way" || err == nil {
68 t.Fatalf("Line 3: %d, %s, %v", code, msg, err)
69 }
70 if e, ok := err.(*Error); !ok || e.Code != code || e.Msg != msg {
71 t.Fatalf("Line 3: wrong error %v\n", err)
72 }
73 code, msg, err = r.ReadCodeLine(1)
74 if code != 0 || msg != "" || err != io.EOF {
75 t.Fatalf("EOF: %d, %s, %v", code, msg, err)
76 }
77 }
78
79 func TestReadDotLines(t *testing.T) {
80 r := reader("dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanother\n")
81 s, err := r.ReadDotLines()
82 want := []string{"dotlines", "foo", ".bar", "..baz", "quux", ""}
83 if !reflect.DeepEqual(s, want) || err != nil {
84 t.Fatalf("ReadDotLines: %v, %v", s, err)
85 }
86
87 s, err = r.ReadDotLines()
88 want = []string{"another"}
89 if !reflect.DeepEqual(s, want) || err != io.ErrUnexpectedEOF {
90 t.Fatalf("ReadDotLines2: %v, %v", s, err)
91 }
92 }
93
94 func TestReadDotBytes(t *testing.T) {
95 r := reader("dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanot.her\r\n")
96 b, err := r.ReadDotBytes()
97 want := []byte("dotlines\nfoo\n.bar\n..baz\nquux\n\n")
98 if !reflect.DeepEqual(b, want) || err != nil {
99 t.Fatalf("ReadDotBytes: %q, %v", b, err)
100 }
101
102 b, err = r.ReadDotBytes()
103 want = []byte("anot.her\n")
104 if !reflect.DeepEqual(b, want) || err != io.ErrUnexpectedEOF {
105 t.Fatalf("ReadDotBytes2: %q, %v", b, err)
106 }
107 }
108
109 func TestReadMIMEHeader(t *testing.T) {
110 r := reader("my-key: Value 1 \r\nLong-key: Even \n Longer Value\r\nmy-Key: Value 2\r\n\n")
111 m, err := r.ReadMIMEHeader()
112 want := MIMEHeader{
113 "My-Key": {"Value 1", "Value 2"},
114 "Long-Key": {"Even Longer Value"},
115 }
116 if !reflect.DeepEqual(m, want) || err != nil {
117 t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
118 }
119 }
120
121 func TestReadMIMEHeaderSingle(t *testing.T) {
122 r := reader("Foo: bar\n\n")
123 m, err := r.ReadMIMEHeader()
124 want := MIMEHeader{"Foo": {"bar"}}
125 if !reflect.DeepEqual(m, want) || err != nil {
126 t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
127 }
128 }
129
130 func TestReadMIMEHeaderNoKey(t *testing.T) {
131 r := reader(": bar\ntest-1: 1\n\n")
132 m, err := r.ReadMIMEHeader()
133 want := MIMEHeader{"Test-1": {"1"}}
134 if !reflect.DeepEqual(m, want) || err != nil {
135 t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
136 }
137 }
138
139 func TestLargeReadMIMEHeader(t *testing.T) {
140 data := make([]byte, 16*1024)
141 for i := 0; i < len(data); i++ {
142 data[i] = 'x'
143 }
144 sdata := string(data)
145 r := reader("Cookie: " + sdata + "\r\n\n")
146 m, err := r.ReadMIMEHeader()
147 if err != nil {
148 t.Fatalf("ReadMIMEHeader: %v", err)
149 }
150 cookie := m.Get("Cookie")
151 if cookie != sdata {
152 t.Fatalf("ReadMIMEHeader: %v bytes, want %v bytes", len(cookie), len(sdata))
153 }
154 }
155
156
157
158 func TestReadMIMEHeaderNonCompliant(t *testing.T) {
159
160 r := reader("Foo: bar\r\n" +
161 "Content-Language: en\r\n" +
162 "SID : 0\r\n" +
163 "Audio Mode : None\r\n" +
164 "Privilege : 127\r\n\r\n")
165 m, err := r.ReadMIMEHeader()
166 want := MIMEHeader{
167 "Foo": {"bar"},
168 "Content-Language": {"en"},
169 "SID ": {"0"},
170 "Audio Mode ": {"None"},
171 "Privilege ": {"127"},
172 }
173 if !reflect.DeepEqual(m, want) || err != nil {
174 t.Fatalf("ReadMIMEHeader =\n%v, %v; want:\n%v", m, err, want)
175 }
176 }
177
178 func TestReadMIMEHeaderMalformed(t *testing.T) {
179 inputs := []string{
180 "No colon first line\r\nFoo: foo\r\n\r\n",
181 " No colon first line with leading space\r\nFoo: foo\r\n\r\n",
182 "\tNo colon first line with leading tab\r\nFoo: foo\r\n\r\n",
183 " First: line with leading space\r\nFoo: foo\r\n\r\n",
184 "\tFirst: line with leading tab\r\nFoo: foo\r\n\r\n",
185 "Foo: foo\r\nNo colon second line\r\n\r\n",
186 "Foo-\n\tBar: foo\r\n\r\n",
187 "Foo-\r\n\tBar: foo\r\n\r\n",
188 "Foo\r\n\t: foo\r\n\r\n",
189 "Foo-\n\tBar",
190 }
191
192 for _, input := range inputs {
193 r := reader(input)
194 if m, err := r.ReadMIMEHeader(); err == nil {
195 t.Errorf("ReadMIMEHeader(%q) = %v, %v; want nil, err", input, m, err)
196 }
197 }
198 }
199
200
201 func TestReadMIMEHeaderTrimContinued(t *testing.T) {
202
203
204
205 r := reader("" +
206 "a:\n" +
207 " 0 \r\n" +
208 "b:1 \t\r\n" +
209 "c: 2\r\n" +
210 " 3\t\n" +
211 " \t 4 \r\n\n")
212 m, err := r.ReadMIMEHeader()
213 if err != nil {
214 t.Fatal(err)
215 }
216 want := MIMEHeader{
217 "A": {"0"},
218 "B": {"1"},
219 "C": {"2 3 4"},
220 }
221 if !reflect.DeepEqual(m, want) {
222 t.Fatalf("ReadMIMEHeader mismatch.\n got: %q\nwant: %q", m, want)
223 }
224 }
225
226 type readResponseTest struct {
227 in string
228 inCode int
229 wantCode int
230 wantMsg string
231 }
232
233 var readResponseTests = []readResponseTest{
234 {"230-Anonymous access granted, restrictions apply\n" +
235 "Read the file README.txt,\n" +
236 "230 please",
237 23,
238 230,
239 "Anonymous access granted, restrictions apply\nRead the file README.txt,\n please",
240 },
241
242 {"230 Anonymous access granted, restrictions apply\n",
243 23,
244 230,
245 "Anonymous access granted, restrictions apply",
246 },
247
248 {"400-A\n400-B\n400 C",
249 4,
250 400,
251 "A\nB\nC",
252 },
253
254 {"400-A\r\n400-B\r\n400 C\r\n",
255 4,
256 400,
257 "A\nB\nC",
258 },
259 }
260
261
262 func TestRFC959Lines(t *testing.T) {
263 for i, tt := range readResponseTests {
264 r := reader(tt.in + "\nFOLLOWING DATA")
265 code, msg, err := r.ReadResponse(tt.inCode)
266 if err != nil {
267 t.Errorf("#%d: ReadResponse: %v", i, err)
268 continue
269 }
270 if code != tt.wantCode {
271 t.Errorf("#%d: code=%d, want %d", i, code, tt.wantCode)
272 }
273 if msg != tt.wantMsg {
274 t.Errorf("#%d: msg=%q, want %q", i, msg, tt.wantMsg)
275 }
276 }
277 }
278
279
280 func TestReadMultiLineError(t *testing.T) {
281 r := reader("550-5.1.1 The email account that you tried to reach does not exist. Please try\n" +
282 "550-5.1.1 double-checking the recipient's email address for typos or\n" +
283 "550-5.1.1 unnecessary spaces. Learn more at\n" +
284 "Unexpected but legal text!\n" +
285 "550 5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp\n")
286
287 wantMsg := "5.1.1 The email account that you tried to reach does not exist. Please try\n" +
288 "5.1.1 double-checking the recipient's email address for typos or\n" +
289 "5.1.1 unnecessary spaces. Learn more at\n" +
290 "Unexpected but legal text!\n" +
291 "5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp"
292
293 code, msg, err := r.ReadResponse(250)
294 if err == nil {
295 t.Errorf("ReadResponse: no error, want error")
296 }
297 if code != 550 {
298 t.Errorf("ReadResponse: code=%d, want %d", code, 550)
299 }
300 if msg != wantMsg {
301 t.Errorf("ReadResponse: msg=%q, want %q", msg, wantMsg)
302 }
303 if err != nil && err.Error() != "550 "+wantMsg {
304 t.Errorf("ReadResponse: error=%q, want %q", err.Error(), "550 "+wantMsg)
305 }
306 }
307
308 func TestCommonHeaders(t *testing.T) {
309 commonHeaderOnce.Do(initCommonHeader)
310 for h := range commonHeader {
311 if h != CanonicalMIMEHeaderKey(h) {
312 t.Errorf("Non-canonical header %q in commonHeader", h)
313 }
314 }
315 b := []byte("content-Length")
316 want := "Content-Length"
317 n := testing.AllocsPerRun(200, func() {
318 if x := canonicalMIMEHeaderKey(b); x != want {
319 t.Fatalf("canonicalMIMEHeaderKey(%q) = %q; want %q", b, x, want)
320 }
321 })
322 if n > 0 {
323 t.Errorf("canonicalMIMEHeaderKey allocs = %v; want 0", n)
324 }
325 }
326
327 var clientHeaders = strings.Replace(`Host: golang.org
328 Connection: keep-alive
329 Cache-Control: max-age=0
330 Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
331 User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3
332 Accept-Encoding: gzip,deflate,sdch
333 Accept-Language: en-US,en;q=0.8,fr-CH;q=0.6
334 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
335 COOKIE: __utma=000000000.0000000000.0000000000.0000000000.0000000000.00; __utmb=000000000.0.00.0000000000; __utmc=000000000; __utmz=000000000.0000000000.00.0.utmcsr=code.google.com|utmccn=(referral)|utmcmd=referral|utmcct=/p/go/issues/detail
336 Non-Interned: test
337
338 `, "\n", "\r\n", -1)
339
340 var serverHeaders = strings.Replace(`Content-Type: text/html; charset=utf-8
341 Content-Encoding: gzip
342 Date: Thu, 27 Sep 2012 09:03:33 GMT
343 Server: Google Frontend
344 Cache-Control: private
345 Content-Length: 2298
346 VIA: 1.1 proxy.example.com:80 (XXX/n.n.n-nnn)
347 Connection: Close
348 Non-Interned: test
349
350 `, "\n", "\r\n", -1)
351
352 func BenchmarkReadMIMEHeader(b *testing.B) {
353 b.ReportAllocs()
354 for _, set := range []struct {
355 name string
356 headers string
357 }{
358 {"client_headers", clientHeaders},
359 {"server_headers", serverHeaders},
360 } {
361 b.Run(set.name, func(b *testing.B) {
362 var buf bytes.Buffer
363 br := bufio.NewReader(&buf)
364 r := NewReader(br)
365
366 for i := 0; i < b.N; i++ {
367 buf.WriteString(set.headers)
368 if _, err := r.ReadMIMEHeader(); err != nil {
369 b.Fatal(err)
370 }
371 }
372 })
373 }
374 }
375
376 func BenchmarkUncommon(b *testing.B) {
377 b.ReportAllocs()
378 var buf bytes.Buffer
379 br := bufio.NewReader(&buf)
380 r := NewReader(br)
381 for i := 0; i < b.N; i++ {
382 buf.WriteString("uncommon-header-for-benchmark: foo\r\n\r\n")
383 h, err := r.ReadMIMEHeader()
384 if err != nil {
385 b.Fatal(err)
386 }
387 if _, ok := h["Uncommon-Header-For-Benchmark"]; !ok {
388 b.Fatal("Missing result header.")
389 }
390 }
391 }
392
View as plain text