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