@@ -15,6 +15,7 @@ import (
1515 "path"
1616 "strconv"
1717 "strings"
18+ "sync"
1819
1920 "github.com/labstack/echo/v5"
2021)
@@ -118,13 +119,12 @@ const directoryListHTMLTemplate = `
118119 </header>
119120 <ul>
120121 {{ range .Files }}
121- {{ $href := .Name }}{{ if ne $.Name "/" }}{{ $href = print $.Name "/" .Name }}{{ end }}
122122 <li>
123123 {{ if .Dir }}
124124 {{ $name := print .Name "/" }}
125- <a class="dir" href="{{ $href }}">{{ $name }}</a>
125+ <a class="dir" href="{{ $name }}">{{ $name }}</a>
126126 {{ else }}
127- <a class="file" href="{{ $href }}">{{ .Name }}</a>
127+ <a class="file" href="{{ .Name }}">{{ .Name }}</a>
128128 <span>{{ .Size }}</span>
129129 {{ end }}
130130 </li>
@@ -157,7 +157,10 @@ func (config StaticConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
157157 // Defaults
158158 if config .Root == "" {
159159 config .Root = "." // For security we want to restrict to CWD.
160+ } else {
161+ config .Root = path .Clean (config .Root ) // fs.Open is very picky about ``, `.`, `..` in paths, so remove some of them up.
160162 }
163+
161164 if config .Skipper == nil {
162165 config .Skipper = DefaultStaticConfig .Skipper
163166 }
@@ -173,6 +176,19 @@ func (config StaticConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
173176 return nil , fmt .Errorf ("echo static middleware directory list template parsing error: %w" , tErr )
174177 }
175178
179+ var once * sync.Once
180+ var fsErr error
181+ currentFS := config .Filesystem
182+ if config .Filesystem == nil {
183+ once = & sync.Once {}
184+ } else if config .Root != "." {
185+ tmpFs , fErr := fs .Sub (config .Filesystem , path .Join ("." , config .Root ))
186+ if fErr != nil {
187+ return nil , fmt .Errorf ("static middleware failed to create sub-filesystem from config.Root, error: %w" , fErr )
188+ }
189+ currentFS = tmpFs
190+ }
191+
176192 return func (next echo.HandlerFunc ) echo.HandlerFunc {
177193 return func (c * echo.Context ) (err error ) {
178194 if config .Skipper (c ) {
@@ -197,8 +213,7 @@ func (config StaticConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
197213 // 3. The "/" prefix forces absolute path interpretation, removing ".." components
198214 // 4. Backslashes are treated as literal characters (not path separators), preventing traversal
199215 // See static_windows.go for Go 1.20+ filepath.Clean compatibility notes
200- requestedPath := path .Clean ("/" + p ) // "/"+ for security
201- filePath := path .Join (config .Root , requestedPath )
216+ filePath := path .Clean ("./" + p )
202217
203218 if config .IgnoreBase {
204219 routePath := path .Base (strings .TrimRight (c .Path (), "/*" ))
@@ -209,9 +224,17 @@ func (config StaticConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
209224 }
210225 }
211226
212- currentFS := config .Filesystem
213- if currentFS == nil {
214- currentFS = c .Echo ().Filesystem
227+ if once != nil {
228+ once .Do (func () {
229+ if tmp , tmpErr := fs .Sub (c .Echo ().Filesystem , config .Root ); tmpErr != nil {
230+ fsErr = fmt .Errorf ("static middleware failed to create sub-filesystem: %w" , tmpErr )
231+ } else {
232+ currentFS = tmp
233+ }
234+ })
235+ if fsErr != nil {
236+ return fsErr
237+ }
215238 }
216239
217240 file , err := currentFS .Open (filePath )
@@ -231,7 +254,7 @@ func (config StaticConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
231254 return err
232255 }
233256 // is case HTML5 mode is enabled + echo 404 we serve index to the client
234- file , err = currentFS .Open (path . Join ( config .Root , config . Index ) )
257+ file , err = currentFS .Open (config .Index )
235258 if err != nil {
236259 return err
237260 }
@@ -248,7 +271,7 @@ func (config StaticConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
248271 index , err := currentFS .Open (path .Join (filePath , config .Index ))
249272 if err != nil {
250273 if config .Browse {
251- return listDir (dirListTemplate , requestedPath , filePath , currentFS , c .Response ())
274+ return listDir (dirListTemplate , filePath , currentFS , c .Response ())
252275 }
253276
254277 return next (c )
@@ -278,7 +301,7 @@ func serveFile(c *echo.Context, file fs.File, info os.FileInfo) error {
278301 return nil
279302}
280303
281- func listDir (t * template.Template , requestedPath string , pathInFs string , filesystem fs.FS , res http.ResponseWriter ) error {
304+ func listDir (t * template.Template , pathInFs string , filesystem fs.FS , res http.ResponseWriter ) error {
282305 files , err := fs .ReadDir (filesystem , pathInFs )
283306 if err != nil {
284307 return fmt .Errorf ("static middleware failed to read directory for listing: %w" , err )
@@ -290,7 +313,7 @@ func listDir(t *template.Template, requestedPath string, pathInFs string, filesy
290313 Name string
291314 Files []any
292315 }{
293- Name : requestedPath ,
316+ Name : pathInFs ,
294317 }
295318
296319 for _ , f := range files {
0 commit comments