diff --git a/.github/testdata/fs/img/fiberpng b/.github/testdata/fs/img/fiberpng new file mode 100644 index 0000000000..fa981bcf26 Binary files /dev/null and b/.github/testdata/fs/img/fiberpng differ diff --git a/.github/testdata/fs/img/fiberpng.jpeg b/.github/testdata/fs/img/fiberpng.jpeg new file mode 100644 index 0000000000..fa981bcf26 Binary files /dev/null and b/.github/testdata/fs/img/fiberpng.jpeg differ diff --git a/.github/testdata/fs/img/fiberpng.notvalidext b/.github/testdata/fs/img/fiberpng.notvalidext new file mode 100644 index 0000000000..fa981bcf26 Binary files /dev/null and b/.github/testdata/fs/img/fiberpng.notvalidext differ diff --git a/app.go b/app.go index 38a3d17319..e1e607e6fa 100644 --- a/app.go +++ b/app.go @@ -142,7 +142,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa // Default: false StrictRouting bool `json:"strict_routing"` - // When set to true, enables case sensitive routing. + // When set to true, enables case-sensitive routing. // E.g. "/FoO" and "/foo" are treated as different routes. // By default this is disabled and both "/FoO" and "/foo" will execute the same handler. // diff --git a/app_test.go b/app_test.go index 6b493de1eb..9699c85bce 100644 --- a/app_test.go +++ b/app_test.go @@ -451,9 +451,9 @@ func Test_App_Use_CaseSensitive(t *testing.T) { require.NoError(t, err, "app.Test(req)") require.Equal(t, StatusOK, resp.StatusCode, "Status code") - // check the detected path when the case insensitive recognition is activated + // check the detected path when the case-insensitive recognition is activated app.config.CaseSensitive = false - // check the case sensitive feature + // check the case-sensitive feature resp, err = app.Test(httptest.NewRequest(MethodGet, "/AbC", nil)) require.NoError(t, err, "app.Test(req)") require.Equal(t, StatusOK, resp.StatusCode, "Status code") diff --git a/ctx.go b/ctx.go index 9e61d0903e..1bbf2ba130 100644 --- a/ctx.go +++ b/ctx.go @@ -83,29 +83,29 @@ type SendFile struct { // You have to set Content-Encoding header to compress the file. // Available compression methods are gzip, br, and zstd. // - // Optional. Default value false + // Optional. Default: false Compress bool `json:"compress"` // When set to true, enables byte range requests. // - // Optional. Default value false + // Optional. Default: false ByteRange bool `json:"byte_range"` // When set to true, enables direct download. // - // Optional. Default: false. + // Optional. Default: false Download bool `json:"download"` // Expiration duration for inactive file handlers. // Use a negative time.Duration to disable it. // - // Optional. Default value 10 * time.Second. + // Optional. Default: 10 * time.Second CacheDuration time.Duration `json:"cache_duration"` // The value for the Cache-Control HTTP-header // that is set on the file response. MaxAge is defined in seconds. // - // Optional. Default value 0. + // Optional. Default: 0 MaxAge int `json:"max_age"` } @@ -1496,9 +1496,10 @@ func (c *DefaultCtx) Send(body []byte) error { return nil } -// SendFile transfers the file from the given path. -// The file is not compressed by default, enable this by passing a 'true' argument -// Sets the Content-Type response HTTP header field based on the filenames extension. +// SendFile transfers the file from the specified path. +// By default, the file is not compressed. To enable compression, set SendFile.Compress to true. +// The Content-Type response HTTP header field is set based on the file's extension. +// If the file extension is missing or invalid, the Content-Type is detected from the file's format. func (c *DefaultCtx) SendFile(file string, config ...SendFile) error { // Save the filename, we will need it in the error message if the file isn't found filename := file diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go index aa317ecb00..0714b24329 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -270,9 +270,10 @@ type Ctx interface { // Send sets the HTTP response body without copying it. // From this point onward the body argument must not be changed. Send(body []byte) error - // SendFile transfers the file from the given path. - // The file is not compressed by default, enable this by passing a 'true' argument - // Sets the Content-Type response HTTP header field based on the filenames extension. + // SendFile transfers the file from the specified path. + // By default, the file is not compressed. To enable compression, set SendFile.Compress to true. + // The Content-Type response HTTP header field is set based on the file's extension. + // If the file extension is missing or invalid, the Content-Type is detected from the file's format. SendFile(file string, config ...SendFile) error // SendStatus sets the HTTP status code and if the response body is empty, // it sets the correct status message in the body. diff --git a/ctx_test.go b/ctx_test.go index eef29a97ad..3b6b44ebac 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -3122,6 +3122,49 @@ func Test_Ctx_SendFile(t *testing.T) { app.ReleaseCtx(c) } +// go test -race -run Test_Ctx_SendFile_ContentType +func Test_Ctx_SendFile_ContentType(t *testing.T) { + t.Parallel() + app := New() + + // 1) simple case + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + err := c.SendFile("./.github/testdata/fs/img/fiber.png") + // check expectation + require.NoError(t, err) + require.Equal(t, StatusOK, c.Response().StatusCode()) + require.Equal(t, "image/png", string(c.Response().Header.Peek(HeaderContentType))) + app.ReleaseCtx(c) + + // 2) set by valid file extension, not file header + // see: https://github.com/valyala/fasthttp/blob/d795f13985f16622a949ea9fc3459cf54dc78b3e/fs.go#L1638 + c = app.AcquireCtx(&fasthttp.RequestCtx{}) + err = c.SendFile("./.github/testdata/fs/img/fiberpng.jpeg") + // check expectation + require.NoError(t, err) + require.Equal(t, StatusOK, c.Response().StatusCode()) + require.Equal(t, "image/jpeg", string(c.Response().Header.Peek(HeaderContentType))) + app.ReleaseCtx(c) + + // 3) set by file header if extension is invalid + c = app.AcquireCtx(&fasthttp.RequestCtx{}) + err = c.SendFile("./.github/testdata/fs/img/fiberpng.notvalidext") + // check expectation + require.NoError(t, err) + require.Equal(t, StatusOK, c.Response().StatusCode()) + require.Equal(t, "image/png", string(c.Response().Header.Peek(HeaderContentType))) + app.ReleaseCtx(c) + + // 4) set by file header if extension is missing + c = app.AcquireCtx(&fasthttp.RequestCtx{}) + err = c.SendFile("./.github/testdata/fs/img/fiberpng") + // check expectation + require.NoError(t, err) + require.Equal(t, StatusOK, c.Response().StatusCode()) + require.Equal(t, "image/png", string(c.Response().Header.Peek(HeaderContentType))) + app.ReleaseCtx(c) +} + func Test_Ctx_SendFile_Download(t *testing.T) { t.Parallel() app := New() diff --git a/docs/api/ctx.md b/docs/api/ctx.md index b771d37eab..52d8144187 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -1709,7 +1709,7 @@ app.Get("/", func(c fiber.Ctx) error { ## SendFile -Transfers the file from the given path. Sets the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) response HTTP header field based on the **filenames** extension. +Transfers the file from the given path. Sets the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) response HTTP header field based on the **file** extension or format. ```go title="Config" title="Config" // SendFile defines configuration options when to transfer file with SendFile. @@ -1725,29 +1725,29 @@ type SendFile struct { // You have to set Content-Encoding header to compress the file. // Available compression methods are gzip, br, and zstd. // - // Optional. Default value false + // Optional. Default: false Compress bool `json:"compress"` // When set to true, enables byte range requests. // - // Optional. Default value false + // Optional. Default: false ByteRange bool `json:"byte_range"` // When set to true, enables direct download. // - // Optional. Default: false. + // Optional. Default: false Download bool `json:"download"` // Expiration duration for inactive file handlers. // Use a negative time.Duration to disable it. // - // Optional. Default value 10 * time.Second. + // Optional. Default: 10 * time.Second CacheDuration time.Duration `json:"cache_duration"` // The value for the Cache-Control HTTP-header // that is set on the file response. MaxAge is defined in seconds. // - // Optional. Default value 0. + // Optional. Default: 0 MaxAge int `json:"max_age"` } ``` @@ -1761,7 +1761,7 @@ app.Get("/not-found", func(c fiber.Ctx) error { return c.SendFile("./public/404.html"); // Disable compression - return c.SendFile("./static/index.html", SendFile{ + return c.SendFile("./static/index.html", fiber.SendFile{ Compress: false, }); }) @@ -1783,7 +1783,7 @@ You can set `CacheDuration` config property to `-1` to disable caching. ```go title="Example" app.Get("/file", func(c fiber.Ctx) error { - return c.SendFile("style.css", SendFile{ + return c.SendFile("style.css", fiber.SendFile{ CacheDuration: -1, }) }) @@ -1797,16 +1797,16 @@ You can use multiple SendFile with different configurations in single route. Fib app.Get("/file", func(c fiber.Ctx) error { switch c.Query("config") { case "filesystem": - return c.SendFile("style.css", SendFile{ + return c.SendFile("style.css", fiber.SendFile{ FS: os.DirFS(".") }) case "filesystem-compress": - return c.SendFile("style.css", SendFile{ + return c.SendFile("style.css", fiber.SendFile{ FS: os.DirFS("."), Compress: true, }) case "compress": - return c.SendFile("style.css", SendFile{ + return c.SendFile("style.css", fiber.SendFile{ Compress: true, }) default: diff --git a/router.go b/router.go index d2429c194e..27e3bc7e74 100644 --- a/router.go +++ b/router.go @@ -52,7 +52,7 @@ type Route struct { Name string `json:"name"` // Route's name //nolint:revive // Having both a Path (uppercase) and a path (lowercase) is fine Path string `json:"path"` // Original registered route path - Params []string `json:"params"` // Case sensitive param keys + Params []string `json:"params"` // Case-sensitive param keys Handlers []Handler `json:"-"` // Ctx handlers routeParser routeParser // Parameter parser // Data for routing @@ -316,7 +316,7 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler if pathRaw[0] != '/' { pathRaw = "/" + pathRaw } - // Create a stripped path in-case sensitive / trailing slashes + // Create a stripped path in case-sensitive / trailing slashes pathPretty := pathRaw // Case-sensitive routing, all to lowercase if !app.config.CaseSensitive {