github.com/cloudwego/hertz@v0.9.3/pkg/protocol/http1/req/header_test.go (about) 1 /* 2 * Copyright 2022 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * The MIT License (MIT) 17 * 18 * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors 19 * 20 * Permission is hereby granted, free of charge, to any person obtaining a copy 21 * of this software and associated documentation files (the "Software"), to deal 22 * in the Software without restriction, including without limitation the rights 23 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 * copies of the Software, and to permit persons to whom the Software is 25 * furnished to do so, subject to the following conditions: 26 * 27 * The above copyright notice and this permission notice shall be included in 28 * all copies or substantial portions of the Software. 29 * 30 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 * THE SOFTWARE. 37 * 38 * This file may have been modified by CloudWeGo authors. All CloudWeGo 39 * Modifications are Copyright 2022 CloudWeGo Authors. 40 */ 41 42 package req 43 44 import ( 45 "bufio" 46 "bytes" 47 "errors" 48 "fmt" 49 "net/http" 50 "strings" 51 "testing" 52 53 errs "github.com/cloudwego/hertz/pkg/common/errors" 54 "github.com/cloudwego/hertz/pkg/common/test/assert" 55 "github.com/cloudwego/hertz/pkg/common/test/mock" 56 "github.com/cloudwego/hertz/pkg/protocol" 57 "github.com/cloudwego/hertz/pkg/protocol/consts" 58 "github.com/cloudwego/netpoll" 59 ) 60 61 func TestRequestHeader_Read(t *testing.T) { 62 s := "PUT /foo/bar HTTP/1.1\r\nExpect: 100-continue\r\nUser-Agent: foo\r\nHost: 127.0.0.1\r\nConnection: Keep-Alive\r\nContent-Length: 5\r\nContent-Type: foo/bar\r\n\r\nabcdef4343" 63 zr := mock.NewZeroCopyReader(s) 64 rh := protocol.RequestHeader{} 65 ReadHeader(&rh, zr) 66 67 // firstline 68 assert.DeepEqual(t, []byte(consts.MethodPut), rh.Method()) 69 assert.DeepEqual(t, []byte("/foo/bar"), rh.RequestURI()) 70 assert.True(t, rh.IsHTTP11()) 71 72 // headers 73 assert.DeepEqual(t, 5, rh.ContentLength()) 74 assert.DeepEqual(t, []byte("foo/bar"), rh.ContentType()) 75 count := 0 76 rh.VisitAll(func(key, value []byte) { 77 count += 1 78 }) 79 assert.DeepEqual(t, 6, count) 80 assert.DeepEqual(t, []byte("foo"), rh.UserAgent()) 81 assert.DeepEqual(t, []byte("127.0.0.1"), rh.Host()) 82 assert.DeepEqual(t, []byte("100-continue"), rh.Peek("Expect")) 83 } 84 85 func TestRequestHeaderMultiLineValue(t *testing.T) { 86 s := "HTTP/1.1 200 OK\r\n" + 87 "EmptyValue1:\r\n" + 88 "Content-Type: foo/bar;\r\n\tnewline;\r\n another/newline\r\n" + 89 "Foo: Bar\r\n" + 90 "Multi-Line: one;\r\n two\r\n" + 91 "Values: v1;\r\n v2; v3;\r\n v4;\tv5\r\n" + 92 "\r\n" 93 94 header := new(protocol.RequestHeader) 95 if _, err := parse(header, []byte(s)); err != nil { 96 t.Fatalf("parse headers with multi-line values failed, %s", err) 97 } 98 response, err := http.ReadResponse(bufio.NewReader(strings.NewReader(s)), nil) 99 if err != nil { 100 t.Fatalf("parse response using net/http failed, %s", err) 101 } 102 103 for name, vals := range response.Header { 104 got := string(header.Peek(name)) 105 want := vals[0] 106 107 if got != want { 108 t.Errorf("unexpected %s got: %q want: %q", name, got, want) 109 } 110 } 111 } 112 113 func TestRequestHeader_Peek(t *testing.T) { 114 s := "PUT /foo/bar HTTP/1.1\r\nExpect: 100-continue\r\nUser-Agent: foo\r\nHost: 127.0.0.1\r\nConnection: Keep-Alive\r\nContent-Length: 5\r\nTransfer-Encoding: foo\r\nContent-Type: foo/bar\r\n\r\nabcdef4343" 115 zr := mock.NewZeroCopyReader(s) 116 rh := protocol.RequestHeader{} 117 ReadHeader(&rh, zr) 118 assert.DeepEqual(t, []byte("100-continue"), rh.Peek("Expect")) 119 assert.DeepEqual(t, []byte("127.0.0.1"), rh.Peek("Host")) 120 assert.DeepEqual(t, []byte("foo"), rh.Peek("User-Agent")) 121 assert.DeepEqual(t, []byte("Keep-Alive"), rh.Peek("Connection")) 122 assert.DeepEqual(t, []byte(""), rh.Peek("Content-Length")) 123 assert.DeepEqual(t, []byte("foo/bar"), rh.Peek("Content-Type")) 124 } 125 126 func TestRequestHeaderSetGet(t *testing.T) { 127 t.Parallel() 128 129 h := &protocol.RequestHeader{} 130 h.SetRequestURI("/aa/bbb") 131 h.SetMethod(consts.MethodPost) 132 h.Set("foo", "bar") 133 h.Set("host", "12345") 134 h.Set("content-type", "aaa/bbb") 135 h.Set("content-length", "1234") 136 h.Set("user-agent", "aaabbb") 137 h.Set("referer", "axcv") 138 h.Set("baz", "xxxxx") 139 h.Set("transfer-encoding", "chunked") 140 h.Set("connection", "close") 141 142 expectRequestHeaderGet(t, h, "Foo", "bar") 143 expectRequestHeaderGet(t, h, consts.HeaderHost, "12345") 144 expectRequestHeaderGet(t, h, consts.HeaderContentType, "aaa/bbb") 145 expectRequestHeaderGet(t, h, consts.HeaderContentLength, "1234") 146 expectRequestHeaderGet(t, h, "USER-AGent", "aaabbb") 147 expectRequestHeaderGet(t, h, consts.HeaderReferer, "axcv") 148 expectRequestHeaderGet(t, h, "baz", "xxxxx") 149 expectRequestHeaderGet(t, h, consts.HeaderTransferEncoding, "") 150 expectRequestHeaderGet(t, h, "connecTION", "close") 151 if !h.ConnectionClose() { 152 t.Fatalf("unset connection: close") 153 } 154 155 if h.ContentLength() != 1234 { 156 t.Fatalf("Unexpected content-length %d. Expected %d", h.ContentLength(), 1234) 157 } 158 159 w := &bytes.Buffer{} 160 bw := bufio.NewWriter(w) 161 zw := netpoll.NewWriter(bw) 162 err := WriteHeader(h, zw) 163 if err != nil { 164 t.Fatalf("Unexpected error when writing request header: %s", err) 165 } 166 if err := bw.Flush(); err != nil { 167 t.Fatalf("Unexpected error when flushing request header: %s", err) 168 } 169 zw.Flush() 170 bw.Flush() 171 172 var h1 protocol.RequestHeader 173 br := bufio.NewReader(w) 174 zr := mock.ZeroCopyReader{Reader: br} 175 if err = ReadHeader(&h1, zr); err != nil { 176 t.Fatalf("Unexpected error when reading request header: %s", err) 177 } 178 179 if h1.ContentLength() != h.ContentLength() { 180 t.Fatalf("Unexpected Content-Length %d. Expected %d", h1.ContentLength(), h.ContentLength()) 181 } 182 183 expectRequestHeaderGet(t, &h1, "Foo", "bar") 184 expectRequestHeaderGet(t, &h1, "HOST", "12345") 185 expectRequestHeaderGet(t, &h1, consts.HeaderContentType, "aaa/bbb") 186 expectRequestHeaderGet(t, &h1, consts.HeaderContentLength, "1234") 187 expectRequestHeaderGet(t, &h1, "USER-AGent", "aaabbb") 188 expectRequestHeaderGet(t, &h1, consts.HeaderReferer, "axcv") 189 expectRequestHeaderGet(t, &h1, "baz", "xxxxx") 190 expectRequestHeaderGet(t, &h1, consts.HeaderTransferEncoding, "") 191 expectRequestHeaderGet(t, &h1, consts.HeaderConnection, "close") 192 if !h1.ConnectionClose() { 193 t.Fatalf("unset connection: close") 194 } 195 } 196 197 func TestRequestHeaderCookie(t *testing.T) { 198 t.Parallel() 199 200 var h protocol.RequestHeader 201 h.SetRequestURI("/foobar") 202 h.Set(consts.HeaderHost, "foobar.com") 203 204 h.SetCookie("foo", "bar") 205 h.SetCookie("привет", "мир") 206 207 if string(h.Cookie("foo")) != "bar" { 208 t.Fatalf("Unexpected cookie value %q. Expected %q", h.Cookie("foo"), "bar") 209 } 210 if string(h.Cookie("привет")) != "мир" { 211 t.Fatalf("Unexpected cookie value %q. Expected %q", h.Cookie("привет"), "мир") 212 } 213 214 w := &bytes.Buffer{} 215 zw := netpoll.NewWriter(w) 216 if err := WriteHeader(&h, zw); err != nil { 217 t.Fatalf("Unexpected error: %s", err) 218 } 219 if err := zw.Flush(); err != nil { 220 t.Fatalf("Unexpected error: %s", err) 221 } 222 223 var h1 protocol.RequestHeader 224 br := bufio.NewReader(w) 225 zr := mock.ZeroCopyReader{Reader: br} 226 if err := ReadHeader(&h1, zr); err != nil { 227 t.Fatalf("Unexpected error: %s", err) 228 } 229 230 if !bytes.Equal(h1.Cookie("foo"), h.Cookie("foo")) { 231 t.Fatalf("Unexpected cookie value %q. Expected %q", h1.Cookie("foo"), h.Cookie("foo")) 232 } 233 h1.DelCookie("foo") 234 if len(h1.Cookie("foo")) > 0 { 235 t.Fatalf("Unexpected cookie found: %q", h1.Cookie("foo")) 236 } 237 if !bytes.Equal(h1.Cookie("привет"), h.Cookie("привет")) { 238 t.Fatalf("Unexpected cookie value %q. Expected %q", h1.Cookie("привет"), h.Cookie("привет")) 239 } 240 h1.DelCookie("привет") 241 if len(h1.Cookie("привет")) > 0 { 242 t.Fatalf("Unexpected cookie found: %q", h1.Cookie("привет")) 243 } 244 245 h.DelAllCookies() 246 if len(h.Cookie("foo")) > 0 { 247 t.Fatalf("Unexpected cookie found: %q", h.Cookie("foo")) 248 } 249 if len(h.Cookie("привет")) > 0 { 250 t.Fatalf("Unexpected cookie found: %q", h.Cookie("привет")) 251 } 252 } 253 254 func TestRequestRawHeaders(t *testing.T) { 255 t.Parallel() 256 257 kvs := "hOsT: foobar\r\n" + 258 "value: b\r\n" + 259 "\r\n" 260 t.Run("normalized", func(t *testing.T) { 261 s := "GET / HTTP/1.1\r\n" + kvs 262 exp := kvs 263 var h protocol.RequestHeader 264 zr := mock.NewZeroCopyReader(s) 265 if err := ReadHeader(&h, zr); err != nil { 266 t.Fatalf("unexpected error: %s", err) 267 } 268 if string(h.Host()) != "foobar" { 269 t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar") 270 } 271 v2 := h.Peek("Value") 272 if !bytes.Equal(v2, []byte{'b'}) { 273 t.Fatalf("expecting non empty value. Got %q", v2) 274 } 275 if raw := h.RawHeaders(); string(raw) != exp { 276 t.Fatalf("expected header %q, got %q", exp, raw) 277 } 278 }) 279 for _, n := range []int{0, 1, 4, 8} { 280 t.Run(fmt.Sprintf("post-%dk", n), func(t *testing.T) { 281 l := 1024 * n 282 body := make([]byte, l) 283 for i := range body { 284 body[i] = 'a' 285 } 286 cl := fmt.Sprintf("Content-Length: %d\r\n", l) 287 s := "POST / HTTP/1.1\r\n" + cl + kvs + string(body) 288 exp := cl + kvs 289 var h protocol.RequestHeader 290 zr := mock.NewZeroCopyReader(s) 291 if err := ReadHeader(&h, zr); err != nil { 292 t.Fatalf("unexpected error: %s", err) 293 } 294 if string(h.Host()) != "foobar" { 295 t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar") 296 } 297 v2 := h.Peek("Value") 298 if !bytes.Equal(v2, []byte{'b'}) { 299 t.Fatalf("expecting non empty value. Got %q", v2) 300 } 301 if raw := h.RawHeaders(); string(raw) != exp { 302 t.Fatalf("expected header %q, got %q", exp, raw) 303 } 304 }) 305 } 306 t.Run("http10", func(t *testing.T) { 307 s := "GET / HTTP/1.0\r\n" + kvs 308 exp := kvs 309 var h protocol.RequestHeader 310 zr := mock.NewZeroCopyReader(s) 311 if err := ReadHeader(&h, zr); err != nil { 312 t.Fatalf("unexpected error: %s", err) 313 } 314 if string(h.Host()) != "foobar" { 315 t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar") 316 } 317 v2 := h.Peek("Value") 318 if !bytes.Equal(v2, []byte{'b'}) { 319 t.Fatalf("expecting non empty value. Got %q", v2) 320 } 321 if raw := h.RawHeaders(); string(raw) != exp { 322 t.Fatalf("expected header %q, got %q", exp, raw) 323 } 324 }) 325 t.Run("no-kvs", func(t *testing.T) { 326 s := "GET / HTTP/1.1\r\n\r\n" 327 exp := "" 328 var h protocol.RequestHeader 329 h.DisableNormalizing() 330 zr := mock.NewZeroCopyReader(s) 331 if err := ReadHeader(&h, zr); err != nil { 332 t.Fatalf("unexpected error: %s", err) 333 } 334 if string(h.Host()) != "" { 335 t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "") 336 } 337 v1 := h.Peek("NoKey") 338 if len(v1) > 0 { 339 t.Fatalf("expecting empty value. Got %q", v1) 340 } 341 if raw := h.RawHeaders(); string(raw) != exp { 342 t.Fatalf("expected header %q, got %q", exp, raw) 343 } 344 }) 345 } 346 347 func TestRequestHeaderEmptyValueFromHeader(t *testing.T) { 348 t.Parallel() 349 350 var h1 protocol.RequestHeader 351 h1.SetRequestURI("/foo/bar") 352 h1.SetHost("foobar") 353 h1.Set("EmptyValue1", "") 354 h1.Set("EmptyValue2", " ") 355 s := h1.String() 356 357 var h protocol.RequestHeader 358 zr := mock.NewZeroCopyReader(s) 359 if err := ReadHeader(&h, zr); err != nil { 360 t.Fatalf("unexpected error: %s", err) 361 } 362 if string(h.Host()) != string(h1.Host()) { 363 t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), h1.Host()) 364 } 365 v1 := h.Peek("EmptyValue1") 366 if len(v1) > 0 { 367 t.Fatalf("expecting empty value. Got %q", v1) 368 } 369 v2 := h.Peek("EmptyValue2") 370 if len(v2) > 0 { 371 t.Fatalf("expecting empty value. Got %q", v2) 372 } 373 } 374 375 func TestRequestHeaderEmptyValueFromString(t *testing.T) { 376 t.Parallel() 377 378 s := "GET / HTTP/1.1\r\n" + 379 "EmptyValue1:\r\n" + 380 "Host: foobar\r\n" + 381 "EmptyValue2: \r\n" + 382 "\r\n" 383 var h protocol.RequestHeader 384 zr := mock.NewZeroCopyReader(s) 385 if err := ReadHeader(&h, zr); err != nil { 386 t.Fatalf("unexpected error: %s", err) 387 } 388 if string(h.Host()) != "foobar" { 389 t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar") 390 } 391 v1 := h.Peek("EmptyValue1") 392 if len(v1) > 0 { 393 t.Fatalf("expecting empty value. Got %q", v1) 394 } 395 v2 := h.Peek("EmptyValue2") 396 if len(v2) > 0 { 397 t.Fatalf("expecting empty value. Got %q", v2) 398 } 399 } 400 401 func expectRequestHeaderGet(t *testing.T, h *protocol.RequestHeader, key, expectedValue string) { 402 if string(h.Peek(key)) != expectedValue { 403 t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.Peek(key), expectedValue) 404 } 405 } 406 407 func TestRequestHeader_PeekIfExists(t *testing.T) { 408 s := "PUT /foo/bar HTTP/1.1\r\nExpect: 100-continue\r\nexists: \r\nContent-Type: foo/bar\r\n\r\nabcdef4343" 409 rh := protocol.RequestHeader{} 410 err := ReadHeader(&rh, mock.NewZeroCopyReader(s)) 411 if err != nil { 412 t.Fatal(err) 413 } 414 assert.DeepEqual(t, []byte{}, rh.Peek("exists")) 415 assert.DeepEqual(t, []byte(nil), rh.Peek("non-exists")) 416 } 417 418 func TestRequestHeaderError(t *testing.T) { 419 er := mock.EOFReader{} 420 rh := protocol.RequestHeader{} 421 err := ReadHeader(&rh, &er) 422 assert.True(t, errors.Is(err, errs.ErrNothingRead)) 423 } 424 425 func TestReadHeader(t *testing.T) { 426 s := "P" 427 zr := mock.NewZeroCopyReader(s) 428 rh := protocol.RequestHeader{} 429 err := ReadHeader(&rh, zr) 430 assert.NotNil(t, err) 431 } 432 433 func TestParseHeaders(t *testing.T) { 434 rh := protocol.RequestHeader{} 435 _, err := parseHeaders(&rh, []byte{' '}) 436 assert.NotNil(t, err) 437 } 438 439 func TestTryRead(t *testing.T) { 440 rh := protocol.RequestHeader{} 441 s := "P" 442 zr := mock.NewZeroCopyReader(s) 443 err := tryRead(&rh, zr, 0) 444 assert.Nil(t, err) 445 } 446 447 func TestParseFirstLine(t *testing.T) { 448 tests := []struct { 449 input []byte 450 method string 451 uri string 452 protocol string 453 err error 454 }{ 455 // Test case 1: n < 0 456 { 457 input: []byte("GET /path/to/resource HTTP/1.0\r\n"), 458 method: "GET", 459 uri: "/path/to/resource", 460 protocol: "HTTP/1.0", 461 err: nil, 462 }, 463 // Test case 2: n == 0 464 { 465 input: []byte(" /path/to/resource HTTP/1.1\r\n"), 466 method: "", 467 uri: "", 468 protocol: "", 469 err: fmt.Errorf("requestURI cannot be empty in"), 470 }, 471 // Test case 3: !bytes.Equal(b[n+1:], bytestr.StrHTTP11) 472 { 473 input: []byte("POST /path/to/resource HTTP/1.2\r\n"), 474 method: "POST", 475 uri: "/path/to/resource", 476 protocol: "HTTP/1.0", 477 err: nil, 478 }, 479 } 480 481 for _, tc := range tests { 482 header := &protocol.RequestHeader{} 483 _, err := parseFirstLine(header, tc.input) 484 if tc.err != nil { 485 assert.NotNil(t, err) 486 } else { 487 assert.Nil(t, err) 488 } 489 } 490 } 491 492 func TestParse(t *testing.T) { 493 tests := []struct { 494 name string 495 input []byte 496 expected int 497 wantErr bool 498 }{ 499 // normal test 500 { 501 name: "normal", 502 input: []byte("GET /path/to/resource HTTP/1.1\r\nHost: example.com\r\n\r\n"), 503 expected: len([]byte("GET /path/to/resource HTTP/1.1\r\nHost: example.com\r\n\r\n")), 504 wantErr: false, 505 }, 506 // parseFirstLine error 507 { 508 name: "parseFirstLine error", 509 input: []byte("INVALID_LINE\r\nHost: example.com\r\n\r\n"), 510 expected: 0, 511 wantErr: true, 512 }, 513 // ext.ReadRawHeaders error 514 { 515 name: "ext.ReadRawHeaders error", 516 input: []byte("GET /path/to/resource HTTP/1.1\r\nINVALID_HEADER\r\n\r\n"), 517 expected: 0, 518 wantErr: true, 519 }, 520 // parseHeaders error 521 { 522 name: "parseHeaders error", 523 input: []byte("GET /path/to/resource HTTP/1.1\r\nHost: example.com\r\nINVALID_HEADER\r\n"), 524 expected: 0, 525 wantErr: true, 526 }, 527 } 528 529 for _, tc := range tests { 530 t.Run(tc.name, func(t *testing.T) { 531 header := &protocol.RequestHeader{} 532 bytesRead, err := parse(header, tc.input) 533 if (err != nil) != tc.wantErr { 534 t.Errorf("Expected error: %v, but got: %v", tc.wantErr, err) 535 } 536 if bytesRead != tc.expected { 537 t.Errorf("Expected bytes read: %d, but got: %d", tc.expected, bytesRead) 538 } 539 }) 540 } 541 }