Home | History | Annotate | Download | only in http
      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 	"errors"
     11 	"fmt"
     12 	"io"
     13 	"mime"
     14 	"mime/multipart"
     15 	"net/textproto"
     16 	"net/url"
     17 	"os"
     18 	"path"
     19 	"path/filepath"
     20 	"sort"
     21 	"strconv"
     22 	"strings"
     23 	"time"
     24 )
     25 
     26 // A Dir implements FileSystem using the native file system restricted to a
     27 // specific directory tree.
     28 //
     29 // While the FileSystem.Open method takes '/'-separated paths, a Dir's string
     30 // value is a filename on the native file system, not a URL, so it is separated
     31 // by filepath.Separator, which isn't necessarily '/'.
     32 //
     33 // Note that Dir will allow access to files and directories starting with a
     34 // period, which could expose sensitive directories like a .git directory or
     35 // sensitive files like .htpasswd. To exclude files with a leading period,
     36 // remove the files/directories from the server or create a custom FileSystem
     37 // implementation.
     38 //
     39 // An empty Dir is treated as ".".
     40 type Dir string
     41 
     42 // mapDirOpenError maps the provided non-nil error from opening name
     43 // to a possibly better non-nil error. In particular, it turns OS-specific errors
     44 // about opening files in non-directories into os.ErrNotExist. See Issue 18984.
     45 func mapDirOpenError(originalErr error, name string) error {
     46 	if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
     47 		return originalErr
     48 	}
     49 
     50 	parts := strings.Split(name, string(filepath.Separator))
     51 	for i := range parts {
     52 		if parts[i] == "" {
     53 			continue
     54 		}
     55 		fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
     56 		if err != nil {
     57 			return originalErr
     58 		}
     59 		if !fi.IsDir() {
     60 			return os.ErrNotExist
     61 		}
     62 	}
     63 	return originalErr
     64 }
     65 
     66 func (d Dir) Open(name string) (File, error) {
     67 	if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
     68 		return nil, errors.New("http: invalid character in file path")
     69 	}
     70 	dir := string(d)
     71 	if dir == "" {
     72 		dir = "."
     73 	}
     74 	fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
     75 	f, err := os.Open(fullName)
     76 	if err != nil {
     77 		return nil, mapDirOpenError(err, fullName)
     78 	}
     79 	return f, nil
     80 }
     81 
     82 // A FileSystem implements access to a collection of named files.
     83 // The elements in a file path are separated by slash ('/', U+002F)
     84 // characters, regardless of host operating system convention.
     85 type FileSystem interface {
     86 	Open(name string) (File, error)
     87 }
     88 
     89 // A File is returned by a FileSystem's Open method and can be
     90 // served by the FileServer implementation.
     91 //
     92 // The methods should behave the same as those on an *os.File.
     93 type File interface {
     94 	io.Closer
     95 	io.Reader
     96 	io.Seeker
     97 	Readdir(count int) ([]os.FileInfo, error)
     98 	Stat() (os.FileInfo, error)
     99 }
    100 
    101 func dirList(w ResponseWriter, r *Request, f File) {
    102 	dirs, err := f.Readdir(-1)
    103 	if err != nil {
    104 		logf(r, "http: error reading directory: %v", err)
    105 		Error(w, "Error reading directory", StatusInternalServerError)
    106 		return
    107 	}
    108 	sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
    109 
    110 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
    111 	fmt.Fprintf(w, "<pre>\n")
    112 	for _, d := range dirs {
    113 		name := d.Name()
    114 		if d.IsDir() {
    115 			name += "/"
    116 		}
    117 		// name may contain '?' or '#', which must be escaped to remain
    118 		// part of the URL path, and not indicate the start of a query
    119 		// string or fragment.
    120 		url := url.URL{Path: name}
    121 		fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
    122 	}
    123 	fmt.Fprintf(w, "</pre>\n")
    124 }
    125 
    126 // ServeContent replies to the request using the content in the
    127 // provided ReadSeeker. The main benefit of ServeContent over io.Copy
    128 // is that it handles Range requests properly, sets the MIME type, and
    129 // handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
    130 // and If-Range requests.
    131 //
    132 // If the response's Content-Type header is not set, ServeContent
    133 // first tries to deduce the type from name's file extension and,
    134 // if that fails, falls back to reading the first block of the content
    135 // and passing it to DetectContentType.
    136 // The name is otherwise unused; in particular it can be empty and is
    137 // never sent in the response.
    138 //
    139 // If modtime is not the zero time or Unix epoch, ServeContent
    140 // includes it in a Last-Modified header in the response. If the
    141 // request includes an If-Modified-Since header, ServeContent uses
    142 // modtime to decide whether the content needs to be sent at all.
    143 //
    144 // The content's Seek method must work: ServeContent uses
    145 // a seek to the end of the content to determine its size.
    146 //
    147 // If the caller has set w's ETag header formatted per RFC 7232, section 2.3,
    148 // ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range.
    149 //
    150 // Note that *os.File implements the io.ReadSeeker interface.
    151 func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
    152 	sizeFunc := func() (int64, error) {
    153 		size, err := content.Seek(0, io.SeekEnd)
    154 		if err != nil {
    155 			return 0, errSeeker
    156 		}
    157 		_, err = content.Seek(0, io.SeekStart)
    158 		if err != nil {
    159 			return 0, errSeeker
    160 		}
    161 		return size, nil
    162 	}
    163 	serveContent(w, req, name, modtime, sizeFunc, content)
    164 }
    165 
    166 // errSeeker is returned by ServeContent's sizeFunc when the content
    167 // doesn't seek properly. The underlying Seeker's error text isn't
    168 // included in the sizeFunc reply so it's not sent over HTTP to end
    169 // users.
    170 var errSeeker = errors.New("seeker can't seek")
    171 
    172 // errNoOverlap is returned by serveContent's parseRange if first-byte-pos of
    173 // all of the byte-range-spec values is greater than the content size.
    174 var errNoOverlap = errors.New("invalid range: failed to overlap")
    175 
    176 // if name is empty, filename is unknown. (used for mime type, before sniffing)
    177 // if modtime.IsZero(), modtime is unknown.
    178 // content must be seeked to the beginning of the file.
    179 // The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
    180 func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
    181 	setLastModified(w, modtime)
    182 	done, rangeReq := checkPreconditions(w, r, modtime)
    183 	if done {
    184 		return
    185 	}
    186 
    187 	code := StatusOK
    188 
    189 	// If Content-Type isn't set, use the file's extension to find it, but
    190 	// if the Content-Type is unset explicitly, do not sniff the type.
    191 	ctypes, haveType := w.Header()["Content-Type"]
    192 	var ctype string
    193 	if !haveType {
    194 		ctype = mime.TypeByExtension(filepath.Ext(name))
    195 		if ctype == "" {
    196 			// read a chunk to decide between utf-8 text and binary
    197 			var buf [sniffLen]byte
    198 			n, _ := io.ReadFull(content, buf[:])
    199 			ctype = DetectContentType(buf[:n])
    200 			_, err := content.Seek(0, io.SeekStart) // rewind to output whole file
    201 			if err != nil {
    202 				Error(w, "seeker can't seek", StatusInternalServerError)
    203 				return
    204 			}
    205 		}
    206 		w.Header().Set("Content-Type", ctype)
    207 	} else if len(ctypes) > 0 {
    208 		ctype = ctypes[0]
    209 	}
    210 
    211 	size, err := sizeFunc()
    212 	if err != nil {
    213 		Error(w, err.Error(), StatusInternalServerError)
    214 		return
    215 	}
    216 
    217 	// handle Content-Range header.
    218 	sendSize := size
    219 	var sendContent io.Reader = content
    220 	if size >= 0 {
    221 		ranges, err := parseRange(rangeReq, size)
    222 		if err != nil {
    223 			if err == errNoOverlap {
    224 				w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
    225 			}
    226 			Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
    227 			return
    228 		}
    229 		if sumRangesSize(ranges) > size {
    230 			// The total number of bytes in all the ranges
    231 			// is larger than the size of the file by
    232 			// itself, so this is probably an attack, or a
    233 			// dumb client. Ignore the range request.
    234 			ranges = nil
    235 		}
    236 		switch {
    237 		case len(ranges) == 1:
    238 			// RFC 2616, Section 14.16:
    239 			// "When an HTTP message includes the content of a single
    240 			// range (for example, a response to a request for a
    241 			// single range, or to a request for a set of ranges
    242 			// that overlap without any holes), this content is
    243 			// transmitted with a Content-Range header, and a
    244 			// Content-Length header showing the number of bytes
    245 			// actually transferred.
    246 			// ...
    247 			// A response to a request for a single range MUST NOT
    248 			// be sent using the multipart/byteranges media type."
    249 			ra := ranges[0]
    250 			if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
    251 				Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
    252 				return
    253 			}
    254 			sendSize = ra.length
    255 			code = StatusPartialContent
    256 			w.Header().Set("Content-Range", ra.contentRange(size))
    257 		case len(ranges) > 1:
    258 			sendSize = rangesMIMESize(ranges, ctype, size)
    259 			code = StatusPartialContent
    260 
    261 			pr, pw := io.Pipe()
    262 			mw := multipart.NewWriter(pw)
    263 			w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
    264 			sendContent = pr
    265 			defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
    266 			go func() {
    267 				for _, ra := range ranges {
    268 					part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
    269 					if err != nil {
    270 						pw.CloseWithError(err)
    271 						return
    272 					}
    273 					if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
    274 						pw.CloseWithError(err)
    275 						return
    276 					}
    277 					if _, err := io.CopyN(part, content, ra.length); err != nil {
    278 						pw.CloseWithError(err)
    279 						return
    280 					}
    281 				}
    282 				mw.Close()
    283 				pw.Close()
    284 			}()
    285 		}
    286 
    287 		w.Header().Set("Accept-Ranges", "bytes")
    288 		if w.Header().Get("Content-Encoding") == "" {
    289 			w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
    290 		}
    291 	}
    292 
    293 	w.WriteHeader(code)
    294 
    295 	if r.Method != "HEAD" {
    296 		io.CopyN(w, sendContent, sendSize)
    297 	}
    298 }
    299 
    300 // scanETag determines if a syntactically valid ETag is present at s. If so,
    301 // the ETag and remaining text after consuming ETag is returned. Otherwise,
    302 // it returns "", "".
    303 func scanETag(s string) (etag string, remain string) {
    304 	s = textproto.TrimString(s)
    305 	start := 0
    306 	if strings.HasPrefix(s, "W/") {
    307 		start = 2
    308 	}
    309 	if len(s[start:]) < 2 || s[start] != '"' {
    310 		return "", ""
    311 	}
    312 	// ETag is either W/"text" or "text".
    313 	// See RFC 7232 2.3.
    314 	for i := start + 1; i < len(s); i++ {
    315 		c := s[i]
    316 		switch {
    317 		// Character values allowed in ETags.
    318 		case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
    319 		case c == '"':
    320 			return s[:i+1], s[i+1:]
    321 		default:
    322 			return "", ""
    323 		}
    324 	}
    325 	return "", ""
    326 }
    327 
    328 // etagStrongMatch reports whether a and b match using strong ETag comparison.
    329 // Assumes a and b are valid ETags.
    330 func etagStrongMatch(a, b string) bool {
    331 	return a == b && a != "" && a[0] == '"'
    332 }
    333 
    334 // etagWeakMatch reports whether a and b match using weak ETag comparison.
    335 // Assumes a and b are valid ETags.
    336 func etagWeakMatch(a, b string) bool {
    337 	return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
    338 }
    339 
    340 // condResult is the result of an HTTP request precondition check.
    341 // See https://tools.ietf.org/html/rfc7232 section 3.
    342 type condResult int
    343 
    344 const (
    345 	condNone condResult = iota
    346 	condTrue
    347 	condFalse
    348 )
    349 
    350 func checkIfMatch(w ResponseWriter, r *Request) condResult {
    351 	im := r.Header.Get("If-Match")
    352 	if im == "" {
    353 		return condNone
    354 	}
    355 	for {
    356 		im = textproto.TrimString(im)
    357 		if len(im) == 0 {
    358 			break
    359 		}
    360 		if im[0] == ',' {
    361 			im = im[1:]
    362 			continue
    363 		}
    364 		if im[0] == '*' {
    365 			return condTrue
    366 		}
    367 		etag, remain := scanETag(im)
    368 		if etag == "" {
    369 			break
    370 		}
    371 		if etagStrongMatch(etag, w.Header().get("Etag")) {
    372 			return condTrue
    373 		}
    374 		im = remain
    375 	}
    376 
    377 	return condFalse
    378 }
    379 
    380 func checkIfUnmodifiedSince(r *Request, modtime time.Time) condResult {
    381 	ius := r.Header.Get("If-Unmodified-Since")
    382 	if ius == "" || isZeroTime(modtime) {
    383 		return condNone
    384 	}
    385 	if t, err := ParseTime(ius); err == nil {
    386 		// The Date-Modified header truncates sub-second precision, so
    387 		// use mtime < t+1s instead of mtime <= t to check for unmodified.
    388 		if modtime.Before(t.Add(1 * time.Second)) {
    389 			return condTrue
    390 		}
    391 		return condFalse
    392 	}
    393 	return condNone
    394 }
    395 
    396 func checkIfNoneMatch(w ResponseWriter, r *Request) condResult {
    397 	inm := r.Header.get("If-None-Match")
    398 	if inm == "" {
    399 		return condNone
    400 	}
    401 	buf := inm
    402 	for {
    403 		buf = textproto.TrimString(buf)
    404 		if len(buf) == 0 {
    405 			break
    406 		}
    407 		if buf[0] == ',' {
    408 			buf = buf[1:]
    409 		}
    410 		if buf[0] == '*' {
    411 			return condFalse
    412 		}
    413 		etag, remain := scanETag(buf)
    414 		if etag == "" {
    415 			break
    416 		}
    417 		if etagWeakMatch(etag, w.Header().get("Etag")) {
    418 			return condFalse
    419 		}
    420 		buf = remain
    421 	}
    422 	return condTrue
    423 }
    424 
    425 func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
    426 	if r.Method != "GET" && r.Method != "HEAD" {
    427 		return condNone
    428 	}
    429 	ims := r.Header.Get("If-Modified-Since")
    430 	if ims == "" || isZeroTime(modtime) {
    431 		return condNone
    432 	}
    433 	t, err := ParseTime(ims)
    434 	if err != nil {
    435 		return condNone
    436 	}
    437 	// The Date-Modified header truncates sub-second precision, so
    438 	// use mtime < t+1s instead of mtime <= t to check for unmodified.
    439 	if modtime.Before(t.Add(1 * time.Second)) {
    440 		return condFalse
    441 	}
    442 	return condTrue
    443 }
    444 
    445 func checkIfRange(w ResponseWriter, r *Request, modtime time.Time) condResult {
    446 	if r.Method != "GET" && r.Method != "HEAD" {
    447 		return condNone
    448 	}
    449 	ir := r.Header.get("If-Range")
    450 	if ir == "" {
    451 		return condNone
    452 	}
    453 	etag, _ := scanETag(ir)
    454 	if etag != "" {
    455 		if etagStrongMatch(etag, w.Header().Get("Etag")) {
    456 			return condTrue
    457 		} else {
    458 			return condFalse
    459 		}
    460 	}
    461 	// The If-Range value is typically the ETag value, but it may also be
    462 	// the modtime date. See golang.org/issue/8367.
    463 	if modtime.IsZero() {
    464 		return condFalse
    465 	}
    466 	t, err := ParseTime(ir)
    467 	if err != nil {
    468 		return condFalse
    469 	}
    470 	if t.Unix() == modtime.Unix() {
    471 		return condTrue
    472 	}
    473 	return condFalse
    474 }
    475 
    476 var unixEpochTime = time.Unix(0, 0)
    477 
    478 // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
    479 func isZeroTime(t time.Time) bool {
    480 	return t.IsZero() || t.Equal(unixEpochTime)
    481 }
    482 
    483 func setLastModified(w ResponseWriter, modtime time.Time) {
    484 	if !isZeroTime(modtime) {
    485 		w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
    486 	}
    487 }
    488 
    489 func writeNotModified(w ResponseWriter) {
    490 	// RFC 7232 section 4.1:
    491 	// a sender SHOULD NOT generate representation metadata other than the
    492 	// above listed fields unless said metadata exists for the purpose of
    493 	// guiding cache updates (e.g., Last-Modified might be useful if the
    494 	// response does not have an ETag field).
    495 	h := w.Header()
    496 	delete(h, "Content-Type")
    497 	delete(h, "Content-Length")
    498 	if h.Get("Etag") != "" {
    499 		delete(h, "Last-Modified")
    500 	}
    501 	w.WriteHeader(StatusNotModified)
    502 }
    503 
    504 // checkPreconditions evaluates request preconditions and reports whether a precondition
    505 // resulted in sending StatusNotModified or StatusPreconditionFailed.
    506 func checkPreconditions(w ResponseWriter, r *Request, modtime time.Time) (done bool, rangeHeader string) {
    507 	// This function carefully follows RFC 7232 section 6.
    508 	ch := checkIfMatch(w, r)
    509 	if ch == condNone {
    510 		ch = checkIfUnmodifiedSince(r, modtime)
    511 	}
    512 	if ch == condFalse {
    513 		w.WriteHeader(StatusPreconditionFailed)
    514 		return true, ""
    515 	}
    516 	switch checkIfNoneMatch(w, r) {
    517 	case condFalse:
    518 		if r.Method == "GET" || r.Method == "HEAD" {
    519 			writeNotModified(w)
    520 			return true, ""
    521 		} else {
    522 			w.WriteHeader(StatusPreconditionFailed)
    523 			return true, ""
    524 		}
    525 	case condNone:
    526 		if checkIfModifiedSince(r, modtime) == condFalse {
    527 			writeNotModified(w)
    528 			return true, ""
    529 		}
    530 	}
    531 
    532 	rangeHeader = r.Header.get("Range")
    533 	if rangeHeader != "" && checkIfRange(w, r, modtime) == condFalse {
    534 		rangeHeader = ""
    535 	}
    536 	return false, rangeHeader
    537 }
    538 
    539 // name is '/'-separated, not filepath.Separator.
    540 func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
    541 	const indexPage = "/index.html"
    542 
    543 	// redirect .../index.html to .../
    544 	// can't use Redirect() because that would make the path absolute,
    545 	// which would be a problem running under StripPrefix
    546 	if strings.HasSuffix(r.URL.Path, indexPage) {
    547 		localRedirect(w, r, "./")
    548 		return
    549 	}
    550 
    551 	f, err := fs.Open(name)
    552 	if err != nil {
    553 		msg, code := toHTTPError(err)
    554 		Error(w, msg, code)
    555 		return
    556 	}
    557 	defer f.Close()
    558 
    559 	d, err := f.Stat()
    560 	if err != nil {
    561 		msg, code := toHTTPError(err)
    562 		Error(w, msg, code)
    563 		return
    564 	}
    565 
    566 	if redirect {
    567 		// redirect to canonical path: / at end of directory url
    568 		// r.URL.Path always begins with /
    569 		url := r.URL.Path
    570 		if d.IsDir() {
    571 			if url[len(url)-1] != '/' {
    572 				localRedirect(w, r, path.Base(url)+"/")
    573 				return
    574 			}
    575 		} else {
    576 			if url[len(url)-1] == '/' {
    577 				localRedirect(w, r, "../"+path.Base(url))
    578 				return
    579 			}
    580 		}
    581 	}
    582 
    583 	// redirect if the directory name doesn't end in a slash
    584 	if d.IsDir() {
    585 		url := r.URL.Path
    586 		if url[len(url)-1] != '/' {
    587 			localRedirect(w, r, path.Base(url)+"/")
    588 			return
    589 		}
    590 	}
    591 
    592 	// use contents of index.html for directory, if present
    593 	if d.IsDir() {
    594 		index := strings.TrimSuffix(name, "/") + indexPage
    595 		ff, err := fs.Open(index)
    596 		if err == nil {
    597 			defer ff.Close()
    598 			dd, err := ff.Stat()
    599 			if err == nil {
    600 				name = index
    601 				d = dd
    602 				f = ff
    603 			}
    604 		}
    605 	}
    606 
    607 	// Still a directory? (we didn't find an index.html file)
    608 	if d.IsDir() {
    609 		if checkIfModifiedSince(r, d.ModTime()) == condFalse {
    610 			writeNotModified(w)
    611 			return
    612 		}
    613 		w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
    614 		dirList(w, r, f)
    615 		return
    616 	}
    617 
    618 	// serveContent will check modification time
    619 	sizeFunc := func() (int64, error) { return d.Size(), nil }
    620 	serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
    621 }
    622 
    623 // toHTTPError returns a non-specific HTTP error message and status code
    624 // for a given non-nil error value. It's important that toHTTPError does not
    625 // actually return err.Error(), since msg and httpStatus are returned to users,
    626 // and historically Go's ServeContent always returned just "404 Not Found" for
    627 // all errors. We don't want to start leaking information in error messages.
    628 func toHTTPError(err error) (msg string, httpStatus int) {
    629 	if os.IsNotExist(err) {
    630 		return "404 page not found", StatusNotFound
    631 	}
    632 	if os.IsPermission(err) {
    633 		return "403 Forbidden", StatusForbidden
    634 	}
    635 	// Default:
    636 	return "500 Internal Server Error", StatusInternalServerError
    637 }
    638 
    639 // localRedirect gives a Moved Permanently response.
    640 // It does not convert relative paths to absolute paths like Redirect does.
    641 func localRedirect(w ResponseWriter, r *Request, newPath string) {
    642 	if q := r.URL.RawQuery; q != "" {
    643 		newPath += "?" + q
    644 	}
    645 	w.Header().Set("Location", newPath)
    646 	w.WriteHeader(StatusMovedPermanently)
    647 }
    648 
    649 // ServeFile replies to the request with the contents of the named
    650 // file or directory.
    651 //
    652 // If the provided file or directory name is a relative path, it is
    653 // interpreted relative to the current directory and may ascend to parent
    654 // directories. If the provided name is constructed from user input, it
    655 // should be sanitized before calling ServeFile. As a precaution, ServeFile
    656 // will reject requests where r.URL.Path contains a ".." path element.
    657 //
    658 // As a special case, ServeFile redirects any request where r.URL.Path
    659 // ends in "/index.html" to the same path, without the final
    660 // "index.html". To avoid such redirects either modify the path or
    661 // use ServeContent.
    662 func ServeFile(w ResponseWriter, r *Request, name string) {
    663 	if containsDotDot(r.URL.Path) {
    664 		// Too many programs use r.URL.Path to construct the argument to
    665 		// serveFile. Reject the request under the assumption that happened
    666 		// here and ".." may not be wanted.
    667 		// Note that name might not contain "..", for example if code (still
    668 		// incorrectly) used filepath.Join(myDir, r.URL.Path).
    669 		Error(w, "invalid URL path", StatusBadRequest)
    670 		return
    671 	}
    672 	dir, file := filepath.Split(name)
    673 	serveFile(w, r, Dir(dir), file, false)
    674 }
    675 
    676 func containsDotDot(v string) bool {
    677 	if !strings.Contains(v, "..") {
    678 		return false
    679 	}
    680 	for _, ent := range strings.FieldsFunc(v, isSlashRune) {
    681 		if ent == ".." {
    682 			return true
    683 		}
    684 	}
    685 	return false
    686 }
    687 
    688 func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
    689 
    690 type fileHandler struct {
    691 	root FileSystem
    692 }
    693 
    694 // FileServer returns a handler that serves HTTP requests
    695 // with the contents of the file system rooted at root.
    696 //
    697 // To use the operating system's file system implementation,
    698 // use http.Dir:
    699 //
    700 //     http.Handle("/", http.FileServer(http.Dir("/tmp")))
    701 //
    702 // As a special case, the returned file server redirects any request
    703 // ending in "/index.html" to the same path, without the final
    704 // "index.html".
    705 func FileServer(root FileSystem) Handler {
    706 	return &fileHandler{root}
    707 }
    708 
    709 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
    710 	upath := r.URL.Path
    711 	if !strings.HasPrefix(upath, "/") {
    712 		upath = "/" + upath
    713 		r.URL.Path = upath
    714 	}
    715 	serveFile(w, r, f.root, path.Clean(upath), true)
    716 }
    717 
    718 // httpRange specifies the byte range to be sent to the client.
    719 type httpRange struct {
    720 	start, length int64
    721 }
    722 
    723 func (r httpRange) contentRange(size int64) string {
    724 	return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
    725 }
    726 
    727 func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
    728 	return textproto.MIMEHeader{
    729 		"Content-Range": {r.contentRange(size)},
    730 		"Content-Type":  {contentType},
    731 	}
    732 }
    733 
    734 // parseRange parses a Range header string as per RFC 2616.
    735 // errNoOverlap is returned if none of the ranges overlap.
    736 func parseRange(s string, size int64) ([]httpRange, error) {
    737 	if s == "" {
    738 		return nil, nil // header not present
    739 	}
    740 	const b = "bytes="
    741 	if !strings.HasPrefix(s, b) {
    742 		return nil, errors.New("invalid range")
    743 	}
    744 	var ranges []httpRange
    745 	noOverlap := false
    746 	for _, ra := range strings.Split(s[len(b):], ",") {
    747 		ra = strings.TrimSpace(ra)
    748 		if ra == "" {
    749 			continue
    750 		}
    751 		i := strings.Index(ra, "-")
    752 		if i < 0 {
    753 			return nil, errors.New("invalid range")
    754 		}
    755 		start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
    756 		var r httpRange
    757 		if start == "" {
    758 			// If no start is specified, end specifies the
    759 			// range start relative to the end of the file.
    760 			i, err := strconv.ParseInt(end, 10, 64)
    761 			if err != nil {
    762 				return nil, errors.New("invalid range")
    763 			}
    764 			if i > size {
    765 				i = size
    766 			}
    767 			r.start = size - i
    768 			r.length = size - r.start
    769 		} else {
    770 			i, err := strconv.ParseInt(start, 10, 64)
    771 			if err != nil || i < 0 {
    772 				return nil, errors.New("invalid range")
    773 			}
    774 			if i >= size {
    775 				// If the range begins after the size of the content,
    776 				// then it does not overlap.
    777 				noOverlap = true
    778 				continue
    779 			}
    780 			r.start = i
    781 			if end == "" {
    782 				// If no end is specified, range extends to end of the file.
    783 				r.length = size - r.start
    784 			} else {
    785 				i, err := strconv.ParseInt(end, 10, 64)
    786 				if err != nil || r.start > i {
    787 					return nil, errors.New("invalid range")
    788 				}
    789 				if i >= size {
    790 					i = size - 1
    791 				}
    792 				r.length = i - r.start + 1
    793 			}
    794 		}
    795 		ranges = append(ranges, r)
    796 	}
    797 	if noOverlap && len(ranges) == 0 {
    798 		// The specified ranges did not overlap with the content.
    799 		return nil, errNoOverlap
    800 	}
    801 	return ranges, nil
    802 }
    803 
    804 // countingWriter counts how many bytes have been written to it.
    805 type countingWriter int64
    806 
    807 func (w *countingWriter) Write(p []byte) (n int, err error) {
    808 	*w += countingWriter(len(p))
    809 	return len(p), nil
    810 }
    811 
    812 // rangesMIMESize returns the number of bytes it takes to encode the
    813 // provided ranges as a multipart response.
    814 func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
    815 	var w countingWriter
    816 	mw := multipart.NewWriter(&w)
    817 	for _, ra := range ranges {
    818 		mw.CreatePart(ra.mimeHeader(contentType, contentSize))
    819 		encSize += ra.length
    820 	}
    821 	mw.Close()
    822 	encSize += int64(w)
    823 	return
    824 }
    825 
    826 func sumRangesSize(ranges []httpRange) (size int64) {
    827 	for _, ra := range ranges {
    828 		size += ra.length
    829 	}
    830 	return
    831 }
    832