diff --git a/src/net/http/fs.go b/src/net/http/fs.go index 3993c68c1e..54b12c68e0 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -78,15 +78,11 @@ func (d Dir) Open(name string) (File, error) { if dir == "" { dir = "." } - if fi, _ := os.Stat(dir); fi != nil && !fi.IsDir() { - return nil, errors.New("http: attempting to traverse a non-directory") - } fullName := filepath.Join(dir, path) f, err := os.Open(fullName) if err != nil { return nil, mapOpenError(err, fullName, filepath.Separator, os.Stat) } - return f, nil } @@ -664,11 +660,16 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec localRedirect(w, r, path.Base(url)+"/") return } - } else { - if url[len(url)-1] == '/' { - localRedirect(w, r, "../"+path.Base(url)) + } else if url[len(url)-1] == '/' { + base := path.Base(url) + if base == "/" || base == "." { + // The FileSystem maps a path like "/" or "/./" to a file instead of a directory. + msg := "http: attempting to traverse a non-directory" + Error(w, msg, StatusInternalServerError) return } + localRedirect(w, r, "../"+base) + return } } diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go index 3ed018f87d..158ff53ac0 100644 --- a/src/net/http/fs_test.go +++ b/src/net/http/fs_test.go @@ -475,6 +475,12 @@ func TestDirJoin(t *testing.T) { test(Dir("/etc"), "/hosts") test(Dir("/etc"), "hosts") test(Dir("/etc"), "../../../../hosts") + + // Not really directories, but since we use this trick in + // ServeFile, test it: + test(Dir("/etc/hosts"), "") + test(Dir("/etc/hosts"), "/") + test(Dir("/etc/hosts"), "../") } func TestEmptyDirOpenCWD(t *testing.T) { @@ -491,22 +497,6 @@ func TestEmptyDirOpenCWD(t *testing.T) { test(Dir("./")) } -// issue 63769 -func TestDirNormalFile(t *testing.T) { - test := func(d Dir, filename string) { - f, err := d.Open(filename) - if err == nil { - f.Close() - t.Fatalf("got nil, want error") - } - } - - test(Dir("testdata/index.html"), "") - test(Dir("testdata/index.html"), "/") - test(Dir("testdata/index.html"), "..") - test(Dir("testdata/index.html"), "../") -} - func TestServeFileContentType(t *testing.T) { run(t, testServeFileContentType) } func testServeFileContentType(t *testing.T, mode testMode) { const ctype = "icecream/chocolate" @@ -1682,15 +1672,25 @@ func (grw gzipResponseWriter) Flush() { // Issue 63769 func TestFileServerDirWithRootFile(t *testing.T) { run(t, testFileServerDirWithRootFile) } func testFileServerDirWithRootFile(t *testing.T, mode testMode) { - ts := newClientServerTest(t, mode, FileServer(Dir("testdata/index.html"))).ts - defer ts.Close() + testDirFile := func(t *testing.T, h Handler) { + ts := newClientServerTest(t, mode, h).ts + defer ts.Close() - res, err := ts.Client().Get(ts.URL) - if err != nil { - t.Fatal(err) + res, err := ts.Client().Get(ts.URL) + if err != nil { + t.Fatal(err) + } + if g, w := res.StatusCode, StatusInternalServerError; g != w { + t.Errorf("StatusCode mismatch: got %d, want: %d", g, w) + } + res.Body.Close() } - if g, w := res.StatusCode, StatusInternalServerError; g != w { - t.Errorf("StatusCode mismatch: got %d, want: %d", g, w) - } - res.Body.Close() + + t.Run("FileServer", func(t *testing.T) { + testDirFile(t, FileServer(Dir("testdata/index.html"))) + }) + + t.Run("FileServerFS", func(t *testing.T) { + testDirFile(t, FileServerFS(os.DirFS("testdata/index.html"))) + }) }