Skip to content

net/http: prevent redirect loop in serveFile if "/" is a normal file #63860

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
13 changes: 9 additions & 4 deletions src/net/http/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
// specific directory tree.
//
// While the [FileSystem.Open] method takes '/'-separated paths, a Dir's string
// value is a filename on the native file system, not a URL, so it is separated
// value is a directory path on the native file system, not a URL, so it is separated
// by [filepath.Separator], which isn't necessarily '/'.
//
// Note that Dir could expose sensitive files and directories. Dir will follow
Expand Down Expand Up @@ -660,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
}
}

Expand Down
26 changes: 26 additions & 0 deletions src/net/http/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1668,3 +1668,29 @@ func (grw gzipResponseWriter) Flush() {
fw.Flush()
}
}

// Issue 63769
func TestFileServerDirWithRootFile(t *testing.T) { run(t, testFileServerDirWithRootFile) }
func testFileServerDirWithRootFile(t *testing.T, mode testMode) {
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)
}
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")))
})
}