github.com/cloudwego/hertz@v0.9.3/pkg/app/fs.go (about)

     1  /*
     2   * Copyright 2022 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * The MIT License (MIT)
    17   *
    18   * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors
    19   *
    20   * Permission is hereby granted, free of charge, to any person obtaining a copy
    21   * of this software and associated documentation files (the "Software"), to deal
    22   * in the Software without restriction, including without limitation the rights
    23   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    24   * copies of the Software, and to permit persons to whom the Software is
    25   * furnished to do so, subject to the following conditions:
    26   *
    27   * The above copyright notice and this permission notice shall be included in
    28   * all copies or substantial portions of the Software.
    29   *
    30   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    31   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    32   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    33   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    34   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    35   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    36   * THE SOFTWARE.
    37   *
    38   * This file may have been modified by CloudWeGo authors. All CloudWeGo
    39   * Modifications are Copyright 2022 CloudWeGo Authors.
    40   */
    41  
    42  package app
    43  
    44  import (
    45  	"bytes"
    46  	"compress/gzip"
    47  	"context"
    48  	"fmt"
    49  	"html"
    50  	"io"
    51  	"io/ioutil"
    52  	"mime"
    53  	"net/http"
    54  	"os"
    55  	"path/filepath"
    56  	"sort"
    57  	"strings"
    58  	"sync"
    59  	"time"
    60  
    61  	"github.com/cloudwego/hertz/internal/bytesconv"
    62  	"github.com/cloudwego/hertz/internal/bytestr"
    63  	"github.com/cloudwego/hertz/internal/nocopy"
    64  	"github.com/cloudwego/hertz/pkg/common/bytebufferpool"
    65  	"github.com/cloudwego/hertz/pkg/common/compress"
    66  	"github.com/cloudwego/hertz/pkg/common/errors"
    67  	"github.com/cloudwego/hertz/pkg/common/hlog"
    68  	"github.com/cloudwego/hertz/pkg/common/utils"
    69  	"github.com/cloudwego/hertz/pkg/network"
    70  	"github.com/cloudwego/hertz/pkg/protocol"
    71  	"github.com/cloudwego/hertz/pkg/protocol/consts"
    72  )
    73  
    74  var (
    75  	errDirIndexRequired   = errors.NewPublic("directory index required")
    76  	errNoCreatePermission = errors.NewPublic("no 'create file' permissions")
    77  
    78  	rootFSOnce sync.Once
    79  	rootFS     = &FS{
    80  		Root:               "/",
    81  		GenerateIndexPages: true,
    82  		Compress:           true,
    83  		AcceptByteRange:    true,
    84  	}
    85  	rootFSHandler  HandlerFunc
    86  	strInvalidHost = []byte("invalid-host")
    87  )
    88  
    89  // PathRewriteFunc must return new request path based on arbitrary ctx
    90  // info such as ctx.Path().
    91  //
    92  // Path rewriter is used in FS for translating the current request
    93  // to the local filesystem path relative to FS.Root.
    94  //
    95  // The returned path must not contain '/../' substrings due to security reasons,
    96  // since such paths may refer files outside FS.Root.
    97  //
    98  // The returned path may refer to ctx members. For example, ctx.Path().
    99  type PathRewriteFunc func(ctx *RequestContext) []byte
   100  
   101  // FS represents settings for request handler serving static files
   102  // from the local filesystem.
   103  //
   104  // It is prohibited copying FS values. Create new values instead.
   105  type FS struct {
   106  	noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used
   107  
   108  	// Path to the root directory to serve files from.
   109  	Root string
   110  
   111  	// List of index file names to try opening during directory access.
   112  	//
   113  	// For example:
   114  	//
   115  	//     * index.html
   116  	//     * index.htm
   117  	//     * my-super-index.xml
   118  	//
   119  	// By default the list is empty.
   120  	IndexNames []string
   121  
   122  	// Index pages for directories without files matching IndexNames
   123  	// are automatically generated if set.
   124  	//
   125  	// Directory index generation may be quite slow for directories
   126  	// with many files (more than 1K), so it is discouraged enabling
   127  	// index pages' generation for such directories.
   128  	//
   129  	// By default index pages aren't generated.
   130  	GenerateIndexPages bool
   131  
   132  	// Transparently compresses responses if set to true.
   133  	//
   134  	// The server tries minimizing CPU usage by caching compressed files.
   135  	// It adds CompressedFileSuffix suffix to the original file name and
   136  	// tries saving the resulting compressed file under the new file name.
   137  	// So it is advisable to give the server write access to Root
   138  	// and to all inner folders in order to minimize CPU usage when serving
   139  	// compressed responses.
   140  	//
   141  	// Transparent compression is disabled by default.
   142  	Compress bool
   143  
   144  	// Enables byte range requests if set to true.
   145  	//
   146  	// Byte range requests are disabled by default.
   147  	AcceptByteRange bool
   148  
   149  	// Path rewriting function.
   150  	//
   151  	// By default request path is not modified.
   152  	PathRewrite PathRewriteFunc
   153  
   154  	// PathNotFound fires when file is not found in filesystem
   155  	// this functions tries to replace "Cannot open requested path"
   156  	// server response giving to the programmer the control of server flow.
   157  	//
   158  	// By default PathNotFound returns
   159  	// "Cannot open requested path"
   160  	PathNotFound HandlerFunc
   161  
   162  	// Expiration duration for inactive file handlers.
   163  	//
   164  	// FSHandlerCacheDuration is used by default.
   165  	CacheDuration time.Duration
   166  
   167  	// Suffix to add to the name of cached compressed file.
   168  	//
   169  	// This value has sense only if Compress is set.
   170  	//
   171  	// FSCompressedFileSuffix is used by default.
   172  	CompressedFileSuffix string
   173  
   174  	once sync.Once
   175  	h    HandlerFunc
   176  }
   177  
   178  type byteRangeUpdater interface {
   179  	UpdateByteRange(startPos, endPos int) error
   180  }
   181  
   182  type fsSmallFileReader struct {
   183  	ff       *fsFile
   184  	startPos int
   185  	endPos   int
   186  }
   187  
   188  func (r *fsSmallFileReader) Close() error {
   189  	ff := r.ff
   190  	ff.decReadersCount()
   191  	r.ff = nil
   192  	r.startPos = 0
   193  	r.endPos = 0
   194  	ff.h.smallFileReaderPool.Put(r)
   195  	return nil
   196  }
   197  
   198  func (r *fsSmallFileReader) UpdateByteRange(startPos, endPos int) error {
   199  	r.startPos = startPos
   200  	r.endPos = endPos + 1
   201  	return nil
   202  }
   203  
   204  func (r *fsSmallFileReader) Read(p []byte) (int, error) {
   205  	tailLen := r.endPos - r.startPos
   206  	if tailLen <= 0 {
   207  		return 0, io.EOF
   208  	}
   209  	if len(p) > tailLen {
   210  		p = p[:tailLen]
   211  	}
   212  
   213  	ff := r.ff
   214  	if ff.f != nil {
   215  		n, err := ff.f.ReadAt(p, int64(r.startPos))
   216  		r.startPos += n
   217  		return n, err
   218  	}
   219  
   220  	n := copy(p, ff.dirIndex[r.startPos:])
   221  	r.startPos += n
   222  	return n, nil
   223  }
   224  
   225  func (r *fsSmallFileReader) WriteTo(w io.Writer) (int64, error) {
   226  	ff := r.ff
   227  
   228  	var n int
   229  	var err error
   230  	if ff.f == nil {
   231  		n, err = w.Write(ff.dirIndex[r.startPos:r.endPos])
   232  		return int64(n), err
   233  	}
   234  
   235  	if rf, ok := w.(io.ReaderFrom); ok {
   236  		return rf.ReadFrom(r)
   237  	}
   238  
   239  	curPos := r.startPos
   240  	bufv := utils.CopyBufPool.Get()
   241  	buf := bufv.([]byte)
   242  	for err == nil {
   243  		tailLen := r.endPos - curPos
   244  		if tailLen <= 0 {
   245  			break
   246  		}
   247  		if len(buf) > tailLen {
   248  			buf = buf[:tailLen]
   249  		}
   250  		n, err = ff.f.ReadAt(buf, int64(curPos))
   251  		nw, errw := w.Write(buf[:n])
   252  		curPos += nw
   253  		if errw == nil && nw != n {
   254  			panic("BUG: Write(p) returned (n, nil), where n != len(p)")
   255  		}
   256  		if err == nil {
   257  			err = errw
   258  		}
   259  	}
   260  	utils.CopyBufPool.Put(bufv)
   261  
   262  	if err == io.EOF {
   263  		err = nil
   264  	}
   265  	return int64(curPos - r.startPos), err
   266  }
   267  
   268  // ServeFile returns HTTP response containing compressed file contents
   269  // from the given path.
   270  //
   271  // HTTP response may contain uncompressed file contents in the following cases:
   272  //
   273  //   - Missing 'Accept-Encoding: gzip' request header.
   274  //   - No write access to directory containing the file.
   275  //
   276  // Directory contents is returned if path points to directory.
   277  //
   278  // Use ServeFileUncompressed is you don't need serving compressed file contents.
   279  func ServeFile(ctx *RequestContext, path string) {
   280  	rootFSOnce.Do(func() {
   281  		rootFSHandler = rootFS.NewRequestHandler()
   282  	})
   283  	if len(path) == 0 || path[0] != '/' {
   284  		// extend relative path to absolute path
   285  		var err error
   286  		if path, err = filepath.Abs(path); err != nil {
   287  			hlog.SystemLogger().Errorf("Cannot resolve path=%q to absolute file error=%s", path, err)
   288  			ctx.AbortWithMsg("Internal Server Error", consts.StatusInternalServerError)
   289  			return
   290  		}
   291  	}
   292  	ctx.Request.SetRequestURI(path)
   293  	rootFSHandler(context.Background(), ctx)
   294  }
   295  
   296  // NewRequestHandler returns new request handler with the given FS settings.
   297  //
   298  // The returned handler caches requested file handles
   299  // for FS.CacheDuration.
   300  // Make sure your program has enough 'max open files' limit aka
   301  // 'ulimit -n' if FS.Root folder contains many files.
   302  //
   303  // Do not create multiple request handlers from a single FS instance -
   304  // just reuse a single request handler.
   305  func (fs *FS) NewRequestHandler() HandlerFunc {
   306  	fs.once.Do(fs.initRequestHandler)
   307  	return fs.h
   308  }
   309  
   310  func (fs *FS) initRequestHandler() {
   311  	root := fs.Root
   312  
   313  	// serve files from the current working directory if root is empty
   314  	if len(root) == 0 {
   315  		root = "."
   316  	}
   317  
   318  	// strip trailing slashes from the root path
   319  	for len(root) > 0 && root[len(root)-1] == '/' {
   320  		root = root[:len(root)-1]
   321  	}
   322  
   323  	cacheDuration := fs.CacheDuration
   324  	if cacheDuration <= 0 {
   325  		cacheDuration = consts.FSHandlerCacheDuration
   326  	}
   327  	compressedFileSuffix := fs.CompressedFileSuffix
   328  	if len(compressedFileSuffix) == 0 {
   329  		compressedFileSuffix = consts.FSCompressedFileSuffix
   330  	}
   331  
   332  	h := &fsHandler{
   333  		root:                 root,
   334  		indexNames:           fs.IndexNames,
   335  		pathRewrite:          fs.PathRewrite,
   336  		generateIndexPages:   fs.GenerateIndexPages,
   337  		compress:             fs.Compress,
   338  		pathNotFound:         fs.PathNotFound,
   339  		acceptByteRange:      fs.AcceptByteRange,
   340  		cacheDuration:        cacheDuration,
   341  		compressedFileSuffix: compressedFileSuffix,
   342  		cache:                make(map[string]*fsFile),
   343  		compressedCache:      make(map[string]*fsFile),
   344  	}
   345  
   346  	go func() {
   347  		var pendingFiles []*fsFile
   348  		for {
   349  			time.Sleep(cacheDuration / 2)
   350  			pendingFiles = h.cleanCache(pendingFiles)
   351  		}
   352  	}()
   353  
   354  	fs.h = h.handleRequest
   355  }
   356  
   357  type fsHandler struct {
   358  	root                 string
   359  	indexNames           []string
   360  	pathRewrite          PathRewriteFunc
   361  	pathNotFound         HandlerFunc
   362  	generateIndexPages   bool
   363  	compress             bool
   364  	acceptByteRange      bool
   365  	cacheDuration        time.Duration
   366  	compressedFileSuffix string
   367  
   368  	cache           map[string]*fsFile
   369  	compressedCache map[string]*fsFile
   370  	cacheLock       sync.Mutex
   371  
   372  	smallFileReaderPool sync.Pool
   373  }
   374  
   375  // bigFileReader attempts to trigger sendfile
   376  // for sending big files over the wire.
   377  type bigFileReader struct {
   378  	f  *os.File
   379  	ff *fsFile
   380  	r  io.Reader
   381  	lr io.LimitedReader
   382  }
   383  
   384  func (r *bigFileReader) UpdateByteRange(startPos, endPos int) error {
   385  	if _, err := r.f.Seek(int64(startPos), 0); err != nil {
   386  		return err
   387  	}
   388  	r.r = &r.lr
   389  	r.lr.R = r.f
   390  	r.lr.N = int64(endPos - startPos + 1)
   391  	return nil
   392  }
   393  
   394  func (r *bigFileReader) Read(p []byte) (int, error) {
   395  	return r.r.Read(p)
   396  }
   397  
   398  func (r *bigFileReader) WriteTo(w io.Writer) (int64, error) {
   399  	if rf, ok := w.(io.ReaderFrom); ok {
   400  		// fast path. Sendfile must be triggered
   401  		return rf.ReadFrom(r.r)
   402  	}
   403  	zw := network.NewWriter(w)
   404  	// slow pathw
   405  	return utils.CopyZeroAlloc(zw, r.r)
   406  }
   407  
   408  func (r *bigFileReader) Close() error {
   409  	r.r = r.f
   410  	n, err := r.f.Seek(0, 0)
   411  	if err == nil {
   412  		if n != 0 {
   413  			panic("BUG: File.Seek(0,0) returned (non-zero, nil)")
   414  		}
   415  
   416  		ff := r.ff
   417  		ff.bigFilesLock.Lock()
   418  		ff.bigFiles = append(ff.bigFiles, r)
   419  		ff.bigFilesLock.Unlock()
   420  	} else {
   421  		r.f.Close()
   422  	}
   423  	r.ff.decReadersCount()
   424  	return err
   425  }
   426  
   427  func (h *fsHandler) cleanCache(pendingFiles []*fsFile) []*fsFile {
   428  	var filesToRelease []*fsFile
   429  
   430  	h.cacheLock.Lock()
   431  
   432  	// Close files which couldn't be closed before due to non-zero
   433  	// readers count on the previous run.
   434  	var remainingFiles []*fsFile
   435  	for _, ff := range pendingFiles {
   436  		if ff.readersCount > 0 {
   437  			remainingFiles = append(remainingFiles, ff)
   438  		} else {
   439  			filesToRelease = append(filesToRelease, ff)
   440  		}
   441  	}
   442  	pendingFiles = remainingFiles
   443  
   444  	pendingFiles, filesToRelease = cleanCacheNolock(h.cache, pendingFiles, filesToRelease, h.cacheDuration)
   445  	pendingFiles, filesToRelease = cleanCacheNolock(h.compressedCache, pendingFiles, filesToRelease, h.cacheDuration)
   446  
   447  	h.cacheLock.Unlock()
   448  
   449  	for _, ff := range filesToRelease {
   450  		ff.Release()
   451  	}
   452  
   453  	return pendingFiles
   454  }
   455  
   456  func (h *fsHandler) compressAndOpenFSFile(filePath string) (*fsFile, error) {
   457  	f, err := os.Open(filePath)
   458  	if err != nil {
   459  		return nil, err
   460  	}
   461  
   462  	fileInfo, err := f.Stat()
   463  	if err != nil {
   464  		f.Close()
   465  		return nil, fmt.Errorf("cannot obtain info for file %q: %s", filePath, err)
   466  	}
   467  
   468  	if fileInfo.IsDir() {
   469  		f.Close()
   470  		return nil, errDirIndexRequired
   471  	}
   472  
   473  	if strings.HasSuffix(filePath, h.compressedFileSuffix) ||
   474  		fileInfo.Size() > consts.FsMaxCompressibleFileSize ||
   475  		!isFileCompressible(f, consts.FsMinCompressRatio) {
   476  		return h.newFSFile(f, fileInfo, false)
   477  	}
   478  
   479  	compressedFilePath := filePath + h.compressedFileSuffix
   480  	absPath, err := filepath.Abs(compressedFilePath)
   481  	if err != nil {
   482  		f.Close()
   483  		return nil, fmt.Errorf("cannot determine absolute path for %q: %s", compressedFilePath, err)
   484  	}
   485  
   486  	flock := getFileLock(absPath)
   487  	flock.Lock()
   488  	ff, err := h.compressFileNolock(f, fileInfo, filePath, compressedFilePath)
   489  	flock.Unlock()
   490  
   491  	return ff, err
   492  }
   493  
   494  func (h *fsHandler) newCompressedFSFile(filePath string) (*fsFile, error) {
   495  	f, err := os.Open(filePath)
   496  	if err != nil {
   497  		return nil, fmt.Errorf("cannot open compressed file %q: %s", filePath, err)
   498  	}
   499  	fileInfo, err := f.Stat()
   500  	if err != nil {
   501  		f.Close()
   502  		return nil, fmt.Errorf("cannot obtain info for compressed file %q: %s", filePath, err)
   503  	}
   504  	return h.newFSFile(f, fileInfo, true)
   505  }
   506  
   507  func (h *fsHandler) compressFileNolock(f *os.File, fileInfo os.FileInfo, filePath, compressedFilePath string) (*fsFile, error) {
   508  	// Attempt to open compressed file created by another concurrent
   509  	// goroutine.
   510  	// It is safe opening such a file, since the file creation
   511  	// is guarded by file mutex - see getFileLock call.
   512  	if _, err := os.Stat(compressedFilePath); err == nil {
   513  		f.Close()
   514  		return h.newCompressedFSFile(compressedFilePath)
   515  	}
   516  
   517  	// Create temporary file, so concurrent goroutines don't use
   518  	// it until it is created.
   519  	tmpFilePath := compressedFilePath + ".tmp"
   520  	zf, err := os.Create(tmpFilePath)
   521  	if err != nil {
   522  		f.Close()
   523  		if !os.IsPermission(err) {
   524  			return nil, fmt.Errorf("cannot create temporary file %q: %s", tmpFilePath, err)
   525  		}
   526  		return nil, errNoCreatePermission
   527  	}
   528  
   529  	zw := compress.AcquireStacklessGzipWriter(zf, compress.CompressDefaultCompression)
   530  	zrw := network.NewWriter(zw)
   531  	_, err = utils.CopyZeroAlloc(zrw, f)
   532  	if err1 := zw.Flush(); err == nil {
   533  		err = err1
   534  	}
   535  	compress.ReleaseStacklessGzipWriter(zw, compress.CompressDefaultCompression)
   536  	zf.Close()
   537  	f.Close()
   538  	if err != nil {
   539  		return nil, fmt.Errorf("error when compressing file %q to %q: %s", filePath, tmpFilePath, err)
   540  	}
   541  	if err = os.Chtimes(tmpFilePath, time.Now(), fileInfo.ModTime()); err != nil {
   542  		return nil, fmt.Errorf("cannot change modification time to %s for tmp file %q: %s",
   543  			fileInfo.ModTime(), tmpFilePath, err)
   544  	}
   545  	if err = os.Rename(tmpFilePath, compressedFilePath); err != nil {
   546  		return nil, fmt.Errorf("cannot move compressed file from %q to %q: %s", tmpFilePath, compressedFilePath, err)
   547  	}
   548  	return h.newCompressedFSFile(compressedFilePath)
   549  }
   550  
   551  func (h *fsHandler) openFSFile(filePath string, mustCompress bool) (*fsFile, error) {
   552  	filePathOriginal := filePath
   553  	if mustCompress {
   554  		filePath += h.compressedFileSuffix
   555  	}
   556  
   557  	f, err := os.Open(filePath)
   558  	if err != nil {
   559  		if mustCompress && os.IsNotExist(err) {
   560  			return h.compressAndOpenFSFile(filePathOriginal)
   561  		}
   562  		return nil, err
   563  	}
   564  
   565  	fileInfo, err := f.Stat()
   566  	if err != nil {
   567  		f.Close()
   568  		return nil, fmt.Errorf("cannot obtain info for file %q: %s", filePath, err)
   569  	}
   570  
   571  	if fileInfo.IsDir() {
   572  		f.Close()
   573  		if mustCompress {
   574  			return nil, fmt.Errorf("directory with unexpected suffix found: %q. Suffix: %q",
   575  				filePath, h.compressedFileSuffix)
   576  		}
   577  		return nil, errDirIndexRequired
   578  	}
   579  
   580  	if mustCompress {
   581  		fileInfoOriginal, err := os.Stat(filePathOriginal)
   582  		if err != nil {
   583  			f.Close()
   584  			return nil, fmt.Errorf("cannot obtain info for original file %q: %s", filePathOriginal, err)
   585  		}
   586  
   587  		if fileInfoOriginal.ModTime() != fileInfo.ModTime() {
   588  			// The compressed file became stale. Re-create it.
   589  			f.Close()
   590  			os.Remove(filePath)
   591  			return h.compressAndOpenFSFile(filePathOriginal)
   592  		}
   593  	}
   594  
   595  	return h.newFSFile(f, fileInfo, mustCompress)
   596  }
   597  
   598  func (h *fsHandler) newFSFile(f *os.File, fileInfo os.FileInfo, compressed bool) (*fsFile, error) {
   599  	n := fileInfo.Size()
   600  	contentLength := int(n)
   601  	if n != int64(contentLength) {
   602  		f.Close()
   603  		return nil, fmt.Errorf("too big file: %d bytes", n)
   604  	}
   605  
   606  	// detect content-type
   607  	ext := fileExtension(fileInfo.Name(), compressed, h.compressedFileSuffix)
   608  	contentType := mime.TypeByExtension(ext)
   609  	if len(contentType) == 0 {
   610  		data, err := readFileHeader(f, compressed)
   611  		if err != nil {
   612  			return nil, fmt.Errorf("cannot read header of the file %q: %s", f.Name(), err)
   613  		}
   614  		contentType = http.DetectContentType(data)
   615  	}
   616  
   617  	lastModified := fileInfo.ModTime()
   618  	ff := &fsFile{
   619  		h:               h,
   620  		f:               f,
   621  		contentType:     contentType,
   622  		contentLength:   contentLength,
   623  		compressed:      compressed,
   624  		lastModified:    lastModified,
   625  		lastModifiedStr: bytesconv.AppendHTTPDate(make([]byte, 0, len(http.TimeFormat)), lastModified),
   626  
   627  		t: time.Now(),
   628  	}
   629  	return ff, nil
   630  }
   631  
   632  func (h *fsHandler) createDirIndex(base *protocol.URI, dirPath string, mustCompress bool) (*fsFile, error) {
   633  	w := &bytebufferpool.ByteBuffer{}
   634  
   635  	basePathEscaped := html.EscapeString(string(base.Path()))
   636  	fmt.Fprintf(w, "<html><head><title>%s</title><style>.dir { font-weight: bold }</style></head><body>", basePathEscaped)
   637  	fmt.Fprintf(w, "<h1>%s</h1>", basePathEscaped)
   638  	fmt.Fprintf(w, "<ul>")
   639  
   640  	if len(basePathEscaped) > 1 {
   641  		var parentURI protocol.URI
   642  		base.CopyTo(&parentURI)
   643  		parentURI.Update(string(base.Path()) + "/..")
   644  		parentPathEscaped := html.EscapeString(string(parentURI.Path()))
   645  		fmt.Fprintf(w, `<li><a href="%s" class="dir">..</a></li>`, parentPathEscaped)
   646  	}
   647  
   648  	f, err := os.Open(dirPath)
   649  	if err != nil {
   650  		return nil, err
   651  	}
   652  
   653  	fileinfos, err := f.Readdir(0)
   654  	f.Close()
   655  	if err != nil {
   656  		return nil, err
   657  	}
   658  
   659  	fm := make(map[string]os.FileInfo, len(fileinfos))
   660  	filenames := make([]string, 0, len(fileinfos))
   661  	for _, fi := range fileinfos {
   662  		name := fi.Name()
   663  		if strings.HasSuffix(name, h.compressedFileSuffix) {
   664  			// Do not show compressed files on index page.
   665  			continue
   666  		}
   667  		fm[name] = fi
   668  		filenames = append(filenames, name)
   669  	}
   670  
   671  	var u protocol.URI
   672  	base.CopyTo(&u)
   673  	u.Update(string(u.Path()) + "/")
   674  
   675  	sort.Strings(filenames)
   676  	for _, name := range filenames {
   677  		u.Update(name)
   678  		pathEscaped := html.EscapeString(string(u.Path()))
   679  		fi := fm[name]
   680  		auxStr := "dir"
   681  		className := "dir"
   682  		if !fi.IsDir() {
   683  			auxStr = fmt.Sprintf("file, %d bytes", fi.Size())
   684  			className = "file"
   685  		}
   686  		fmt.Fprintf(w, `<li><a href="%s" class="%s">%s</a>, %s, last modified %s</li>`,
   687  			pathEscaped, className, html.EscapeString(name), auxStr, fsModTime(fi.ModTime()))
   688  	}
   689  
   690  	fmt.Fprintf(w, "</ul></body></html>")
   691  	if mustCompress {
   692  		var zbuf bytebufferpool.ByteBuffer
   693  		zbuf.B = compress.AppendGzipBytesLevel(zbuf.B, w.B, compress.CompressDefaultCompression)
   694  		w = &zbuf
   695  	}
   696  
   697  	dirIndex := w.B
   698  	lastModified := time.Now()
   699  	ff := &fsFile{
   700  		h:               h,
   701  		dirIndex:        dirIndex,
   702  		contentType:     "text/html; charset=utf-8",
   703  		contentLength:   len(dirIndex),
   704  		compressed:      mustCompress,
   705  		lastModified:    lastModified,
   706  		lastModifiedStr: bytesconv.AppendHTTPDate(make([]byte, 0, len(http.TimeFormat)), lastModified),
   707  
   708  		t: lastModified,
   709  	}
   710  	return ff, nil
   711  }
   712  
   713  func (h *fsHandler) openIndexFile(ctx *RequestContext, dirPath string, mustCompress bool) (*fsFile, error) {
   714  	for _, indexName := range h.indexNames {
   715  		indexFilePath := dirPath + "/" + indexName
   716  		ff, err := h.openFSFile(indexFilePath, mustCompress)
   717  		if err == nil {
   718  			return ff, nil
   719  		}
   720  		if !os.IsNotExist(err) {
   721  			return nil, fmt.Errorf("cannot open file %q: %s", indexFilePath, err)
   722  		}
   723  	}
   724  
   725  	if !h.generateIndexPages {
   726  		return nil, fmt.Errorf("cannot access directory without index page. Directory %q", dirPath)
   727  	}
   728  
   729  	return h.createDirIndex(ctx.URI(), dirPath, mustCompress)
   730  }
   731  
   732  func (ff *fsFile) decReadersCount() {
   733  	ff.h.cacheLock.Lock()
   734  	defer ff.h.cacheLock.Unlock()
   735  	ff.readersCount--
   736  	if ff.readersCount < 0 {
   737  		panic("BUG: negative fsFile.readersCount!")
   738  	}
   739  }
   740  
   741  func (ff *fsFile) bigFileReader() (io.Reader, error) {
   742  	if ff.f == nil {
   743  		panic("BUG: ff.f must be non-nil in bigFileReader")
   744  	}
   745  
   746  	var r io.Reader
   747  
   748  	ff.bigFilesLock.Lock()
   749  	n := len(ff.bigFiles)
   750  	if n > 0 {
   751  		r = ff.bigFiles[n-1]
   752  		ff.bigFiles = ff.bigFiles[:n-1]
   753  	}
   754  	ff.bigFilesLock.Unlock()
   755  
   756  	if r != nil {
   757  		return r, nil
   758  	}
   759  
   760  	f, err := os.Open(ff.f.Name())
   761  	if err != nil {
   762  		return nil, fmt.Errorf("cannot open already opened file: %s", err)
   763  	}
   764  	return &bigFileReader{
   765  		f:  f,
   766  		ff: ff,
   767  		r:  f,
   768  	}, nil
   769  }
   770  
   771  func (ff *fsFile) NewReader() (io.Reader, error) {
   772  	if ff.isBig() {
   773  		r, err := ff.bigFileReader()
   774  		if err != nil {
   775  			ff.decReadersCount()
   776  		}
   777  		return r, err
   778  	}
   779  	return ff.smallFileReader(), nil
   780  }
   781  
   782  func (ff *fsFile) smallFileReader() io.Reader {
   783  	v := ff.h.smallFileReaderPool.Get()
   784  	if v == nil {
   785  		v = &fsSmallFileReader{}
   786  	}
   787  	r := v.(*fsSmallFileReader)
   788  	r.ff = ff
   789  	r.endPos = ff.contentLength
   790  	if r.startPos > 0 {
   791  		panic("BUG: fsSmallFileReader with non-nil startPos found in the pool")
   792  	}
   793  	return r
   794  }
   795  
   796  func (h *fsHandler) handleRequest(c context.Context, ctx *RequestContext) {
   797  	var path []byte
   798  	if h.pathRewrite != nil {
   799  		path = h.pathRewrite(ctx)
   800  	} else {
   801  		path = ctx.Path()
   802  	}
   803  	path = stripTrailingSlashes(path)
   804  
   805  	if n := bytes.IndexByte(path, 0); n >= 0 {
   806  		hlog.SystemLogger().Errorf("Cannot serve path with nil byte at position=%d, path=%q", n, path)
   807  		ctx.AbortWithMsg("Are you a hacker?", consts.StatusBadRequest)
   808  		return
   809  	}
   810  	if h.pathRewrite != nil {
   811  		// There is no need to check for '/../' if path = ctx.Path(),
   812  		// since ctx.Path must normalize and sanitize the path.
   813  
   814  		if n := bytes.Index(path, bytestr.StrSlashDotDotSlash); n >= 0 {
   815  			hlog.SystemLogger().Errorf("Cannot serve path with '/../' at position=%d due to security reasons, path=%q", n, path)
   816  			ctx.AbortWithMsg("Internal Server Error", consts.StatusInternalServerError)
   817  			return
   818  		}
   819  	}
   820  
   821  	mustCompress := false
   822  	fileCache := h.cache
   823  	byteRange := ctx.Request.Header.PeekRange()
   824  	if len(byteRange) == 0 && h.compress && ctx.Request.Header.HasAcceptEncodingBytes(bytestr.StrGzip) {
   825  		mustCompress = true
   826  		fileCache = h.compressedCache
   827  	}
   828  
   829  	h.cacheLock.Lock()
   830  	ff, ok := fileCache[string(path)]
   831  	if ok {
   832  		ff.readersCount++
   833  	}
   834  	h.cacheLock.Unlock()
   835  
   836  	if !ok {
   837  		pathStr := string(path)
   838  		filePath := h.root + pathStr
   839  		var err error
   840  		ff, err = h.openFSFile(filePath, mustCompress)
   841  
   842  		if mustCompress && err == errNoCreatePermission {
   843  			hlog.SystemLogger().Errorf("Insufficient permissions for saving compressed file for path=%q. Serving uncompressed file. "+
   844  				"Allow write access to the directory with this file in order to improve hertz performance", filePath)
   845  			mustCompress = false
   846  			ff, err = h.openFSFile(filePath, mustCompress)
   847  		}
   848  		if err == errDirIndexRequired {
   849  			ff, err = h.openIndexFile(ctx, filePath, mustCompress)
   850  			if err != nil {
   851  				hlog.SystemLogger().Errorf("Cannot open dir index, path=%q, error=%s", filePath, err)
   852  				ctx.AbortWithMsg("Directory index is forbidden", consts.StatusForbidden)
   853  				return
   854  			}
   855  		} else if err != nil {
   856  			hlog.SystemLogger().Errorf("Cannot open file=%q, error=%s", filePath, err)
   857  			if h.pathNotFound == nil {
   858  				ctx.AbortWithMsg("Cannot open requested path", consts.StatusNotFound)
   859  			} else {
   860  				ctx.SetStatusCode(consts.StatusNotFound)
   861  				h.pathNotFound(c, ctx)
   862  			}
   863  			return
   864  		}
   865  
   866  		h.cacheLock.Lock()
   867  		ff1, ok := fileCache[pathStr]
   868  		if !ok {
   869  			fileCache[pathStr] = ff
   870  			ff.readersCount++
   871  		} else {
   872  			ff1.readersCount++
   873  		}
   874  		h.cacheLock.Unlock()
   875  
   876  		if ok {
   877  			// The file has been already opened by another
   878  			// goroutine, so close the current file and use
   879  			// the file opened by another goroutine instead.
   880  			ff.Release()
   881  			ff = ff1
   882  		}
   883  	}
   884  
   885  	if !ctx.IfModifiedSince(ff.lastModified) {
   886  		ff.decReadersCount()
   887  		ctx.NotModified()
   888  		return
   889  	}
   890  
   891  	r, err := ff.NewReader()
   892  	if err != nil {
   893  		hlog.SystemLogger().Errorf("Cannot obtain file reader for path=%q, error=%s", path, err)
   894  		ctx.AbortWithMsg("Internal Server Error", consts.StatusInternalServerError)
   895  		return
   896  	}
   897  
   898  	hdr := &ctx.Response.Header
   899  	if ff.compressed {
   900  		hdr.SetContentEncodingBytes(bytestr.StrGzip)
   901  	}
   902  
   903  	statusCode := consts.StatusOK
   904  	contentLength := ff.contentLength
   905  	if h.acceptByteRange {
   906  		hdr.SetCanonical(bytestr.StrAcceptRanges, bytestr.StrBytes)
   907  		if len(byteRange) > 0 {
   908  			startPos, endPos, err := ParseByteRange(byteRange, contentLength)
   909  			if err != nil {
   910  				r.(io.Closer).Close()
   911  				hlog.SystemLogger().Errorf("Cannot parse byte range %q for path=%q,error=%s", byteRange, path, err)
   912  				ctx.AbortWithMsg("Range Not Satisfiable", consts.StatusRequestedRangeNotSatisfiable)
   913  				return
   914  			}
   915  
   916  			if err = r.(byteRangeUpdater).UpdateByteRange(startPos, endPos); err != nil {
   917  				r.(io.Closer).Close()
   918  				hlog.SystemLogger().Errorf("Cannot seek byte range %q for path=%q, error=%s", byteRange, path, err)
   919  				ctx.AbortWithMsg("Internal Server Error", consts.StatusInternalServerError)
   920  				return
   921  			}
   922  
   923  			hdr.SetContentRange(startPos, endPos, contentLength)
   924  			contentLength = endPos - startPos + 1
   925  			statusCode = consts.StatusPartialContent
   926  		}
   927  	}
   928  
   929  	hdr.SetCanonical(bytestr.StrLastModified, ff.lastModifiedStr)
   930  	if !ctx.IsHead() {
   931  		ctx.SetBodyStream(r, contentLength)
   932  	} else {
   933  		ctx.Response.ResetBody()
   934  		ctx.Response.SkipBody = true
   935  		ctx.Response.Header.SetContentLength(contentLength)
   936  		if rc, ok := r.(io.Closer); ok {
   937  			if err := rc.Close(); err != nil {
   938  				hlog.SystemLogger().Errorf("Cannot close file reader: error=%s", err)
   939  				ctx.AbortWithMsg("Internal Server Error", consts.StatusInternalServerError)
   940  				return
   941  			}
   942  		}
   943  	}
   944  	hdr.SetNoDefaultContentType(true)
   945  	if len(hdr.ContentType()) == 0 {
   946  		ctx.SetContentType(ff.contentType)
   947  	}
   948  	ctx.SetStatusCode(statusCode)
   949  }
   950  
   951  type fsFile struct {
   952  	h             *fsHandler
   953  	f             *os.File
   954  	dirIndex      []byte
   955  	contentType   string
   956  	contentLength int
   957  	compressed    bool
   958  
   959  	lastModified    time.Time
   960  	lastModifiedStr []byte
   961  
   962  	t            time.Time
   963  	readersCount int
   964  
   965  	bigFiles     []*bigFileReader
   966  	bigFilesLock sync.Mutex
   967  }
   968  
   969  func (ff *fsFile) Release() {
   970  	if ff.f != nil {
   971  		ff.f.Close()
   972  
   973  		if ff.isBig() {
   974  			ff.bigFilesLock.Lock()
   975  			for _, r := range ff.bigFiles {
   976  				r.f.Close()
   977  			}
   978  			ff.bigFilesLock.Unlock()
   979  		}
   980  	}
   981  }
   982  
   983  func (ff *fsFile) isBig() bool {
   984  	return ff.contentLength > consts.MaxSmallFileSize && len(ff.dirIndex) == 0
   985  }
   986  
   987  func cleanCacheNolock(cache map[string]*fsFile, pendingFiles, filesToRelease []*fsFile, cacheDuration time.Duration) ([]*fsFile, []*fsFile) {
   988  	t := time.Now()
   989  	for k, ff := range cache {
   990  		if t.Sub(ff.t) > cacheDuration {
   991  			if ff.readersCount > 0 {
   992  				// There are pending readers on stale file handle,
   993  				// so we cannot close it. Put it into pendingFiles
   994  				// so it will be closed later.
   995  				pendingFiles = append(pendingFiles, ff)
   996  			} else {
   997  				filesToRelease = append(filesToRelease, ff)
   998  			}
   999  			delete(cache, k)
  1000  		}
  1001  	}
  1002  	return pendingFiles, filesToRelease
  1003  }
  1004  
  1005  func stripTrailingSlashes(path []byte) []byte {
  1006  	for len(path) > 0 && path[len(path)-1] == '/' {
  1007  		path = path[:len(path)-1]
  1008  	}
  1009  	return path
  1010  }
  1011  
  1012  func isFileCompressible(f *os.File, minCompressRatio float64) bool {
  1013  	// Try compressing the first 4kb of the file
  1014  	// and see if it can be compressed by more than
  1015  	// the given minCompressRatio.
  1016  	b := bytebufferpool.Get()
  1017  	zw := compress.AcquireStacklessGzipWriter(b, compress.CompressDefaultCompression)
  1018  	lr := &io.LimitedReader{
  1019  		R: f,
  1020  		N: 4096,
  1021  	}
  1022  	zrw := network.NewWriter(zw)
  1023  	_, err := utils.CopyZeroAlloc(zrw, lr)
  1024  	compress.ReleaseStacklessGzipWriter(zw, compress.CompressDefaultCompression)
  1025  	f.Seek(0, 0) //nolint:errcheck
  1026  	if err != nil {
  1027  		return false
  1028  	}
  1029  
  1030  	n := 4096 - lr.N
  1031  	zn := len(b.B)
  1032  	bytebufferpool.Put(b)
  1033  	return float64(zn) < float64(n)*minCompressRatio
  1034  }
  1035  
  1036  var (
  1037  	filesLockMap     = make(map[string]*sync.Mutex)
  1038  	filesLockMapLock sync.Mutex
  1039  )
  1040  
  1041  func getFileLock(absPath string) *sync.Mutex {
  1042  	filesLockMapLock.Lock()
  1043  	flock := filesLockMap[absPath]
  1044  	if flock == nil {
  1045  		flock = &sync.Mutex{}
  1046  		filesLockMap[absPath] = flock
  1047  	}
  1048  	filesLockMapLock.Unlock()
  1049  	return flock
  1050  }
  1051  
  1052  func fileExtension(path string, compressed bool, compressedFileSuffix string) string {
  1053  	if compressed && strings.HasSuffix(path, compressedFileSuffix) {
  1054  		path = path[:len(path)-len(compressedFileSuffix)]
  1055  	}
  1056  	n := strings.LastIndexByte(path, '.')
  1057  	if n < 0 {
  1058  		return ""
  1059  	}
  1060  	return path[n:]
  1061  }
  1062  
  1063  func readFileHeader(f *os.File, compressed bool) ([]byte, error) {
  1064  	r := io.Reader(f)
  1065  	var zr *gzip.Reader
  1066  	if compressed {
  1067  		var err error
  1068  		if zr, err = compress.AcquireGzipReader(f); err != nil {
  1069  			return nil, err
  1070  		}
  1071  		r = zr
  1072  	}
  1073  
  1074  	lr := &io.LimitedReader{
  1075  		R: r,
  1076  		N: 512,
  1077  	}
  1078  	data, err := ioutil.ReadAll(lr)
  1079  	if _, err := f.Seek(0, 0); err != nil {
  1080  		return nil, err
  1081  	}
  1082  
  1083  	if zr != nil {
  1084  		compress.ReleaseGzipReader(zr)
  1085  	}
  1086  
  1087  	return data, err
  1088  }
  1089  
  1090  func fsModTime(t time.Time) time.Time {
  1091  	return t.In(time.UTC).Truncate(time.Second)
  1092  }
  1093  
  1094  // ParseByteRange parses 'Range: bytes=...' header value.
  1095  //
  1096  // It follows https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 .
  1097  func ParseByteRange(byteRange []byte, contentLength int) (startPos, endPos int, err error) {
  1098  	b := byteRange
  1099  	if !bytes.HasPrefix(b, bytestr.StrBytes) {
  1100  		return 0, 0, fmt.Errorf("unsupported range units: %q. Expecting %q", byteRange, bytestr.StrBytes)
  1101  	}
  1102  
  1103  	b = b[len(bytestr.StrBytes):]
  1104  	if len(b) == 0 || b[0] != '=' {
  1105  		return 0, 0, fmt.Errorf("missing byte range in %q", byteRange)
  1106  	}
  1107  	b = b[1:]
  1108  
  1109  	n := bytes.IndexByte(b, '-')
  1110  	if n < 0 {
  1111  		return 0, 0, fmt.Errorf("missing the end position of byte range in %q", byteRange)
  1112  	}
  1113  
  1114  	if n == 0 {
  1115  		v, err := bytesconv.ParseUint(b[n+1:])
  1116  		if err != nil {
  1117  			return 0, 0, err
  1118  		}
  1119  		startPos := contentLength - v
  1120  		if startPos < 0 {
  1121  			startPos = 0
  1122  		}
  1123  		return startPos, contentLength - 1, nil
  1124  	}
  1125  
  1126  	if startPos, err = bytesconv.ParseUint(b[:n]); err != nil {
  1127  		return 0, 0, err
  1128  	}
  1129  	if startPos >= contentLength {
  1130  		return 0, 0, fmt.Errorf("the start position of byte range cannot exceed %d. byte range %q", contentLength-1, byteRange)
  1131  	}
  1132  
  1133  	b = b[n+1:]
  1134  	if len(b) == 0 {
  1135  		return startPos, contentLength - 1, nil
  1136  	}
  1137  
  1138  	if endPos, err = bytesconv.ParseUint(b); err != nil {
  1139  		return 0, 0, err
  1140  	}
  1141  	if endPos >= contentLength {
  1142  		endPos = contentLength - 1
  1143  	}
  1144  	if endPos < startPos {
  1145  		return 0, 0, fmt.Errorf("the start position of byte range cannot exceed the end position. byte range %q", byteRange)
  1146  	}
  1147  	return startPos, endPos, nil
  1148  }
  1149  
  1150  // NewVHostPathRewriter returns path rewriter, which strips slashesCount
  1151  // leading slashes from the path and prepends the path with request's host,
  1152  // thus simplifying virtual hosting for static files.
  1153  //
  1154  // Examples:
  1155  //
  1156  //   - host=foobar.com, slashesCount=0, original path="/foo/bar".
  1157  //     Resulting path: "/foobar.com/foo/bar"
  1158  //
  1159  //   - host=img.aaa.com, slashesCount=1, original path="/images/123/456.jpg"
  1160  //     Resulting path: "/img.aaa.com/123/456.jpg"
  1161  func NewVHostPathRewriter(slashesCount int) PathRewriteFunc {
  1162  	return func(ctx *RequestContext) []byte {
  1163  		path := stripLeadingSlashes(ctx.Path(), slashesCount)
  1164  		host := ctx.Host()
  1165  		if n := bytes.IndexByte(host, '/'); n >= 0 {
  1166  			host = nil
  1167  		}
  1168  		if len(host) == 0 {
  1169  			host = strInvalidHost
  1170  		}
  1171  		b := bytebufferpool.Get()
  1172  		b.B = append(b.B, '/')
  1173  		b.B = append(b.B, host...)
  1174  		b.B = append(b.B, path...)
  1175  		ctx.URI().SetPathBytes(b.B)
  1176  		bytebufferpool.Put(b)
  1177  
  1178  		return ctx.Path()
  1179  	}
  1180  }
  1181  
  1182  func stripLeadingSlashes(path []byte, stripSlashes int) []byte {
  1183  	for stripSlashes > 0 && len(path) > 0 {
  1184  		if path[0] != '/' {
  1185  			panic("BUG: path must start with slash")
  1186  		}
  1187  		n := bytes.IndexByte(path[1:], '/')
  1188  		if n < 0 {
  1189  			path = path[:0]
  1190  			break
  1191  		}
  1192  		path = path[n+1:]
  1193  		stripSlashes--
  1194  	}
  1195  	return path
  1196  }
  1197  
  1198  // ServeFileUncompressed returns HTTP response containing file contents
  1199  // from the given path.
  1200  //
  1201  // Directory contents is returned if path points to directory.
  1202  //
  1203  // ServeFile may be used for saving network traffic when serving files
  1204  // with good compression ratio.
  1205  func ServeFileUncompressed(ctx *RequestContext, path string) {
  1206  	ctx.Request.Header.DelBytes(bytestr.StrAcceptEncoding)
  1207  	ServeFile(ctx, path)
  1208  }
  1209  
  1210  // NewPathSlashesStripper returns path rewriter, which strips slashesCount
  1211  // leading slashes from the path.
  1212  //
  1213  // Examples:
  1214  //
  1215  //   - slashesCount = 0, original path: "/foo/bar", result: "/foo/bar"
  1216  //   - slashesCount = 1, original path: "/foo/bar", result: "/bar"
  1217  //   - slashesCount = 2, original path: "/foo/bar", result: ""
  1218  //
  1219  // The returned path rewriter may be used as FS.PathRewrite .
  1220  func NewPathSlashesStripper(slashesCount int) PathRewriteFunc {
  1221  	return func(ctx *RequestContext) []byte {
  1222  		return stripLeadingSlashes(ctx.Path(), slashesCount)
  1223  	}
  1224  }