mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-13 15:46:49 +03:00
8fe6af3619
* Fix flaky race tests * Drain streamed body in content length test
1235 lines
32 KiB
Go
1235 lines
32 KiB
Go
package fasthttp
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"embed"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
"time"
|
|
)
|
|
|
|
//go:embed fasthttputil fs.go README.md testdata examples
|
|
var fsTestFilesystem embed.FS
|
|
|
|
func TestFSServeFileHead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.Header.SetMethod(MethodHead)
|
|
req.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
ServeFS(&ctx, fsTestFilesystem, "fs.go")
|
|
|
|
var resp Response
|
|
resp.SkipBody = true
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
ce := resp.Header.ContentEncoding()
|
|
if len(ce) > 0 {
|
|
t.Fatalf("Unexpected 'Content-Encoding' %q", ce)
|
|
}
|
|
|
|
body := resp.Body()
|
|
if len(body) > 0 {
|
|
t.Fatalf("unexpected response body %q. Expecting empty body", body)
|
|
}
|
|
|
|
expectedBody, err := getFileContents("/fs.go")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
contentLength := resp.Header.ContentLength()
|
|
if contentLength != len(expectedBody) {
|
|
t.Fatalf("unexpected Content-Length: %d. expecting %d", contentLength, len(expectedBody))
|
|
}
|
|
}
|
|
|
|
func TestServeFSDoesNotLeakCacheCleaner(t *testing.T) {
|
|
testFS := fstest.MapFS{
|
|
"index.txt": {Data: []byte("body")},
|
|
}
|
|
|
|
runtime.GC()
|
|
before := runtime.NumGoroutine()
|
|
|
|
const calls = 25
|
|
for range calls {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.SetRequestURI("http://foobar.com/original")
|
|
ctx.Init(&req, nil, TestLogger{t})
|
|
|
|
ServeFS(&ctx, testFS, "index.txt")
|
|
if ctx.Response.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code %d. expecting %d", ctx.Response.StatusCode(), StatusOK)
|
|
}
|
|
}
|
|
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
runtime.GC()
|
|
runtime.Gosched()
|
|
|
|
after := runtime.NumGoroutine()
|
|
if leaked := after - before; leaked <= 3 {
|
|
return
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
after := runtime.NumGoroutine()
|
|
if leaked := after - before; leaked > 3 {
|
|
t.Fatalf("ServeFS left %d persistent goroutines; expected no cache cleaner goroutine leak", leaked)
|
|
}
|
|
}
|
|
|
|
func TestServeFSLiteral(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testFS := fstest.MapFS{
|
|
"space name.txt": {Data: []byte("space")},
|
|
"hash#name.txt": {Data: []byte("hash")},
|
|
"percent%61name.txt": {Data: []byte("percent")},
|
|
"query?name.txt": {Data: []byte("query")},
|
|
}
|
|
|
|
for name, file := range testFS {
|
|
t.Run(name, func(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.SetRequestURI("http://foobar.com/original")
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
ServeFSLiteral(&ctx, testFS, name)
|
|
|
|
resp := readResponseFromCtx(t, &ctx, false)
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code %d. expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
if !bytes.Equal(resp.Body(), file.Data) {
|
|
t.Fatalf("unexpected body %q. expecting %q", resp.Body(), file.Data)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServeFSSpecialCharsNotLiteral(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testFS := fstest.MapFS{
|
|
"hash#name.txt": {Data: []byte("hash")},
|
|
"percent%61name.txt": {Data: []byte("percent")},
|
|
"query?name.txt": {Data: []byte("query")},
|
|
}
|
|
|
|
for name := range testFS {
|
|
t.Run(name, func(t *testing.T) {
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.SetRequestURI("http://foobar.com/original")
|
|
ctx.Init(&req, nil, TestLogger{t})
|
|
|
|
ServeFS(&ctx, testFS, name)
|
|
|
|
resp := readResponseFromCtx(t, &ctx, false)
|
|
if resp.StatusCode() == StatusOK {
|
|
t.Fatalf("ServeFS should fail for special-character path %q without literal mode, but got 200", name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFSServeFileCompressed(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
ctx.Init(&Request{}, nil, nil)
|
|
|
|
var resp Response
|
|
|
|
expectedBody, err := getFileContents("/fs.go")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// should prefer brotli over zstd, gzip and ignore unknown encoding
|
|
ctx.Request.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, zstd, br, wompwomp")
|
|
ServeFS(&ctx, fsTestFilesystem, "fs.go")
|
|
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ce := resp.Header.ContentEncoding()
|
|
if string(ce) != "br" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q", string(ce), "br")
|
|
}
|
|
|
|
vary := resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err := resp.BodyUnbrotli()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on unbrotli response body: %v", err)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expected len=%d", len(body), len(expectedBody))
|
|
}
|
|
|
|
// should prefer zstd over gzip and ignore unknown encoding
|
|
ctx.Request.Reset()
|
|
ctx.Request.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, zstd, wompwomp")
|
|
ServeFS(&ctx, fsTestFilesystem, "fs.go")
|
|
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ce = resp.Header.ContentEncoding()
|
|
if string(ce) != "zstd" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q", string(ce), "zstd")
|
|
}
|
|
|
|
vary = resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err = resp.BodyUnzstd()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on unzstd response body: %v", err)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expected len=%d", len(body), len(expectedBody))
|
|
}
|
|
|
|
// should prefer gzip and ignore unknown encoding
|
|
ctx.Request.Reset()
|
|
ctx.Request.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, wompwomp")
|
|
ServeFS(&ctx, fsTestFilesystem, "fs.go")
|
|
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ce = resp.Header.ContentEncoding()
|
|
if string(ce) != "gzip" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q", string(ce), "gzip")
|
|
}
|
|
|
|
vary = resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err = resp.BodyGunzip()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on gunzip response body: %v", err)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expected len=%d", len(body), len(expectedBody))
|
|
}
|
|
}
|
|
|
|
func TestFSServeFileUncompressed(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
ctx.Init(&Request{}, nil, nil)
|
|
|
|
var resp Response
|
|
|
|
expectedBody, err := getFileContents("/fs.go")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
ctx.Request.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, zstd, br, wompwomp")
|
|
ServeFileUncompressed(&ctx, "fs.go")
|
|
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ce := resp.Header.ContentEncoding()
|
|
if len(ce) > 0 {
|
|
t.Fatalf("unexpected 'Content-Encoding': %q. Expecting \"\"", string(ce))
|
|
}
|
|
|
|
vary := resp.Header.PeekBytes(strVary)
|
|
if len(vary) > 0 {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting \"\"", string(vary))
|
|
}
|
|
|
|
body := resp.Body()
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expecting len=%d", len(body), len(expectedBody))
|
|
}
|
|
}
|
|
|
|
func TestFSFSByteRangeConcurrent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
FS: fsTestFilesystem,
|
|
Root: "",
|
|
AcceptByteRange: true,
|
|
CleanStop: stop,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
concurrency := 10
|
|
ch := make(chan struct{}, concurrency)
|
|
for range concurrency {
|
|
go func() {
|
|
for range 5 {
|
|
testFSByteRange(t, h, "/fs.go")
|
|
testFSByteRange(t, h, "/README.md")
|
|
}
|
|
ch <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
for range concurrency {
|
|
select {
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
case <-ch:
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFSFSByteRangeSingleThread(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
FS: fsTestFilesystem,
|
|
Root: ".",
|
|
AcceptByteRange: true,
|
|
CleanStop: stop,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
testFSByteRange(t, h, "/fs.go")
|
|
testFSByteRange(t, h, "/README.md")
|
|
}
|
|
|
|
func TestFSFSCompressConcurrent(t *testing.T) {
|
|
t.Parallel()
|
|
// go 1.16 timeout may occur
|
|
if strings.HasPrefix(runtime.Version(), "go1.16") {
|
|
t.SkipNow()
|
|
}
|
|
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
FS: fsTestFilesystem,
|
|
Root: ".",
|
|
GenerateIndexPages: true,
|
|
Compress: true,
|
|
CompressBrotli: true,
|
|
CompressZstd: true,
|
|
CleanStop: stop,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
concurrency := 4
|
|
ch := make(chan struct{}, concurrency)
|
|
for range concurrency {
|
|
go func() {
|
|
for range 5 {
|
|
testFSFSCompress(t, h, "/fs.go")
|
|
testFSFSCompress(t, h, "/examples/")
|
|
testFSFSCompress(t, h, "/README.md")
|
|
}
|
|
ch <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
for range concurrency {
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second * 4):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFSFSCompressSingleThread(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
FS: fsTestFilesystem,
|
|
Root: ".",
|
|
GenerateIndexPages: true,
|
|
Compress: true,
|
|
CompressBrotli: true,
|
|
CompressZstd: true,
|
|
CleanStop: stop,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
testFSFSCompress(t, h, "/fs.go")
|
|
testFSFSCompress(t, h, "/examples/")
|
|
testFSFSCompress(t, h, "/README.md")
|
|
}
|
|
|
|
func testFSFSCompress(t *testing.T, h RequestHandler, filePath string) {
|
|
var ctx RequestCtx
|
|
ctx.Init(&Request{}, nil, nil)
|
|
|
|
var resp Response
|
|
|
|
// get uncompressed
|
|
ctx.Request.SetRequestURI(filePath)
|
|
h(&ctx)
|
|
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ce := resp.Header.ContentEncoding()
|
|
if len(ce) > 0 {
|
|
t.Fatalf("unexpected 'Content-Encoding': %q. Expecting \"\"", string(ce))
|
|
}
|
|
|
|
vary := resp.Header.PeekBytes(strVary)
|
|
if len(vary) > 0 {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting \"\"", string(vary))
|
|
}
|
|
|
|
expectedBody := bytes.Clone(resp.Body())
|
|
|
|
// should prefer brotli over zstd, gzip and ignore unknown encoding
|
|
ctx.Request.Reset()
|
|
ctx.Request.SetRequestURI(filePath)
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, zstd, br, wompwomp")
|
|
h(&ctx)
|
|
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v. filePath=%q", err, filePath)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", resp.StatusCode(), StatusOK, filePath)
|
|
}
|
|
|
|
ce = resp.Header.ContentEncoding()
|
|
if string(ce) != "br" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q. filePath=%q", string(ce), "br", filePath)
|
|
}
|
|
|
|
vary = resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err := resp.BodyUnbrotli()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on unbrotli response body: %v. filePath=%q", err, filePath)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expecting len=%d. filePath=%q", len(body), len(expectedBody), filePath)
|
|
}
|
|
|
|
// should prefer zstd over gzip and ignore unknown encoding
|
|
ctx.Request.Reset()
|
|
ctx.Request.SetRequestURI(filePath)
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, zstd, wompwomp")
|
|
h(&ctx)
|
|
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v. filePath=%q", err, filePath)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", resp.StatusCode(), StatusOK, filePath)
|
|
}
|
|
|
|
ce = resp.Header.ContentEncoding()
|
|
if string(ce) != "zstd" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q. filePath=%q", string(ce), "zstd", filePath)
|
|
}
|
|
|
|
vary = resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err = resp.BodyUnzstd()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on unzstd response body: %v. filePath=%q", err, filePath)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expecting len=%d. filePath=%q", len(body), len(expectedBody), filePath)
|
|
}
|
|
|
|
// should prefer gzip and ignore unknown encoding
|
|
ctx.Request.Reset()
|
|
ctx.Request.SetRequestURI(filePath)
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, wompwomp")
|
|
h(&ctx)
|
|
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v. filePath=%q", err, filePath)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", resp.StatusCode(), StatusOK, filePath)
|
|
}
|
|
|
|
ce = resp.Header.ContentEncoding()
|
|
if string(ce) != "gzip" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q. filePath=%q", string(ce), "gzip", filePath)
|
|
}
|
|
|
|
vary = resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err = resp.BodyGunzip()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on gunzip response body: %v. filePath=%q", err, filePath)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expecting len=%d. filePath=%q", len(body), len(expectedBody), filePath)
|
|
}
|
|
}
|
|
|
|
func TestFSServeFileContentType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.Header.SetMethod(MethodGet)
|
|
req.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
ServeFS(&ctx, fsTestFilesystem, "testdata/test.png")
|
|
|
|
var resp Response
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
expected := []byte("image/png")
|
|
if !bytes.Equal(resp.Header.ContentType(), expected) {
|
|
t.Fatalf("Unexpected Content-Type, expected: %q got %q", expected, resp.Header.ContentType())
|
|
}
|
|
}
|
|
|
|
func TestFSServeFileDirectoryRedirect(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.SetRequestURI("http://foobar.com")
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ServeFS(&ctx, fsTestFilesystem, "fasthttputil")
|
|
if ctx.Response.StatusCode() != StatusFound {
|
|
t.Fatalf("Unexpected status code %d for directory '/fasthttputil' without trailing slash. Expecting %d.", ctx.Response.StatusCode(), StatusFound)
|
|
}
|
|
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ServeFS(&ctx, fsTestFilesystem, "fasthttputil/")
|
|
if ctx.Response.StatusCode() != StatusOK {
|
|
t.Fatalf("Unexpected status code %d for directory '/fasthttputil/' with trailing slash. Expecting %d.", ctx.Response.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ServeFS(&ctx, fsTestFilesystem, "fs.go")
|
|
if ctx.Response.StatusCode() != StatusOK {
|
|
t.Fatalf("Unexpected status code %d for file '/fs.go'. Expecting %d.", ctx.Response.StatusCode(), StatusOK)
|
|
}
|
|
}
|
|
|
|
var dirTestFilesystem = os.DirFS(".")
|
|
|
|
func TestDirFSServeFileHead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.Header.SetMethod(MethodHead)
|
|
req.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
ServeFS(&ctx, dirTestFilesystem, "fs.go")
|
|
|
|
var resp Response
|
|
resp.SkipBody = true
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
ce := resp.Header.ContentEncoding()
|
|
if len(ce) > 0 {
|
|
t.Fatalf("Unexpected 'Content-Encoding' %q", ce)
|
|
}
|
|
|
|
body := resp.Body()
|
|
if len(body) > 0 {
|
|
t.Fatalf("unexpected response body %q. Expecting empty body", body)
|
|
}
|
|
|
|
expectedBody, err := getFileContents("/fs.go")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
contentLength := resp.Header.ContentLength()
|
|
if contentLength != len(expectedBody) {
|
|
t.Fatalf("unexpected Content-Length: %d. expecting %d", contentLength, len(expectedBody))
|
|
}
|
|
}
|
|
|
|
func TestDirFSServeFileCompressed(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
ctx.Init(&Request{}, nil, nil)
|
|
|
|
var resp Response
|
|
|
|
expectedBody, err := getFileContents("/fs.go")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// should prefer brotli over zstd, gzip and ignore unknown encoding
|
|
ctx.Request.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, zstd, br, wompwomp")
|
|
ServeFS(&ctx, dirTestFilesystem, "fs.go")
|
|
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ce := resp.Header.ContentEncoding()
|
|
if string(ce) != "br" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q", string(ce), "br")
|
|
}
|
|
|
|
vary := resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err := resp.BodyUnbrotli()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on unbrotli response body: %v", err)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expecting len=%d", len(body), len(expectedBody))
|
|
}
|
|
|
|
// should prefer zstd over gzip and ignore unknown encoding
|
|
ctx.Request.Reset()
|
|
ctx.Request.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, zstd, wompwomp")
|
|
ServeFS(&ctx, dirTestFilesystem, "fs.go")
|
|
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ce = resp.Header.ContentEncoding()
|
|
if string(ce) != "zstd" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q", string(ce), "zstd")
|
|
}
|
|
|
|
vary = resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err = resp.BodyUnzstd()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on unzstd response body: %v", err)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expecting len=%d", len(body), len(expectedBody))
|
|
}
|
|
|
|
// should prefer gzip and ignore unknown encoding
|
|
ctx.Request.Reset()
|
|
ctx.Request.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, wompwomp")
|
|
ServeFS(&ctx, dirTestFilesystem, "fs.go")
|
|
|
|
s = ctx.Response.String()
|
|
br = bufio.NewReader(bytes.NewBufferString(s))
|
|
if err = resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ce = resp.Header.ContentEncoding()
|
|
if string(ce) != "gzip" {
|
|
t.Fatalf("unexpected 'Content-Encoding' %q. Expecting %q", string(ce), "gzip")
|
|
}
|
|
|
|
vary = resp.Header.PeekBytes(strVary)
|
|
if !bytes.Equal(vary, strAcceptEncoding) {
|
|
t.Fatalf("unexpected 'Vary': %q. Expecting %q", string(vary), HeaderAcceptEncoding)
|
|
}
|
|
|
|
body, err = resp.BodyGunzip()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on gunzip response body: %v", err)
|
|
}
|
|
if !bytes.Equal(body, expectedBody) {
|
|
t.Fatalf("unexpected body: len=%d. Expecting len=%d", len(body), len(expectedBody))
|
|
}
|
|
}
|
|
|
|
func TestDirFSFSByteRangeConcurrent(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip()
|
|
}
|
|
|
|
t.Parallel()
|
|
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
FS: dirTestFilesystem,
|
|
Root: "",
|
|
AcceptByteRange: true,
|
|
CleanStop: stop,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
concurrency := 10
|
|
ch := make(chan struct{}, concurrency)
|
|
for range concurrency {
|
|
go func() {
|
|
for range 5 {
|
|
testFSByteRange(t, h, "/fs.go")
|
|
testFSByteRange(t, h, "/README.md")
|
|
}
|
|
ch <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
for range concurrency {
|
|
select {
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timeout")
|
|
case <-ch:
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDirFSFSByteRangeSingleThread(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
FS: dirTestFilesystem,
|
|
Root: ".",
|
|
AcceptByteRange: true,
|
|
CleanStop: stop,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
testFSByteRange(t, h, "/fs.go")
|
|
testFSByteRange(t, h, "/README.md")
|
|
}
|
|
|
|
func TestDirFSFSCompressConcurrent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
FS: dirTestFilesystem,
|
|
Root: ".",
|
|
GenerateIndexPages: true,
|
|
Compress: true,
|
|
CompressBrotli: true,
|
|
CompressZstd: true,
|
|
CleanStop: stop,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
concurrency := 4
|
|
ch := make(chan struct{}, concurrency)
|
|
for range concurrency {
|
|
go func() {
|
|
for range 5 {
|
|
testFSFSCompress(t, h, "/fs.go")
|
|
testFSFSCompress(t, h, "/examples/")
|
|
testFSFSCompress(t, h, "/README.md")
|
|
}
|
|
ch <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
for range concurrency {
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(testTimeout(time.Second * 2)):
|
|
t.Fatalf("timeout")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDirFSFSCompressSingleThread(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
FS: dirTestFilesystem,
|
|
Root: ".",
|
|
GenerateIndexPages: true,
|
|
Compress: true,
|
|
CompressBrotli: true,
|
|
CompressZstd: true,
|
|
CleanStop: stop,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
testFSFSCompress(t, h, "/fs.go")
|
|
testFSFSCompress(t, h, "/examples/")
|
|
testFSFSCompress(t, h, "/README.md")
|
|
}
|
|
|
|
func TestDirFSServeFileContentType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.Header.SetMethod(MethodGet)
|
|
req.SetRequestURI("http://foobar.com/baz")
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
ServeFS(&ctx, dirTestFilesystem, "testdata/test.png")
|
|
|
|
var resp Response
|
|
s := ctx.Response.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := resp.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
expected := []byte("image/png")
|
|
if !bytes.Equal(resp.Header.ContentType(), expected) {
|
|
t.Fatalf("Unexpected Content-Type, expected: %q got %q", expected, resp.Header.ContentType())
|
|
}
|
|
}
|
|
|
|
func TestDirFSServeFileDirectoryRedirect(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var ctx RequestCtx
|
|
var req Request
|
|
req.SetRequestURI("http://foobar.com")
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ServeFS(&ctx, dirTestFilesystem, "fasthttputil")
|
|
if ctx.Response.StatusCode() != StatusFound {
|
|
t.Fatalf("Unexpected status code %d for directory '/fasthttputil' without trailing slash. Expecting %d.", ctx.Response.StatusCode(), StatusFound)
|
|
}
|
|
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ServeFS(&ctx, dirTestFilesystem, "fasthttputil/")
|
|
if ctx.Response.StatusCode() != StatusOK {
|
|
t.Fatalf("Unexpected status code %d for directory '/fasthttputil/' with trailing slash. Expecting %d.", ctx.Response.StatusCode(), StatusOK)
|
|
}
|
|
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ServeFS(&ctx, dirTestFilesystem, "fs.go")
|
|
if ctx.Response.StatusCode() != StatusOK {
|
|
t.Fatalf("Unexpected status code %d for file '/fs.go'. Expecting %d.", ctx.Response.StatusCode(), StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestFSFSGenerateIndexOsDirFS(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("dirFS", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fs := &FS{
|
|
FS: dirTestFilesystem,
|
|
Root: ".",
|
|
GenerateIndexPages: true,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
var ctx RequestCtx
|
|
var req Request
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
h(&ctx)
|
|
|
|
cases := []string{"/", "//", ""}
|
|
for _, c := range cases {
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
|
|
req.Header.SetMethod(MethodGet)
|
|
req.SetRequestURI("http://foobar.com" + c)
|
|
h(&ctx)
|
|
|
|
if ctx.Response.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code %d for path %q. Expecting %d", ctx.Response.StatusCode(), ctx.Response.StatusCode(), StatusOK)
|
|
}
|
|
|
|
if !bytes.Contains(ctx.Response.Body(), []byte("fasthttputil")) {
|
|
t.Fatalf("unexpected body %q. Expecting to contain %q", ctx.Response.Body(), "fasthttputil")
|
|
}
|
|
|
|
if !bytes.Contains(ctx.Response.Body(), []byte("fs.go")) {
|
|
t.Fatalf("unexpected body %q. Expecting to contain %q", ctx.Response.Body(), "fs.go")
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("embedFS", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fs := &FS{
|
|
FS: fsTestFilesystem,
|
|
Root: ".",
|
|
GenerateIndexPages: true,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
var ctx RequestCtx
|
|
var req Request
|
|
ctx.Init(&req, nil, nil)
|
|
|
|
h(&ctx)
|
|
|
|
cases := []string{"/", "//", ""}
|
|
for _, c := range cases {
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
|
|
req.Header.SetMethod(MethodGet)
|
|
req.SetRequestURI("http://foobar.com" + c)
|
|
h(&ctx)
|
|
|
|
if ctx.Response.StatusCode() != StatusOK {
|
|
t.Fatalf("unexpected status code %d for path %q. Expecting %d", ctx.Response.StatusCode(), ctx.Response.StatusCode(), StatusOK)
|
|
}
|
|
|
|
if !bytes.Contains(ctx.Response.Body(), []byte("fasthttputil")) {
|
|
t.Fatalf("unexpected body %q. Expecting to contain %q", ctx.Response.Body(), "fasthttputil")
|
|
}
|
|
|
|
if !bytes.Contains(ctx.Response.Body(), []byte("fs.go")) {
|
|
t.Fatalf("unexpected body %q. Expecting to contain %q", ctx.Response.Body(), "fs.go")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFSRootEnforcement(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
memFS := fstest.MapFS{
|
|
"public/index.html": {Data: []byte("<h1>Public</h1>")},
|
|
"secret/admin.json": {Data: []byte(`{"admin": true, "key": "s3cret"}`)},
|
|
"public/nested/info": {Data: []byte("nested")},
|
|
}
|
|
|
|
// Skip dirfs subtests on Windows: the FS handler pools open file handles
|
|
// (via bigFileReader) which prevents t.TempDir cleanup.
|
|
// The mapfs subtests exercise the same root enforcement logic.
|
|
var tmpDir string
|
|
if runtime.GOOS != "windows" {
|
|
tmpDir = t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(tmpDir, "public"), 0o755); err != nil {
|
|
t.Fatalf("cannot create public dir: %v", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(tmpDir, "secret"), 0o755); err != nil {
|
|
t.Fatalf("cannot create secret dir: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "public", "index.html"), []byte("<h1>Public</h1>"), 0o644); err != nil {
|
|
t.Fatalf("cannot create public index: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "secret", "admin.json"), []byte(`{"admin": true, "key": "s3cret"}`), 0o644); err != nil {
|
|
t.Fatalf("cannot create secret admin file: %v", err)
|
|
}
|
|
}
|
|
|
|
type testCase struct {
|
|
name string
|
|
root string
|
|
filesystem fs.FS
|
|
pathRewrite PathRewriteFunc
|
|
}
|
|
|
|
cases := make([]testCase, 0, 9)
|
|
for _, root := range []string{"public", "public/", "./public", "/public"} {
|
|
cases = append(cases, testCase{
|
|
name: "mapfs/" + root,
|
|
root: root,
|
|
filesystem: memFS,
|
|
})
|
|
if tmpDir != "" {
|
|
cases = append(cases, testCase{
|
|
name: "dirfs/" + root,
|
|
root: root,
|
|
filesystem: os.DirFS(tmpDir),
|
|
})
|
|
}
|
|
}
|
|
|
|
cases = append(cases, testCase{
|
|
name: "mapfs/pathrewrite-no-leading-slash",
|
|
root: "./public/",
|
|
filesystem: memFS,
|
|
pathRewrite: func(ctx *RequestCtx) []byte {
|
|
return bytes.TrimPrefix(ctx.Path(), []byte("/"))
|
|
},
|
|
})
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
Root: tc.root,
|
|
FS: tc.filesystem,
|
|
AllowEmptyRoot: true,
|
|
CleanStop: stop,
|
|
PathRewrite: tc.pathRewrite,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
var ctx RequestCtx
|
|
ctx.Init(&Request{}, nil, TestLogger{t: t})
|
|
|
|
checkStatus := func(uri string, expected int) {
|
|
ctx.Request.Reset()
|
|
ctx.Response.Reset()
|
|
ctx.Request.SetRequestURI(uri)
|
|
h(&ctx)
|
|
if ctx.Response.StatusCode() != expected {
|
|
t.Fatalf("unexpected status code for %s: %d. Expecting %d", uri, ctx.Response.StatusCode(), expected)
|
|
}
|
|
}
|
|
|
|
checkStatus("http://localhost/index.html", StatusOK)
|
|
checkStatus("http://localhost/secret/admin.json", StatusNotFound)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHasDotDotPathSegment(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
path string
|
|
want bool
|
|
}{
|
|
{path: "", want: false},
|
|
{path: ".", want: false},
|
|
{path: "..", want: true},
|
|
{path: "../secret.txt", want: true},
|
|
{path: "/../secret.txt", want: true},
|
|
{path: "nested/../info", want: true},
|
|
{path: "nested/..", want: true},
|
|
{path: "nested/..hidden/info", want: false},
|
|
{path: "nested..", want: false},
|
|
{path: "/index.html", want: false},
|
|
}
|
|
|
|
if filepath.Separator == '\\' {
|
|
testCases = append(testCases,
|
|
struct {
|
|
path string
|
|
want bool
|
|
}{path: `..\secret.txt`, want: true},
|
|
struct {
|
|
path string
|
|
want bool
|
|
}{path: `nested\..\info`, want: true},
|
|
)
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.path, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := hasDotDotPathSegment([]byte(tc.path)); got != tc.want {
|
|
t.Fatalf("unexpected result for %q: got %v want %v", tc.path, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFSPathRewriteRejectsDotDotSegments(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
publicDir := filepath.Join(tmpDir, "public")
|
|
if err := os.MkdirAll(publicDir, 0o755); err != nil {
|
|
t.Fatalf("cannot create public dir: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(publicDir, "index.html"), []byte("<h1>Public</h1>"), 0o644); err != nil {
|
|
t.Fatalf("cannot create public index: %v", err)
|
|
}
|
|
secretPath := filepath.Join(tmpDir, "secret.txt")
|
|
if err := os.WriteFile(secretPath, []byte("TOP_SECRET"), 0o644); err != nil {
|
|
t.Fatalf("cannot create secret file: %v", err)
|
|
}
|
|
|
|
type testCase struct {
|
|
name string
|
|
pathRewrite PathRewriteFunc
|
|
requestURI string
|
|
}
|
|
|
|
testCases := []testCase{
|
|
{
|
|
name: "prefix-stripper-leading-dotdot",
|
|
pathRewrite: NewPathPrefixStripper(len("/static/")),
|
|
requestURI: "http://localhost/aaaaaaa../secret.txt",
|
|
},
|
|
{
|
|
name: "custom-leading-dotdot",
|
|
pathRewrite: func(ctx *RequestCtx) []byte {
|
|
return []byte("../secret.txt")
|
|
},
|
|
requestURI: "http://localhost/ignored",
|
|
},
|
|
{
|
|
name: "custom-trailing-dotdot",
|
|
pathRewrite: func(ctx *RequestCtx) []byte {
|
|
return []byte("nested/..")
|
|
},
|
|
requestURI: "http://localhost/ignored",
|
|
},
|
|
{
|
|
name: "custom-exact-dotdot",
|
|
pathRewrite: func(ctx *RequestCtx) []byte {
|
|
return []byte("..")
|
|
},
|
|
requestURI: "http://localhost/ignored",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
stop := make(chan struct{})
|
|
defer close(stop)
|
|
|
|
fs := &FS{
|
|
Root: publicDir,
|
|
AllowEmptyRoot: true,
|
|
CleanStop: stop,
|
|
PathRewrite: tc.pathRewrite,
|
|
}
|
|
h := fs.NewRequestHandler()
|
|
|
|
var ctx RequestCtx
|
|
ctx.Init(&Request{}, nil, TestLogger{t: t})
|
|
ctx.Request.SetRequestURI(tc.requestURI)
|
|
|
|
h(&ctx)
|
|
|
|
if ctx.Response.StatusCode() != StatusInternalServerError {
|
|
t.Fatalf("unexpected status code for %s: %d. Expecting %d", tc.name, ctx.Response.StatusCode(), StatusInternalServerError)
|
|
}
|
|
if bytes.Contains(ctx.Response.Body(), []byte("TOP_SECRET")) {
|
|
t.Fatalf("unexpected secret disclosure for %s", tc.name)
|
|
}
|
|
})
|
|
}
|
|
}
|