fs.go 39 KB

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