github.com/lovishpuri/go-40569/src@v0.0.0-20230519171745-f8623e7c56cf/net/textproto/reader_test.go (about) 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 "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 // TestReaderUpcomingHeaderKeys is testing an internal function, but it's very 134 // difficult to test well via the external API. 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 // TestReadMIMEHeaderNonCompliant checks that we don't normalize headers 196 // with spaces before colons, and accept spaces in keys. 197 func TestReadMIMEHeaderNonCompliant(t *testing.T) { 198 // These invalid headers will be rejected by net/http according to RFC 7230. 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 // Special case: "Foo:Bar: foo" is the header "Foo". 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 // Test that continued lines are properly trimmed. Issue 11204. 286 func TestReadMIMEHeaderTrimContinued(t *testing.T) { 287 // In this header, \n and \r\n terminated lines are mixed on purpose. 288 // We expect each line to be trimmed (prefix and suffix) before being concatenated. 289 // Keep the spaces as they are. 290 r := reader("" + // for code formatting purpose. 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 // Test that reading a header doesn't overallocate. Issue 58975. 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 // 32k is large and we actually allocate substantially less, 327 // but prior to the fix for #58975 we allocated ~400k in this case. 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 // See https://www.ietf.org/rfc/rfc959.txt page 36. 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 // Test that multi-line errors are appropriately and fully read. Issue 10230. 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 // Regression test for data race reported in issue 46363: 436 // ReadMIMEHeader reads commonHeader before commonHeader has been initialized. 437 // Run this test with the race detector enabled to catch the reported data race. 438 439 // Reset commonHeaderOnce, so that commonHeader will have to be initialized 440 commonHeaderOnce = sync.Once{} 441 commonHeader = nil 442 443 // Test for data race by calling ReadMIMEHeader and CanonicalMIMEHeaderKey concurrently 444 445 // Send MIME header over net.Conn 446 r, w := net.Pipe() 447 go func() { 448 // ReadMIMEHeader calls canonicalMIMEHeaderKey, which reads from commonHeader 449 NewConn(r).ReadMIMEHeader() 450 }() 451 w.Write([]byte("A: 1\r\nB: 2\r\nC: 3\r\n\r\n")) 452 453 // CanonicalMIMEHeaderKey calls commonHeaderOnce.Do(initCommonHeader) which initializes commonHeader 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 }