github.com/cloudwego/hertz@v0.9.3/pkg/app/fs_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 app 43 44 import ( 45 "bytes" 46 "context" 47 "fmt" 48 "io" 49 "io/ioutil" 50 "math/rand" 51 "os" 52 "path" 53 "testing" 54 "time" 55 56 "github.com/cloudwego/hertz/pkg/common/test/assert" 57 "github.com/cloudwego/hertz/pkg/common/test/mock" 58 "github.com/cloudwego/hertz/pkg/protocol" 59 "github.com/cloudwego/hertz/pkg/protocol/consts" 60 "github.com/cloudwego/hertz/pkg/protocol/http1/resp" 61 ) 62 63 func TestNewVHostPathRewriter(t *testing.T) { 64 t.Parallel() 65 66 var ctx RequestContext 67 var req protocol.Request 68 req.Header.SetHost("foobar.com") 69 req.SetRequestURI("/foo/bar/baz") 70 req.CopyTo(&ctx.Request) 71 72 f := NewVHostPathRewriter(0) 73 path := f(&ctx) 74 expectedPath := "/foobar.com/foo/bar/baz" 75 if string(path) != expectedPath { 76 t.Fatalf("unexpected path %q. Expecting %q", path, expectedPath) 77 } 78 79 ctx.Request.Reset() 80 ctx.Request.SetRequestURI("https://aaa.bbb.cc/one/two/three/four?asdf=dsf") 81 f = NewVHostPathRewriter(2) 82 path = f(&ctx) 83 expectedPath = "/aaa.bbb.cc/three/four" 84 if string(path) != expectedPath { 85 t.Fatalf("unexpected path %q. Expecting %q", path, expectedPath) 86 } 87 } 88 89 func TestNewVHostPathRewriterMaliciousHost(t *testing.T) { 90 var ctx RequestContext 91 var req protocol.Request 92 req.Header.SetHost("/../../../etc/passwd") 93 req.SetRequestURI("/foo/bar/baz") 94 req.CopyTo(&ctx.Request) 95 96 f := NewVHostPathRewriter(0) 97 path := f(&ctx) 98 expectedPath := "/invalid-host/foo/bar/baz" 99 if string(path) != expectedPath { 100 t.Fatalf("unexpected path %q. Expecting %q", path, expectedPath) 101 } 102 } 103 104 func testPathNotFound(t *testing.T, pathNotFoundFunc HandlerFunc) { 105 var ctx RequestContext 106 var req protocol.Request 107 req.SetRequestURI("http//some.url/file") 108 req.CopyTo(&ctx.Request) 109 110 fs := &FS{ 111 Root: "./", 112 PathNotFound: pathNotFoundFunc, 113 } 114 fs.NewRequestHandler()(context.Background(), &ctx) 115 116 if pathNotFoundFunc == nil { 117 // different to ... 118 if !bytes.Equal(ctx.Response.Body(), 119 []byte("Cannot open requested path")) { 120 t.Fatalf("response defers. Response: %q", ctx.Response.Body()) 121 } 122 } else { 123 // Equals to ... 124 if bytes.Equal(ctx.Response.Body(), 125 []byte("Cannot open requested path")) { 126 t.Fatalf("response defers. Response: %q", ctx.Response.Body()) 127 } 128 } 129 } 130 131 func TestPathNotFound(t *testing.T) { 132 t.Parallel() 133 134 testPathNotFound(t, nil) 135 } 136 137 func TestPathNotFoundFunc(t *testing.T) { 138 t.Parallel() 139 140 testPathNotFound(t, func(c context.Context, ctx *RequestContext) { 141 ctx.WriteString("Not found hehe") //nolint:errcheck 142 }) 143 } 144 145 func TestServeFileHead(t *testing.T) { 146 t.Parallel() 147 148 var ctx RequestContext 149 var req protocol.Request 150 req.Header.SetMethod(consts.MethodHead) 151 req.SetRequestURI("http://foobar.com/baz") 152 req.CopyTo(&ctx.Request) 153 154 ServeFile(&ctx, "fs.go") 155 156 var r protocol.Response 157 r.SkipBody = true 158 s := resp.GetHTTP1Response(&ctx.Response).String() 159 zr := mock.NewZeroCopyReader(s) 160 if err := resp.Read(&r, zr); err != nil { 161 t.Fatalf("unexpected error: %s", err) 162 } 163 164 ce := r.Header.ContentEncoding() 165 if len(ce) > 0 { 166 t.Fatalf("Unexpected 'Content-Encoding' %q", ce) 167 } 168 169 body := r.Body() 170 if len(body) > 0 { 171 t.Fatalf("unexpected response body %q. Expecting empty body", body) 172 } 173 174 expectedBody, err := getFileContents("/fs.go") 175 if err != nil { 176 t.Fatalf("unexpected error: %s", err) 177 } 178 contentLength := r.Header.ContentLength() 179 if contentLength != len(expectedBody) { 180 t.Fatalf("unexpected Content-Length: %d. expecting %d", contentLength, len(expectedBody)) 181 } 182 } 183 184 func TestServeFileSmallNoReadFrom(t *testing.T) { 185 t.Parallel() 186 187 teststr := "hello, world!" 188 189 tempdir, err := ioutil.TempDir("", "httpexpect") 190 if err != nil { 191 t.Fatal(err) 192 } 193 defer os.RemoveAll(tempdir) 194 195 if err := ioutil.WriteFile( 196 path.Join(tempdir, "hello"), []byte(teststr), 0o666); err != nil { 197 t.Fatal(err) 198 } 199 200 var ctx RequestContext 201 var req protocol.Request 202 req.SetRequestURI("http://foobar.com/baz") 203 req.CopyTo(&ctx.Request) 204 205 ServeFile(&ctx, path.Join(tempdir, "hello")) 206 207 reader, ok := ctx.Response.BodyStream().(*fsSmallFileReader) 208 if !ok { 209 t.Fatal("expected fsSmallFileReader") 210 } 211 212 buf := bytes.NewBuffer(nil) 213 214 n, err := reader.WriteTo(pureWriter{buf}) 215 if err != nil { 216 t.Fatal(err) 217 } 218 219 if n != int64(len(teststr)) { 220 t.Fatalf("expected %d bytes, got %d bytes", len(teststr), n) 221 } 222 223 body := buf.String() 224 if body != teststr { 225 t.Fatalf("expected '%s'", teststr) 226 } 227 228 data := make([]byte, len([]byte(teststr))) 229 nn, err := reader.Read(data) 230 assert.DeepEqual(t, len([]byte(teststr)), nn) 231 assert.Nil(t, err) 232 assert.DeepEqual(t, teststr, string(data)) 233 assert.DeepEqual(t, reader.startPos, len([]byte(teststr))) 234 235 nn, err = reader.Read(data) 236 assert.DeepEqual(t, 0, nn) 237 assert.DeepEqual(t, io.EOF, err) 238 239 data1 := make([]byte, 2) 240 reader.startPos = len([]byte(teststr)) - 1 241 nn, err = reader.Read(data1) 242 assert.DeepEqual(t, []byte("!"), []byte{data1[0]}) 243 assert.DeepEqual(t, 1, nn) 244 assert.DeepEqual(t, nil, err) 245 246 reader.startPos = 0 247 reader.ff.f = nil 248 buf = bytes.NewBuffer(nil) 249 reader.ff.dirIndex = make([]byte, len([]byte(teststr))) 250 n, err = reader.WriteTo(pureWriter{buf}) 251 assert.DeepEqual(t, int64(len(teststr)), n) 252 assert.Nil(t, err) 253 } 254 255 type pureWriter struct { 256 w io.Writer 257 } 258 259 func (pw pureWriter) Write(p []byte) (nn int, err error) { 260 return pw.w.Write(p) 261 } 262 263 func TestServeFileCompressed(t *testing.T) { 264 t.Parallel() 265 266 var ctx RequestContext 267 var req protocol.Request 268 req.SetRequestURI("http://foobar.com/baz") 269 req.Header.Set(consts.HeaderAcceptEncoding, "gzip") 270 req.CopyTo(&ctx.Request) 271 272 ServeFile(&ctx, "fs.go") 273 274 var r protocol.Response 275 s := resp.GetHTTP1Response(&ctx.Response).String() 276 zr := mock.NewZeroCopyReader(s) 277 if err := resp.Read(&r, zr); err != nil { 278 t.Fatalf("unexpected error: %s", err) 279 } 280 ce := r.Header.ContentEncoding() 281 if string(ce) != "gzip" { 282 t.Fatalf("Unexpected 'Content-Encoding' %q. Expecting %q", ce, "gzip") 283 } 284 285 body, err := r.BodyGunzip() 286 if err != nil { 287 t.Fatalf("unexpected error: %s", err) 288 } 289 expectedBody, err := getFileContents("/fs.go") 290 if err != nil { 291 t.Fatalf("unexpected error: %s", err) 292 } 293 if !bytes.Equal(body, expectedBody) { 294 t.Fatalf("unexpected body %q. expecting %q", body, expectedBody) 295 } 296 } 297 298 func TestServeFileUncompressed(t *testing.T) { 299 t.Parallel() 300 301 var ctx RequestContext 302 var req protocol.Request 303 req.SetRequestURI("http://foobar.com/baz") 304 req.Header.Set(consts.HeaderAcceptEncoding, "gzip") 305 req.CopyTo(&ctx.Request) 306 307 ServeFileUncompressed(&ctx, "fs.go") 308 309 var r protocol.Response 310 s := resp.GetHTTP1Response(&ctx.Response).String() 311 zr := mock.NewZeroCopyReader(s) 312 if err := resp.Read(&r, zr); err != nil { 313 t.Fatalf("unexpected error: %s", err) 314 } 315 316 ce := r.Header.ContentEncoding() 317 if len(ce) > 0 { 318 t.Fatalf("Unexpected 'Content-Encoding' %q", ce) 319 } 320 321 body := r.Body() 322 expectedBody, err := getFileContents("/fs.go") 323 if err != nil { 324 t.Fatalf("unexpected error: %s", err) 325 } 326 if !bytes.Equal(body, expectedBody) { 327 t.Fatalf("unexpected body %q. expecting %q", body, expectedBody) 328 } 329 } 330 331 func TestFSByteRangeConcurrent(t *testing.T) { 332 t.Parallel() 333 334 fs := &FS{ 335 Root: ".", 336 AcceptByteRange: true, 337 } 338 h := fs.NewRequestHandler() 339 340 concurrency := 10 341 ch := make(chan struct{}, concurrency) 342 for i := 0; i < concurrency; i++ { 343 go func() { 344 for j := 0; j < 5; j++ { 345 testFSByteRange(t, h, "/fs.go") 346 } 347 ch <- struct{}{} 348 }() 349 } 350 351 for i := 0; i < concurrency; i++ { 352 select { 353 case <-time.After(time.Second): 354 t.Fatalf("timeout") 355 case <-ch: 356 } 357 } 358 } 359 360 func TestFSByteRangeSingleThread(t *testing.T) { 361 t.Parallel() 362 363 fs := &FS{ 364 Root: ".", 365 AcceptByteRange: true, 366 } 367 h := fs.NewRequestHandler() 368 369 testFSByteRange(t, h, "/fs.go") 370 } 371 372 func testFSByteRange(t *testing.T, h HandlerFunc, filePath string) { 373 var ctx RequestContext 374 req := &protocol.Request{} 375 req.CopyTo(&ctx.Request) 376 377 expectedBody, err := getFileContents(filePath) 378 if err != nil { 379 t.Fatalf("cannot read file %q: %s", filePath, err) 380 } 381 382 fileSize := len(expectedBody) 383 startPos := rand.Intn(fileSize) 384 endPos := rand.Intn(fileSize) 385 if endPos < startPos { 386 startPos, endPos = endPos, startPos 387 } 388 389 ctx.Request.SetRequestURI(filePath) 390 ctx.Request.Header.SetByteRange(startPos, endPos) 391 h(context.Background(), &ctx) 392 393 var r protocol.Response 394 s := resp.GetHTTP1Response(&ctx.Response).String() 395 zr := mock.NewZeroCopyReader(s) 396 if err := resp.Read(&r, zr); err != nil { 397 t.Fatalf("unexpected error: %s. filePath=%q", err, filePath) 398 } 399 if r.StatusCode() != consts.StatusPartialContent { 400 t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", r.StatusCode(), consts.StatusPartialContent, filePath) 401 } 402 cr := r.Header.Peek(consts.HeaderContentRange) 403 404 expectedCR := fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, fileSize) 405 if string(cr) != expectedCR { 406 t.Fatalf("unexpected content-range %q. Expecting %q. filePath=%q", cr, expectedCR, filePath) 407 } 408 body := r.Body() 409 bodySize := endPos - startPos + 1 410 if len(body) != bodySize { 411 t.Fatalf("unexpected body size %d. Expecting %d. filePath=%q, startPos=%d, endPos=%d", 412 len(body), bodySize, filePath, startPos, endPos) 413 } 414 415 expectedBody = expectedBody[startPos : endPos+1] 416 if !bytes.Equal(body, expectedBody) { 417 t.Fatalf("unexpected body %q. Expecting %q. filePath=%q, startPos=%d, endPos=%d", 418 body, expectedBody, filePath, startPos, endPos) 419 } 420 } 421 422 func getFileContents(path string) ([]byte, error) { 423 path = "." + path 424 f, err := os.Open(path) 425 if err != nil { 426 return nil, err 427 } 428 defer f.Close() 429 return ioutil.ReadAll(f) 430 } 431 432 func TestParseByteRangeSuccess(t *testing.T) { 433 t.Parallel() 434 435 testParseByteRangeSuccess(t, "bytes=0-0", 1, 0, 0) 436 testParseByteRangeSuccess(t, "bytes=1234-6789", 6790, 1234, 6789) 437 438 testParseByteRangeSuccess(t, "bytes=123-", 456, 123, 455) 439 testParseByteRangeSuccess(t, "bytes=-1", 1, 0, 0) 440 testParseByteRangeSuccess(t, "bytes=-123", 456, 333, 455) 441 442 // End position exceeding content-length. It should be updated to content-length-1. 443 // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 444 testParseByteRangeSuccess(t, "bytes=1-2345", 234, 1, 233) 445 testParseByteRangeSuccess(t, "bytes=0-2345", 2345, 0, 2344) 446 447 // Start position overflow. Whole range must be returned. 448 // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 449 testParseByteRangeSuccess(t, "bytes=-567", 56, 0, 55) 450 } 451 452 func testParseByteRangeSuccess(t *testing.T, v string, contentLength, startPos, endPos int) { 453 startPos1, endPos1, err := ParseByteRange([]byte(v), contentLength) 454 if err != nil { 455 t.Fatalf("unexpected error: %s. v=%q, contentLength=%d", err, v, contentLength) 456 } 457 if startPos1 != startPos { 458 t.Fatalf("unexpected startPos=%d. Expecting %d. v=%q, contentLength=%d", startPos1, startPos, v, contentLength) 459 } 460 if endPos1 != endPos { 461 t.Fatalf("unexpected endPos=%d. Expectind %d. v=%q, contentLength=%d", endPos1, endPos, v, contentLength) 462 } 463 } 464 465 func TestParseByteRangeError(t *testing.T) { 466 t.Parallel() 467 468 // invalid value 469 testParseByteRangeError(t, "asdfasdfas", 1234) 470 471 // invalid units 472 testParseByteRangeError(t, "foobar=1-34", 600) 473 474 // missing '-' 475 testParseByteRangeError(t, "bytes=1234", 1235) 476 477 // non-numeric range 478 testParseByteRangeError(t, "bytes=foobar", 123) 479 testParseByteRangeError(t, "bytes=1-foobar", 123) 480 testParseByteRangeError(t, "bytes=df-344", 545) 481 482 // multiple byte ranges 483 testParseByteRangeError(t, "bytes=1-2,4-6", 123) 484 485 // byte range exceeding contentLength 486 testParseByteRangeError(t, "bytes=123-", 12) 487 488 // startPos exceeding endPos 489 testParseByteRangeError(t, "bytes=123-34", 1234) 490 } 491 492 func testParseByteRangeError(t *testing.T, v string, contentLength int) { 493 _, _, err := ParseByteRange([]byte(v), contentLength) 494 if err == nil { 495 t.Fatalf("expecting error when parsing byte range %q", v) 496 } 497 } 498 499 func TestFSCompressConcurrent(t *testing.T) { 500 // This test can't run parallel as files in / might by changed by other tests. 501 502 fs := &FS{ 503 Root: ".", 504 GenerateIndexPages: true, 505 Compress: true, 506 } 507 h := fs.NewRequestHandler() 508 509 concurrency := 4 510 ch := make(chan struct{}, concurrency) 511 for i := 0; i < concurrency; i++ { 512 go func() { 513 for j := 0; j < 5; j++ { 514 testFSCompress(t, h, "/fs.go") 515 testFSCompress(t, h, "/") 516 } 517 ch <- struct{}{} 518 }() 519 } 520 521 for i := 0; i < concurrency; i++ { 522 select { 523 case <-ch: 524 case <-time.After(time.Second): 525 t.Fatalf("timeout") 526 } 527 } 528 } 529 530 func TestFSCompressSingleThread(t *testing.T) { 531 // This test can't run parallel as files in / might by changed by other tests. 532 533 fs := &FS{ 534 Root: ".", 535 GenerateIndexPages: true, 536 Compress: true, 537 } 538 h := fs.NewRequestHandler() 539 540 testFSCompress(t, h, "/fs.go") 541 testFSCompress(t, h, "/") 542 } 543 544 func testFSCompress(t *testing.T, h HandlerFunc, filePath string) { 545 var ctx RequestContext 546 req := &protocol.Request{} 547 req.CopyTo(&ctx.Request) 548 549 // request uncompressed file 550 ctx.Request.Reset() 551 ctx.Request.SetRequestURI(filePath) 552 h(context.Background(), &ctx) 553 554 var r protocol.Response 555 s := resp.GetHTTP1Response(&ctx.Response).String() 556 zr := mock.NewZeroCopyReader(s) 557 if err := resp.Read(&r, zr); err != nil { 558 t.Fatalf("unexpected error: %s. filePath=%q", err, filePath) 559 } 560 if r.StatusCode() != consts.StatusOK { 561 t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", r.StatusCode(), consts.StatusOK, filePath) 562 } 563 ce := r.Header.ContentEncoding() 564 if string(ce) != "" { 565 t.Fatalf("unexpected content-encoding %q. Expecting empty string. filePath=%q", ce, filePath) 566 } 567 body := string(r.Body()) 568 569 // request compressed file 570 ctx.Request.Reset() 571 ctx.Request.SetRequestURI(filePath) 572 ctx.Request.Header.Set(consts.HeaderAcceptEncoding, "gzip") 573 h(context.Background(), &ctx) 574 s = resp.GetHTTP1Response(&ctx.Response).String() 575 zr = mock.NewZeroCopyReader(s) 576 if err := resp.Read(&r, zr); err != nil { 577 t.Fatalf("unexpected error: %s. filePath=%q", err, filePath) 578 } 579 if r.StatusCode() != consts.StatusOK { 580 t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", r.StatusCode(), consts.StatusOK, filePath) 581 } 582 ce = r.Header.ContentEncoding() 583 if string(ce) != "gzip" { 584 t.Fatalf("unexpected content-encoding %q. Expecting %q. filePath=%q", ce, "gzip", filePath) 585 } 586 zbody, err := r.BodyGunzip() 587 if err != nil { 588 t.Fatalf("unexpected error when gunzipping response body: %s. filePath=%q", err, filePath) 589 } 590 if string(zbody) != body { 591 t.Fatalf("unexpected body len=%d. Expected len=%d. FilePath=%q", len(zbody), len(body), filePath) 592 } 593 } 594 595 func TestFileLock(t *testing.T) { 596 t.Parallel() 597 598 for i := 0; i < 10; i++ { 599 filePath := fmt.Sprintf("foo/bar/%d.jpg", i) 600 lock := getFileLock(filePath) 601 lock.Lock() 602 time.Sleep(time.Microsecond) 603 lock.Unlock() // nolint:staticcheck 604 } 605 606 for i := 0; i < 10; i++ { 607 filePath := fmt.Sprintf("foo/bar/%d.jpg", i) 608 lock := getFileLock(filePath) 609 lock.Lock() 610 time.Sleep(time.Microsecond) 611 lock.Unlock() // nolint:staticcheck 612 } 613 } 614 615 func TestStripPathSlashes(t *testing.T) { 616 t.Parallel() 617 618 testStripPathSlashes(t, "", 0, "") 619 testStripPathSlashes(t, "", 10, "") 620 testStripPathSlashes(t, "/", 0, "") 621 testStripPathSlashes(t, "/", 1, "") 622 testStripPathSlashes(t, "/", 10, "") 623 testStripPathSlashes(t, "/foo/bar/baz", 0, "/foo/bar/baz") 624 testStripPathSlashes(t, "/foo/bar/baz", 1, "/bar/baz") 625 testStripPathSlashes(t, "/foo/bar/baz", 2, "/baz") 626 testStripPathSlashes(t, "/foo/bar/baz", 3, "") 627 testStripPathSlashes(t, "/foo/bar/baz", 10, "") 628 629 // trailing slash 630 testStripPathSlashes(t, "/foo/bar/", 0, "/foo/bar") 631 testStripPathSlashes(t, "/foo/bar/", 1, "/bar") 632 testStripPathSlashes(t, "/foo/bar/", 2, "") 633 testStripPathSlashes(t, "/foo/bar/", 3, "") 634 } 635 636 func testStripPathSlashes(t *testing.T, path string, stripSlashes int, expectedPath string) { 637 s := stripLeadingSlashes([]byte(path), stripSlashes) 638 s = stripTrailingSlashes(s) 639 if string(s) != expectedPath { 640 t.Fatalf("unexpected path after stripping %q with stripSlashes=%d: %q. Expecting %q", path, stripSlashes, s, expectedPath) 641 } 642 } 643 644 func TestFileExtension(t *testing.T) { 645 t.Parallel() 646 647 testFileExtension(t, "foo.bar", false, "zzz", ".bar") 648 testFileExtension(t, "foobar", false, "zzz", "") 649 testFileExtension(t, "foo.bar.baz", false, "zzz", ".baz") 650 testFileExtension(t, "", false, "zzz", "") 651 testFileExtension(t, "/a/b/c.d/efg.jpg", false, ".zzz", ".jpg") 652 653 testFileExtension(t, "foo.bar", true, ".zzz", ".bar") 654 testFileExtension(t, "foobar.zzz", true, ".zzz", "") 655 testFileExtension(t, "foo.bar.baz.hertz.gz", true, ".hertz.gz", ".baz") 656 testFileExtension(t, "", true, ".zzz", "") 657 testFileExtension(t, "/a/b/c.d/efg.jpg.xxx", true, ".xxx", ".jpg") 658 } 659 660 func testFileExtension(t *testing.T, path string, compressed bool, compressedFileSuffix, expectedExt string) { 661 ext := fileExtension(path, compressed, compressedFileSuffix) 662 if ext != expectedExt { 663 t.Fatalf("unexpected file extension for file %q: %q. Expecting %q", path, ext, expectedExt) 664 } 665 } 666 667 func TestServeFileContentType(t *testing.T) { 668 t.Parallel() 669 670 var ctx RequestContext 671 var req protocol.Request 672 req.Header.SetMethod(consts.MethodGet) 673 req.SetRequestURI("http://foobar.com/baz") 674 req.CopyTo(&ctx.Request) 675 676 ServeFile(&ctx, "../common/testdata/test.png") 677 678 var r protocol.Response 679 s := resp.GetHTTP1Response(&ctx.Response).String() 680 zr := mock.NewZeroCopyReader(s) 681 if err := resp.Read(&r, zr); err != nil { 682 t.Fatalf("unexpected error: %s", err) 683 } 684 685 expected := []byte(consts.MIMEImagePNG) 686 if !bytes.Equal(r.Header.ContentType(), expected) { 687 t.Fatalf("Unexpected Content-Type, expected: %q got %q", expected, r.Header.ContentType()) 688 } 689 } 690 691 func TestFileSmallUpdateByteRange(t *testing.T) { 692 r := &fsSmallFileReader{} 693 err := r.UpdateByteRange(1, 1) 694 assert.Nil(t, err) 695 assert.DeepEqual(t, 1, r.startPos) 696 assert.DeepEqual(t, 2, r.endPos) 697 }