mirror of
https://github.com/labstack/echo.git
synced 2025-07-05 00:58:47 +02:00
* Add optional filesystem to static middleware.
This commit is contained in:
@ -42,6 +42,10 @@ type (
|
|||||||
// the filesystem path is not doubled
|
// the filesystem path is not doubled
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
IgnoreBase bool `yaml:"ignoreBase"`
|
IgnoreBase bool `yaml:"ignoreBase"`
|
||||||
|
|
||||||
|
// Filesystem provides access to the static content.
|
||||||
|
// Optional. Defaults to http.Dir(config.Root)
|
||||||
|
Filesystem http.FileSystem `yaml:"-"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -146,6 +150,10 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
|||||||
if config.Index == "" {
|
if config.Index == "" {
|
||||||
config.Index = DefaultStaticConfig.Index
|
config.Index = DefaultStaticConfig.Index
|
||||||
}
|
}
|
||||||
|
if config.Filesystem == nil {
|
||||||
|
config.Filesystem = http.Dir(config.Root)
|
||||||
|
config.Root = "."
|
||||||
|
}
|
||||||
|
|
||||||
// Index template
|
// Index template
|
||||||
t, err := template.New("index").Parse(html)
|
t, err := template.New("index").Parse(html)
|
||||||
@ -178,49 +186,73 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := os.Stat(name)
|
file, err := openFile(config.Filesystem, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
if err = next(c); err != nil {
|
return err
|
||||||
if he, ok := err.(*echo.HTTPError); ok {
|
|
||||||
if config.HTML5 && he.Code == http.StatusNotFound {
|
|
||||||
return c.File(filepath.Join(config.Root, config.Index))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.IsDir() {
|
if err = next(c); err == nil {
|
||||||
index := filepath.Join(name, config.Index)
|
return err
|
||||||
fi, err = os.Stat(index)
|
}
|
||||||
|
|
||||||
|
he, ok := err.(*echo.HTTPError)
|
||||||
|
if !(ok && config.HTML5 && he.Code == http.StatusNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err = openFile(config.Filesystem, filepath.Join(config.Root, config.Index))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
info, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
index, err := openFile(config.Filesystem, filepath.Join(name, config.Index))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if config.Browse {
|
if config.Browse {
|
||||||
return listDir(t, name, c.Response())
|
return listDir(t, name, file, c.Response())
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.File(index)
|
defer index.Close()
|
||||||
|
|
||||||
|
info, err = index.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.File(name)
|
return serveFile(c, index, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serveFile(c, file, info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listDir(t *template.Template, name string, res *echo.Response) (err error) {
|
func openFile(fs http.FileSystem, name string) (http.File, error) {
|
||||||
file, err := os.Open(name)
|
pathWithSlashes := filepath.ToSlash(name)
|
||||||
if err != nil {
|
return fs.Open(pathWithSlashes)
|
||||||
return
|
}
|
||||||
}
|
|
||||||
files, err := file.Readdir(-1)
|
func serveFile(c echo.Context, file http.File, info os.FileInfo) error {
|
||||||
|
http.ServeContent(c.Response(), c.Request(), info.Name(), info.ModTime(), file)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listDir(t *template.Template, name string, dir http.File, res *echo.Response) (err error) {
|
||||||
|
files, err := dir.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
106
middleware/static_1_16_test.go
Normal file
106
middleware/static_1_16_test.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// +build go1.16
|
||||||
|
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStatic_CustomFS(t *testing.T) {
|
||||||
|
var testCases = []struct {
|
||||||
|
name string
|
||||||
|
filesystem fs.FS
|
||||||
|
root string
|
||||||
|
whenURL string
|
||||||
|
expectContains string
|
||||||
|
expectCode int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ok, serve index with Echo message",
|
||||||
|
whenURL: "/",
|
||||||
|
filesystem: os.DirFS("../_fixture"),
|
||||||
|
expectCode: http.StatusOK,
|
||||||
|
expectContains: "<title>Echo</title>",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "ok, serve index with Echo message",
|
||||||
|
whenURL: "/_fixture/",
|
||||||
|
filesystem: os.DirFS(".."),
|
||||||
|
expectCode: http.StatusOK,
|
||||||
|
expectContains: "<title>Echo</title>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok, serve file from map fs",
|
||||||
|
whenURL: "/file.txt",
|
||||||
|
filesystem: fstest.MapFS{
|
||||||
|
"file.txt": &fstest.MapFile{Data: []byte("file.txt is ok")},
|
||||||
|
},
|
||||||
|
expectCode: http.StatusOK,
|
||||||
|
expectContains: "file.txt is ok",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nok, missing file in map fs",
|
||||||
|
whenURL: "/file.txt",
|
||||||
|
expectCode: http.StatusNotFound,
|
||||||
|
filesystem: fstest.MapFS{
|
||||||
|
"file2.txt": &fstest.MapFile{Data: []byte("file2.txt is ok")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nok, file is not a subpath of root",
|
||||||
|
whenURL: `/../../secret.txt`,
|
||||||
|
root: "/nested/folder",
|
||||||
|
filesystem: fstest.MapFS{
|
||||||
|
"secret.txt": &fstest.MapFile{Data: []byte("this is a secret")},
|
||||||
|
},
|
||||||
|
expectCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nok, backslash is forbidden",
|
||||||
|
whenURL: `/..\..\secret.txt`,
|
||||||
|
expectCode: http.StatusNotFound,
|
||||||
|
root: "/nested/folder",
|
||||||
|
filesystem: fstest.MapFS{
|
||||||
|
"secret.txt": &fstest.MapFile{Data: []byte("this is a secret")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
config := StaticConfig{
|
||||||
|
Root: ".",
|
||||||
|
Filesystem: http.FS(tc.filesystem),
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.root != "" {
|
||||||
|
config.Root = tc.root
|
||||||
|
}
|
||||||
|
|
||||||
|
middlewareFunc := StaticWithConfig(config)
|
||||||
|
e.Use(middlewareFunc)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
e.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectCode, rec.Code)
|
||||||
|
if tc.expectContains != "" {
|
||||||
|
responseBody := rec.Body.String()
|
||||||
|
assert.Contains(t, responseBody, tc.expectContains)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -94,6 +94,32 @@ func TestStatic(t *testing.T) {
|
|||||||
expectCode: http.StatusNotFound,
|
expectCode: http.StatusNotFound,
|
||||||
expectContains: "{\"message\":\"Not Found\"}\n",
|
expectContains: "{\"message\":\"Not Found\"}\n",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ok, do not serve file, when a handler took care of the request",
|
||||||
|
whenURL: "/regular-handler",
|
||||||
|
expectCode: http.StatusOK,
|
||||||
|
expectContains: "ok",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nok, when html5 fail if the index file does not exist",
|
||||||
|
givenConfig: &StaticConfig{
|
||||||
|
Root: "../_fixture",
|
||||||
|
HTML5: true,
|
||||||
|
Index: "missing.html",
|
||||||
|
},
|
||||||
|
whenURL: "/random",
|
||||||
|
expectCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok, serve from http.FileSystem",
|
||||||
|
givenConfig: &StaticConfig{
|
||||||
|
Root: "_fixture",
|
||||||
|
Filesystem: http.Dir(".."),
|
||||||
|
},
|
||||||
|
whenURL: "/",
|
||||||
|
expectCode: http.StatusOK,
|
||||||
|
expectContains: "<title>Echo</title>",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@ -115,6 +141,9 @@ func TestStatic(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
// middleware is on root level
|
// middleware is on root level
|
||||||
e.Use(middlewareFunc)
|
e.Use(middlewareFunc)
|
||||||
|
e.GET("/regular-handler", func(c echo.Context) error {
|
||||||
|
return c.String(http.StatusOK, "ok")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
||||||
|
Reference in New Issue
Block a user