1
2
3
4
5 package textproto
6
7 import (
8 "bufio"
9 "bytes"
10 "io"
11 "net"
12 "reflect"
13 "runtime"
14 "strings"
15 "sync"
16 "testing"
17 )
18
19 func reader(s string) *Reader {
20 return NewReader(bufio.NewReader(strings.NewReader(s)))
21 }
22
23 func TestReadLine(t *testing.T) {
24 r := reader("line1\nline2\n")
25 s, err := r.ReadLine()
26 if s != "line1" || err != nil {
27 t.Fatalf("Line 1: %s, %v", s, err)
28 }
29 s, err = r.ReadLine()
30 if s != "line2" || err != nil {
31 t.Fatalf("Line 2: %s, %v", s, err)
32 }
33 s, err = r.ReadLine()
34 if s != "" || err != io.EOF {
35 t.Fatalf("EOF: %s, %v", s, err)
36 }
37 }
38
39 func TestReadContinuedLine(t *testing.T) {
40 r := reader("line1\nline\n 2\nline3\n")
41 s, err := r.ReadContinuedLine()
42 if s != "line1" || err != nil {
43 t.Fatalf("Line 1: %s, %v", s, err)
44 }
45 s, err = r.ReadContinuedLine()
46 if s != "line 2" || err != nil {
47 t.Fatalf("Line 2: %s, %v", s, err)
48 }
49 s, err = r.ReadContinuedLine()
50 if s != "line3" || err != nil {
51 t.Fatalf("Line 3: %s, %v", s, err)
52 }
53 s, err = r.ReadContinuedLine()
54 if s != "" || err != io.EOF {
55 t.Fatalf("EOF: %s, %v", s, err)
56 }
57 }
58
59 func TestReadCodeLine(t *testing.T) {
60 r := reader("123 hi\n234 bye\n345 no way\n")
61 code, msg, err := r.ReadCodeLine(0)
62 if code != 123 || msg != "hi" || err != nil {
63 t.Fatalf("Line 1: %d, %s, %v", code, msg, err)
64 }
65 code, msg, err = r.ReadCodeLine(23)
66 if code != 234 || msg != "bye" || err != nil {
67 t.Fatalf("Line 2: %d, %s, %v", code, msg, err)
68 }
69 code, msg, err = r.ReadCodeLine(346)
70 if code != 345 || msg != "no way" || err == nil {
71 t.Fatalf("Line 3: %d, %s, %v", code, msg, err)
72 }
73 if e, ok := err.(*Error); !ok || e.Code != code || e.Msg != msg {
74 t.Fatalf("Line 3: wrong error %v\n", err)
75 }
76 code, msg, err = r.ReadCodeLine(1)
77 if code != 0 || msg != "" || err != io.EOF {
78 t.Fatalf("EOF: %d, %s, %v", code, msg, err)
79 }
80 }
81
82 func TestReadDotLines(t *testing.T) {
83 r := reader("dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanother\n")
84 s, err := r.ReadDotLines()
85 want := []string{"dotlines", "foo", ".bar", "..baz", "quux", ""}
86 if !reflect.DeepEqual(s, want) || err != nil {
87 t.Fatalf("ReadDotLines: %v, %v", s, err)
88 }
89
90 s, err = r.ReadDotLines()
91 want = []string{"another"}
92 if !reflect.DeepEqual(s, want) || err != io.ErrUnexpectedEOF {
93 t.Fatalf("ReadDotLines2: %v, %v", s, err)
94 }
95 }
96
97 func TestReadDotBytes(t *testing.T) {
98 r := reader("dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanot.her\r\n")
99 b, err := r.ReadDotBytes()
100 want := []byte("dotlines\nfoo\n.bar\n..baz\nquux\n\n")
101 if !reflect.DeepEqual(b, want) || err != nil {
102 t.Fatalf("ReadDotBytes: %q, %v", b, err)
103 }
104
105 b, err = r.ReadDotBytes()
106 want = []byte("anot.her\n")
107 if !reflect.DeepEqual(b, want) || err != io.ErrUnexpectedEOF {
108 t.Fatalf("ReadDotBytes2: %q, %v", b, err)
109 }
110 }
111
112 func TestReadMIMEHeader(t *testing.T) {
113 r := reader("my-key: Value 1 \r\nLong-key: Even \n Longer Value\r\nmy-Key: Value 2\r\n\n")
114 m, err := r.ReadMIMEHeader()
115 want := MIMEHeader{
116 "My-Key": {"Value 1", "Value 2"},
117 "Long-Key": {"Even Longer Value"},
118 }
119 if !reflect.DeepEqual(m, want) || err != nil {
120 t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
121 }
122 }
123
124 func TestReadMIMEHeaderSingle(t *testing.T) {
125 r := reader("Foo: bar\n\n")
126 m, err := r.ReadMIMEHeader()
127 want := MIMEHeader{"Foo": {"bar"}}
128 if !reflect.DeepEqual(m, want) || err != nil {
129 t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
130 }
131 }
132
133
134
135 func TestReaderUpcomingHeaderKeys(t *testing.T) {
136 for _, test := range []struct {
137 input string
138 want int
139 }{{
140 input: "",
141 want: 0,
142 }, {
143 input: "A: v",
144 want: 1,
145 }, {
146 input: "A: v\r\nB: v\r\n",
147 want: 2,
148 }, {
149 input: "A: v\nB: v\n",
150 want: 2,
151 }, {
152 input: "A: v\r\n continued\r\n still continued\r\nB: v\r\n\r\n",
153 want: 2,
154 }, {
155 input: "A: v\r\n\r\nB: v\r\nC: v\r\n",
156 want: 1,
157 }, {
158 input: "A: v" + strings.Repeat("\n", 1000),
159 want: 1,
160 }} {
161 r := reader(test.input)
162 got := r.upcomingHeaderKeys()
163 if test.want != got {
164 t.Fatalf("upcomingHeaderKeys(%q): %v; want %v", test.input, got, test.want)
165 }
166 }
167 }
168
169 func TestReadMIMEHeaderNoKey(t *testing.T) {
170 r := reader(": bar\ntest-1: 1\n\n")
171 m, err := r.ReadMIMEHeader()
172 want := MIMEHeader{"Test-1": {"1"}}
173 if !reflect.DeepEqual(m, want) || err != nil {
174 t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
175 }
176 }
177
178 func TestLargeReadMIMEHeader(t *testing.T) {
179 data := make([]byte, 16*1024)
180 for i := 0; i < len(data); i++ {
181 data[i] = 'x'
182 }
183 sdata := string(data)
184 r := reader("Cookie: " + sdata + "\r\n\n")
185 m, err := r.ReadMIMEHeader()
186 if err != nil {
187 t.Fatalf("ReadMIMEHeader: %v", err)
188 }
189 cookie := m.Get("Cookie")
190 if cookie != sdata {
191 t.Fatalf("ReadMIMEHeader: %v bytes, want %v bytes", len(cookie), len(sdata))
192 }
193 }
194
195
196
197 func TestReadMIMEHeaderNonCompliant(t *testing.T) {
198
199 r := reader("Foo: bar\r\n" +
200 "Content-Language: en\r\n" +
201 "SID : 0\r\n" +
202 "Audio Mode : None\r\n" +
203 "Privilege : 127\r\n\r\n")
204 m, err := r.ReadMIMEHeader()
205 want := MIMEHeader{
206 "Foo": {"bar"},
207 "Content-Language": {"en"},
208 "SID ": {"0"},
209 "Audio Mode ": {"None"},
210 "Privilege ": {"127"},
211 }
212 if !reflect.DeepEqual(m, want) || err != nil {
213 t.Fatalf("ReadMIMEHeader =\n%v, %v; want:\n%v", m, err, want)
214 }
215 }
216
217 func TestReadMIMEHeaderMalformed(t *testing.T) {
218 inputs := []string{
219 "No colon first line\r\nFoo: foo\r\n\r\n",
220 " No colon first line with leading space\r\nFoo: foo\r\n\r\n",
221 "\tNo colon first line with leading tab\r\nFoo: foo\r\n\r\n",
222 " First: line with leading space\r\nFoo: foo\r\n\r\n",
223 "\tFirst: line with leading tab\r\nFoo: foo\r\n\r\n",
224 "Foo: foo\r\nNo colon second line\r\n\r\n",
225 "Foo-\n\tBar: foo\r\n\r\n",
226 "Foo-\r\n\tBar: foo\r\n\r\n",
227 "Foo\r\n\t: foo\r\n\r\n",
228 "Foo-\n\tBar",
229 "Foo \tBar: foo\r\n\r\n",
230 }
231 for _, input := range inputs {
232 r := reader(input)
233 if m, err := r.ReadMIMEHeader(); err == nil || err == io.EOF {
234 t.Errorf("ReadMIMEHeader(%q) = %v, %v; want nil, err", input, m, err)
235 }
236 }
237 }
238
239 func TestReadMIMEHeaderBytes(t *testing.T) {
240 for i := 0; i <= 0xff; i++ {
241 s := "Foo" + string(rune(i)) + "Bar: foo\r\n\r\n"
242 r := reader(s)
243 wantErr := true
244 switch {
245 case i >= '0' && i <= '9':
246 wantErr = false
247 case i >= 'a' && i <= 'z':
248 wantErr = false
249 case i >= 'A' && i <= 'Z':
250 wantErr = false
251 case i == '!' || i == '#' || i == '$' || i == '%' || i == '&' || i == '\'' || i == '*' || i == '+' || i == '-' || i == '.' || i == '^' || i == '_' || i == '`' || i == '|' || i == '~':
252 wantErr = false
253 case i == ':':
254
255 wantErr = false
256 case i == ' ':
257 wantErr = false
258 }
259 m, err := r.ReadMIMEHeader()
260 if err != nil != wantErr {
261 t.Errorf("ReadMIMEHeader(%q) = %v, %v; want error=%v", s, m, err, wantErr)
262 }
263 }
264 for i := 0; i <= 0xff; i++ {
265 s := "Foo: foo" + string(rune(i)) + "bar\r\n\r\n"
266 r := reader(s)
267 wantErr := true
268 switch {
269 case i >= 0x21 && i <= 0x7e:
270 wantErr = false
271 case i == ' ':
272 wantErr = false
273 case i == '\t':
274 wantErr = false
275 case i >= 0x80 && i <= 0xff:
276 wantErr = false
277 }
278 m, err := r.ReadMIMEHeader()
279 if (err != nil) != wantErr {
280 t.Errorf("ReadMIMEHeader(%q) = %v, %v; want error=%v", s, m, err, wantErr)
281 }
282 }
283 }
284
285
286 func TestReadMIMEHeaderTrimContinued(t *testing.T) {
287
288
289
290 r := reader("" +
291 "a:\n" +
292 " 0 \r\n" +
293 "b:1 \t\r\n" +
294 "c: 2\r\n" +
295 " 3\t\n" +
296 " \t 4 \r\n\n")
297 m, err := r.ReadMIMEHeader()
298 if err != nil {
299 t.Fatal(err)
300 }
301 want := MIMEHeader{
302 "A": {"0"},
303 "B": {"1"},
304 "C": {"2 3 4"},
305 }
306 if !reflect.DeepEqual(m, want) {
307 t.Fatalf("ReadMIMEHeader mismatch.\n got: %q\nwant: %q", m, want)
308 }
309 }
310
311
312 func TestReadMIMEHeaderAllocations(t *testing.T) {
313 var totalAlloc uint64
314 const count = 200
315 for i := 0; i < count; i++ {
316 r := reader("A: b\r\n\r\n" + strings.Repeat("\n", 4096))
317 var m1, m2 runtime.MemStats
318 runtime.ReadMemStats(&m1)
319 _, err := r.ReadMIMEHeader()
320 if err != nil {
321 t.Fatalf("ReadMIMEHeader: %v", err)
322 }
323 runtime.ReadMemStats(&m2)
324 totalAlloc += m2.TotalAlloc - m1.TotalAlloc
325 }
326
327
328 if got, want := totalAlloc/count, uint64(32768); got > want {
329 t.Fatalf("ReadMIMEHeader allocated %v bytes, want < %v", got, want)
330 }
331 }
332
333 type readResponseTest struct {
334 in string
335 inCode int
336 wantCode int
337 wantMsg string
338 }
339
340 var readResponseTests = []readResponseTest{
341 {"230-Anonymous access granted, restrictions apply\n" +
342 "Read the file README.txt,\n" +
343 "230 please",
344 23,
345 230,
346 "Anonymous access granted, restrictions apply\nRead the file README.txt,\n please",
347 },
348
349 {"230 Anonymous access granted, restrictions apply\n",
350 23,
351 230,
352 "Anonymous access granted, restrictions apply",
353 },
354
355 {"400-A\n400-B\n400 C",
356 4,
357 400,
358 "A\nB\nC",
359 },
360
361 {"400-A\r\n400-B\r\n400 C\r\n",
362 4,
363 400,
364 "A\nB\nC",
365 },
366 }
367
368
369 func TestRFC959Lines(t *testing.T) {
370 for i, tt := range readResponseTests {
371 r := reader(tt.in + "\nFOLLOWING DATA")
372 code, msg, err := r.ReadResponse(tt.inCode)
373 if err != nil {
374 t.Errorf("#%d: ReadResponse: %v", i, err)
375 continue
376 }
377 if code != tt.wantCode {
378 t.Errorf("#%d: code=%d, want %d", i, code, tt.wantCode)
379 }
380 if msg != tt.wantMsg {
381 t.Errorf("#%d: msg=%q, want %q", i, msg, tt.wantMsg)
382 }
383 }
384 }
385
386
387 func TestReadMultiLineError(t *testing.T) {
388 r := reader("550-5.1.1 The email account that you tried to reach does not exist. Please try\n" +
389 "550-5.1.1 double-checking the recipient's email address for typos or\n" +
390 "550-5.1.1 unnecessary spaces. Learn more at\n" +
391 "Unexpected but legal text!\n" +
392 "550 5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp\n")
393
394 wantMsg := "5.1.1 The email account that you tried to reach does not exist. Please try\n" +
395 "5.1.1 double-checking the recipient's email address for typos or\n" +
396 "5.1.1 unnecessary spaces. Learn more at\n" +
397 "Unexpected but legal text!\n" +
398 "5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp"
399
400 code, msg, err := r.ReadResponse(250)
401 if err == nil {
402 t.Errorf("ReadResponse: no error, want error")
403 }
404 if code != 550 {
405 t.Errorf("ReadResponse: code=%d, want %d", code, 550)
406 }
407 if msg != wantMsg {
408 t.Errorf("ReadResponse: msg=%q, want %q", msg, wantMsg)
409 }
410 if err != nil && err.Error() != "550 "+wantMsg {
411 t.Errorf("ReadResponse: error=%q, want %q", err.Error(), "550 "+wantMsg)
412 }
413 }
414
415 func TestCommonHeaders(t *testing.T) {
416 commonHeaderOnce.Do(initCommonHeader)
417 for h := range commonHeader {
418 if h != CanonicalMIMEHeaderKey(h) {
419 t.Errorf("Non-canonical header %q in commonHeader", h)
420 }
421 }
422 b := []byte("content-Length")
423 want := "Content-Length"
424 n := testing.AllocsPerRun(200, func() {
425 if x, _ := canonicalMIMEHeaderKey(b); x != want {
426 t.Fatalf("canonicalMIMEHeaderKey(%q) = %q; want %q", b, x, want)
427 }
428 })
429 if n > 0 {
430 t.Errorf("canonicalMIMEHeaderKey allocs = %v; want 0", n)
431 }
432 }
433
434 func TestIssue46363(t *testing.T) {
435
436
437
438
439
440 commonHeaderOnce = sync.Once{}
441 commonHeader = nil
442
443
444
445
446 r, w := net.Pipe()
447 go func() {
448
449 NewConn(r).ReadMIMEHeader()
450 }()
451 w.Write([]byte("A: 1\r\nB: 2\r\nC: 3\r\n\r\n"))
452
453
454 CanonicalMIMEHeaderKey("a")
455
456 if commonHeader == nil {
457 t.Fatal("CanonicalMIMEHeaderKey should initialize commonHeader")
458 }
459 }
460
461 var clientHeaders = strings.Replace(`Host: golang.org
462 Connection: keep-alive
463 Cache-Control: max-age=0
464 Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
465 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
466 Accept-Encoding: gzip,deflate,sdch
467 Accept-Language: en-US,en;q=0.8,fr-CH;q=0.6
468 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
469 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
470 Non-Interned: test
471
472 `, "\n", "\r\n", -1)
473
474 var serverHeaders = strings.Replace(`Content-Type: text/html; charset=utf-8
475 Content-Encoding: gzip
476 Date: Thu, 27 Sep 2012 09:03:33 GMT
477 Server: Google Frontend
478 Cache-Control: private
479 Content-Length: 2298
480 VIA: 1.1 proxy.example.com:80 (XXX/n.n.n-nnn)
481 Connection: Close
482 Non-Interned: test
483
484 `, "\n", "\r\n", -1)
485
486 func BenchmarkReadMIMEHeader(b *testing.B) {
487 b.ReportAllocs()
488 for _, set := range []struct {
489 name string
490 headers string
491 }{
492 {"client_headers", clientHeaders},
493 {"server_headers", serverHeaders},
494 } {
495 b.Run(set.name, func(b *testing.B) {
496 var buf bytes.Buffer
497 br := bufio.NewReader(&buf)
498 r := NewReader(br)
499
500 for i := 0; i < b.N; i++ {
501 buf.WriteString(set.headers)
502 if _, err := r.ReadMIMEHeader(); err != nil {
503 b.Fatal(err)
504 }
505 }
506 })
507 }
508 }
509
510 func BenchmarkUncommon(b *testing.B) {
511 b.ReportAllocs()
512 var buf bytes.Buffer
513 br := bufio.NewReader(&buf)
514 r := NewReader(br)
515 for i := 0; i < b.N; i++ {
516 buf.WriteString("uncommon-header-for-benchmark: foo\r\n\r\n")
517 h, err := r.ReadMIMEHeader()
518 if err != nil {
519 b.Fatal(err)
520 }
521 if _, ok := h["Uncommon-Header-For-Benchmark"]; !ok {
522 b.Fatal("Missing result header.")
523 }
524 }
525 }
526
View as plain text