net/http: make http.FileServer return 404 when a path is invalid/unsafe

This PR adds error handling in net/http toHTTPError to return a 404
instead of a 500 when net/http fs.Dir.Open throws the error http:
invalid or unsafe file path.

Fixes #72091

Change-Id: I7941c8fca5160a4a82732dc1d05b9b95eac84fbf
GitHub-Last-Rev: 04b5019dfb
GitHub-Pull-Request: golang/go#72108
Reviewed-on: https://go-review.googlesource.com/c/go/+/654975
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Grégoire Lodi 2025-03-05 09:44:46 +00:00 committed by Gopher Robot
parent 37026a7c56
commit 061efaa8a7
2 changed files with 30 additions and 1 deletions

View file

@ -67,6 +67,11 @@ func mapOpenError(originalErr error, name string, sep rune, stat func(string) (f
return originalErr
}
// errInvalidUnsafePath is returned by Dir.Open when the call to
// filepath.Localize fails. filepath.Localize returns an error if the path
// cannot be represented by the operating system.
var errInvalidUnsafePath = errors.New("http: invalid or unsafe file path")
// Open implements [FileSystem] using [os.Open], opening files for reading rooted
// and relative to the directory d.
func (d Dir) Open(name string) (File, error) {
@ -76,7 +81,7 @@ func (d Dir) Open(name string) (File, error) {
}
path, err := filepath.Localize(path)
if err != nil {
return nil, errors.New("http: invalid or unsafe file path")
return nil, errInvalidUnsafePath
}
dir := string(d)
if dir == "" {
@ -768,6 +773,9 @@ func toHTTPError(err error) (msg string, httpStatus int) {
if errors.Is(err, fs.ErrPermission) {
return "403 Forbidden", StatusForbidden
}
if errors.Is(err, errInvalidUnsafePath) {
return "404 page not found", StatusNotFound
}
// Default:
return "500 Internal Server Error", StatusInternalServerError
}

View file

@ -733,6 +733,27 @@ func testFileServerZeroByte(t *testing.T, mode testMode) {
}
}
func TestFileServerNullByte(t *testing.T) { run(t, testFileServerNullByte) }
func testFileServerNullByte(t *testing.T, mode testMode) {
ts := newClientServerTest(t, mode, FileServer(Dir("testdata"))).ts
for _, path := range []string{
"/file%00",
"/%00",
"/file/qwe/%00",
} {
res, err := ts.Client().Get(ts.URL + path)
if err != nil {
t.Fatal(err)
}
res.Body.Close()
if res.StatusCode != 404 {
t.Errorf("Get(%q): got status %v, want 404", path, res.StatusCode)
}
}
}
func TestFileServerNamesEscape(t *testing.T) { run(t, testFileServerNamesEscape) }
func testFileServerNamesEscape(t *testing.T, mode testMode) {
ts := newClientServerTest(t, mode, FileServer(Dir("testdata"))).ts