Skip to content

Commit 7c75310

Browse files
authored
Clean on go1.20 (#2406)
* Fix tests failing on Go 1.20 on Windows. Clean works differently on 1.20. Use path.Clean instead with some workaround related to errors.
1 parent 04ba8e2 commit 7c75310

File tree

4 files changed

+51
-23
lines changed

4 files changed

+51
-23
lines changed

middleware/context_timeout_test.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func TestContextTimeoutWithDefaultErrorMessage(t *testing.T) {
148148
c := e.NewContext(req, rec)
149149

150150
err := m(func(c echo.Context) error {
151-
if err := sleepWithContext(c.Request().Context(), time.Duration(20*time.Millisecond)); err != nil {
151+
if err := sleepWithContext(c.Request().Context(), time.Duration(80*time.Millisecond)); err != nil {
152152
return err
153153
}
154154
return c.String(http.StatusOK, "Hello, World!")
@@ -176,7 +176,7 @@ func TestContextTimeoutCanHandleContextDeadlineOnNextHandler(t *testing.T) {
176176
return nil
177177
}
178178

179-
timeout := 10 * time.Millisecond
179+
timeout := 50 * time.Millisecond
180180
m := ContextTimeoutWithConfig(ContextTimeoutConfig{
181181
Timeout: timeout,
182182
ErrorHandler: timeoutErrorHandler,
@@ -189,11 +189,10 @@ func TestContextTimeoutCanHandleContextDeadlineOnNextHandler(t *testing.T) {
189189
c := e.NewContext(req, rec)
190190

191191
err := m(func(c echo.Context) error {
192-
// NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
193-
// the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
194-
// difference over 500microseconds (0.5millisecond) response seems to be reliable
192+
// NOTE: Very short periods are not reliable for tests due to Go routine scheduling and the unpredictable order
193+
// for 1) request and 2) time goroutine. For most OS this works as expected, but MacOS seems most flaky.
195194

196-
if err := sleepWithContext(c.Request().Context(), time.Duration(20*time.Millisecond)); err != nil {
195+
if err := sleepWithContext(c.Request().Context(), 100*time.Millisecond); err != nil {
197196
return err
198197
}
199198

middleware/static.go

+11-17
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"net/url"
99
"os"
1010
"path"
11-
"path/filepath"
1211
"strings"
1312

1413
"github.com/labstack/echo/v4"
@@ -157,9 +156,9 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
157156
}
158157

159158
// Index template
160-
t, err := template.New("index").Parse(html)
161-
if err != nil {
162-
panic(fmt.Sprintf("echo: %v", err))
159+
t, tErr := template.New("index").Parse(html)
160+
if tErr != nil {
161+
panic(fmt.Errorf("echo: %w", tErr))
163162
}
164163

165164
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -176,7 +175,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
176175
if err != nil {
177176
return
178177
}
179-
name := filepath.Join(config.Root, filepath.Clean("/"+p)) // "/"+ for security
178+
name := path.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
180179

181180
if config.IgnoreBase {
182181
routePath := path.Base(strings.TrimRight(c.Path(), "/*"))
@@ -187,12 +186,14 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
187186
}
188187
}
189188

190-
file, err := openFile(config.Filesystem, name)
189+
file, err := config.Filesystem.Open(name)
191190
if err != nil {
192-
if !os.IsNotExist(err) {
191+
if !isIgnorableOpenFileError(err) {
193192
return err
194193
}
195194

195+
// file with that path did not exist, so we continue down in middleware/handler chain, hoping that we end up in
196+
// handler that is meant to handle this request
196197
if err = next(c); err == nil {
197198
return err
198199
}
@@ -202,7 +203,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
202203
return err
203204
}
204205

205-
file, err = openFile(config.Filesystem, filepath.Join(config.Root, config.Index))
206+
file, err = config.Filesystem.Open(path.Join(config.Root, config.Index))
206207
if err != nil {
207208
return err
208209
}
@@ -216,15 +217,13 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
216217
}
217218

218219
if info.IsDir() {
219-
index, err := openFile(config.Filesystem, filepath.Join(name, config.Index))
220+
index, err := config.Filesystem.Open(path.Join(name, config.Index))
220221
if err != nil {
221222
if config.Browse {
222223
return listDir(t, name, file, c.Response())
223224
}
224225

225-
if os.IsNotExist(err) {
226-
return next(c)
227-
}
226+
return next(c)
228227
}
229228

230229
defer index.Close()
@@ -242,11 +241,6 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
242241
}
243242
}
244243

245-
func openFile(fs http.FileSystem, name string) (http.File, error) {
246-
pathWithSlashes := filepath.ToSlash(name)
247-
return fs.Open(pathWithSlashes)
248-
}
249-
250244
func serveFile(c echo.Context, file http.File, info os.FileInfo) error {
251245
http.ServeContent(c.Response(), c.Request(), info.Name(), info.ModTime(), file)
252246
return nil

middleware/static_other.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build !windows
2+
3+
package middleware
4+
5+
import (
6+
"os"
7+
)
8+
9+
// We ignore these errors as there could be handler that matches request path.
10+
func isIgnorableOpenFileError(err error) bool {
11+
return os.IsNotExist(err)
12+
}

middleware/static_windows.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package middleware
2+
3+
import (
4+
"os"
5+
)
6+
7+
// We ignore these errors as there could be handler that matches request path.
8+
//
9+
// As of Go 1.20 filepath.Clean has different behaviour on OS related filesystems so we need to use path.Clean
10+
// on Windows which has some caveats. The Open methods might return different errors than earlier versions and
11+
// as of 1.20 path checks are more strict on the provided path and considers [UNC](https://en.wikipedia.org/wiki/Path_(computing)#UNC)
12+
// paths with missing host etc parts as invalid. Previously it would result you `fs.ErrNotExist`.
13+
//
14+
// For 1.20@Windows we need to treat those errors the same as `fs.ErrNotExists` so we can continue handling
15+
// errors in the middleware/handler chain. Otherwise we might end up with status 500 instead of finding a route
16+
// or return 404 not found.
17+
func isIgnorableOpenFileError(err error) bool {
18+
if os.IsNotExist(err) {
19+
return true
20+
}
21+
errTxt := err.Error()
22+
return errTxt == "http: invalid or unsafe file path" || errTxt == "invalid path"
23+
}

0 commit comments

Comments
 (0)