github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/src/net/http/fs_test.go (about)

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package http_test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"mime"
    15  	"mime/multipart"
    16  	"net"
    17  	. "net/http"
    18  	"net/http/httptest"
    19  	"net/url"
    20  	"os"
    21  	"os/exec"
    22  	"path"
    23  	"path/filepath"
    24  	"reflect"
    25  	"regexp"
    26  	"runtime"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  )
    31  
    32  const (
    33  	testFile    = "testdata/file"
    34  	testFileLen = 11
    35  )
    36  
    37  type wantRange struct {
    38  	start, end int64 // range [start,end)
    39  }
    40  
    41  var ServeFileRangeTests = []struct {
    42  	r      string
    43  	code   int
    44  	ranges []wantRange
    45  }{
    46  	{r: "", code: StatusOK},
    47  	{r: "bytes=0-4", code: StatusPartialContent, ranges: []wantRange{{0, 5}}},
    48  	{r: "bytes=2-", code: StatusPartialContent, ranges: []wantRange{{2, testFileLen}}},
    49  	{r: "bytes=-5", code: StatusPartialContent, ranges: []wantRange{{testFileLen - 5, testFileLen}}},
    50  	{r: "bytes=3-7", code: StatusPartialContent, ranges: []wantRange{{3, 8}}},
    51  	{r: "bytes=0-0,-2", code: StatusPartialContent, ranges: []wantRange{{0, 1}, {testFileLen - 2, testFileLen}}},
    52  	{r: "bytes=0-1,5-8", code: StatusPartialContent, ranges: []wantRange{{0, 2}, {5, 9}}},
    53  	{r: "bytes=0-1,5-", code: StatusPartialContent, ranges: []wantRange{{0, 2}, {5, testFileLen}}},
    54  	{r: "bytes=5-1000", code: StatusPartialContent, ranges: []wantRange{{5, testFileLen}}},
    55  	{r: "bytes=0-,1-,2-,3-,4-", code: StatusOK}, // ignore wasteful range request
    56  	{r: "bytes=0-9", code: StatusPartialContent, ranges: []wantRange{{0, testFileLen - 1}}},
    57  	{r: "bytes=0-10", code: StatusPartialContent, ranges: []wantRange{{0, testFileLen}}},
    58  	{r: "bytes=0-11", code: StatusPartialContent, ranges: []wantRange{{0, testFileLen}}},
    59  	{r: "bytes=10-11", code: StatusPartialContent, ranges: []wantRange{{testFileLen - 1, testFileLen}}},
    60  	{r: "bytes=10-", code: StatusPartialContent, ranges: []wantRange{{testFileLen - 1, testFileLen}}},
    61  	{r: "bytes=11-", code: StatusRequestedRangeNotSatisfiable},
    62  	{r: "bytes=11-12", code: StatusRequestedRangeNotSatisfiable},
    63  	{r: "bytes=12-12", code: StatusRequestedRangeNotSatisfiable},
    64  	{r: "bytes=11-100", code: StatusRequestedRangeNotSatisfiable},
    65  	{r: "bytes=12-100", code: StatusRequestedRangeNotSatisfiable},
    66  	{r: "bytes=100-", code: StatusRequestedRangeNotSatisfiable},
    67  	{r: "bytes=100-1000", code: StatusRequestedRangeNotSatisfiable},
    68  }
    69  
    70  func TestServeFile(t *testing.T) {
    71  	defer afterTest(t)
    72  	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
    73  		ServeFile(w, r, "testdata/file")
    74  	}))
    75  	defer ts.Close()
    76  
    77  	var err error
    78  
    79  	file, err := ioutil.ReadFile(testFile)
    80  	if err != nil {
    81  		t.Fatal("reading file:", err)
    82  	}
    83  
    84  	// set up the Request (re-used for all tests)
    85  	var req Request
    86  	req.Header = make(Header)
    87  	if req.URL, err = url.Parse(ts.URL); err != nil {
    88  		t.Fatal("ParseURL:", err)
    89  	}
    90  	req.Method = "GET"
    91  
    92  	// straight GET
    93  	_, body := getBody(t, "straight get", req)
    94  	if !bytes.Equal(body, file) {
    95  		t.Fatalf("body mismatch: got %q, want %q", body, file)
    96  	}
    97  
    98  	// Range tests
    99  Cases:
   100  	for _, rt := range ServeFileRangeTests {
   101  		if rt.r != "" {
   102  			req.Header.Set("Range", rt.r)
   103  		}
   104  		resp, body := getBody(t, fmt.Sprintf("range test %q", rt.r), req)
   105  		if resp.StatusCode != rt.code {
   106  			t.Errorf("range=%q: StatusCode=%d, want %d", rt.r, resp.StatusCode, rt.code)
   107  		}
   108  		if rt.code == StatusRequestedRangeNotSatisfiable {
   109  			continue
   110  		}
   111  		wantContentRange := ""
   112  		if len(rt.ranges) == 1 {
   113  			rng := rt.ranges[0]
   114  			wantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen)
   115  		}
   116  		cr := resp.Header.Get("Content-Range")
   117  		if cr != wantContentRange {
   118  			t.Errorf("range=%q: Content-Range = %q, want %q", rt.r, cr, wantContentRange)
   119  		}
   120  		ct := resp.Header.Get("Content-Type")
   121  		if len(rt.ranges) == 1 {
   122  			rng := rt.ranges[0]
   123  			wantBody := file[rng.start:rng.end]
   124  			if !bytes.Equal(body, wantBody) {
   125  				t.Errorf("range=%q: body = %q, want %q", rt.r, body, wantBody)
   126  			}
   127  			if strings.HasPrefix(ct, "multipart/byteranges") {
   128  				t.Errorf("range=%q content-type = %q; unexpected multipart/byteranges", rt.r, ct)
   129  			}
   130  		}
   131  		if len(rt.ranges) > 1 {
   132  			typ, params, err := mime.ParseMediaType(ct)
   133  			if err != nil {
   134  				t.Errorf("range=%q content-type = %q; %v", rt.r, ct, err)
   135  				continue
   136  			}
   137  			if typ != "multipart/byteranges" {
   138  				t.Errorf("range=%q content-type = %q; want multipart/byteranges", rt.r, typ)
   139  				continue
   140  			}
   141  			if params["boundary"] == "" {
   142  				t.Errorf("range=%q content-type = %q; lacks boundary", rt.r, ct)
   143  				continue
   144  			}
   145  			if g, w := resp.ContentLength, int64(len(body)); g != w {
   146  				t.Errorf("range=%q Content-Length = %d; want %d", rt.r, g, w)
   147  				continue
   148  			}
   149  			mr := multipart.NewReader(bytes.NewReader(body), params["boundary"])
   150  			for ri, rng := range rt.ranges {
   151  				part, err := mr.NextPart()
   152  				if err != nil {
   153  					t.Errorf("range=%q, reading part index %d: %v", rt.r, ri, err)
   154  					continue Cases
   155  				}
   156  				wantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen)
   157  				if g, w := part.Header.Get("Content-Range"), wantContentRange; g != w {
   158  					t.Errorf("range=%q: part Content-Range = %q; want %q", rt.r, g, w)
   159  				}
   160  				body, err := ioutil.ReadAll(part)
   161  				if err != nil {
   162  					t.Errorf("range=%q, reading part index %d body: %v", rt.r, ri, err)
   163  					continue Cases
   164  				}
   165  				wantBody := file[rng.start:rng.end]
   166  				if !bytes.Equal(body, wantBody) {
   167  					t.Errorf("range=%q: body = %q, want %q", rt.r, body, wantBody)
   168  				}
   169  			}
   170  			_, err = mr.NextPart()
   171  			if err != io.EOF {
   172  				t.Errorf("range=%q; expected final error io.EOF; got %v", rt.r, err)
   173  			}
   174  		}
   175  	}
   176  }
   177  
   178  func TestServeFile_DotDot(t *testing.T) {
   179  	tests := []struct {
   180  		req        string
   181  		wantStatus int
   182  	}{
   183  		{"/testdata/file", 200},
   184  		{"/../file", 400},
   185  		{"/..", 400},
   186  		{"/../", 400},
   187  		{"/../foo", 400},
   188  		{"/..\\foo", 400},
   189  		{"/file/a", 200},
   190  		{"/file/a..", 200},
   191  		{"/file/a/..", 400},
   192  		{"/file/a\\..", 400},
   193  	}
   194  	for _, tt := range tests {
   195  		req, err := ReadRequest(bufio.NewReader(strings.NewReader("GET " + tt.req + " HTTP/1.1\r\nHost: foo\r\n\r\n")))
   196  		if err != nil {
   197  			t.Errorf("bad request %q: %v", tt.req, err)
   198  			continue
   199  		}
   200  		rec := httptest.NewRecorder()
   201  		ServeFile(rec, req, "testdata/file")
   202  		if rec.Code != tt.wantStatus {
   203  			t.Errorf("for request %q, status = %d; want %d", tt.req, rec.Code, tt.wantStatus)
   204  		}
   205  	}
   206  }
   207  
   208  var fsRedirectTestData = []struct {
   209  	original, redirect string
   210  }{
   211  	{"/test/index.html", "/test/"},
   212  	{"/test/testdata", "/test/testdata/"},
   213  	{"/test/testdata/file/", "/test/testdata/file"},
   214  }
   215  
   216  func TestFSRedirect(t *testing.T) {
   217  	defer afterTest(t)
   218  	ts := httptest.NewServer(StripPrefix("/test", FileServer(Dir("."))))
   219  	defer ts.Close()
   220  
   221  	for _, data := range fsRedirectTestData {
   222  		res, err := Get(ts.URL + data.original)
   223  		if err != nil {
   224  			t.Fatal(err)
   225  		}
   226  		res.Body.Close()
   227  		if g, e := res.Request.URL.Path, data.redirect; g != e {
   228  			t.Errorf("redirect from %s: got %s, want %s", data.original, g, e)
   229  		}
   230  	}
   231  }
   232  
   233  type testFileSystem struct {
   234  	open func(name string) (File, error)
   235  }
   236  
   237  func (fs *testFileSystem) Open(name string) (File, error) {
   238  	return fs.open(name)
   239  }
   240  
   241  func TestFileServerCleans(t *testing.T) {
   242  	defer afterTest(t)
   243  	ch := make(chan string, 1)
   244  	fs := FileServer(&testFileSystem{func(name string) (File, error) {
   245  		ch <- name
   246  		return nil, errors.New("file does not exist")
   247  	}})
   248  	tests := []struct {
   249  		reqPath, openArg string
   250  	}{
   251  		{"/foo.txt", "/foo.txt"},
   252  		{"//foo.txt", "/foo.txt"},
   253  		{"/../foo.txt", "/foo.txt"},
   254  	}
   255  	req, _ := NewRequest("GET", "http://example.com", nil)
   256  	for n, test := range tests {
   257  		rec := httptest.NewRecorder()
   258  		req.URL.Path = test.reqPath
   259  		fs.ServeHTTP(rec, req)
   260  		if got := <-ch; got != test.openArg {
   261  			t.Errorf("test %d: got %q, want %q", n, got, test.openArg)
   262  		}
   263  	}
   264  }
   265  
   266  func TestFileServerEscapesNames(t *testing.T) {
   267  	defer afterTest(t)
   268  	const dirListPrefix = "<pre>\n"
   269  	const dirListSuffix = "\n</pre>\n"
   270  	tests := []struct {
   271  		name, escaped string
   272  	}{
   273  		{`simple_name`, `<a href="simple_name">simple_name</a>`},
   274  		{`"'<>&`, `<a href="%22%27%3C%3E&">&#34;&#39;&lt;&gt;&amp;</a>`},
   275  		{`?foo=bar#baz`, `<a href="%3Ffoo=bar%23baz">?foo=bar#baz</a>`},
   276  		{`<combo>?foo`, `<a href="%3Ccombo%3E%3Ffoo">&lt;combo&gt;?foo</a>`},
   277  		{`foo:bar`, `<a href="./foo:bar">foo:bar</a>`},
   278  	}
   279  
   280  	// We put each test file in its own directory in the fakeFS so we can look at it in isolation.
   281  	fs := make(fakeFS)
   282  	for i, test := range tests {
   283  		testFile := &fakeFileInfo{basename: test.name}
   284  		fs[fmt.Sprintf("/%d", i)] = &fakeFileInfo{
   285  			dir:     true,
   286  			modtime: time.Unix(1000000000, 0).UTC(),
   287  			ents:    []*fakeFileInfo{testFile},
   288  		}
   289  		fs[fmt.Sprintf("/%d/%s", i, test.name)] = testFile
   290  	}
   291  
   292  	ts := httptest.NewServer(FileServer(&fs))
   293  	defer ts.Close()
   294  	for i, test := range tests {
   295  		url := fmt.Sprintf("%s/%d", ts.URL, i)
   296  		res, err := Get(url)
   297  		if err != nil {
   298  			t.Fatalf("test %q: Get: %v", test.name, err)
   299  		}
   300  		b, err := ioutil.ReadAll(res.Body)
   301  		if err != nil {
   302  			t.Fatalf("test %q: read Body: %v", test.name, err)
   303  		}
   304  		s := string(b)
   305  		if !strings.HasPrefix(s, dirListPrefix) || !strings.HasSuffix(s, dirListSuffix) {
   306  			t.Errorf("test %q: listing dir, full output is %q, want prefix %q and suffix %q", test.name, s, dirListPrefix, dirListSuffix)
   307  		}
   308  		if trimmed := strings.TrimSuffix(strings.TrimPrefix(s, dirListPrefix), dirListSuffix); trimmed != test.escaped {
   309  			t.Errorf("test %q: listing dir, filename escaped to %q, want %q", test.name, trimmed, test.escaped)
   310  		}
   311  		res.Body.Close()
   312  	}
   313  }
   314  
   315  func TestFileServerSortsNames(t *testing.T) {
   316  	defer afterTest(t)
   317  	const contents = "I am a fake file"
   318  	dirMod := time.Unix(123, 0).UTC()
   319  	fileMod := time.Unix(1000000000, 0).UTC()
   320  	fs := fakeFS{
   321  		"/": &fakeFileInfo{
   322  			dir:     true,
   323  			modtime: dirMod,
   324  			ents: []*fakeFileInfo{
   325  				{
   326  					basename: "b",
   327  					modtime:  fileMod,
   328  					contents: contents,
   329  				},
   330  				{
   331  					basename: "a",
   332  					modtime:  fileMod,
   333  					contents: contents,
   334  				},
   335  			},
   336  		},
   337  	}
   338  
   339  	ts := httptest.NewServer(FileServer(&fs))
   340  	defer ts.Close()
   341  
   342  	res, err := Get(ts.URL)
   343  	if err != nil {
   344  		t.Fatalf("Get: %v", err)
   345  	}
   346  	defer res.Body.Close()
   347  
   348  	b, err := ioutil.ReadAll(res.Body)
   349  	if err != nil {
   350  		t.Fatalf("read Body: %v", err)
   351  	}
   352  	s := string(b)
   353  	if !strings.Contains(s, "<a href=\"a\">a</a>\n<a href=\"b\">b</a>") {
   354  		t.Errorf("output appears to be unsorted:\n%s", s)
   355  	}
   356  }
   357  
   358  func mustRemoveAll(dir string) {
   359  	err := os.RemoveAll(dir)
   360  	if err != nil {
   361  		panic(err)
   362  	}
   363  }
   364  
   365  func TestFileServerImplicitLeadingSlash(t *testing.T) {
   366  	defer afterTest(t)
   367  	tempDir, err := ioutil.TempDir("", "")
   368  	if err != nil {
   369  		t.Fatalf("TempDir: %v", err)
   370  	}
   371  	defer mustRemoveAll(tempDir)
   372  	if err := ioutil.WriteFile(filepath.Join(tempDir, "foo.txt"), []byte("Hello world"), 0644); err != nil {
   373  		t.Fatalf("WriteFile: %v", err)
   374  	}
   375  	ts := httptest.NewServer(StripPrefix("/bar/", FileServer(Dir(tempDir))))
   376  	defer ts.Close()
   377  	get := func(suffix string) string {
   378  		res, err := Get(ts.URL + suffix)
   379  		if err != nil {
   380  			t.Fatalf("Get %s: %v", suffix, err)
   381  		}
   382  		b, err := ioutil.ReadAll(res.Body)
   383  		if err != nil {
   384  			t.Fatalf("ReadAll %s: %v", suffix, err)
   385  		}
   386  		res.Body.Close()
   387  		return string(b)
   388  	}
   389  	if s := get("/bar/"); !strings.Contains(s, ">foo.txt<") {
   390  		t.Logf("expected a directory listing with foo.txt, got %q", s)
   391  	}
   392  	if s := get("/bar/foo.txt"); s != "Hello world" {
   393  		t.Logf("expected %q, got %q", "Hello world", s)
   394  	}
   395  }
   396  
   397  func TestDirJoin(t *testing.T) {
   398  	if runtime.GOOS == "windows" {
   399  		t.Skip("skipping test on windows")
   400  	}
   401  	wfi, err := os.Stat("/etc/hosts")
   402  	if err != nil {
   403  		t.Skip("skipping test; no /etc/hosts file")
   404  	}
   405  	test := func(d Dir, name string) {
   406  		f, err := d.Open(name)
   407  		if err != nil {
   408  			t.Fatalf("open of %s: %v", name, err)
   409  		}
   410  		defer f.Close()
   411  		gfi, err := f.Stat()
   412  		if err != nil {
   413  			t.Fatalf("stat of %s: %v", name, err)
   414  		}
   415  		if !os.SameFile(gfi, wfi) {
   416  			t.Errorf("%s got different file", name)
   417  		}
   418  	}
   419  	test(Dir("/etc/"), "/hosts")
   420  	test(Dir("/etc/"), "hosts")
   421  	test(Dir("/etc/"), "../../../../hosts")
   422  	test(Dir("/etc"), "/hosts")
   423  	test(Dir("/etc"), "hosts")
   424  	test(Dir("/etc"), "../../../../hosts")
   425  
   426  	// Not really directories, but since we use this trick in
   427  	// ServeFile, test it:
   428  	test(Dir("/etc/hosts"), "")
   429  	test(Dir("/etc/hosts"), "/")
   430  	test(Dir("/etc/hosts"), "../")
   431  }
   432  
   433  func TestEmptyDirOpenCWD(t *testing.T) {
   434  	test := func(d Dir) {
   435  		name := "fs_test.go"
   436  		f, err := d.Open(name)
   437  		if err != nil {
   438  			t.Fatalf("open of %s: %v", name, err)
   439  		}
   440  		defer f.Close()
   441  	}
   442  	test(Dir(""))
   443  	test(Dir("."))
   444  	test(Dir("./"))
   445  }
   446  
   447  func TestServeFileContentType(t *testing.T) {
   448  	defer afterTest(t)
   449  	const ctype = "icecream/chocolate"
   450  	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
   451  		switch r.FormValue("override") {
   452  		case "1":
   453  			w.Header().Set("Content-Type", ctype)
   454  		case "2":
   455  			// Explicitly inhibit sniffing.
   456  			w.Header()["Content-Type"] = []string{}
   457  		}
   458  		ServeFile(w, r, "testdata/file")
   459  	}))
   460  	defer ts.Close()
   461  	get := func(override string, want []string) {
   462  		resp, err := Get(ts.URL + "?override=" + override)
   463  		if err != nil {
   464  			t.Fatal(err)
   465  		}
   466  		if h := resp.Header["Content-Type"]; !reflect.DeepEqual(h, want) {
   467  			t.Errorf("Content-Type mismatch: got %v, want %v", h, want)
   468  		}
   469  		resp.Body.Close()
   470  	}
   471  	get("0", []string{"text/plain; charset=utf-8"})
   472  	get("1", []string{ctype})
   473  	get("2", nil)
   474  }
   475  
   476  func TestServeFileMimeType(t *testing.T) {
   477  	defer afterTest(t)
   478  	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
   479  		ServeFile(w, r, "testdata/style.css")
   480  	}))
   481  	defer ts.Close()
   482  	resp, err := Get(ts.URL)
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  	resp.Body.Close()
   487  	want := "text/css; charset=utf-8"
   488  	if h := resp.Header.Get("Content-Type"); h != want {
   489  		t.Errorf("Content-Type mismatch: got %q, want %q", h, want)
   490  	}
   491  }
   492  
   493  func TestServeFileFromCWD(t *testing.T) {
   494  	defer afterTest(t)
   495  	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
   496  		ServeFile(w, r, "fs_test.go")
   497  	}))
   498  	defer ts.Close()
   499  	r, err := Get(ts.URL)
   500  	if err != nil {
   501  		t.Fatal(err)
   502  	}
   503  	r.Body.Close()
   504  	if r.StatusCode != 200 {
   505  		t.Fatalf("expected 200 OK, got %s", r.Status)
   506  	}
   507  }
   508  
   509  // Issue 13996
   510  func TestServeDirWithoutTrailingSlash(t *testing.T) {
   511  	e := "/testdata/"
   512  	defer afterTest(t)
   513  	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
   514  		ServeFile(w, r, ".")
   515  	}))
   516  	defer ts.Close()
   517  	r, err := Get(ts.URL + "/testdata")
   518  	if err != nil {
   519  		t.Fatal(err)
   520  	}
   521  	r.Body.Close()
   522  	if g := r.Request.URL.Path; g != e {
   523  		t.Errorf("got %s, want %s", g, e)
   524  	}
   525  }
   526  
   527  // Tests that ServeFile doesn't add a Content-Length if a Content-Encoding is
   528  // specified.
   529  func TestServeFileWithContentEncoding_h1(t *testing.T) { testServeFileWithContentEncoding(t, h1Mode) }
   530  func TestServeFileWithContentEncoding_h2(t *testing.T) { testServeFileWithContentEncoding(t, h2Mode) }
   531  func testServeFileWithContentEncoding(t *testing.T, h2 bool) {
   532  	defer afterTest(t)
   533  	cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
   534  		w.Header().Set("Content-Encoding", "foo")
   535  		ServeFile(w, r, "testdata/file")
   536  
   537  		// Because the testdata is so small, it would fit in
   538  		// both the h1 and h2 Server's write buffers. For h1,
   539  		// sendfile is used, though, forcing a header flush at
   540  		// the io.Copy. http2 doesn't do a header flush so
   541  		// buffers all 11 bytes and then adds its own
   542  		// Content-Length. To prevent the Server's
   543  		// Content-Length and test ServeFile only, flush here.
   544  		w.(Flusher).Flush()
   545  	}))
   546  	defer cst.close()
   547  	resp, err := cst.c.Get(cst.ts.URL)
   548  	if err != nil {
   549  		t.Fatal(err)
   550  	}
   551  	resp.Body.Close()
   552  	if g, e := resp.ContentLength, int64(-1); g != e {
   553  		t.Errorf("Content-Length mismatch: got %d, want %d", g, e)
   554  	}
   555  }
   556  
   557  func TestServeIndexHtml(t *testing.T) {
   558  	defer afterTest(t)
   559  	const want = "index.html says hello\n"
   560  	ts := httptest.NewServer(FileServer(Dir(".")))
   561  	defer ts.Close()
   562  
   563  	for _, path := range []string{"/testdata/", "/testdata/index.html"} {
   564  		res, err := Get(ts.URL + path)
   565  		if err != nil {
   566  			t.Fatal(err)
   567  		}
   568  		b, err := ioutil.ReadAll(res.Body)
   569  		if err != nil {
   570  			t.Fatal("reading Body:", err)
   571  		}
   572  		if s := string(b); s != want {
   573  			t.Errorf("for path %q got %q, want %q", path, s, want)
   574  		}
   575  		res.Body.Close()
   576  	}
   577  }
   578  
   579  func TestFileServerZeroByte(t *testing.T) {
   580  	defer afterTest(t)
   581  	ts := httptest.NewServer(FileServer(Dir(".")))
   582  	defer ts.Close()
   583  
   584  	res, err := Get(ts.URL + "/..\x00")
   585  	if err != nil {
   586  		t.Fatal(err)
   587  	}
   588  	b, err := ioutil.ReadAll(res.Body)
   589  	if err != nil {
   590  		t.Fatal("reading Body:", err)
   591  	}
   592  	if res.StatusCode == 200 {
   593  		t.Errorf("got status 200; want an error. Body is:\n%s", string(b))
   594  	}
   595  }
   596  
   597  type fakeFileInfo struct {
   598  	dir      bool
   599  	basename string
   600  	modtime  time.Time
   601  	ents     []*fakeFileInfo
   602  	contents string
   603  	err      error
   604  }
   605  
   606  func (f *fakeFileInfo) Name() string       { return f.basename }
   607  func (f *fakeFileInfo) Sys() interface{}   { return nil }
   608  func (f *fakeFileInfo) ModTime() time.Time { return f.modtime }
   609  func (f *fakeFileInfo) IsDir() bool        { return f.dir }
   610  func (f *fakeFileInfo) Size() int64        { return int64(len(f.contents)) }
   611  func (f *fakeFileInfo) Mode() os.FileMode {
   612  	if f.dir {
   613  		return 0755 | os.ModeDir
   614  	}
   615  	return 0644
   616  }
   617  
   618  type fakeFile struct {
   619  	io.ReadSeeker
   620  	fi     *fakeFileInfo
   621  	path   string // as opened
   622  	entpos int
   623  }
   624  
   625  func (f *fakeFile) Close() error               { return nil }
   626  func (f *fakeFile) Stat() (os.FileInfo, error) { return f.fi, nil }
   627  func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) {
   628  	if !f.fi.dir {
   629  		return nil, os.ErrInvalid
   630  	}
   631  	var fis []os.FileInfo
   632  
   633  	limit := f.entpos + count
   634  	if count <= 0 || limit > len(f.fi.ents) {
   635  		limit = len(f.fi.ents)
   636  	}
   637  	for ; f.entpos < limit; f.entpos++ {
   638  		fis = append(fis, f.fi.ents[f.entpos])
   639  	}
   640  
   641  	if len(fis) == 0 && count > 0 {
   642  		return fis, io.EOF
   643  	} else {
   644  		return fis, nil
   645  	}
   646  }
   647  
   648  type fakeFS map[string]*fakeFileInfo
   649  
   650  func (fs fakeFS) Open(name string) (File, error) {
   651  	name = path.Clean(name)
   652  	f, ok := fs[name]
   653  	if !ok {
   654  		return nil, os.ErrNotExist
   655  	}
   656  	if f.err != nil {
   657  		return nil, f.err
   658  	}
   659  	return &fakeFile{ReadSeeker: strings.NewReader(f.contents), fi: f, path: name}, nil
   660  }
   661  
   662  func TestDirectoryIfNotModified(t *testing.T) {
   663  	defer afterTest(t)
   664  	const indexContents = "I am a fake index.html file"
   665  	fileMod := time.Unix(1000000000, 0).UTC()
   666  	fileModStr := fileMod.Format(TimeFormat)
   667  	dirMod := time.Unix(123, 0).UTC()
   668  	indexFile := &fakeFileInfo{
   669  		basename: "index.html",
   670  		modtime:  fileMod,
   671  		contents: indexContents,
   672  	}
   673  	fs := fakeFS{
   674  		"/": &fakeFileInfo{
   675  			dir:     true,
   676  			modtime: dirMod,
   677  			ents:    []*fakeFileInfo{indexFile},
   678  		},
   679  		"/index.html": indexFile,
   680  	}
   681  
   682  	ts := httptest.NewServer(FileServer(fs))
   683  	defer ts.Close()
   684  
   685  	res, err := Get(ts.URL)
   686  	if err != nil {
   687  		t.Fatal(err)
   688  	}
   689  	b, err := ioutil.ReadAll(res.Body)
   690  	if err != nil {
   691  		t.Fatal(err)
   692  	}
   693  	if string(b) != indexContents {
   694  		t.Fatalf("Got body %q; want %q", b, indexContents)
   695  	}
   696  	res.Body.Close()
   697  
   698  	lastMod := res.Header.Get("Last-Modified")
   699  	if lastMod != fileModStr {
   700  		t.Fatalf("initial Last-Modified = %q; want %q", lastMod, fileModStr)
   701  	}
   702  
   703  	req, _ := NewRequest("GET", ts.URL, nil)
   704  	req.Header.Set("If-Modified-Since", lastMod)
   705  
   706  	res, err = DefaultClient.Do(req)
   707  	if err != nil {
   708  		t.Fatal(err)
   709  	}
   710  	if res.StatusCode != 304 {
   711  		t.Fatalf("Code after If-Modified-Since request = %v; want 304", res.StatusCode)
   712  	}
   713  	res.Body.Close()
   714  
   715  	// Advance the index.html file's modtime, but not the directory's.
   716  	indexFile.modtime = indexFile.modtime.Add(1 * time.Hour)
   717  
   718  	res, err = DefaultClient.Do(req)
   719  	if err != nil {
   720  		t.Fatal(err)
   721  	}
   722  	if res.StatusCode != 200 {
   723  		t.Fatalf("Code after second If-Modified-Since request = %v; want 200; res is %#v", res.StatusCode, res)
   724  	}
   725  	res.Body.Close()
   726  }
   727  
   728  func mustStat(t *testing.T, fileName string) os.FileInfo {
   729  	fi, err := os.Stat(fileName)
   730  	if err != nil {
   731  		t.Fatal(err)
   732  	}
   733  	return fi
   734  }
   735  
   736  func TestServeContent(t *testing.T) {
   737  	defer afterTest(t)
   738  	type serveParam struct {
   739  		name        string
   740  		modtime     time.Time
   741  		content     io.ReadSeeker
   742  		contentType string
   743  		etag        string
   744  	}
   745  	servec := make(chan serveParam, 1)
   746  	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
   747  		p := <-servec
   748  		if p.etag != "" {
   749  			w.Header().Set("ETag", p.etag)
   750  		}
   751  		if p.contentType != "" {
   752  			w.Header().Set("Content-Type", p.contentType)
   753  		}
   754  		ServeContent(w, r, p.name, p.modtime, p.content)
   755  	}))
   756  	defer ts.Close()
   757  
   758  	type testCase struct {
   759  		// One of file or content must be set:
   760  		file    string
   761  		content io.ReadSeeker
   762  
   763  		modtime          time.Time
   764  		serveETag        string // optional
   765  		serveContentType string // optional
   766  		reqHeader        map[string]string
   767  		wantLastMod      string
   768  		wantContentType  string
   769  		wantContentRange string
   770  		wantStatus       int
   771  	}
   772  	htmlModTime := mustStat(t, "testdata/index.html").ModTime()
   773  	tests := map[string]testCase{
   774  		"no_last_modified": {
   775  			file:            "testdata/style.css",
   776  			wantContentType: "text/css; charset=utf-8",
   777  			wantStatus:      200,
   778  		},
   779  		"with_last_modified": {
   780  			file:            "testdata/index.html",
   781  			wantContentType: "text/html; charset=utf-8",
   782  			modtime:         htmlModTime,
   783  			wantLastMod:     htmlModTime.UTC().Format(TimeFormat),
   784  			wantStatus:      200,
   785  		},
   786  		"not_modified_modtime": {
   787  			file:    "testdata/style.css",
   788  			modtime: htmlModTime,
   789  			reqHeader: map[string]string{
   790  				"If-Modified-Since": htmlModTime.UTC().Format(TimeFormat),
   791  			},
   792  			wantStatus: 304,
   793  		},
   794  		"not_modified_modtime_with_contenttype": {
   795  			file:             "testdata/style.css",
   796  			serveContentType: "text/css", // explicit content type
   797  			modtime:          htmlModTime,
   798  			reqHeader: map[string]string{
   799  				"If-Modified-Since": htmlModTime.UTC().Format(TimeFormat),
   800  			},
   801  			wantStatus: 304,
   802  		},
   803  		"not_modified_etag": {
   804  			file:      "testdata/style.css",
   805  			serveETag: `"foo"`,
   806  			reqHeader: map[string]string{
   807  				"If-None-Match": `"foo"`,
   808  			},
   809  			wantStatus: 304,
   810  		},
   811  		"not_modified_etag_no_seek": {
   812  			content:   panicOnSeek{nil}, // should never be called
   813  			serveETag: `"foo"`,
   814  			reqHeader: map[string]string{
   815  				"If-None-Match": `"foo"`,
   816  			},
   817  			wantStatus: 304,
   818  		},
   819  		"range_good": {
   820  			file:      "testdata/style.css",
   821  			serveETag: `"A"`,
   822  			reqHeader: map[string]string{
   823  				"Range": "bytes=0-4",
   824  			},
   825  			wantStatus:       StatusPartialContent,
   826  			wantContentType:  "text/css; charset=utf-8",
   827  			wantContentRange: "bytes 0-4/8",
   828  		},
   829  		"range_no_overlap": {
   830  			file:      "testdata/style.css",
   831  			serveETag: `"A"`,
   832  			reqHeader: map[string]string{
   833  				"Range": "bytes=10-20",
   834  			},
   835  			wantStatus:       StatusRequestedRangeNotSatisfiable,
   836  			wantContentType:  "text/plain; charset=utf-8",
   837  			wantContentRange: "bytes */8",
   838  		},
   839  		// An If-Range resource for entity "A", but entity "B" is now current.
   840  		// The Range request should be ignored.
   841  		"range_no_match": {
   842  			file:      "testdata/style.css",
   843  			serveETag: `"A"`,
   844  			reqHeader: map[string]string{
   845  				"Range":    "bytes=0-4",
   846  				"If-Range": `"B"`,
   847  			},
   848  			wantStatus:      200,
   849  			wantContentType: "text/css; charset=utf-8",
   850  		},
   851  		"range_with_modtime": {
   852  			file:    "testdata/style.css",
   853  			modtime: time.Date(2014, 6, 25, 17, 12, 18, 0 /* nanos */, time.UTC),
   854  			reqHeader: map[string]string{
   855  				"Range":    "bytes=0-4",
   856  				"If-Range": "Wed, 25 Jun 2014 17:12:18 GMT",
   857  			},
   858  			wantStatus:       StatusPartialContent,
   859  			wantContentType:  "text/css; charset=utf-8",
   860  			wantContentRange: "bytes 0-4/8",
   861  			wantLastMod:      "Wed, 25 Jun 2014 17:12:18 GMT",
   862  		},
   863  		"range_with_modtime_nanos": {
   864  			file:    "testdata/style.css",
   865  			modtime: time.Date(2014, 6, 25, 17, 12, 18, 123 /* nanos */, time.UTC),
   866  			reqHeader: map[string]string{
   867  				"Range":    "bytes=0-4",
   868  				"If-Range": "Wed, 25 Jun 2014 17:12:18 GMT",
   869  			},
   870  			wantStatus:       StatusPartialContent,
   871  			wantContentType:  "text/css; charset=utf-8",
   872  			wantContentRange: "bytes 0-4/8",
   873  			wantLastMod:      "Wed, 25 Jun 2014 17:12:18 GMT",
   874  		},
   875  		"unix_zero_modtime": {
   876  			content:         strings.NewReader("<html>foo"),
   877  			modtime:         time.Unix(0, 0),
   878  			wantStatus:      StatusOK,
   879  			wantContentType: "text/html; charset=utf-8",
   880  		},
   881  	}
   882  	for testName, tt := range tests {
   883  		var content io.ReadSeeker
   884  		if tt.file != "" {
   885  			f, err := os.Open(tt.file)
   886  			if err != nil {
   887  				t.Fatalf("test %q: %v", testName, err)
   888  			}
   889  			defer f.Close()
   890  			content = f
   891  		} else {
   892  			content = tt.content
   893  		}
   894  
   895  		servec <- serveParam{
   896  			name:        filepath.Base(tt.file),
   897  			content:     content,
   898  			modtime:     tt.modtime,
   899  			etag:        tt.serveETag,
   900  			contentType: tt.serveContentType,
   901  		}
   902  		req, err := NewRequest("GET", ts.URL, nil)
   903  		if err != nil {
   904  			t.Fatal(err)
   905  		}
   906  		for k, v := range tt.reqHeader {
   907  			req.Header.Set(k, v)
   908  		}
   909  		res, err := DefaultClient.Do(req)
   910  		if err != nil {
   911  			t.Fatal(err)
   912  		}
   913  		io.Copy(ioutil.Discard, res.Body)
   914  		res.Body.Close()
   915  		if res.StatusCode != tt.wantStatus {
   916  			t.Errorf("test %q: status = %d; want %d", testName, res.StatusCode, tt.wantStatus)
   917  		}
   918  		if g, e := res.Header.Get("Content-Type"), tt.wantContentType; g != e {
   919  			t.Errorf("test %q: content-type = %q, want %q", testName, g, e)
   920  		}
   921  		if g, e := res.Header.Get("Content-Range"), tt.wantContentRange; g != e {
   922  			t.Errorf("test %q: content-range = %q, want %q", testName, g, e)
   923  		}
   924  		if g, e := res.Header.Get("Last-Modified"), tt.wantLastMod; g != e {
   925  			t.Errorf("test %q: last-modified = %q, want %q", testName, g, e)
   926  		}
   927  	}
   928  }
   929  
   930  // Issue 12991
   931  func TestServerFileStatError(t *testing.T) {
   932  	rec := httptest.NewRecorder()
   933  	r, _ := NewRequest("GET", "http://foo/", nil)
   934  	redirect := false
   935  	name := "file.txt"
   936  	fs := issue12991FS{}
   937  	ExportServeFile(rec, r, fs, name, redirect)
   938  	if body := rec.Body.String(); !strings.Contains(body, "403") || !strings.Contains(body, "Forbidden") {
   939  		t.Errorf("wanted 403 forbidden message; got: %s", body)
   940  	}
   941  }
   942  
   943  type issue12991FS struct{}
   944  
   945  func (issue12991FS) Open(string) (File, error) { return issue12991File{}, nil }
   946  
   947  type issue12991File struct{ File }
   948  
   949  func (issue12991File) Stat() (os.FileInfo, error) { return nil, os.ErrPermission }
   950  func (issue12991File) Close() error               { return nil }
   951  
   952  func TestServeContentErrorMessages(t *testing.T) {
   953  	defer afterTest(t)
   954  	fs := fakeFS{
   955  		"/500": &fakeFileInfo{
   956  			err: errors.New("random error"),
   957  		},
   958  		"/403": &fakeFileInfo{
   959  			err: &os.PathError{Err: os.ErrPermission},
   960  		},
   961  	}
   962  	ts := httptest.NewServer(FileServer(fs))
   963  	defer ts.Close()
   964  	for _, code := range []int{403, 404, 500} {
   965  		res, err := DefaultClient.Get(fmt.Sprintf("%s/%d", ts.URL, code))
   966  		if err != nil {
   967  			t.Errorf("Error fetching /%d: %v", code, err)
   968  			continue
   969  		}
   970  		if res.StatusCode != code {
   971  			t.Errorf("For /%d, status code = %d; want %d", code, res.StatusCode, code)
   972  		}
   973  		res.Body.Close()
   974  	}
   975  }
   976  
   977  // verifies that sendfile is being used on Linux
   978  func TestLinuxSendfile(t *testing.T) {
   979  	defer afterTest(t)
   980  	if runtime.GOOS != "linux" {
   981  		t.Skip("skipping; linux-only test")
   982  	}
   983  	if _, err := exec.LookPath("strace"); err != nil {
   984  		t.Skip("skipping; strace not found in path")
   985  	}
   986  
   987  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   988  	if err != nil {
   989  		t.Fatal(err)
   990  	}
   991  	lnf, err := ln.(*net.TCPListener).File()
   992  	if err != nil {
   993  		t.Fatal(err)
   994  	}
   995  	defer ln.Close()
   996  
   997  	syscalls := "sendfile,sendfile64"
   998  	switch runtime.GOARCH {
   999  	case "mips64", "mips64le", "s390x":
  1000  		// strace on the above platforms doesn't support sendfile64
  1001  		// and will error out if we specify that with `-e trace='.
  1002  		syscalls = "sendfile"
  1003  	}
  1004  
  1005  	var buf bytes.Buffer
  1006  	child := exec.Command("strace", "-f", "-q", "-e", "trace="+syscalls, os.Args[0], "-test.run=TestLinuxSendfileChild")
  1007  	child.ExtraFiles = append(child.ExtraFiles, lnf)
  1008  	child.Env = append([]string{"GO_WANT_HELPER_PROCESS=1"}, os.Environ()...)
  1009  	child.Stdout = &buf
  1010  	child.Stderr = &buf
  1011  	if err := child.Start(); err != nil {
  1012  		t.Skipf("skipping; failed to start straced child: %v", err)
  1013  	}
  1014  
  1015  	res, err := Get(fmt.Sprintf("http://%s/", ln.Addr()))
  1016  	if err != nil {
  1017  		t.Fatalf("http client error: %v", err)
  1018  	}
  1019  	_, err = io.Copy(ioutil.Discard, res.Body)
  1020  	if err != nil {
  1021  		t.Fatalf("client body read error: %v", err)
  1022  	}
  1023  	res.Body.Close()
  1024  
  1025  	// Force child to exit cleanly.
  1026  	Post(fmt.Sprintf("http://%s/quit", ln.Addr()), "", nil)
  1027  	child.Wait()
  1028  
  1029  	rx := regexp.MustCompile(`sendfile(64)?\(\d+,\s*\d+,\s*NULL,\s*\d+\)\s*=\s*\d+\s*\n`)
  1030  	rxResume := regexp.MustCompile(`<\.\.\. sendfile(64)? resumed> \)\s*=\s*\d+\s*\n`)
  1031  	out := buf.String()
  1032  	if !rx.MatchString(out) && !rxResume.MatchString(out) {
  1033  		t.Errorf("no sendfile system call found in:\n%s", out)
  1034  	}
  1035  }
  1036  
  1037  func getBody(t *testing.T, testName string, req Request) (*Response, []byte) {
  1038  	r, err := DefaultClient.Do(&req)
  1039  	if err != nil {
  1040  		t.Fatalf("%s: for URL %q, send error: %v", testName, req.URL.String(), err)
  1041  	}
  1042  	b, err := ioutil.ReadAll(r.Body)
  1043  	if err != nil {
  1044  		t.Fatalf("%s: for URL %q, reading body: %v", testName, req.URL.String(), err)
  1045  	}
  1046  	return r, b
  1047  }
  1048  
  1049  // TestLinuxSendfileChild isn't a real test. It's used as a helper process
  1050  // for TestLinuxSendfile.
  1051  func TestLinuxSendfileChild(*testing.T) {
  1052  	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
  1053  		return
  1054  	}
  1055  	defer os.Exit(0)
  1056  	fd3 := os.NewFile(3, "ephemeral-port-listener")
  1057  	ln, err := net.FileListener(fd3)
  1058  	if err != nil {
  1059  		panic(err)
  1060  	}
  1061  	mux := NewServeMux()
  1062  	mux.Handle("/", FileServer(Dir("testdata")))
  1063  	mux.HandleFunc("/quit", func(ResponseWriter, *Request) {
  1064  		os.Exit(0)
  1065  	})
  1066  	s := &Server{Handler: mux}
  1067  	err = s.Serve(ln)
  1068  	if err != nil {
  1069  		panic(err)
  1070  	}
  1071  }
  1072  
  1073  func TestFileServerCleanPath(t *testing.T) {
  1074  	tests := []struct {
  1075  		path     string
  1076  		wantCode int
  1077  		wantOpen []string
  1078  	}{
  1079  		{"/", 200, []string{"/", "/index.html"}},
  1080  		{"/dir", 301, []string{"/dir"}},
  1081  		{"/dir/", 200, []string{"/dir", "/dir/index.html"}},
  1082  	}
  1083  	for _, tt := range tests {
  1084  		var log []string
  1085  		rr := httptest.NewRecorder()
  1086  		req, _ := NewRequest("GET", "http://foo.localhost"+tt.path, nil)
  1087  		FileServer(fileServerCleanPathDir{&log}).ServeHTTP(rr, req)
  1088  		if !reflect.DeepEqual(log, tt.wantOpen) {
  1089  			t.Logf("For %s: Opens = %q; want %q", tt.path, log, tt.wantOpen)
  1090  		}
  1091  		if rr.Code != tt.wantCode {
  1092  			t.Logf("For %s: Response code = %d; want %d", tt.path, rr.Code, tt.wantCode)
  1093  		}
  1094  	}
  1095  }
  1096  
  1097  type fileServerCleanPathDir struct {
  1098  	log *[]string
  1099  }
  1100  
  1101  func (d fileServerCleanPathDir) Open(path string) (File, error) {
  1102  	*(d.log) = append(*(d.log), path)
  1103  	if path == "/" || path == "/dir" || path == "/dir/" {
  1104  		// Just return back something that's a directory.
  1105  		return Dir(".").Open(".")
  1106  	}
  1107  	return nil, os.ErrNotExist
  1108  }
  1109  
  1110  type panicOnSeek struct{ io.ReadSeeker }