PageRenderTime 57ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/third_party/gofrontend/libgo/go/net/http/fs_test.go

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