Skip to content

Commit 0e16d67

Browse files
pascaldekloedr2chase
authored andcommitted
net/http: FileServer method check + minimal OPTIONS implementation
FileServer provides a read-only service. Methods other than GET or HEAD should be denied with an Allow header. Fixes #53501 Change-Id: I1d31b405eefd90565ecd474ac3f8d8d6e3b15072 Reviewed-on: https://go-review.googlesource.com/c/go/+/413554 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent eeb1ba7 commit 0e16d67

File tree

2 files changed

+57
-5
lines changed

2 files changed

+57
-5
lines changed

src/net/http/fs.go

+15-5
Original file line numberDiff line numberDiff line change
@@ -843,12 +843,22 @@ func FileServer(root FileSystem) Handler {
843843
}
844844

845845
func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
846-
upath := r.URL.Path
847-
if !strings.HasPrefix(upath, "/") {
848-
upath = "/" + upath
849-
r.URL.Path = upath
846+
const options = MethodOptions + ", " + MethodGet + ", " + MethodHead
847+
848+
switch r.Method {
849+
case MethodGet, MethodHead:
850+
if !strings.HasPrefix(r.URL.Path, "/") {
851+
r.URL.Path = "/" + r.URL.Path
852+
}
853+
serveFile(w, r, f.root, path.Clean(r.URL.Path), true)
854+
855+
case MethodOptions:
856+
w.Header().Set("Allow", options)
857+
858+
default:
859+
w.Header().Set("Allow", options)
860+
Error(w, "read-only", StatusMethodNotAllowed)
850861
}
851-
serveFile(w, r, f.root, path.Clean(upath), true)
852862
}
853863

854864
// httpRange specifies the byte range to be sent to the client.

src/net/http/fs_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"reflect"
2525
"regexp"
2626
"runtime"
27+
"sort"
2728
"strings"
2829
"testing"
2930
"time"
@@ -404,6 +405,47 @@ func TestFileServerImplicitLeadingSlash(t *testing.T) {
404405
}
405406
}
406407

408+
func TestFileServerMethodOptions(t *testing.T) {
409+
defer afterTest(t)
410+
const want = "GET, HEAD, OPTIONS"
411+
ts := httptest.NewServer(FileServer(Dir(".")))
412+
defer ts.Close()
413+
414+
tests := []struct {
415+
method string
416+
wantStatus int
417+
}{
418+
{MethodOptions, StatusOK},
419+
420+
{MethodDelete, StatusMethodNotAllowed},
421+
{MethodPut, StatusMethodNotAllowed},
422+
{MethodPost, StatusMethodNotAllowed},
423+
}
424+
425+
for _, test := range tests {
426+
req, err := NewRequest(test.method, ts.URL, nil)
427+
if err != nil {
428+
t.Fatal(err)
429+
}
430+
res, err := ts.Client().Do(req)
431+
if err != nil {
432+
t.Fatal(err)
433+
}
434+
defer res.Body.Close()
435+
436+
if res.StatusCode != test.wantStatus {
437+
t.Errorf("%s got status %q, want code %d", test.method, res.Status, test.wantStatus)
438+
}
439+
440+
a := strings.Split(res.Header.Get("Allow"), ", ")
441+
sort.Strings(a)
442+
got := strings.Join(a, ", ")
443+
if got != want {
444+
t.Errorf("%s got Allow header %q, want %q", test.method, got, want)
445+
}
446+
}
447+
}
448+
407449
func TestDirJoin(t *testing.T) {
408450
if runtime.GOOS == "windows" {
409451
t.Skip("skipping test on windows")

0 commit comments

Comments
 (0)