diff --git a/src/net/http/fs.go b/src/net/http/fs.go index 75720234c25..394c87d29a7 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 794dabc40a6..9b235d278a5 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 {