@@ -69,8 +69,15 @@ type Echo struct {
6969 serveHTTPFunc func (http.ResponseWriter , * http.Request )
7070
7171 Binder Binder
72- // Filesystem is the file system used for serving static files. Defaults to the current working directory.
73- Filesystem fs.FS
72+
73+ // Filesystem is the file system used for serving static files. Defaults to the current working directory (os.Getwd()).
74+ //
75+ // Note: fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
76+ // so if you have `fs := os.DirFS("/tmp")` and you try to `fs.Open("/tmp/file.txt")` it will fail, but "file.txt"
77+ // would succeed. `echo.NewDefaultFS("/tmp")` overwrites this behavior and allows you to use Open with a matching
78+ // absolute path prefix.
79+ Filesystem fs.FS
80+
7481 Renderer Renderer
7582 Validator Validator
7683 JSONSerializer JSONSerializer
@@ -324,10 +331,11 @@ func NewWithConfig(config Config) *Echo {
324331
325332// New creates an instance of Echo.
326333func New () * Echo {
334+ dir , _ := os .Getwd ()
327335 logger := slog .New (slog .NewJSONHandler (os .Stdout , nil ))
328336 e := & Echo {
329337 Logger : logger ,
330- Filesystem : newDefaultFS ( ),
338+ Filesystem : NewDefaultFS ( dir ),
331339 Binder : & DefaultBinder {},
332340 JSONSerializer : & DefaultJSONSerializer {},
333341 formParseMaxMemory : defaultMemory ,
@@ -781,7 +789,7 @@ func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {
781789 return h
782790}
783791
784- // defaultFS emulates os.Open behaviour with filesystem opened by `os.DirFs`. Difference between `os.Open` and `fs.Open`
792+ // defaultFS emulates os.Open behavior with filesystem opened by `os.DirFs`. Difference between `os.Open` and `fs.Open`
785793// is that FS does not allow to open path that start with `..` or `/` etc. For example previously you could have `../images`
786794// in your application but `fs := os.DirFS("./")` would not allow you to use `fs.Open("../images")` and this would break
787795// all old applications that rely on being able to traverse up from current executable run path.
@@ -791,15 +799,28 @@ type defaultFS struct {
791799 prefix string
792800}
793801
794- func newDefaultFS () * defaultFS {
795- dir , _ := os .Getwd ()
802+ // NewDefaultFS returns a new defaultFS instance which allows `fs.FS.Open` to have absolute paths as input if it matches
803+ // then given dir as prefix.
804+ func NewDefaultFS (dir string ) fs.FS {
796805 return & defaultFS {
797806 prefix : dir ,
798807 fs : os .DirFS (dir ),
799808 }
800809}
801810
802811func (fs defaultFS ) Open (name string ) (fs.File , error ) {
812+ // fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
813+ // For example `f.Name()` returns file names as absolute paths (e.g. `/tmp/data.csv`) so in case user wants to open
814+ // a file with an absolute path we need to remove prefix and then call fs.FS.Open().
815+ // not to force users to cut prefix from file name we do it here.
816+ if filepath .IsAbs (name ) {
817+ if strings .HasPrefix (name , fs .prefix ) {
818+ name = name [len (fs .prefix ):]
819+ if len (name ) > 1 && os .IsPathSeparator (name [0 ]) {
820+ name = name [1 :]
821+ }
822+ }
823+ }
803824 return fs .fs .Open (name )
804825}
805826
0 commit comments