Home | History | Annotate | Download | only in textproto
      1 // Copyright 2010 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package textproto
      6 
      7 import (
      8 	"bufio"
      9 	"bytes"
     10 	"io"
     11 	"reflect"
     12 	"strings"
     13 	"testing"
     14 )
     15 
     16 type canonicalHeaderKeyTest struct {
     17 	in, out string
     18 }
     19 
     20 var canonicalHeaderKeyTests = []canonicalHeaderKeyTest{
     21 	{"a-b-c", "A-B-C"},
     22 	{"a-1-c", "A-1-C"},
     23 	{"User-Agent", "User-Agent"},
     24 	{"uSER-aGENT", "User-Agent"},
     25 	{"user-agent", "User-Agent"},
     26 	{"USER-AGENT", "User-Agent"},
     27 
     28 	// Other valid tchar bytes in tokens:
     29 	{"foo-bar_baz", "Foo-Bar_baz"},
     30 	{"foo-bar$baz", "Foo-Bar$baz"},
     31 	{"foo-bar~baz", "Foo-Bar~baz"},
     32 	{"foo-bar*baz", "Foo-Bar*baz"},
     33 
     34 	// Non-ASCII or anything with spaces or non-token chars is unchanged:
     35 	{"ser-agenT", "ser-agenT"},
     36 	{"a B", "a B"},
     37 
     38 	// This caused a panic due to mishandling of a space:
     39 	{"C Ontent-Transfer-Encoding", "C Ontent-Transfer-Encoding"},
     40 	{"foo bar", "foo bar"},
     41 }
     42 
     43 func TestCanonicalMIMEHeaderKey(t *testing.T) {
     44 	for _, tt := range canonicalHeaderKeyTests {
     45 		if s := CanonicalMIMEHeaderKey(tt.in); s != tt.out {
     46 			t.Errorf("CanonicalMIMEHeaderKey(%q) = %q, want %q", tt.in, s, tt.out)
     47 		}
     48 	}
     49 }
     50 
     51 func reader(s string) *Reader {
     52 	return NewReader(bufio.NewReader(strings.NewReader(s)))
     53 }
     54 
     55 func TestReadLine(t *testing.T) {
     56 	r := reader("line1\nline2\n")
     57 	s, err := r.ReadLine()
     58 	if s != "line1" || err != nil {
     59 		t.Fatalf("Line 1: %s, %v", s, err)
     60 	}
     61 	s, err = r.ReadLine()
     62 	if s != "line2" || err != nil {
     63 		t.Fatalf("Line 2: %s, %v", s, err)
     64 	}
     65 	s, err = r.ReadLine()
     66 	if s != "" || err != io.EOF {
     67 		t.Fatalf("EOF: %s, %v", s, err)
     68 	}
     69 }
     70 
     71 func TestReadContinuedLine(t *testing.T) {
     72 	r := reader("line1\nline\n 2\nline3\n")
     73 	s, err := r.ReadContinuedLine()
     74 	if s != "line1" || err != nil {
     75 		t.Fatalf("Line 1: %s, %v", s, err)
     76 	}
     77 	s, err = r.ReadContinuedLine()
     78 	if s != "line 2" || err != nil {
     79 		t.Fatalf("Line 2: %s, %v", s, err)
     80 	}
     81 	s, err = r.ReadContinuedLine()
     82 	if s != "line3" || err != nil {
     83 		t.Fatalf("Line 3: %s, %v", s, err)
     84 	}
     85 	s, err = r.ReadContinuedLine()
     86 	if s != "" || err != io.EOF {
     87 		t.Fatalf("EOF: %s, %v", s, err)
     88 	}
     89 }
     90 
     91 func TestReadCodeLine(t *testing.T) {
     92 	r := reader("123 hi\n234 bye\n345 no way\n")
     93 	code, msg, err := r.ReadCodeLine(0)
     94 	if code != 123 || msg != "hi" || err != nil {
     95 		t.Fatalf("Line 1: %d, %s, %v", code, msg, err)
     96 	}
     97 	code, msg, err = r.ReadCodeLine(23)
     98 	if code != 234 || msg != "bye" || err != nil {
     99 		t.Fatalf("Line 2: %d, %s, %v", code, msg, err)
    100 	}
    101 	code, msg, err = r.ReadCodeLine(346)
    102 	if code != 345 || msg != "no way" || err == nil {
    103 		t.Fatalf("Line 3: %d, %s, %v", code, msg, err)
    104 	}
    105 	if e, ok := err.(*Error); !ok || e.Code != code || e.Msg != msg {
    106 		t.Fatalf("Line 3: wrong error %v\n", err)
    107 	}
    108 	code, msg, err = r.ReadCodeLine(1)
    109 	if code != 0 || msg != "" || err != io.EOF {
    110 		t.Fatalf("EOF: %d, %s, %v", code, msg, err)
    111 	}
    112 }
    113 
    114 func TestReadDotLines(t *testing.T) {
    115 	r := reader("dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanother\n")
    116 	s, err := r.ReadDotLines()
    117 	want := []string{"dotlines", "foo", ".bar", "..baz", "quux", ""}
    118 	if !reflect.DeepEqual(s, want) || err != nil {
    119 		t.Fatalf("ReadDotLines: %v, %v", s, err)
    120 	}
    121 
    122 	s, err = r.ReadDotLines()
    123 	want = []string{"another"}
    124 	if !reflect.DeepEqual(s, want) || err != io.ErrUnexpectedEOF {
    125 		t.Fatalf("ReadDotLines2: %v, %v", s, err)
    126 	}
    127 }
    128 
    129 func TestReadDotBytes(t *testing.T) {
    130 	r := reader("dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanot.her\r\n")
    131 	b, err := r.ReadDotBytes()
    132 	want := []byte("dotlines\nfoo\n.bar\n..baz\nquux\n\n")
    133 	if !reflect.DeepEqual(b, want) || err != nil {
    134 		t.Fatalf("ReadDotBytes: %q, %v", b, err)
    135 	}
    136 
    137 	b, err = r.ReadDotBytes()
    138 	want = []byte("anot.her\n")
    139 	if !reflect.DeepEqual(b, want) || err != io.ErrUnexpectedEOF {
    140 		t.Fatalf("ReadDotBytes2: %q, %v", b, err)
    141 	}
    142 }
    143 
    144 func TestReadMIMEHeader(t *testing.T) {
    145 	r := reader("my-key: Value 1  \r\nLong-key: Even \n Longer Value\r\nmy-Key: Value 2\r\n\n")
    146 	m, err := r.ReadMIMEHeader()
    147 	want := MIMEHeader{
    148 		"My-Key":   {"Value 1", "Value 2"},
    149 		"Long-Key": {"Even Longer Value"},
    150 	}
    151 	if !reflect.DeepEqual(m, want) || err != nil {
    152 		t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
    153 	}
    154 }
    155 
    156 func TestReadMIMEHeaderSingle(t *testing.T) {
    157 	r := reader("Foo: bar\n\n")
    158 	m, err := r.ReadMIMEHeader()
    159 	want := MIMEHeader{"Foo": {"bar"}}
    160 	if !reflect.DeepEqual(m, want) || err != nil {
    161 		t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
    162 	}
    163 }
    164 
    165 func TestReadMIMEHeaderNoKey(t *testing.T) {
    166 	r := reader(": bar\ntest-1: 1\n\n")
    167 	m, err := r.ReadMIMEHeader()
    168 	want := MIMEHeader{"Test-1": {"1"}}
    169 	if !reflect.DeepEqual(m, want) || err != nil {
    170 		t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
    171 	}
    172 }
    173 
    174 func TestLargeReadMIMEHeader(t *testing.T) {
    175 	data := make([]byte, 16*1024)
    176 	for i := 0; i < len(data); i++ {
    177 		data[i] = 'x'
    178 	}
    179 	sdata := string(data)
    180 	r := reader("Cookie: " + sdata + "\r\n\n")
    181 	m, err := r.ReadMIMEHeader()
    182 	if err != nil {
    183 		t.Fatalf("ReadMIMEHeader: %v", err)
    184 	}
    185 	cookie := m.Get("Cookie")
    186 	if cookie != sdata {
    187 		t.Fatalf("ReadMIMEHeader: %v bytes, want %v bytes", len(cookie), len(sdata))
    188 	}
    189 }
    190 
    191 // Test that we read slightly-bogus MIME headers seen in the wild,
    192 // with spaces before colons, and spaces in keys.
    193 func TestReadMIMEHeaderNonCompliant(t *testing.T) {
    194 	// Invalid HTTP response header as sent by an Axis security
    195 	// camera: (this is handled by IE, Firefox, Chrome, curl, etc.)
    196 	r := reader("Foo: bar\r\n" +
    197 		"Content-Language: en\r\n" +
    198 		"SID : 0\r\n" +
    199 		"Audio Mode : None\r\n" +
    200 		"Privilege : 127\r\n\r\n")
    201 	m, err := r.ReadMIMEHeader()
    202 	want := MIMEHeader{
    203 		"Foo":              {"bar"},
    204 		"Content-Language": {"en"},
    205 		"Sid":              {"0"},
    206 		"Audio Mode":       {"None"},
    207 		"Privilege":        {"127"},
    208 	}
    209 	if !reflect.DeepEqual(m, want) || err != nil {
    210 		t.Fatalf("ReadMIMEHeader =\n%v, %v; want:\n%v", m, err, want)
    211 	}
    212 }
    213 
    214 func TestReadMIMEHeaderMalformed(t *testing.T) {
    215 	inputs := []string{
    216 		"No colon first line\r\nFoo: foo\r\n\r\n",
    217 		" No colon first line with leading space\r\nFoo: foo\r\n\r\n",
    218 		"\tNo colon first line with leading tab\r\nFoo: foo\r\n\r\n",
    219 		" First: line with leading space\r\nFoo: foo\r\n\r\n",
    220 		"\tFirst: line with leading tab\r\nFoo: foo\r\n\r\n",
    221 		"Foo: foo\r\nNo colon second line\r\n\r\n",
    222 	}
    223 
    224 	for _, input := range inputs {
    225 		r := reader(input)
    226 		if m, err := r.ReadMIMEHeader(); err == nil {
    227 			t.Errorf("ReadMIMEHeader(%q) = %v, %v; want nil, err", input, m, err)
    228 		}
    229 	}
    230 }
    231 
    232 // Test that continued lines are properly trimmed. Issue 11204.
    233 func TestReadMIMEHeaderTrimContinued(t *testing.T) {
    234 	// In this header, \n and \r\n terminated lines are mixed on purpose.
    235 	// We expect each line to be trimmed (prefix and suffix) before being concatenated.
    236 	// Keep the spaces as they are.
    237 	r := reader("" + // for code formatting purpose.
    238 		"a:\n" +
    239 		" 0 \r\n" +
    240 		"b:1 \t\r\n" +
    241 		"c: 2\r\n" +
    242 		" 3\t\n" +
    243 		"  \t 4  \r\n\n")
    244 	m, err := r.ReadMIMEHeader()
    245 	if err != nil {
    246 		t.Fatal(err)
    247 	}
    248 	want := MIMEHeader{
    249 		"A": {"0"},
    250 		"B": {"1"},
    251 		"C": {"2 3 4"},
    252 	}
    253 	if !reflect.DeepEqual(m, want) {
    254 		t.Fatalf("ReadMIMEHeader mismatch.\n got: %q\nwant: %q", m, want)
    255 	}
    256 }
    257 
    258 type readResponseTest struct {
    259 	in       string
    260 	inCode   int
    261 	wantCode int
    262 	wantMsg  string
    263 }
    264 
    265 var readResponseTests = []readResponseTest{
    266 	{"230-Anonymous access granted, restrictions apply\n" +
    267 		"Read the file README.txt,\n" +
    268 		"230  please",
    269 		23,
    270 		230,
    271 		"Anonymous access granted, restrictions apply\nRead the file README.txt,\n please",
    272 	},
    273 
    274 	{"230 Anonymous access granted, restrictions apply\n",
    275 		23,
    276 		230,
    277 		"Anonymous access granted, restrictions apply",
    278 	},
    279 
    280 	{"400-A\n400-B\n400 C",
    281 		4,
    282 		400,
    283 		"A\nB\nC",
    284 	},
    285 
    286 	{"400-A\r\n400-B\r\n400 C\r\n",
    287 		4,
    288 		400,
    289 		"A\nB\nC",
    290 	},
    291 }
    292 
    293 // See http://www.ietf.org/rfc/rfc959.txt page 36.
    294 func TestRFC959Lines(t *testing.T) {
    295 	for i, tt := range readResponseTests {
    296 		r := reader(tt.in + "\nFOLLOWING DATA")
    297 		code, msg, err := r.ReadResponse(tt.inCode)
    298 		if err != nil {
    299 			t.Errorf("#%d: ReadResponse: %v", i, err)
    300 			continue
    301 		}
    302 		if code != tt.wantCode {
    303 			t.Errorf("#%d: code=%d, want %d", i, code, tt.wantCode)
    304 		}
    305 		if msg != tt.wantMsg {
    306 			t.Errorf("#%d: msg=%q, want %q", i, msg, tt.wantMsg)
    307 		}
    308 	}
    309 }
    310 
    311 // Test that multi-line errors are appropriately and fully read. Issue 10230.
    312 func TestReadMultiLineError(t *testing.T) {
    313 	r := reader("550-5.1.1 The email account that you tried to reach does not exist. Please try\n" +
    314 		"550-5.1.1 double-checking the recipient's email address for typos or\n" +
    315 		"550-5.1.1 unnecessary spaces. Learn more at\n" +
    316 		"Unexpected but legal text!\n" +
    317 		"550 5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp\n")
    318 
    319 	wantMsg := "5.1.1 The email account that you tried to reach does not exist. Please try\n" +
    320 		"5.1.1 double-checking the recipient's email address for typos or\n" +
    321 		"5.1.1 unnecessary spaces. Learn more at\n" +
    322 		"Unexpected but legal text!\n" +
    323 		"5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp"
    324 
    325 	code, msg, err := r.ReadResponse(250)
    326 	if err == nil {
    327 		t.Errorf("ReadResponse: no error, want error")
    328 	}
    329 	if code != 550 {
    330 		t.Errorf("ReadResponse: code=%d, want %d", code, 550)
    331 	}
    332 	if msg != wantMsg {
    333 		t.Errorf("ReadResponse: msg=%q, want %q", msg, wantMsg)
    334 	}
    335 	if err.Error() != "550 "+wantMsg {
    336 		t.Errorf("ReadResponse: error=%q, want %q", err.Error(), "550 "+wantMsg)
    337 	}
    338 }
    339 
    340 func TestCommonHeaders(t *testing.T) {
    341 	for h := range commonHeader {
    342 		if h != CanonicalMIMEHeaderKey(h) {
    343 			t.Errorf("Non-canonical header %q in commonHeader", h)
    344 		}
    345 	}
    346 	b := []byte("content-Length")
    347 	want := "Content-Length"
    348 	n := testing.AllocsPerRun(200, func() {
    349 		if x := canonicalMIMEHeaderKey(b); x != want {
    350 			t.Fatalf("canonicalMIMEHeaderKey(%q) = %q; want %q", b, x, want)
    351 		}
    352 	})
    353 	if n > 0 {
    354 		t.Errorf("canonicalMIMEHeaderKey allocs = %v; want 0", n)
    355 	}
    356 }
    357 
    358 var clientHeaders = strings.Replace(`Host: golang.org
    359 Connection: keep-alive
    360 Cache-Control: max-age=0
    361 Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
    362 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
    363 Accept-Encoding: gzip,deflate,sdch
    364 Accept-Language: en-US,en;q=0.8,fr-CH;q=0.6
    365 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
    366 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
    367 Non-Interned: test
    368 
    369 `, "\n", "\r\n", -1)
    370 
    371 var serverHeaders = strings.Replace(`Content-Type: text/html; charset=utf-8
    372 Content-Encoding: gzip
    373 Date: Thu, 27 Sep 2012 09:03:33 GMT
    374 Server: Google Frontend
    375 Cache-Control: private
    376 Content-Length: 2298
    377 VIA: 1.1 proxy.example.com:80 (XXX/n.n.n-nnn)
    378 Connection: Close
    379 Non-Interned: test
    380 
    381 `, "\n", "\r\n", -1)
    382 
    383 func BenchmarkReadMIMEHeader(b *testing.B) {
    384 	b.ReportAllocs()
    385 	var buf bytes.Buffer
    386 	br := bufio.NewReader(&buf)
    387 	r := NewReader(br)
    388 	for i := 0; i < b.N; i++ {
    389 		var want int
    390 		var find string
    391 		if (i & 1) == 1 {
    392 			buf.WriteString(clientHeaders)
    393 			want = 10
    394 			find = "Cookie"
    395 		} else {
    396 			buf.WriteString(serverHeaders)
    397 			want = 9
    398 			find = "Via"
    399 		}
    400 		h, err := r.ReadMIMEHeader()
    401 		if err != nil {
    402 			b.Fatal(err)
    403 		}
    404 		if len(h) != want {
    405 			b.Fatalf("wrong number of headers: got %d, want %d", len(h), want)
    406 		}
    407 		if _, ok := h[find]; !ok {
    408 			b.Fatalf("did not find key %s", find)
    409 		}
    410 	}
    411 }
    412 
    413 func BenchmarkUncommon(b *testing.B) {
    414 	b.ReportAllocs()
    415 	var buf bytes.Buffer
    416 	br := bufio.NewReader(&buf)
    417 	r := NewReader(br)
    418 	for i := 0; i < b.N; i++ {
    419 		buf.WriteString("uncommon-header-for-benchmark: foo\r\n\r\n")
    420 		h, err := r.ReadMIMEHeader()
    421 		if err != nil {
    422 			b.Fatal(err)
    423 		}
    424 		if _, ok := h["Uncommon-Header-For-Benchmark"]; !ok {
    425 			b.Fatal("Missing result header.")
    426 		}
    427 	}
    428 }
    429