mirror of
https://github.com/labstack/echo.git
synced 2024-11-24 08:22:21 +02:00
* Add optional filesystem to static middleware.
This commit is contained in:
parent
de3f87eb23
commit
b643e6834e
@ -42,6 +42,10 @@ type (
|
||||
// the filesystem path is not doubled
|
||||
// Optional. Default value false.
|
||||
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 == "" {
|
||||
config.Index = DefaultStaticConfig.Index
|
||||
}
|
||||
if config.Filesystem == nil {
|
||||
config.Filesystem = http.Dir(config.Root)
|
||||
config.Root = "."
|
||||
}
|
||||
|
||||
// Index template
|
||||
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 os.IsNotExist(err) {
|
||||
if err = next(c); err != nil {
|
||||
if he, ok := err.(*echo.HTTPError); ok {
|
||||
if config.HTML5 && he.Code == http.StatusNotFound {
|
||||
return c.File(filepath.Join(config.Root, config.Index))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = next(c); err == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
index := filepath.Join(name, config.Index)
|
||||
fi, err = os.Stat(index)
|
||||
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 config.Browse {
|
||||
return listDir(t, name, c.Response())
|
||||
return listDir(t, name, file, c.Response())
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
return next(c)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return c.File(index)
|
||||
defer index.Close()
|
||||
|
||||
info, err = index.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return serveFile(c, index, info)
|
||||
}
|
||||
|
||||
return c.File(name)
|
||||
return serveFile(c, file, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listDir(t *template.Template, name string, res *echo.Response) (err error) {
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
files, err := file.Readdir(-1)
|
||||
func openFile(fs http.FileSystem, name string) (http.File, error) {
|
||||
pathWithSlashes := filepath.ToSlash(name)
|
||||
return fs.Open(pathWithSlashes)
|
||||
}
|
||||
|
||||
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 {
|
||||
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,
|
||||
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 {
|
||||
@ -115,6 +141,9 @@ func TestStatic(t *testing.T) {
|
||||
} else {
|
||||
// middleware is on root level
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user