// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors package echo import ( "github.com/stretchr/testify/assert" "io/fs" "net/http" "net/http/httptest" "os" "strings" "testing" ) func TestEcho_StaticFS(t *testing.T) { var testCases = []struct { name string givenPrefix string givenFs fs.FS givenFsRoot string whenURL string expectStatus int expectHeaderLocation string expectBodyStartsWith string }{ { name: "ok", givenPrefix: "/images", givenFs: os.DirFS("./_fixture/images"), whenURL: "/images/walle.png", expectStatus: http.StatusOK, expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}), }, { name: "ok, from sub fs", givenPrefix: "/images", givenFs: MustSubFS(os.DirFS("./_fixture/"), "images"), whenURL: "/images/walle.png", expectStatus: http.StatusOK, expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}), }, { name: "No file", givenPrefix: "/images", givenFs: os.DirFS("_fixture/scripts"), whenURL: "/images/bolt.png", expectStatus: http.StatusNotFound, expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", }, { name: "Directory", givenPrefix: "/images", givenFs: os.DirFS("_fixture/images"), whenURL: "/images/", expectStatus: http.StatusNotFound, expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", }, { name: "Directory Redirect", givenPrefix: "/", givenFs: os.DirFS("_fixture/"), whenURL: "/folder", expectStatus: http.StatusMovedPermanently, expectHeaderLocation: "/folder/", expectBodyStartsWith: "", }, { name: "Directory Redirect with non-root path", givenPrefix: "/static", givenFs: os.DirFS("_fixture"), whenURL: "/static", expectStatus: http.StatusMovedPermanently, expectHeaderLocation: "/static/", expectBodyStartsWith: "", }, { name: "Prefixed directory 404 (request URL without slash)", givenPrefix: "/folder/", // trailing slash will intentionally not match "/folder" givenFs: os.DirFS("_fixture"), whenURL: "/folder", // no trailing slash expectStatus: http.StatusNotFound, expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", }, { name: "Prefixed directory redirect (without slash redirect to slash)", givenPrefix: "/folder", // no trailing slash shall match /folder and /folder/* givenFs: os.DirFS("_fixture"), whenURL: "/folder", // no trailing slash expectStatus: http.StatusMovedPermanently, expectHeaderLocation: "/folder/", expectBodyStartsWith: "", }, { name: "Directory with index.html", givenPrefix: "/", givenFs: os.DirFS("_fixture"), whenURL: "/", expectStatus: http.StatusOK, expectBodyStartsWith: "", }, { name: "Prefixed directory with index.html (prefix ending with slash)", givenPrefix: "/assets/", givenFs: os.DirFS("_fixture"), whenURL: "/assets/", expectStatus: http.StatusOK, expectBodyStartsWith: "", }, { name: "Prefixed directory with index.html (prefix ending without slash)", givenPrefix: "/assets", givenFs: os.DirFS("_fixture"), whenURL: "/assets/", expectStatus: http.StatusOK, expectBodyStartsWith: "", }, { name: "Sub-directory with index.html", givenPrefix: "/", givenFs: os.DirFS("_fixture"), whenURL: "/folder/", expectStatus: http.StatusOK, expectBodyStartsWith: "", }, { name: "do not allow directory traversal (backslash - windows separator)", givenPrefix: "/", givenFs: os.DirFS("_fixture/"), whenURL: `/..\\middleware/basic_auth.go`, expectStatus: http.StatusNotFound, expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", }, { name: "do not allow directory traversal (slash - unix separator)", givenPrefix: "/", givenFs: os.DirFS("_fixture/"), whenURL: `/../middleware/basic_auth.go`, expectStatus: http.StatusNotFound, expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", }, { name: "open redirect vulnerability", givenPrefix: "/", givenFs: os.DirFS("_fixture/"), whenURL: "/open.redirect.hackercom%2f..", expectStatus: http.StatusMovedPermanently, expectHeaderLocation: "/open.redirect.hackercom/../", // location starting with `//open` would be very bad expectBodyStartsWith: "", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { e := New() tmpFs := tc.givenFs if tc.givenFsRoot != "" { tmpFs = MustSubFS(tmpFs, tc.givenFsRoot) } e.StaticFS(tc.givenPrefix, tmpFs) req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) assert.Equal(t, tc.expectStatus, rec.Code) body := rec.Body.String() if tc.expectBodyStartsWith != "" { assert.True(t, strings.HasPrefix(body, tc.expectBodyStartsWith)) } else { assert.Equal(t, "", body) } if tc.expectHeaderLocation != "" { assert.Equal(t, tc.expectHeaderLocation, rec.Result().Header["Location"][0]) } else { _, ok := rec.Result().Header["Location"] assert.False(t, ok) } }) } } func TestEcho_FileFS(t *testing.T) { var testCases = []struct { name string whenPath string whenFile string whenFS fs.FS givenURL string expectCode int expectStartsWith []byte }{ { name: "ok", whenPath: "/walle", whenFS: os.DirFS("_fixture/images"), whenFile: "walle.png", givenURL: "/walle", expectCode: http.StatusOK, expectStartsWith: []byte{0x89, 0x50, 0x4e}, }, { name: "nok, requesting invalid path", whenPath: "/walle", whenFS: os.DirFS("_fixture/images"), whenFile: "walle.png", givenURL: "/walle.png", expectCode: http.StatusNotFound, expectStartsWith: []byte(`{"message":"Not Found"}`), }, { name: "nok, serving not existent file from filesystem", whenPath: "/walle", whenFS: os.DirFS("_fixture/images"), whenFile: "not-existent.png", givenURL: "/walle", expectCode: http.StatusNotFound, expectStartsWith: []byte(`{"message":"Not Found"}`), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { e := New() e.FileFS(tc.whenPath, tc.whenFile, tc.whenFS) req := httptest.NewRequest(http.MethodGet, tc.givenURL, nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) assert.Equal(t, tc.expectCode, rec.Code) body := rec.Body.Bytes() if len(body) > len(tc.expectStartsWith) { body = body[:len(tc.expectStartsWith)] } assert.Equal(t, tc.expectStartsWith, body) }) } } func TestEcho_StaticPanic(t *testing.T) { var testCases = []struct { name string givenRoot string expectError string }{ { name: "panics for ../", givenRoot: "../assets", expectError: "can not create sub FS, invalid root given, err: sub ../assets: invalid name", }, { name: "panics for /", givenRoot: "/assets", expectError: "can not create sub FS, invalid root given, err: sub /assets: invalid name", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { e := New() e.Filesystem = os.DirFS("./") assert.PanicsWithError(t, tc.expectError, func() { e.Static("../assets", tc.givenRoot) }) }) } }