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  }