diff --git a/src/pkg/net/http/fs.go b/src/pkg/net/http/fs.go index 042e6da113..9df5cc4818 100644 --- a/src/pkg/net/http/fs.go +++ b/src/pkg/net/http/fs.go @@ -13,6 +13,7 @@ import ( "mime" "mime/multipart" "net/textproto" + "net/url" "os" "path" "path/filepath" @@ -75,8 +76,11 @@ func dirList(w ResponseWriter, f File) { if d.IsDir() { name += "/" } - // TODO htmlescape - fmt.Fprintf(w, "%s\n", name, 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") diff --git a/src/pkg/net/http/fs_test.go b/src/pkg/net/http/fs_test.go index ae54edf0cf..f968565f9b 100644 --- a/src/pkg/net/http/fs_test.go +++ b/src/pkg/net/http/fs_test.go @@ -227,6 +227,54 @@ func TestFileServerCleans(t *testing.T) { } } +func TestFileServerEscapesNames(t *testing.T) { + defer afterTest(t) + const dirListPrefix = "
\n"
+	const dirListSuffix = "\n
\n" + tests := []struct { + name, escaped string + }{ + {`simple_name`, `simple_name`}, + {`"'<>&`, `"'<>&`}, + {`?foo=bar#baz`, `?foo=bar#baz`}, + {`?foo`, `<combo>?foo`}, + } + + // We put each test file in its own directory in the fakeFS so we can look at it in isolation. + fs := make(fakeFS) + for i, test := range tests { + testFile := &fakeFileInfo{basename: test.name} + fs[fmt.Sprintf("/%d", i)] = &fakeFileInfo{ + dir: true, + modtime: time.Unix(1000000000, 0).UTC(), + ents: []*fakeFileInfo{testFile}, + } + fs[fmt.Sprintf("/%d/%s", i, test.name)] = testFile + } + + ts := httptest.NewServer(FileServer(&fs)) + defer ts.Close() + for i, test := range tests { + url := fmt.Sprintf("%s/%d", ts.URL, i) + res, err := Get(url) + if err != nil { + t.Fatalf("test %q: Get: %v", test.name, err) + } + b, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatalf("test %q: read Body: %v", test.name, err) + } + s := string(b) + if !strings.HasPrefix(s, dirListPrefix) || !strings.HasSuffix(s, dirListSuffix) { + t.Errorf("test %q: listing dir, full output is %q, want prefix %q and suffix %q", test.name, s, dirListPrefix, dirListSuffix) + } + if trimmed := strings.TrimSuffix(strings.TrimPrefix(s, dirListPrefix), dirListSuffix); trimmed != test.escaped { + t.Errorf("test %q: listing dir, filename escaped to %q, want %q", test.name, trimmed, test.escaped) + } + res.Body.Close() + } +} + func mustRemoveAll(dir string) { err := os.RemoveAll(dir) if err != nil { @@ -457,8 +505,9 @@ func (f *fakeFileInfo) Mode() os.FileMode { type fakeFile struct { io.ReadSeeker - fi *fakeFileInfo - path string // as opened + fi *fakeFileInfo + path string // as opened + entpos int } func (f *fakeFile) Close() error { return nil } @@ -468,10 +517,20 @@ func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) { return nil, os.ErrInvalid } var fis []os.FileInfo - for _, fi := range f.fi.ents { - fis = append(fis, fi) + + limit := f.entpos + count + if count <= 0 || limit > len(f.fi.ents) { + limit = len(f.fi.ents) + } + for ; f.entpos < limit; f.entpos++ { + fis = append(fis, f.fi.ents[f.entpos]) + } + + if len(fis) == 0 && count > 0 { + return fis, io.EOF + } else { + return fis, nil } - return fis, nil } type fakeFS map[string]*fakeFileInfo @@ -480,7 +539,6 @@ func (fs fakeFS) Open(name string) (File, error) { name = path.Clean(name) f, ok := fs[name] if !ok { - println("fake filesystem didn't find file", name) return nil, os.ErrNotExist } return &fakeFile{ReadSeeker: strings.NewReader(f.contents), fi: f, path: name}, nil