diff --git a/src/net/http/fs.go b/src/net/http/fs.go index 48ba05a664..92bd94f72d 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -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 } diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go index 3149ca35ac..e94209e25c 100644 --- a/src/net/http/fs_test.go +++ b/src/net/http/fs_test.go @@ -234,6 +234,31 @@ func TestServeFile_DotDot(t *testing.T) { } } +func TestServeFile_InvalidUnsafePath(t *testing.T) { + tests := []struct { + req string + wantStatus int + }{ + {"/testdata/file", 200}, + {"/%00/file", 404}, + {"/file%00", 404}, + {"/%00", 404}, + } + for _, tt := range tests { + req, err := ReadRequest(bufio.NewReader(strings.NewReader("GET " + tt.req + " HTTP/1.1\r\nHost: foo\r\n\r\n"))) + if err != nil { + t.Errorf("bad request %q: %v", tt.req, err) + continue + } + rec := httptest.NewRecorder() + ServeFile(rec, req, "testdata/file") + if rec.Code != tt.wantStatus { + t.Logf("%v", rec.Result()) + t.Errorf("for request %q, status = %d; want %d", tt.req, rec.Code, tt.wantStatus) + } + } +} + // Tests that this doesn't panic. (Issue 30165) func TestServeFileDirPanicEmptyPath(t *testing.T) { rec := httptest.NewRecorder() @@ -733,6 +758,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