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 // Test that continued lines are properly trimmed. Issue 11204.
    215 func TestReadMIMEHeaderTrimContinued(t *testing.T) {
    216 	// In this header, \n and \r\n terminated lines are mixed on purpose.
    217 	// We expect each line to be trimmed (prefix and suffix) before being concatenated.
    218 	// Keep the spaces as they are.
    219 	r := reader("" + // for code formatting purpose.
    220 		"a:\n" +
    221 		" 0 \r\n" +
    222 		"b:1 \t\r\n" +
    223 		"c: 2\r\n" +
    224 		" 3\t\n" +
    225 		"  \t 4  \r\n\n")
    226 	m, err := r.ReadMIMEHeader()
    227 	if err != nil {
    228 		t.Fatal(err)
    229 	}
    230 	want := MIMEHeader{
    231 		"A": {"0"},
    232 		"B": {"1"},
    233 		"C": {"2 3 4"},
    234 	}
    235 	if !reflect.DeepEqual(m, want) {
    236 		t.Fatalf("ReadMIMEHeader mismatch.\n got: %q\nwant: %q", m, want)
    237 	}
    238 }
    239 
    240 type readResponseTest struct {
    241 	in       string
    242 	inCode   int
    243 	wantCode int
    244 	wantMsg  string
    245 }
    246 
    247 var readResponseTests = []readResponseTest{
    248 	{"230-Anonymous access granted, restrictions apply\n" +
    249 		"Read the file README.txt,\n" +
    250 		"230  please",
    251 		23,
    252 		230,
    253 		"Anonymous access granted, restrictions apply\nRead the file README.txt,\n please",
    254 	},
    255 
    256 	{"230 Anonymous access granted, restrictions apply\n",
    257 		23,
    258 		230,
    259 		"Anonymous access granted, restrictions apply",
    260 	},
    261 
    262 	{"400-A\n400-B\n400 C",
    263 		4,
    264 		400,
    265 		"A\nB\nC",
    266 	},
    267 
    268 	{"400-A\r\n400-B\r\n400 C\r\n",
    269 		4,
    270 		400,
    271 		"A\nB\nC",
    272 	},
    273 }
    274 
    275 // See http://www.ietf.org/rfc/rfc959.txt page 36.
    276 func TestRFC959Lines(t *testing.T) {
    277 	for i, tt := range readResponseTests {
    278 		r := reader(tt.in + "\nFOLLOWING DATA")
    279 		code, msg, err := r.ReadResponse(tt.inCode)
    280 		if err != nil {
    281 			t.Errorf("#%d: ReadResponse: %v", i, err)
    282 			continue
    283 		}
    284 		if code != tt.wantCode {
    285 			t.Errorf("#%d: code=%d, want %d", i, code, tt.wantCode)
    286 		}
    287 		if msg != tt.wantMsg {
    288 			t.Errorf("#%d: msg=%q, want %q", i, msg, tt.wantMsg)
    289 		}
    290 	}
    291 }
    292 
    293 // Test that multi-line errors are appropriately and fully read. Issue 10230.
    294 func TestReadMultiLineError(t *testing.T) {
    295 	r := reader("550-5.1.1 The email account that you tried to reach does not exist. Please try\n" +
    296 		"550-5.1.1 double-checking the recipient's email address for typos or\n" +
    297 		"550-5.1.1 unnecessary spaces. Learn more at\n" +
    298 		"Unexpected but legal text!\n" +
    299 		"550 5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp\n")
    300 
    301 	wantMsg := "5.1.1 The email account that you tried to reach does not exist. Please try\n" +
    302 		"5.1.1 double-checking the recipient's email address for typos or\n" +
    303 		"5.1.1 unnecessary spaces. Learn more at\n" +
    304 		"Unexpected but legal text!\n" +
    305 		"5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp"
    306 
    307 	code, msg, err := r.ReadResponse(250)
    308 	if err == nil {
    309 		t.Errorf("ReadResponse: no error, want error")
    310 	}
    311 	if code != 550 {
    312 		t.Errorf("ReadResponse: code=%d, want %d", code, 550)
    313 	}
    314 	if msg != wantMsg {
    315 		t.Errorf("ReadResponse: msg=%q, want %q", msg, wantMsg)
    316 	}
    317 	if err.Error() != "550 "+wantMsg {
    318 		t.Errorf("ReadResponse: error=%q, want %q", err.Error(), "550 "+wantMsg)
    319 	}
    320 }
    321 
    322 func TestCommonHeaders(t *testing.T) {
    323 	for h := range commonHeader {
    324 		if h != CanonicalMIMEHeaderKey(h) {
    325 			t.Errorf("Non-canonical header %q in commonHeader", h)
    326 		}
    327 	}
    328 	b := []byte("content-Length")
    329 	want := "Content-Length"
    330 	n := testing.AllocsPerRun(200, func() {
    331 		if x := canonicalMIMEHeaderKey(b); x != want {
    332 			t.Fatalf("canonicalMIMEHeaderKey(%q) = %q; want %q", b, x, want)
    333 		}
    334 	})
    335 	if n > 0 {
    336 		t.Errorf("canonicalMIMEHeaderKey allocs = %v; want 0", n)
    337 	}
    338 }
    339 
    340 var clientHeaders = strings.Replace(`Host: golang.org
    341 Connection: keep-alive
    342 Cache-Control: max-age=0
    343 Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
    344 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
    345 Accept-Encoding: gzip,deflate,sdch
    346 Accept-Language: en-US,en;q=0.8,fr-CH;q=0.6
    347 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
    348 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
    349 Non-Interned: test
    350 
    351 `, "\n", "\r\n", -1)
    352 
    353 var serverHeaders = strings.Replace(`Content-Type: text/html; charset=utf-8
    354 Content-Encoding: gzip
    355 Date: Thu, 27 Sep 2012 09:03:33 GMT
    356 Server: Google Frontend
    357 Cache-Control: private
    358 Content-Length: 2298
    359 VIA: 1.1 proxy.example.com:80 (XXX/n.n.n-nnn)
    360 Connection: Close
    361 Non-Interned: test
    362 
    363 `, "\n", "\r\n", -1)
    364 
    365 func BenchmarkReadMIMEHeader(b *testing.B) {
    366 	b.ReportAllocs()
    367 	var buf bytes.Buffer
    368 	br := bufio.NewReader(&buf)
    369 	r := NewReader(br)
    370 	for i := 0; i < b.N; i++ {
    371 		var want int
    372 		var find string
    373 		if (i & 1) == 1 {
    374 			buf.WriteString(clientHeaders)
    375 			want = 10
    376 			find = "Cookie"
    377 		} else {
    378 			buf.WriteString(serverHeaders)
    379 			want = 9
    380 			find = "Via"
    381 		}
    382 		h, err := r.ReadMIMEHeader()
    383 		if err != nil {
    384 			b.Fatal(err)
    385 		}
    386 		if len(h) != want {
    387 			b.Fatalf("wrong number of headers: got %d, want %d", len(h), want)
    388 		}
    389 		if _, ok := h[find]; !ok {
    390 			b.Fatalf("did not find key %s", find)
    391 		}
    392 	}
    393 }
    394 
    395 func BenchmarkUncommon(b *testing.B) {
    396 	b.ReportAllocs()
    397 	var buf bytes.Buffer
    398 	br := bufio.NewReader(&buf)
    399 	r := NewReader(br)
    400 	for i := 0; i < b.N; i++ {
    401 		buf.WriteString("uncommon-header-for-benchmark: foo\r\n\r\n")
    402 		h, err := r.ReadMIMEHeader()
    403 		if err != nil {
    404 			b.Fatal(err)
    405 		}
    406 		if _, ok := h["Uncommon-Header-For-Benchmark"]; !ok {
    407 			b.Fatal("Missing result header.")
    408 		}
    409 	}
    410 }
    411