mirror of
https://github.com/labstack/echo.git
synced 2025-02-03 13:11:39 +02:00
Fix e.Static
, .File()
, c.Attachment()
being picky with paths starting with ./
, ../
and /
after 4.7.0 introduced echo.Filesystem support (Go1.16+) (#2123)
* Fix `e.Static`, `.File()`, `c.Attachment()` being picky with paths starting with `./`, `../` and `/` after 4.7.0 introduced echo.Filesystem support (Go1.16+)
This commit is contained in:
parent
5ebed440ae
commit
3f5b733425
@ -10,6 +10,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -94,10 +95,12 @@ func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// defaultFS emulates os.Open behaviour with filesystem opened by `os.DirFs`. Difference between `os.Open` and `fs.Open`
|
||||
// is that FS does not allow to open path that start with `..` or `/` etc. For example previously you could have `../images`
|
||||
// in your application but `fs := os.DirFS("./")` would not allow you to use `fs.Open("../images")` and this would break
|
||||
// all old applications that rely on being able to traverse up from current executable run path.
|
||||
// defaultFS exists to preserve pre v4.7.0 behaviour where files were open by `os.Open`.
|
||||
// v4.7 introduced `echo.Filesystem` field which is Go1.16+ `fs.Fs` interface.
|
||||
// Difference between `os.Open` and `fs.Open` is that FS does not allow opening path that start with `.`, `..` or `/`
|
||||
// etc. For example previously you could have `../images` in your application but `fs := os.DirFS("./")` would not
|
||||
// allow you to use `fs.Open("../images")` and this would break all old applications that rely on being able to
|
||||
// traverse up from current executable run path.
|
||||
// NB: private because you really should use fs.FS implementation instances
|
||||
type defaultFS struct {
|
||||
prefix string
|
||||
@ -108,20 +111,26 @@ func newDefaultFS() *defaultFS {
|
||||
dir, _ := os.Getwd()
|
||||
return &defaultFS{
|
||||
prefix: dir,
|
||||
fs: os.DirFS(dir),
|
||||
fs: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (fs defaultFS) Open(name string) (fs.File, error) {
|
||||
if fs.fs == nil {
|
||||
return os.Open(name)
|
||||
}
|
||||
return fs.fs.Open(name)
|
||||
}
|
||||
|
||||
func subFS(currentFs fs.FS, root string) (fs.FS, error) {
|
||||
root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows
|
||||
if dFS, ok := currentFs.(*defaultFS); ok {
|
||||
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS to
|
||||
// allow cases when root is given as `../somepath` which is not valid for fs.FS
|
||||
root = filepath.Join(dFS.prefix, root)
|
||||
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS.
|
||||
// fs.Fs.Open does not like relative paths ("./", "../") and absolute paths at all but prior echo.Filesystem we
|
||||
// were able to use paths like `./myfile.log`, `/etc/hosts` and these would work fine with `os.Open` but not with fs.Fs
|
||||
if isRelativePath(root) {
|
||||
root = filepath.Join(dFS.prefix, root)
|
||||
}
|
||||
return &defaultFS{
|
||||
prefix: root,
|
||||
fs: os.DirFS(root),
|
||||
@ -130,6 +139,21 @@ func subFS(currentFs fs.FS, root string) (fs.FS, error) {
|
||||
return fs.Sub(currentFs, root)
|
||||
}
|
||||
|
||||
func isRelativePath(path string) bool {
|
||||
if path == "" {
|
||||
return true
|
||||
}
|
||||
if path[0] == '/' {
|
||||
return false
|
||||
}
|
||||
if runtime.GOOS == "windows" && strings.IndexByte(path, ':') != -1 {
|
||||
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#file_and_directory_names
|
||||
// https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MustSubFS creates sub FS from current filesystem or panic on failure.
|
||||
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
|
||||
//
|
||||
|
61
echo_test.go
61
echo_test.go
@ -84,6 +84,14 @@ func TestEchoStatic(t *testing.T) {
|
||||
expectStatus: http.StatusOK,
|
||||
expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}),
|
||||
},
|
||||
{
|
||||
name: "ok with relative path for root points to directory",
|
||||
givenPrefix: "/images",
|
||||
givenRoot: "./_fixture/images",
|
||||
whenURL: "/images/walle.png",
|
||||
expectStatus: http.StatusOK,
|
||||
expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}),
|
||||
},
|
||||
{
|
||||
name: "No file",
|
||||
givenPrefix: "/images",
|
||||
@ -246,11 +254,54 @@ func TestEchoStaticRedirectIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEchoFile(t *testing.T) {
|
||||
e := New()
|
||||
e.File("/walle", "_fixture/images/walle.png")
|
||||
c, b := request(http.MethodGet, "/walle", e)
|
||||
assert.Equal(t, http.StatusOK, c)
|
||||
assert.NotEmpty(t, b)
|
||||
var testCases = []struct {
|
||||
name string
|
||||
givenPath string
|
||||
givenFile string
|
||||
whenPath string
|
||||
expectCode int
|
||||
expectStartsWith string
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
givenPath: "/walle",
|
||||
givenFile: "_fixture/images/walle.png",
|
||||
whenPath: "/walle",
|
||||
expectCode: http.StatusOK,
|
||||
expectStartsWith: string([]byte{0x89, 0x50, 0x4e}),
|
||||
},
|
||||
{
|
||||
name: "ok with relative path",
|
||||
givenPath: "/",
|
||||
givenFile: "./go.mod",
|
||||
whenPath: "/",
|
||||
expectCode: http.StatusOK,
|
||||
expectStartsWith: "module github.com/labstack/echo/v",
|
||||
},
|
||||
{
|
||||
name: "nok file does not exist",
|
||||
givenPath: "/",
|
||||
givenFile: "./this-file-does-not-exist",
|
||||
whenPath: "/",
|
||||
expectCode: http.StatusNotFound,
|
||||
expectStartsWith: "{\"message\":\"Not Found\"}\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
e := New() // we are using echo.defaultFS instance
|
||||
e.File(tc.givenPath, tc.givenFile)
|
||||
|
||||
c, b := request(http.MethodGet, tc.whenPath, e)
|
||||
assert.Equal(t, tc.expectCode, c)
|
||||
|
||||
if len(b) > len(tc.expectStartsWith) {
|
||||
b = b[:len(tc.expectStartsWith)]
|
||||
}
|
||||
assert.Equal(t, tc.expectStartsWith, b)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEchoMiddleware(t *testing.T) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user