Skip to content

Commit 25b0017

Browse files
danpbradfitz
authored andcommitted
net/http: make FileServer sort directory entries
Fixes #11879 Change-Id: If021f86b2764e01c69674e6a423699b822596f15 Reviewed-on: https://go-review.googlesource.com/14161 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 1ac84d4 commit 25b0017

File tree

2 files changed

+69
-15
lines changed

2 files changed

+69
-15
lines changed

src/net/http/fs.go

+26-15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"os"
1818
"path"
1919
"path/filepath"
20+
"sort"
2021
"strconv"
2122
"strings"
2223
"time"
@@ -68,24 +69,28 @@ type File interface {
6869
}
6970

7071
func dirList(w ResponseWriter, f File) {
72+
dirs, err := f.Readdir(-1)
73+
if err != nil {
74+
// TODO: log err.Error() to the Server.ErrorLog, once it's possible
75+
// for a handler to get at its Server via the ResponseWriter. See
76+
// Issue 12438.
77+
Error(w, "Error reading directory", StatusInternalServerError)
78+
return
79+
}
80+
sort.Sort(byName(dirs))
81+
7182
w.Header().Set("Content-Type", "text/html; charset=utf-8")
7283
fmt.Fprintf(w, "<pre>\n")
73-
for {
74-
dirs, err := f.Readdir(100)
75-
if err != nil || len(dirs) == 0 {
76-
break
77-
}
78-
for _, d := range dirs {
79-
name := d.Name()
80-
if d.IsDir() {
81-
name += "/"
82-
}
83-
// name may contain '?' or '#', which must be escaped to remain
84-
// part of the URL path, and not indicate the start of a query
85-
// string or fragment.
86-
url := url.URL{Path: name}
87-
fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
84+
for _, d := range dirs {
85+
name := d.Name()
86+
if d.IsDir() {
87+
name += "/"
8888
}
89+
// name may contain '?' or '#', which must be escaped to remain
90+
// part of the URL path, and not indicate the start of a query
91+
// string or fragment.
92+
url := url.URL{Path: name}
93+
fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
8994
}
9095
fmt.Fprintf(w, "</pre>\n")
9196
}
@@ -585,3 +590,9 @@ func sumRangesSize(ranges []httpRange) (size int64) {
585590
}
586591
return
587592
}
593+
594+
type byName []os.FileInfo
595+
596+
func (s byName) Len() int { return len(s) }
597+
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
598+
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

src/net/http/fs_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,49 @@ func TestFileServerEscapesNames(t *testing.T) {
283283
}
284284
}
285285

286+
func TestFileServerSortsNames(t *testing.T) {
287+
defer afterTest(t)
288+
const contents = "I am a fake file"
289+
dirMod := time.Unix(123, 0).UTC()
290+
fileMod := time.Unix(1000000000, 0).UTC()
291+
fs := fakeFS{
292+
"/": &fakeFileInfo{
293+
dir: true,
294+
modtime: dirMod,
295+
ents: []*fakeFileInfo{
296+
{
297+
basename: "b",
298+
modtime: fileMod,
299+
contents: contents,
300+
},
301+
{
302+
basename: "a",
303+
modtime: fileMod,
304+
contents: contents,
305+
},
306+
},
307+
},
308+
}
309+
310+
ts := httptest.NewServer(FileServer(&fs))
311+
defer ts.Close()
312+
313+
res, err := Get(ts.URL)
314+
if err != nil {
315+
t.Fatalf("Get: %v", err)
316+
}
317+
defer res.Body.Close()
318+
319+
b, err := ioutil.ReadAll(res.Body)
320+
if err != nil {
321+
t.Fatalf("read Body: %v", err)
322+
}
323+
s := string(b)
324+
if !strings.Contains(s, "<a href=\"a\">a</a>\n<a href=\"b\">b</a>") {
325+
t.Errorf("output appears to be unsorted:\n%s", s)
326+
}
327+
}
328+
286329
func mustRemoveAll(dir string) {
287330
err := os.RemoveAll(dir)
288331
if err != nil {

0 commit comments

Comments
 (0)