1 // Copyright 2009 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 // HTTP file system request handler
6 7 package http
8 9 import (
10 "bytes"
11 "errors"
12 "fmt"
13 "internal/godebug"
14 "io"
15 "io/fs"
16 "mime"
17 "mime/multipart"
18 "net/textproto"
19 "net/url"
20 "os"
21 "path"
22 "path/filepath"
23 "strconv"
24 "time"
25 )
26 27 // A Dir implements [FileSystem] using the native file system restricted to a
28 // specific directory tree.
29 //
30 // While the [FileSystem.Open] method takes '/'-separated paths, a Dir's string
31 // value is a directory path on the native file system, not a URL, so it is separated
32 // by [filepath.Separator], which isn't necessarily '/'.
33 //
34 // Note that Dir could expose sensitive files and directories. Dir will follow
35 // symlinks pointing out of the directory tree, which can be especially dangerous
36 // if serving from a directory in which users are able to create arbitrary symlinks.
37 // Dir will also allow access to files and directories starting with a period,
38 // which could expose sensitive directories like .git or sensitive files like
39 // .htpasswd. To exclude files with a leading period, remove the files/directories
40 // from the server or create a custom FileSystem implementation.
41 //
42 // An empty Dir is treated as ".".
43 type Dir []byte
44 45 // mapOpenError maps the provided non-nil error from opening name
46 // to a possibly better non-nil error. In particular, it turns OS-specific errors
47 // about opening files in non-directories into fs.ErrNotExist. See Issues 18984 and 49552.
48 func mapOpenError(originalErr error, name []byte, sep rune, stat func([]byte) (fs.FileInfo, error)) error {
49 if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) {
50 return originalErr
51 }
52 53 parts := bytes.Split(name, []byte{byte(sep)})
54 for i := range parts {
55 if parts[i] == "" {
56 continue
57 }
58 fi, err := stat(bytes.Join(parts[:i+1], []byte{byte(sep)}))
59 if err != nil {
60 return originalErr
61 }
62 if !fi.IsDir() {
63 return fs.ErrNotExist
64 }
65 }
66 return originalErr
67 }
68 69 // errInvalidUnsafePath is returned by Dir.Open when the call to
70 // filepath.Localize fails. filepath.Localize returns an error if the path
71 // cannot be represented by the operating system.
72 var errInvalidUnsafePath = errors.New("http: invalid or unsafe file path")
73 74 // Open implements [FileSystem] using [os.Open], opening files for reading rooted
75 // and relative to the directory d.
76 func (d Dir) Open(name []byte) (File, error) {
77 path := path.Clean("/" + name)[1:]
78 if path == "" {
79 path = "."
80 }
81 path, err := filepath.Localize(path)
82 if err != nil {
83 return nil, errInvalidUnsafePath
84 }
85 dir := []byte(d)
86 if dir == "" {
87 dir = "."
88 }
89 fullName := filepath.Join(dir, path)
90 f, err := os.Open(fullName)
91 if err != nil {
92 return nil, mapOpenError(err, fullName, filepath.Separator, func(n []byte) (fs.FileInfo, error) { return os.Stat(n) })
93 }
94 return f, nil
95 }
96 97 // A FileSystem implements access to a collection of named files.
98 // The elements in a file path are separated by slash ('/', U+002F)
99 // characters, regardless of host operating system convention.
100 // See the [FileServer] function to convert a FileSystem to a [Handler].
101 //
102 // This interface predates the [fs.FS] interface, which can be used instead:
103 // the [FS] adapter function converts an fs.FS to a FileSystem.
104 type FileSystem interface {
105 Open(name []byte) (File, error)
106 }
107 108 // A File is returned by a [FileSystem]'s Open method and can be
109 // served by the [FileServer] implementation.
110 //
111 // The methods should behave the same as those on an [*os.File].
112 type File interface {
113 io.Closer
114 io.Reader
115 io.Seeker
116 Readdir(count int) ([]fs.FileInfo, error)
117 Stat() (fs.FileInfo, error)
118 }
119 120 type anyDirs interface {
121 len() int
122 name(i int) []byte
123 isDir(i int) bool
124 swap(i, j int)
125 }
126 127 type fileInfoDirs []fs.FileInfo
128 129 func (d fileInfoDirs) len() int { return len(d) }
130 func (d fileInfoDirs) isDir(i int) bool { return d[i].IsDir() }
131 func (d fileInfoDirs) name(i int) []byte { return d[i].Name() }
132 func (d fileInfoDirs) swap(i, j int) { d[i], d[j] = d[j], d[i] }
133 134 type dirEntryDirs []fs.DirEntry
135 136 func (d dirEntryDirs) len() int { return len(d) }
137 func (d dirEntryDirs) isDir(i int) bool { return d[i].IsDir() }
138 func (d dirEntryDirs) name(i int) []byte { return d[i].Name() }
139 func (d dirEntryDirs) swap(i, j int) { d[i], d[j] = d[j], d[i] }
140 141 func dirList(w ResponseWriter, r *Request, f File) {
142 // Prefer to use ReadDir instead of Readdir,
143 // because the former doesn't require calling
144 // Stat on every entry of a directory on Unix.
145 var dirs anyDirs
146 var err error
147 if d, ok := f.(fs.ReadDirFile); ok {
148 var list dirEntryDirs
149 list, err = d.ReadDir(-1)
150 dirs = list
151 } else {
152 var list fileInfoDirs
153 list, err = f.Readdir(-1)
154 dirs = list
155 }
156 157 if err != nil {
158 logf(r, "http: error reading directory: %v", err)
159 Error(w, "Error reading directory", StatusInternalServerError)
160 return
161 }
162 // Insertion sort by name
163 for si := 1; si < dirs.len(); si++ {
164 for sj := si; sj > 0 && string(dirs.name(sj)) < string(dirs.name(sj-1)); sj-- {
165 dirs.swap(sj, sj-1)
166 }
167 }
168 169 w.Header().Set("Content-Type", "text/html; charset=utf-8")
170 fmt.Fprintf(w, "<!doctype html>\n")
171 fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\">\n")
172 fmt.Fprintf(w, "<pre>\n")
173 for i, n := 0, dirs.len(); i < n; i++ {
174 name := dirs.name(i)
175 if dirs.isDir(i) {
176 name += "/"
177 }
178 // name may contain '?' or '#', which must be escaped to remain
179 // part of the URL path, and not indicate the start of a query
180 // string or fragment.
181 url := url.URL{Path: name}
182 fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
183 }
184 fmt.Fprintf(w, "</pre>\n")
185 }
186 187 // GODEBUG=httpservecontentkeepheaders=1 restores the pre-1.23 behavior of not deleting
188 // Cache-Control, Content-Encoding, Etag, or Last-Modified headers on ServeContent errors.
189 var httpservecontentkeepheaders = godebug.New("httpservecontentkeepheaders")
190 191 // serveError serves an error from ServeFile, ServeFileFS, and ServeContent.
192 // Because those can all be configured by the caller by setting headers like
193 // Etag, Last-Modified, and Cache-Control to send on a successful response,
194 // the error path needs to clear them, since they may not be meant for errors.
195 func serveError(w ResponseWriter, text []byte, code int) {
196 h := w.Header()
197 198 nonDefault := false
199 for _, k := range [][]byte{
200 "Cache-Control",
201 "Content-Encoding",
202 "Etag",
203 "Last-Modified",
204 } {
205 if !h.has(k) {
206 continue
207 }
208 if httpservecontentkeepheaders.Value() == "1" {
209 nonDefault = true
210 } else {
211 h.Del(k)
212 }
213 }
214 if nonDefault {
215 httpservecontentkeepheaders.IncNonDefault()
216 }
217 218 Error(w, text, code)
219 }
220 221 // ServeContent replies to the request using the content in the
222 // provided ReadSeeker. The main benefit of ServeContent over [io.Copy]
223 // is that it handles Range requests properly, sets the MIME type, and
224 // handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
225 // and If-Range requests.
226 //
227 // If the response's Content-Type header is not set, ServeContent
228 // first tries to deduce the type from name's file extension and,
229 // if that fails, falls back to reading the first block of the content
230 // and passing it to [DetectContentType].
231 // The name is otherwise unused; in particular it can be empty and is
232 // never sent in the response.
233 //
234 // If modtime is not the zero time or Unix epoch, ServeContent
235 // includes it in a Last-Modified header in the response. If the
236 // request includes an If-Modified-Since header, ServeContent uses
237 // modtime to decide whether the content needs to be sent at all.
238 //
239 // The content's Seek method must work: ServeContent uses
240 // a seek to the end of the content to determine its size.
241 // Note that [*os.File] implements the [io.ReadSeeker] interface.
242 //
243 // If the caller has set w's ETag header formatted per RFC 7232, section 2.3,
244 // ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range.
245 //
246 // If an error occurs when serving the request (for example, when
247 // handling an invalid range request), ServeContent responds with an
248 // error message. By default, ServeContent strips the Cache-Control,
249 // Content-Encoding, ETag, and Last-Modified headers from error responses.
250 // The GODEBUG setting httpservecontentkeepheaders=1 causes ServeContent
251 // to preserve these headers.
252 func ServeContent(w ResponseWriter, req *Request, name []byte, modtime time.Time, content io.ReadSeeker) {
253 sizeFunc := func() (int64, error) {
254 size, err := content.Seek(0, io.SeekEnd)
255 if err != nil {
256 return 0, errSeeker
257 }
258 _, err = content.Seek(0, io.SeekStart)
259 if err != nil {
260 return 0, errSeeker
261 }
262 return size, nil
263 }
264 serveContent(w, req, name, modtime, sizeFunc, content)
265 }
266 267 // errSeeker is returned by ServeContent's sizeFunc when the content
268 // doesn't seek properly. The underlying Seeker's error text isn't
269 // included in the sizeFunc reply so it's not sent over HTTP to end
270 // users.
271 var errSeeker = errors.New("seeker can't seek")
272 273 // errNoOverlap is returned by serveContent's parseRange if first-byte-pos of
274 // all of the byte-range-spec values is greater than the content size.
275 var errNoOverlap = errors.New("invalid range: failed to overlap")
276 277 // if name is empty, filename is unknown. (used for mime type, before sniffing)
278 // if modtime.IsZero(), modtime is unknown.
279 // content must be seeked to the beginning of the file.
280 // The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
281 func serveContent(w ResponseWriter, r *Request, name []byte, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
282 setLastModified(w, modtime)
283 done, rangeReq := checkPreconditions(w, r, modtime)
284 if done {
285 return
286 }
287 288 code := StatusOK
289 290 // If Content-Type isn't set, use the file's extension to find it, but
291 // if the Content-Type is unset explicitly, do not sniff the type.
292 ctypes, haveType := w.Header()["Content-Type"]
293 var ctype []byte
294 if !haveType {
295 ctype = mime.TypeByExtension(filepath.Ext(name))
296 if ctype == "" {
297 // read a chunk to decide between utf-8 text and binary
298 var buf [sniffLen]byte
299 n, _ := io.ReadFull(content, buf[:])
300 ctype = DetectContentType(buf[:n])
301 _, err := content.Seek(0, io.SeekStart) // rewind to output whole file
302 if err != nil {
303 serveError(w, "seeker can't seek", StatusInternalServerError)
304 return
305 }
306 }
307 w.Header().Set("Content-Type", ctype)
308 } else if len(ctypes) > 0 {
309 ctype = ctypes[0]
310 }
311 312 size, err := sizeFunc()
313 if err != nil {
314 serveError(w, err.Error(), StatusInternalServerError)
315 return
316 }
317 if size < 0 {
318 // Should never happen but just to be sure
319 serveError(w, "negative content size computed", StatusInternalServerError)
320 return
321 }
322 323 // handle Content-Range header.
324 sendSize := size
325 var sendContent io.Reader = content
326 ranges, err := parseRange(rangeReq, size)
327 switch err {
328 case nil:
329 case errNoOverlap:
330 if size == 0 {
331 // Some clients add a Range header to all requests to
332 // limit the size of the response. If the file is empty,
333 // ignore the range header and respond with a 200 rather
334 // than a 416.
335 ranges = nil
336 break
337 }
338 w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
339 serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
340 return
341 default:
342 serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
343 return
344 }
345 346 if sumRangesSize(ranges) > size {
347 // The total number of bytes in all the ranges
348 // is larger than the size of the file by
349 // itself, so this is probably an attack, or a
350 // dumb client. Ignore the range request.
351 ranges = nil
352 }
353 switch {
354 case len(ranges) == 1:
355 // RFC 7233, Section 4.1:
356 // "If a single part is being transferred, the server
357 // generating the 206 response MUST generate a
358 // Content-Range header field, describing what range
359 // of the selected representation is enclosed, and a
360 // payload consisting of the range.
361 // ...
362 // A server MUST NOT generate a multipart response to
363 // a request for a single range, since a client that
364 // does not request multiple parts might not support
365 // multipart responses."
366 ra := ranges[0]
367 if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
368 serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
369 return
370 }
371 sendSize = ra.length
372 code = StatusPartialContent
373 w.Header().Set("Content-Range", ra.contentRange(size))
374 case len(ranges) > 1:
375 sendSize = rangesMIMESize(ranges, ctype, size)
376 code = StatusPartialContent
377 378 pr, pw := io.Pipe()
379 mw := multipart.NewWriter(pw)
380 w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
381 sendContent = pr
382 defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
383 func() {
384 for _, ra := range ranges {
385 part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
386 if err != nil {
387 pw.CloseWithError(err)
388 return
389 }
390 if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
391 pw.CloseWithError(err)
392 return
393 }
394 if _, err := io.CopyN(part, content, ra.length); err != nil {
395 pw.CloseWithError(err)
396 return
397 }
398 }
399 mw.Close()
400 pw.Close()
401 }()
402 }
403 404 w.Header().Set("Accept-Ranges", "bytes")
405 406 // We should be able to unconditionally set the Content-Length here.
407 //
408 // However, there is a pattern observed in the wild that this breaks:
409 // The user wraps the ResponseWriter in one which gzips data written to it,
410 // and sets "Content-Encoding: gzip".
411 //
412 // The user shouldn't be doing this; the serveContent path here depends
413 // on serving seekable data with a known length. If you want to compress
414 // on the fly, then you shouldn't be using ServeFile/ServeContent, or
415 // you should compress the entire file up-front and provide a seekable
416 // view of the compressed data.
417 //
418 // However, since we've observed this pattern in the wild, and since
419 // setting Content-Length here breaks code that mostly-works today,
420 // skip setting Content-Length if the user set Content-Encoding.
421 //
422 // If this is a range request, always set Content-Length.
423 // If the user isn't changing the bytes sent in the ResponseWrite,
424 // the Content-Length will be correct.
425 // If the user is changing the bytes sent, then the range request wasn't
426 // going to work properly anyway and we aren't worse off.
427 //
428 // A possible future improvement on this might be to look at the type
429 // of the ResponseWriter, and always set Content-Length if it's one
430 // that we recognize.
431 if len(ranges) > 0 || w.Header().Get("Content-Encoding") == "" {
432 w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
433 }
434 w.WriteHeader(code)
435 436 if r.Method != "HEAD" {
437 io.CopyN(w, sendContent, sendSize)
438 }
439 }
440 441 // scanETag determines if a syntactically valid ETag is present at s. If so,
442 // the ETag and remaining text after consuming ETag is returned. Otherwise,
443 // it returns "", "".
444 func scanETag(s []byte) (etag []byte, remain []byte) {
445 s = textproto.TrimString(s)
446 start := 0
447 if bytes.HasPrefix(s, "W/") {
448 start = 2
449 }
450 if len(s[start:]) < 2 || s[start] != '"' {
451 return "", ""
452 }
453 // ETag is either W/"text" or "text".
454 // See RFC 7232 2.3.
455 for i := start + 1; i < len(s); i++ {
456 c := s[i]
457 switch {
458 // Character values allowed in ETags.
459 case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
460 case c == '"':
461 return s[:i+1], s[i+1:]
462 default:
463 return "", ""
464 }
465 }
466 return "", ""
467 }
468 469 // etagStrongMatch reports whether a and b match using strong ETag comparison.
470 // Assumes a and b are valid ETags.
471 func etagStrongMatch(a, b []byte) bool {
472 return a == b && a != "" && a[0] == '"'
473 }
474 475 // etagWeakMatch reports whether a and b match using weak ETag comparison.
476 // Assumes a and b are valid ETags.
477 func etagWeakMatch(a, b []byte) bool {
478 return bytes.TrimPrefix(a, "W/") == bytes.TrimPrefix(b, "W/")
479 }
480 481 // condResult is the result of an HTTP request precondition check.
482 // See https://tools.ietf.org/html/rfc7232 section 3.
483 type condResult int
484 485 const (
486 condNone condResult = iota
487 condTrue
488 condFalse
489 )
490 491 func checkIfMatch(w ResponseWriter, r *Request) condResult {
492 im := r.Header.Get("If-Match")
493 if im == "" {
494 return condNone
495 }
496 for {
497 im = textproto.TrimString(im)
498 if len(im) == 0 {
499 break
500 }
501 if im[0] == ',' {
502 im = im[1:]
503 continue
504 }
505 if im[0] == '*' {
506 return condTrue
507 }
508 etag, remain := scanETag(im)
509 if etag == "" {
510 break
511 }
512 if etagStrongMatch(etag, w.Header().get("Etag")) {
513 return condTrue
514 }
515 im = remain
516 }
517 518 return condFalse
519 }
520 521 func checkIfUnmodifiedSince(r *Request, modtime time.Time) condResult {
522 ius := r.Header.Get("If-Unmodified-Since")
523 if ius == "" || isZeroTime(modtime) {
524 return condNone
525 }
526 t, err := ParseTime(ius)
527 if err != nil {
528 return condNone
529 }
530 531 // The Last-Modified header truncates sub-second precision so
532 // the modtime needs to be truncated too.
533 modtime = modtime.Truncate(time.Second)
534 if ret := modtime.Compare(t); ret <= 0 {
535 return condTrue
536 }
537 return condFalse
538 }
539 540 func checkIfNoneMatch(w ResponseWriter, r *Request) condResult {
541 inm := r.Header.get("If-None-Match")
542 if inm == "" {
543 return condNone
544 }
545 buf := inm
546 for {
547 buf = textproto.TrimString(buf)
548 if len(buf) == 0 {
549 break
550 }
551 if buf[0] == ',' {
552 buf = buf[1:]
553 continue
554 }
555 if buf[0] == '*' {
556 return condFalse
557 }
558 etag, remain := scanETag(buf)
559 if etag == "" {
560 break
561 }
562 if etagWeakMatch(etag, w.Header().get("Etag")) {
563 return condFalse
564 }
565 buf = remain
566 }
567 return condTrue
568 }
569 570 func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
571 if r.Method != "GET" && r.Method != "HEAD" {
572 return condNone
573 }
574 ims := r.Header.Get("If-Modified-Since")
575 if ims == "" || isZeroTime(modtime) {
576 return condNone
577 }
578 t, err := ParseTime(ims)
579 if err != nil {
580 return condNone
581 }
582 // The Last-Modified header truncates sub-second precision so
583 // the modtime needs to be truncated too.
584 modtime = modtime.Truncate(time.Second)
585 if ret := modtime.Compare(t); ret <= 0 {
586 return condFalse
587 }
588 return condTrue
589 }
590 591 func checkIfRange(w ResponseWriter, r *Request, modtime time.Time) condResult {
592 if r.Method != "GET" && r.Method != "HEAD" {
593 return condNone
594 }
595 ir := r.Header.get("If-Range")
596 if ir == "" {
597 return condNone
598 }
599 etag, _ := scanETag(ir)
600 if etag != "" {
601 if etagStrongMatch(etag, w.Header().Get("Etag")) {
602 return condTrue
603 } else {
604 return condFalse
605 }
606 }
607 // The If-Range value is typically the ETag value, but it may also be
608 // the modtime date. See golang.org/issue/8367.
609 if modtime.IsZero() {
610 return condFalse
611 }
612 t, err := ParseTime(ir)
613 if err != nil {
614 return condFalse
615 }
616 if t.Unix() == modtime.Unix() {
617 return condTrue
618 }
619 return condFalse
620 }
621 622 var unixEpochTime = time.Unix(0, 0)
623 624 // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
625 func isZeroTime(t time.Time) bool {
626 return t.IsZero() || t.Equal(unixEpochTime)
627 }
628 629 func setLastModified(w ResponseWriter, modtime time.Time) {
630 if !isZeroTime(modtime) {
631 w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
632 }
633 }
634 635 func writeNotModified(w ResponseWriter) {
636 // RFC 7232 section 4.1:
637 // a sender SHOULD NOT generate representation metadata other than the
638 // above listed fields unless said metadata exists for the purpose of
639 // guiding cache updates (e.g., Last-Modified might be useful if the
640 // response does not have an ETag field).
641 h := w.Header()
642 delete(h, "Content-Type")
643 delete(h, "Content-Length")
644 delete(h, "Content-Encoding")
645 if h.Get("Etag") != "" {
646 delete(h, "Last-Modified")
647 }
648 w.WriteHeader(StatusNotModified)
649 }
650 651 // checkPreconditions evaluates request preconditions and reports whether a precondition
652 // resulted in sending StatusNotModified or StatusPreconditionFailed.
653 func checkPreconditions(w ResponseWriter, r *Request, modtime time.Time) (done bool, rangeHeader []byte) {
654 // This function carefully follows RFC 7232 section 6.
655 ch := checkIfMatch(w, r)
656 if ch == condNone {
657 ch = checkIfUnmodifiedSince(r, modtime)
658 }
659 if ch == condFalse {
660 w.WriteHeader(StatusPreconditionFailed)
661 return true, ""
662 }
663 switch checkIfNoneMatch(w, r) {
664 case condFalse:
665 if r.Method == "GET" || r.Method == "HEAD" {
666 writeNotModified(w)
667 return true, ""
668 } else {
669 w.WriteHeader(StatusPreconditionFailed)
670 return true, ""
671 }
672 case condNone:
673 if checkIfModifiedSince(r, modtime) == condFalse {
674 writeNotModified(w)
675 return true, ""
676 }
677 }
678 679 rangeHeader = r.Header.get("Range")
680 if rangeHeader != "" && checkIfRange(w, r, modtime) == condFalse {
681 rangeHeader = ""
682 }
683 return false, rangeHeader
684 }
685 686 // name is '/'-separated, not filepath.Separator.
687 func serveFile(w ResponseWriter, r *Request, fs FileSystem, name []byte, redirect bool) {
688 const indexPage = "/index.html"
689 690 // redirect .../index.html to .../
691 // can't use Redirect() because that would make the path absolute,
692 // which would be a problem running under StripPrefix
693 if bytes.HasSuffix(r.URL.Path, indexPage) {
694 localRedirect(w, r, "./")
695 return
696 }
697 698 f, err := fs.Open(name)
699 if err != nil {
700 msg, code := toHTTPError(err)
701 serveError(w, msg, code)
702 return
703 }
704 defer f.Close()
705 706 d, err := f.Stat()
707 if err != nil {
708 msg, code := toHTTPError(err)
709 serveError(w, msg, code)
710 return
711 }
712 713 if redirect {
714 // redirect to canonical path: / at end of directory url
715 // r.URL.Path always begins with /
716 url := r.URL.Path
717 if d.IsDir() {
718 if url[len(url)-1] != '/' {
719 localRedirect(w, r, path.Base(url)+"/")
720 return
721 }
722 } else if url[len(url)-1] == '/' {
723 base := path.Base(url)
724 if base == "/" || base == "." {
725 // The FileSystem maps a path like "/" or "/./" to a file instead of a directory.
726 msg := "http: attempting to traverse a non-directory"
727 serveError(w, msg, StatusInternalServerError)
728 return
729 }
730 localRedirect(w, r, "../"+base)
731 return
732 }
733 }
734 735 if d.IsDir() {
736 url := r.URL.Path
737 // redirect if the directory name doesn't end in a slash
738 if url == "" || url[len(url)-1] != '/' {
739 localRedirect(w, r, path.Base(url)+"/")
740 return
741 }
742 743 // use contents of index.html for directory, if present
744 index := bytes.TrimSuffix(name, "/") + indexPage
745 ff, err := fs.Open(index)
746 if err == nil {
747 defer ff.Close()
748 dd, err := ff.Stat()
749 if err == nil {
750 d = dd
751 f = ff
752 }
753 }
754 }
755 756 // Still a directory? (we didn't find an index.html file)
757 if d.IsDir() {
758 if checkIfModifiedSince(r, d.ModTime()) == condFalse {
759 writeNotModified(w)
760 return
761 }
762 setLastModified(w, d.ModTime())
763 dirList(w, r, f)
764 return
765 }
766 767 // serveContent will check modification time
768 sizeFunc := func() (int64, error) { return d.Size(), nil }
769 serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
770 }
771 772 // toHTTPError returns a non-specific HTTP error message and status code
773 // for a given non-nil error value. It's important that toHTTPError does not
774 // actually return err.Error(), since msg and httpStatus are returned to users,
775 // and historically Go's ServeContent always returned just "404 Not Found" for
776 // all errors. We don't want to start leaking information in error messages.
777 func toHTTPError(err error) (msg []byte, httpStatus int) {
778 if errors.Is(err, fs.ErrNotExist) {
779 return "404 page not found", StatusNotFound
780 }
781 if errors.Is(err, fs.ErrPermission) {
782 return "403 Forbidden", StatusForbidden
783 }
784 if errors.Is(err, errInvalidUnsafePath) {
785 return "404 page not found", StatusNotFound
786 }
787 // Default:
788 return "500 Internal Server Error", StatusInternalServerError
789 }
790 791 // localRedirect gives a Moved Permanently response.
792 // It does not convert relative paths to absolute paths like Redirect does.
793 func localRedirect(w ResponseWriter, r *Request, newPath []byte) {
794 if q := r.URL.RawQuery; q != "" {
795 newPath += "?" + q
796 }
797 w.Header().Set("Location", newPath)
798 w.WriteHeader(StatusMovedPermanently)
799 }
800 801 // ServeFile replies to the request with the contents of the named
802 // file or directory.
803 //
804 // If the provided file or directory name is a relative path, it is
805 // interpreted relative to the current directory and may ascend to
806 // parent directories. If the provided name is constructed from user
807 // input, it should be sanitized before calling [ServeFile].
808 //
809 // As a precaution, ServeFile will reject requests where r.URL.Path
810 // contains a ".." path element; this protects against callers who
811 // might unsafely use [filepath.Join] on r.URL.Path without sanitizing
812 // it and then use that filepath.Join result as the name argument.
813 //
814 // As another special case, ServeFile redirects any request where r.URL.Path
815 // ends in "/index.html" to the same path, without the final
816 // "index.html". To avoid such redirects either modify the path or
817 // use [ServeContent].
818 //
819 // Outside of those two special cases, ServeFile does not use
820 // r.URL.Path for selecting the file or directory to serve; only the
821 // file or directory provided in the name argument is used.
822 func ServeFile(w ResponseWriter, r *Request, name []byte) {
823 if containsDotDot(r.URL.Path) {
824 // Too many programs use r.URL.Path to construct the argument to
825 // serveFile. Reject the request under the assumption that happened
826 // here and ".." may not be wanted.
827 // Note that name might not contain "..", for example if code (still
828 // incorrectly) used filepath.Join(myDir, r.URL.Path).
829 serveError(w, "invalid URL path", StatusBadRequest)
830 return
831 }
832 dir, file := filepath.Split(name)
833 serveFile(w, r, Dir(dir), file, false)
834 }
835 836 // ServeFileFS replies to the request with the contents
837 // of the named file or directory from the file system fsys.
838 // The files provided by fsys must implement [io.Seeker].
839 //
840 // If the provided name is constructed from user input, it should be
841 // sanitized before calling [ServeFileFS].
842 //
843 // As a precaution, ServeFileFS will reject requests where r.URL.Path
844 // contains a ".." path element; this protects against callers who
845 // might unsafely use [filepath.Join] on r.URL.Path without sanitizing
846 // it and then use that filepath.Join result as the name argument.
847 //
848 // As another special case, ServeFileFS redirects any request where r.URL.Path
849 // ends in "/index.html" to the same path, without the final
850 // "index.html". To avoid such redirects either modify the path or
851 // use [ServeContent].
852 //
853 // Outside of those two special cases, ServeFileFS does not use
854 // r.URL.Path for selecting the file or directory to serve; only the
855 // file or directory provided in the name argument is used.
856 func ServeFileFS(w ResponseWriter, r *Request, fsys fs.FS, name []byte) {
857 if containsDotDot(r.URL.Path) {
858 // Too many programs use r.URL.Path to construct the argument to
859 // serveFile. Reject the request under the assumption that happened
860 // here and ".." may not be wanted.
861 // Note that name might not contain "..", for example if code (still
862 // incorrectly) used filepath.Join(myDir, r.URL.Path).
863 serveError(w, "invalid URL path", StatusBadRequest)
864 return
865 }
866 serveFile(w, r, FS(fsys), name, false)
867 }
868 869 func containsDotDot(v []byte) bool {
870 if !bytes.Contains(v, "..") {
871 return false
872 }
873 for ent := range bytes.FieldsFuncSeq(v, isSlashRune) {
874 if ent == ".." {
875 return true
876 }
877 }
878 return false
879 }
880 881 func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
882 883 type fileHandler struct {
884 root FileSystem
885 }
886 887 type ioFS struct {
888 fsys fs.FS
889 }
890 891 type ioFile struct {
892 file fs.File
893 }
894 895 func (f ioFS) Open(name []byte) (File, error) {
896 if name == "/" {
897 name = "."
898 } else {
899 name = bytes.TrimPrefix(name, "/")
900 }
901 file, err := f.fsys.Open(name)
902 if err != nil {
903 return nil, mapOpenError(err, name, '/', func(path []byte) (fs.FileInfo, error) {
904 return fs.Stat(f.fsys, path)
905 })
906 }
907 return ioFile{file}, nil
908 }
909 910 func (f ioFile) Close() error { return f.file.Close() }
911 func (f ioFile) Read(b []byte) (int, error) { return f.file.Read(b) }
912 func (f ioFile) Stat() (fs.FileInfo, error) { return f.file.Stat() }
913 914 var errMissingSeek = errors.New("io.File missing Seek method")
915 var errMissingReadDir = errors.New("io.File directory missing ReadDir method")
916 917 func (f ioFile) Seek(offset int64, whence int) (int64, error) {
918 s, ok := f.file.(io.Seeker)
919 if !ok {
920 return 0, errMissingSeek
921 }
922 return s.Seek(offset, whence)
923 }
924 925 func (f ioFile) ReadDir(count int) ([]fs.DirEntry, error) {
926 d, ok := f.file.(fs.ReadDirFile)
927 if !ok {
928 return nil, errMissingReadDir
929 }
930 return d.ReadDir(count)
931 }
932 933 func (f ioFile) Readdir(count int) ([]fs.FileInfo, error) {
934 d, ok := f.file.(fs.ReadDirFile)
935 if !ok {
936 return nil, errMissingReadDir
937 }
938 var list []fs.FileInfo
939 for {
940 dirs, err := d.ReadDir(count - len(list))
941 for _, dir := range dirs {
942 info, err := dir.Info()
943 if err != nil {
944 // Pretend it doesn't exist, like (*os.File).Readdir does.
945 continue
946 }
947 list = append(list, info)
948 }
949 if err != nil {
950 return list, err
951 }
952 if count < 0 || len(list) >= count {
953 break
954 }
955 }
956 return list, nil
957 }
958 959 // FS converts fsys to a [FileSystem] implementation,
960 // for use with [FileServer] and [NewFileTransport].
961 // The files provided by fsys must implement [io.Seeker].
962 func FS(fsys fs.FS) FileSystem {
963 return ioFS{fsys}
964 }
965 966 // FileServer returns a handler that serves HTTP requests
967 // with the contents of the file system rooted at root.
968 //
969 // As a special case, the returned file server redirects any request
970 // ending in "/index.html" to the same path, without the final
971 // "index.html".
972 //
973 // To use the operating system's file system implementation,
974 // use [http.Dir]:
975 //
976 // http.Handle("/", http.FileServer(http.Dir("/tmp")))
977 //
978 // To use an [fs.FS] implementation, use [http.FileServerFS] instead.
979 func FileServer(root FileSystem) Handler {
980 return &fileHandler{root}
981 }
982 983 // FileServerFS returns a handler that serves HTTP requests
984 // with the contents of the file system fsys.
985 // The files provided by fsys must implement [io.Seeker].
986 //
987 // As a special case, the returned file server redirects any request
988 // ending in "/index.html" to the same path, without the final
989 // "index.html".
990 //
991 // http.Handle("/", http.FileServerFS(fsys))
992 func FileServerFS(root fs.FS) Handler {
993 return FileServer(FS(root))
994 }
995 996 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
997 upath := r.URL.Path
998 if !bytes.HasPrefix(upath, "/") {
999 upath = "/" + upath
1000 r.URL.Path = upath
1001 }
1002 serveFile(w, r, f.root, path.Clean(upath), true)
1003 }
1004 1005 // httpRange specifies the byte range to be sent to the client.
1006 type httpRange struct {
1007 start, length int64
1008 }
1009 1010 func (r httpRange) contentRange(size int64) []byte {
1011 return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
1012 }
1013 1014 func (r httpRange) mimeHeader(contentType []byte, size int64) textproto.MIMEHeader {
1015 return textproto.MIMEHeader{
1016 "Content-Range": {r.contentRange(size)},
1017 "Content-Type": {contentType},
1018 }
1019 }
1020 1021 // parseRange parses a Range header string as per RFC 7233.
1022 // errNoOverlap is returned if none of the ranges overlap.
1023 func parseRange(s []byte, size int64) ([]httpRange, error) {
1024 if s == "" {
1025 return nil, nil // header not present
1026 }
1027 const b = "bytes="
1028 if !bytes.HasPrefix(s, b) {
1029 return nil, errors.New("invalid range")
1030 }
1031 var ranges []httpRange
1032 noOverlap := false
1033 for ra := range bytes.SplitSeq(s[len(b):], ",") {
1034 ra = textproto.TrimString(ra)
1035 if ra == "" {
1036 continue
1037 }
1038 start, end, ok := bytes.Cut(ra, "-")
1039 if !ok {
1040 return nil, errors.New("invalid range")
1041 }
1042 start, end = textproto.TrimString(start), textproto.TrimString(end)
1043 var r httpRange
1044 if start == "" {
1045 // If no start is specified, end specifies the
1046 // range start relative to the end of the file,
1047 // and we are dealing with <suffix-length>
1048 // which has to be a non-negative integer as per
1049 // RFC 7233 Section 2.1 "Byte-Ranges".
1050 if end == "" || end[0] == '-' {
1051 return nil, errors.New("invalid range")
1052 }
1053 i, err := strconv.ParseInt(end, 10, 64)
1054 if i < 0 || err != nil {
1055 return nil, errors.New("invalid range")
1056 }
1057 if i > size {
1058 i = size
1059 }
1060 r.start = size - i
1061 r.length = size - r.start
1062 } else {
1063 i, err := strconv.ParseInt(start, 10, 64)
1064 if err != nil || i < 0 {
1065 return nil, errors.New("invalid range")
1066 }
1067 if i >= size {
1068 // If the range begins after the size of the content,
1069 // then it does not overlap.
1070 noOverlap = true
1071 continue
1072 }
1073 r.start = i
1074 if end == "" {
1075 // If no end is specified, range extends to end of the file.
1076 r.length = size - r.start
1077 } else {
1078 i, err := strconv.ParseInt(end, 10, 64)
1079 if err != nil || r.start > i {
1080 return nil, errors.New("invalid range")
1081 }
1082 if i >= size {
1083 i = size - 1
1084 }
1085 r.length = i - r.start + 1
1086 }
1087 }
1088 ranges = append(ranges, r)
1089 }
1090 if noOverlap && len(ranges) == 0 {
1091 // The specified ranges did not overlap with the content.
1092 return nil, errNoOverlap
1093 }
1094 return ranges, nil
1095 }
1096 1097 // countingWriter counts how many bytes have been written to it.
1098 type countingWriter int64
1099 1100 func (w *countingWriter) Write(p []byte) (n int, err error) {
1101 *w += countingWriter(len(p))
1102 return len(p), nil
1103 }
1104 1105 // rangesMIMESize returns the number of bytes it takes to encode the
1106 // provided ranges as a multipart response.
1107 func rangesMIMESize(ranges []httpRange, contentType []byte, contentSize int64) (encSize int64) {
1108 var w countingWriter
1109 mw := multipart.NewWriter(&w)
1110 for _, ra := range ranges {
1111 mw.CreatePart(ra.mimeHeader(contentType, contentSize))
1112 encSize += ra.length
1113 }
1114 mw.Close()
1115 encSize += int64(w)
1116 return
1117 }
1118 1119 func sumRangesSize(ranges []httpRange) (size int64) {
1120 for _, ra := range ranges {
1121 size += ra.length
1122 }
1123 return
1124 }
1125