diff --git a/src/net/http/fs.go b/src/net/http/fs.go index 75720234c2..394c87d29a 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -17,6 +17,7 @@ import ( "os" "path" "path/filepath" + "sort" "strconv" "strings" "time" @@ -68,24 +69,28 @@ type File interface { } func dirList(w ResponseWriter, f File) { + dirs, err := f.Readdir(-1) + if err != nil { + // TODO: log err.Error() to the Server.ErrorLog, once it's possible + // for a handler to get at its Server via the ResponseWriter. See + // Issue 12438. + Error(w, "Error reading directory", StatusInternalServerError) + return + } + sort.Sort(byName(dirs)) + w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprintf(w, "
\n")
- for {
- dirs, err := f.Readdir(100)
- if err != nil || len(dirs) == 0 {
- break
- }
- for _, d := range dirs {
- name := d.Name()
- if d.IsDir() {
- name += "/"
- }
- // name may contain '?' or '#', which must be escaped to remain
- // part of the URL path, and not indicate the start of a query
- // string or fragment.
- url := url.URL{Path: name}
- fmt.Fprintf(w, "%s\n", url.String(), htmlReplacer.Replace(name))
+ for _, d := range dirs {
+ name := d.Name()
+ if d.IsDir() {
+ name += "/"
}
+ // name may contain '?' or '#', which must be escaped to remain
+ // part of the URL path, and not indicate the start of a query
+ // string or fragment.
+ url := url.URL{Path: name}
+ fmt.Fprintf(w, "%s\n", url.String(), htmlReplacer.Replace(name))
}
fmt.Fprintf(w, "\n")
}
@@ -585,3 +590,9 @@ func sumRangesSize(ranges []httpRange) (size int64) {
}
return
}
+
+type byName []os.FileInfo
+
+func (s byName) Len() int { return len(s) }
+func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
+func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go
index 794dabc40a..9b235d278a 100644
--- a/src/net/http/fs_test.go
+++ b/src/net/http/fs_test.go
@@ -283,6 +283,49 @@ func TestFileServerEscapesNames(t *testing.T) {
}
}
+func TestFileServerSortsNames(t *testing.T) {
+ defer afterTest(t)
+ const contents = "I am a fake file"
+ dirMod := time.Unix(123, 0).UTC()
+ fileMod := time.Unix(1000000000, 0).UTC()
+ fs := fakeFS{
+ "/": &fakeFileInfo{
+ dir: true,
+ modtime: dirMod,
+ ents: []*fakeFileInfo{
+ {
+ basename: "b",
+ modtime: fileMod,
+ contents: contents,
+ },
+ {
+ basename: "a",
+ modtime: fileMod,
+ contents: contents,
+ },
+ },
+ },
+ }
+
+ ts := httptest.NewServer(FileServer(&fs))
+ defer ts.Close()
+
+ res, err := Get(ts.URL)
+ if err != nil {
+ t.Fatalf("Get: %v", err)
+ }
+ defer res.Body.Close()
+
+ b, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatalf("read Body: %v", err)
+ }
+ s := string(b)
+ if !strings.Contains(s, "a\nb") {
+ t.Errorf("output appears to be unsorted:\n%s", s)
+ }
+}
+
func mustRemoveAll(dir string) {
err := os.RemoveAll(dir)
if err != nil {