github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/src/net/http/fs.go (about)

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