diff --git a/ctx.go b/ctx.go index 230cf95fa7..9cc5a52e3b 100644 --- a/ctx.go +++ b/ctx.go @@ -1350,10 +1350,11 @@ func (c *Ctx) SendFile(file string, compress ...bool) error { // Save the filename, we will need it in the error message if the file isn't found filename := file - // https://github.com/valyala/fasthttp/blob/master/fs.go#L81 + // https://github.com/valyala/fasthttp/blob/c7576cc10cabfc9c993317a2d3f8355497bea156/fs.go#L129-L134 sendFileOnce.Do(func() { sendFileFS = &fasthttp.FS{ - Root: "/", + Root: "", + AllowEmptyRoot: true, GenerateIndexPages: false, AcceptByteRange: true, Compress: true, @@ -1371,13 +1372,16 @@ func (c *Ctx) SendFile(file string, compress ...bool) error { c.pathOriginal = utils.CopyString(c.pathOriginal) // Disable compression if len(compress) == 0 || !compress[0] { - // https://github.com/valyala/fasthttp/blob/master/fs.go#L46 + // https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L55 c.fasthttp.Request.Header.Del(HeaderAcceptEncoding) } - // https://github.com/valyala/fasthttp/blob/master/fs.go#L85 - if len(file) == 0 || file[0] != '/' { - hasTrailingSlash := len(file) > 0 && file[len(file)-1] == '/' + // copy of https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L103-L121 with small adjustments + if len(file) == 0 || !filepath.IsAbs(file) { + // extend relative path to absolute path + hasTrailingSlash := len(file) > 0 && (file[len(file)-1] == '/' || file[len(file)-1] == '\\') + var err error + file = filepath.FromSlash(file) if file, err = filepath.Abs(file); err != nil { return err } @@ -1385,6 +1389,10 @@ func (c *Ctx) SendFile(file string, compress ...bool) error { file += "/" } } + // convert the path to forward slashes regardless the OS in order to set the URI properly + // the handler will convert back to OS path separator before opening the file + file = filepath.ToSlash(file) + // Restore the original requested URL originalURL := utils.CopyString(c.OriginalURL()) defer c.fasthttp.Request.SetRequestURI(originalURL) diff --git a/ctx_test.go b/ctx_test.go index 4c9d75ab83..3be4e6766f 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -20,6 +20,7 @@ import ( "net/http/httptest" "net/url" "os" + "path/filepath" "reflect" "strconv" "strings" @@ -1908,7 +1909,7 @@ func Test_Ctx_SendFile_404(t *testing.T) { t.Parallel() app := New() app.Get("/", func(c *Ctx) error { - err := c.SendFile("./john_dow.go/") + err := c.SendFile(filepath.FromSlash("john_dow.go/")) utils.AssertEqual(t, false, err == nil) return err }) @@ -1922,22 +1923,44 @@ func Test_Ctx_SendFile_404(t *testing.T) { func Test_Ctx_SendFile_Immutable(t *testing.T) { t.Parallel() app := New() - app.Get("/:file", func(c *Ctx) error { - file := c.Params("file") - if err := c.SendFile("./.github/" + file + ".html"); err != nil { + var endpointsForTest []string + addEndpoint := func(file, endpoint string) { + endpointsForTest = append(endpointsForTest, endpoint) + app.Get(endpoint, func(c *Ctx) error { + if err := c.SendFile(file); err != nil { + utils.AssertEqual(t, nil, err) + return err + } + return c.SendStatus(200) + }) + } + + // relative paths + addEndpoint("./.github/index.html", "/relativeWithDot") + addEndpoint(filepath.FromSlash("./.github/index.html"), "/relativeOSWithDot") + addEndpoint(".github/index.html", "/relative") + addEndpoint(filepath.FromSlash(".github/index.html"), "/relativeOS") + + // absolute paths + if path, err := filepath.Abs(".github/index.html"); err != nil { + utils.AssertEqual(t, nil, err) + } else { + addEndpoint(path, "/absolute") + addEndpoint(filepath.FromSlash(path), "/absoluteOS") // os related + } + + for _, endpoint := range endpointsForTest { + t.Run(endpoint, func(t *testing.T) { + // 1st try + resp, err := app.Test(httptest.NewRequest("GET", endpoint, nil)) utils.AssertEqual(t, nil, err) - } - utils.AssertEqual(t, "index", file) - return c.SendString(file) - }) - // 1st try - resp, err := app.Test(httptest.NewRequest("GET", "/index", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, StatusOK, resp.StatusCode) - // 2nd try - resp, err = app.Test(httptest.NewRequest("GET", "/index", nil)) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, StatusOK, resp.StatusCode) + utils.AssertEqual(t, StatusOK, resp.StatusCode) + // 2nd try + resp, err = app.Test(httptest.NewRequest("GET", endpoint, nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, StatusOK, resp.StatusCode) + }) + } } // go test -race -run Test_Ctx_SendFile_RestoreOriginalURL diff --git a/go.mod b/go.mod index ba0e0d82c7..d48909c7ef 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/gofiber/fiber/v2 go 1.16 require ( - github.com/valyala/fasthttp v1.35.0 + github.com/valyala/fasthttp v1.37.0 golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 ) diff --git a/go.sum b/go.sum index 969f84c591..5c4ae8555c 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwc github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.35.0 h1:wwkR8mZn2NbigFsaw2Zj5r+xkmzjbrA/lyTmiSlal/Y= -github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkMbE= +github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff --git a/router.go b/router.go index 5e037da412..69cc3c00da 100644 --- a/router.go +++ b/router.go @@ -134,7 +134,7 @@ func (app *App) next(c *Ctx) (match bool, err error) { } // If c.Next() does not match, return 404 - err = NewError(StatusNotFound, "Cannot " + c.method + " " + c.pathOriginal) + err = NewError(StatusNotFound, "Cannot "+c.method+" "+c.pathOriginal) // If no match, scan stack again if other methods match the request // Moved from app.handler because middleware may break the route chain @@ -326,6 +326,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) Router { // Fileserver settings fs := &fasthttp.FS{ Root: root, + AllowEmptyRoot: true, GenerateIndexPages: false, AcceptByteRange: false, Compress: false,