pax_global_header00006660000000000000000000000064147713671260014530gustar00rootroot0000000000000052 comment=4eb26be6ced8705e491aedcb387fa2e9e45baf12 expressjs-serve-static-83e72ac/000077500000000000000000000000001477136712600166035ustar00rootroot00000000000000expressjs-serve-static-83e72ac/.eslintignore000066400000000000000000000000261477136712600213040ustar00rootroot00000000000000coverage node_modules expressjs-serve-static-83e72ac/.eslintrc.yml000066400000000000000000000002321477136712600212240ustar00rootroot00000000000000root: true extends: - standard - plugin:markdown/recommended plugins: - markdown overrides: - files: '**/*.md' processor: 'markdown/markdown' expressjs-serve-static-83e72ac/.github/000077500000000000000000000000001477136712600201435ustar00rootroot00000000000000expressjs-serve-static-83e72ac/.github/workflows/000077500000000000000000000000001477136712600222005ustar00rootroot00000000000000expressjs-serve-static-83e72ac/.github/workflows/ci.yml000066400000000000000000000046141477136712600233230ustar00rootroot00000000000000name: ci on: push: branches: - master paths-ignore: - '*.md' pull_request: paths-ignore: - '*.md' permissions: contents: read # Cancel in progress workflows # in the scenario where we already had a run going for that PR/branch/tag but then triggered a new run concurrency: group: "${{ github.workflow }} ✨ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" cancel-in-progress: true jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "lts/*" - name: Install dependencies run: npm install --ignore-scripts --include=dev - name: Run lint run: npm run lint test: name: Test - Node.js ${{ matrix.node-version }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] # Node.js release schedule: https://nodejs.org/en/about/releases/ node-version: [18, 19, 20, 21, 22, 23] steps: - uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: check-latest: true node-version: ${{ matrix.node-version }} - name: Configure npm loglevel run: npm config set loglevel error - name: Install dependencies run: npm install - name: Run tests run: npm run test-ci - name: Upload code coverage uses: actions/upload-artifact@v4 with: name: coverage-node-${{ matrix.node-version }}-${{ matrix.os }} path: ./coverage/lcov.info retention-days: 1 coverage: needs: test runs-on: ubuntu-latest permissions: contents: read checks: write steps: - uses: actions/checkout@v4 - name: Install lcov run: sudo apt-get -y install lcov - name: Collect coverage reports uses: actions/download-artifact@v4 with: path: ./coverage pattern: coverage-node-* - name: Merge coverage reports run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./lcov.info - name: Upload coverage report uses: coverallsapp/github-action@v2 with: file: ./lcov.info expressjs-serve-static-83e72ac/.github/workflows/codeql.yml000066400000000000000000000046441477136712600242020ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: ["master"] pull_request: # The branches below must be a subset of the branches above branches: ["master"] schedule: - cron: "0 0 * * 1" permissions: contents: read jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write steps: - name: Checkout repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 with: languages: javascript # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) # - name: Autobuild # uses: github/codeql-action/autobuild@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 with: category: "/language:javascript"expressjs-serve-static-83e72ac/.github/workflows/scorecard.yml000066400000000000000000000054521477136712600246760ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '16 21 * * 1' push: branches: [ "master" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write steps: - name: "Checkout code" uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2.23.2 with: sarif_file: results.sarif expressjs-serve-static-83e72ac/.gitignore000066400000000000000000000001051477136712600205670ustar00rootroot00000000000000.nyc_output/ coverage/ node_modules/ npm-debug.log package-lock.json expressjs-serve-static-83e72ac/HISTORY.md000066400000000000000000000262471477136712600203010ustar00rootroot000000000000002.2.0 / 2025-03-27 ================== * deps: send@^1.2.0 2.1.0 / 2024-09-10 =================== * Changes from 1.16.0 * deps: send@^1.2.0 2.0.0 / 2024-08-23 ================== * deps: * parseurl@^1.3.3 * excape-html@^1.0.3 * encodeurl@^2.0.0 * supertest@^6.3.4 * safe-buffer@^5.2.1 * nyc@^17.0.0 * mocha@^10.7.0 * Changes from 1.x 2.0.0-beta.2 / 2024-03-20 ========================= * deps: send@1.0.0-beta.2 2.0.0-beta.1 / 2022-02-05 ========================= * Change `dotfiles` option default to `'ignore'` * Drop support for Node.js 0.8 * Remove `hidden` option; use `dotfiles` option instead * Remove `mime` export; use `mime-types` package instead * deps: send@1.0.0-beta.1 - Use `mime-types` for file to content type mapping - deps: debug@3.1.0 1.16.0 / 2024-09-10 =================== * Remove link renderization in html while redirecting 1.15.0 / 2022-03-24 =================== * deps: send@0.18.0 - Fix emitted 416 error missing headers property - Limit the headers removed for 304 response - deps: depd@2.0.0 - deps: destroy@1.2.0 - deps: http-errors@2.0.0 - deps: on-finished@2.4.1 - deps: statuses@2.0.1 1.14.2 / 2021-12-15 =================== * deps: send@0.17.2 - deps: http-errors@1.8.1 - deps: ms@2.1.3 - pref: ignore empty http tokens 1.14.1 / 2019-05-10 =================== * Set stricter CSP header in redirect response * deps: send@0.17.1 - deps: range-parser@~1.2.1 1.14.0 / 2019-05-07 =================== * deps: parseurl@~1.3.3 * deps: send@0.17.0 - deps: http-errors@~1.7.2 - deps: mime@1.6.0 - deps: ms@2.1.1 - deps: statuses@~1.5.0 - perf: remove redundant `path.normalize` call 1.13.2 / 2018-02-07 =================== * Fix incorrect end tag in redirects * deps: encodeurl@~1.0.2 - Fix encoding `%` as last character * deps: send@0.16.2 - deps: depd@~1.1.2 - deps: encodeurl@~1.0.2 - deps: statuses@~1.4.0 1.13.1 / 2017-09-29 =================== * Fix regression when `root` is incorrectly set to a file * deps: send@0.16.1 1.13.0 / 2017-09-27 =================== * deps: send@0.16.0 - Add 70 new types for file extensions - Add `immutable` option - Fix missing `` in default error & redirects - Set charset as "UTF-8" for .js and .json - Use instance methods on steam to check for listeners - deps: mime@1.4.1 - perf: improve path validation speed 1.12.6 / 2017-09-22 =================== * deps: send@0.15.6 - deps: debug@2.6.9 - perf: improve `If-Match` token parsing * perf: improve slash collapsing 1.12.5 / 2017-09-21 =================== * deps: parseurl@~1.3.2 - perf: reduce overhead for full URLs - perf: unroll the "fast-path" `RegExp` * deps: send@0.15.5 - Fix handling of modified headers with invalid dates - deps: etag@~1.8.1 - deps: fresh@0.5.2 1.12.4 / 2017-08-05 =================== * deps: send@0.15.4 - deps: debug@2.6.8 - deps: depd@~1.1.1 - deps: http-errors@~1.6.2 1.12.3 / 2017-05-16 =================== * deps: send@0.15.3 - deps: debug@2.6.7 1.12.2 / 2017-04-26 =================== * deps: send@0.15.2 - deps: debug@2.6.4 1.12.1 / 2017-03-04 =================== * deps: send@0.15.1 - Fix issue when `Date.parse` does not return `NaN` on invalid date - Fix strict violation in broken environments 1.12.0 / 2017-02-25 =================== * Send complete HTML document in redirect response * Set default CSP header in redirect response * deps: send@0.15.0 - Fix false detection of `no-cache` request directive - Fix incorrect result when `If-None-Match` has both `*` and ETags - Fix weak `ETag` matching to match spec - Remove usage of `res._headers` private field - Support `If-Match` and `If-Unmodified-Since` headers - Use `res.getHeaderNames()` when available - Use `res.headersSent` when available - deps: debug@2.6.1 - deps: etag@~1.8.0 - deps: fresh@0.5.0 - deps: http-errors@~1.6.1 1.11.2 / 2017-01-23 =================== * deps: send@0.14.2 - deps: http-errors@~1.5.1 - deps: ms@0.7.2 - deps: statuses@~1.3.1 1.11.1 / 2016-06-10 =================== * Fix redirect error when `req.url` contains raw non-URL characters * deps: send@0.14.1 1.11.0 / 2016-06-07 =================== * Use status code 301 for redirects * deps: send@0.14.0 - Add `acceptRanges` option - Add `cacheControl` option - Attempt to combine multiple ranges into single range - Correctly inherit from `Stream` class - Fix `Content-Range` header in 416 responses when using `start`/`end` options - Fix `Content-Range` header missing from default 416 responses - Ignore non-byte `Range` headers - deps: http-errors@~1.5.0 - deps: range-parser@~1.2.0 - deps: statuses@~1.3.0 - perf: remove argument reassignment 1.10.3 / 2016-05-30 =================== * deps: send@0.13.2 - Fix invalid `Content-Type` header when `send.mime.default_type` unset 1.10.2 / 2016-01-19 =================== * deps: parseurl@~1.3.1 - perf: enable strict mode 1.10.1 / 2016-01-16 =================== * deps: escape-html@~1.0.3 - perf: enable strict mode - perf: optimize string replacement - perf: use faster string coercion * deps: send@0.13.1 - deps: depd@~1.1.0 - deps: destroy@~1.0.4 - deps: escape-html@~1.0.3 - deps: range-parser@~1.0.3 1.10.0 / 2015-06-17 =================== * Add `fallthrough` option - Allows declaring this middleware is the final destination - Provides better integration with Express patterns * Fix reading options from options prototype * Improve the default redirect response headers * deps: escape-html@1.0.2 * deps: send@0.13.0 - Allow Node.js HTTP server to set `Date` response header - Fix incorrectly removing `Content-Location` on 304 response - Improve the default redirect response headers - Send appropriate headers on default error response - Use `http-errors` for standard emitted errors - Use `statuses` instead of `http` module for status messages - deps: escape-html@1.0.2 - deps: etag@~1.7.0 - deps: fresh@0.3.0 - deps: on-finished@~2.3.0 - perf: enable strict mode - perf: remove unnecessary array allocations * perf: enable strict mode * perf: remove argument reassignment 1.9.3 / 2015-05-14 ================== * deps: send@0.12.3 - deps: debug@~2.2.0 - deps: depd@~1.0.1 - deps: etag@~1.6.0 - deps: ms@0.7.1 - deps: on-finished@~2.2.1 1.9.2 / 2015-03-14 ================== * deps: send@0.12.2 - Throw errors early for invalid `extensions` or `index` options - deps: debug@~2.1.3 1.9.1 / 2015-02-17 ================== * deps: send@0.12.1 - Fix regression sending zero-length files 1.9.0 / 2015-02-16 ================== * deps: send@0.12.0 - Always read the stat size from the file - Fix mutating passed-in `options` - deps: mime@1.3.4 1.8.1 / 2015-01-20 ================== * Fix redirect loop in Node.js 0.11.14 * deps: send@0.11.1 - Fix root path disclosure 1.8.0 / 2015-01-05 ================== * deps: send@0.11.0 - deps: debug@~2.1.1 - deps: etag@~1.5.1 - deps: ms@0.7.0 - deps: on-finished@~2.2.0 1.7.2 / 2015-01-02 ================== * Fix potential open redirect when mounted at root 1.7.1 / 2014-10-22 ================== * deps: send@0.10.1 - deps: on-finished@~2.1.1 1.7.0 / 2014-10-15 ================== * deps: send@0.10.0 - deps: debug@~2.1.0 - deps: depd@~1.0.0 - deps: etag@~1.5.0 1.6.5 / 2015-02-04 ================== * Fix potential open redirect when mounted at root - Back-ported from v1.7.2 1.6.4 / 2014-10-08 ================== * Fix redirect loop when index file serving disabled 1.6.3 / 2014-09-24 ================== * deps: send@0.9.3 - deps: etag@~1.4.0 1.6.2 / 2014-09-15 ================== * deps: send@0.9.2 - deps: depd@0.4.5 - deps: etag@~1.3.1 - deps: range-parser@~1.0.2 1.6.1 / 2014-09-07 ================== * deps: send@0.9.1 - deps: fresh@0.2.4 1.6.0 / 2014-09-07 ================== * deps: send@0.9.0 - Add `lastModified` option - Use `etag` to generate `ETag` header - deps: debug@~2.0.0 1.5.4 / 2014-09-04 ================== * deps: send@0.8.5 - Fix a path traversal issue when using `root` - Fix malicious path detection for empty string path 1.5.3 / 2014-08-17 ================== * deps: send@0.8.3 1.5.2 / 2014-08-14 ================== * deps: send@0.8.2 - Work around `fd` leak in Node.js 0.10 for `fs.ReadStream` 1.5.1 / 2014-08-09 ================== * Fix parsing of weird `req.originalUrl` values * deps: parseurl@~1.3.0 * deps: utils-merge@1.0.0 1.5.0 / 2014-08-05 ================== * deps: send@0.8.1 - Add `extensions` option 1.4.4 / 2014-08-04 ================== * deps: send@0.7.4 - Fix serving index files without root dir 1.4.3 / 2014-07-29 ================== * deps: send@0.7.3 - Fix incorrect 403 on Windows and Node.js 0.11 1.4.2 / 2014-07-27 ================== * deps: send@0.7.2 - deps: depd@0.4.4 1.4.1 / 2014-07-26 ================== * deps: send@0.7.1 - deps: depd@0.4.3 1.4.0 / 2014-07-21 ================== * deps: parseurl@~1.2.0 - Cache URLs based on original value - Remove no-longer-needed URL mis-parse work-around - Simplify the "fast-path" `RegExp` * deps: send@0.7.0 - Add `dotfiles` option - deps: debug@1.0.4 - deps: depd@0.4.2 1.3.2 / 2014-07-11 ================== * deps: send@0.6.0 - Cap `maxAge` value to 1 year - deps: debug@1.0.3 1.3.1 / 2014-07-09 ================== * deps: parseurl@~1.1.3 - faster parsing of href-only URLs 1.3.0 / 2014-06-28 ================== * Add `setHeaders` option * Include HTML link in redirect response * deps: send@0.5.0 - Accept string for `maxAge` (converted by `ms`) 1.2.3 / 2014-06-11 ================== * deps: send@0.4.3 - Do not throw un-catchable error on file open race condition - Use `escape-html` for HTML escaping - deps: debug@1.0.2 - deps: finished@1.2.2 - deps: fresh@0.2.2 1.2.2 / 2014-06-09 ================== * deps: send@0.4.2 - fix "event emitter leak" warnings - deps: debug@1.0.1 - deps: finished@1.2.1 1.2.1 / 2014-06-02 ================== * use `escape-html` for escaping * deps: send@0.4.1 - Send `max-age` in `Cache-Control` in correct format 1.2.0 / 2014-05-29 ================== * deps: send@0.4.0 - Calculate ETag with md5 for reduced collisions - Fix wrong behavior when index file matches directory - Ignore stream errors after request ends - Skip directories in index file search - deps: debug@0.8.1 1.1.0 / 2014-04-24 ================== * Accept options directly to `send` module * deps: send@0.3.0 1.0.4 / 2014-04-07 ================== * Resolve relative paths at middleware setup * Use parseurl to parse the URL from request 1.0.3 / 2014-03-20 ================== * Do not rely on connect-like environments 1.0.2 / 2014-03-06 ================== * deps: send@0.2.0 1.0.1 / 2014-03-05 ================== * Add mime export for back-compat 1.0.0 / 2014-03-05 ================== * Genesis from `connect` expressjs-serve-static-83e72ac/LICENSE000066400000000000000000000022451477136712600176130ustar00rootroot00000000000000(The MIT License) Copyright (c) 2010 Sencha Inc. Copyright (c) 2011 LearnBoost Copyright (c) 2011 TJ Holowaychuk Copyright (c) 2014-2016 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. expressjs-serve-static-83e72ac/README.md000066400000000000000000000164321477136712600200700ustar00rootroot00000000000000# serve-static [![NPM Version][npm-version-image]][npm-url] [![NPM Downloads][npm-downloads-image]][npm-url] [![CI][github-actions-ci-image]][github-actions-ci-url] [![Test Coverage][coveralls-image]][coveralls-url] ## Install This is a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/). Installation is done using the [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): ```sh $ npm install serve-static ``` ## API ```js var serveStatic = require('serve-static') ``` ### serveStatic(root, options) Create a new middleware function to serve files from within a given root directory. The file to serve will be determined by combining `req.url` with the provided root directory. When a file is not found, instead of sending a 404 response, this module will instead call `next()` to move on to the next middleware, allowing for stacking and fall-backs. #### Options ##### acceptRanges Enable or disable accepting ranged requests, defaults to true. Disabling this will not send `Accept-Ranges` and ignore the contents of the `Range` request header. ##### cacheControl Enable or disable setting `Cache-Control` response header, defaults to true. Disabling this will ignore the `immutable` and `maxAge` options. ##### dotfiles Set how "dotfiles" are treated when encountered. A dotfile is a file or directory that begins with a dot ("."). Note this check is done on the path itself without checking if the path actually exists on the disk. If `root` is specified, only the dotfiles above the root are checked (i.e. the root itself can be within a dotfile when set to "deny"). - `'allow'` No special treatment for dotfiles. - `'deny'` Deny a request for a dotfile and 403/`next()`. - `'ignore'` Pretend like the dotfile does not exist and 404/`next()`. The default value is `'ignore'`. ##### etag Enable or disable etag generation, defaults to true. ##### extensions Set file extension fallbacks. When set, if a file is not found, the given extensions will be added to the file name and search for. The first that exists will be served. Example: `['html', 'htm']`. The default value is `false`. ##### fallthrough Set the middleware to have client errors fall-through as just unhandled requests, otherwise forward a client error. The difference is that client errors like a bad request or a request to a non-existent file will cause this middleware to simply `next()` to your next middleware when this value is `true`. When this value is `false`, these errors (even 404s), will invoke `next(err)`. Typically `true` is desired such that multiple physical directories can be mapped to the same web address or for routes to fill in non-existent files. The value `false` can be used if this middleware is mounted at a path that is designed to be strictly a single file system directory, which allows for short-circuiting 404s for less overhead. This middleware will also reply to all methods. The default value is `true`. ##### immutable Enable or disable the `immutable` directive in the `Cache-Control` response header, defaults to `false`. If set to `true`, the `maxAge` option should also be specified to enable caching. The `immutable` directive will prevent supported clients from making conditional requests during the life of the `maxAge` option to check if the file has changed. ##### index By default this module will send "index.html" files in response to a request on a directory. To disable this set `false` or to supply a new index pass a string or an array in preferred order. ##### lastModified Enable or disable `Last-Modified` header, defaults to true. Uses the file system's last modified value. ##### maxAge Provide a max-age in milliseconds for http caching, defaults to 0. This can also be a string accepted by the [ms](https://www.npmjs.org/package/ms#readme) module. ##### redirect Redirect to trailing "/" when the pathname is a dir. Defaults to `true`. ##### setHeaders Function to set custom headers on response. Alterations to the headers need to occur synchronously. The function is called as `fn(res, path, stat)`, where the arguments are: - `res` the response object - `path` the file path that is being sent - `stat` the stat object of the file that is being sent ## Examples ### Serve files with vanilla node.js http server ```js var finalhandler = require('finalhandler') var http = require('http') var serveStatic = require('serve-static') // Serve up public/ftp folder var serve = serveStatic('public/ftp', { index: ['index.html', 'index.htm'] }) // Create server var server = http.createServer(function onRequest (req, res) { serve(req, res, finalhandler(req, res)) }) // Listen server.listen(3000) ``` ### Serve all files as downloads ```js var contentDisposition = require('content-disposition') var finalhandler = require('finalhandler') var http = require('http') var serveStatic = require('serve-static') // Serve up public/ftp folder var serve = serveStatic('public/ftp', { index: false, setHeaders: setHeaders }) // Set header to force download function setHeaders (res, path) { res.setHeader('Content-Disposition', contentDisposition(path)) } // Create server var server = http.createServer(function onRequest (req, res) { serve(req, res, finalhandler(req, res)) }) // Listen server.listen(3000) ``` ### Serving using express #### Simple This is a simple example of using Express. ```js var express = require('express') var serveStatic = require('serve-static') var app = express() app.use(serveStatic('public/ftp', { index: ['default.html', 'default.htm'] })) app.listen(3000) ``` #### Multiple roots This example shows a simple way to search through multiple directories. Files are searched for in `public-optimized/` first, then `public/` second as a fallback. ```js var express = require('express') var path = require('path') var serveStatic = require('serve-static') var app = express() app.use(serveStatic(path.join(__dirname, 'public-optimized'))) app.use(serveStatic(path.join(__dirname, 'public'))) app.listen(3000) ``` #### Different settings for paths This example shows how to set a different max age depending on the served file. In this example, HTML files are not cached, while everything else is for 1 day. ```js var express = require('express') var path = require('path') var serveStatic = require('serve-static') var app = express() app.use(serveStatic(path.join(__dirname, 'public'), { maxAge: '1d', setHeaders: setCustomCacheControl })) app.listen(3000) function setCustomCacheControl (res, file) { if (path.extname(file) === '.html') { // Custom Cache-Control for HTML files res.setHeader('Cache-Control', 'public, max-age=0') } } ``` ## License [MIT](LICENSE) [coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/serve-static/master [coveralls-url]: https://coveralls.io/r/expressjs/serve-static?branch=master [github-actions-ci-image]: https://badgen.net/github/checks/expressjs/serve-static/master?label=linux [github-actions-ci-url]: https://github.com/expressjs/serve-static/actions/workflows/ci.yml [node-image]: https://badgen.net/npm/node/serve-static [node-url]: https://nodejs.org/en/download/ [npm-downloads-image]: https://badgen.net/npm/dm/serve-static [npm-url]: https://npmjs.org/package/serve-static [npm-version-image]: https://badgen.net/npm/v/serve-static expressjs-serve-static-83e72ac/index.js000066400000000000000000000106111477136712600202470ustar00rootroot00000000000000/*! * serve-static * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014-2016 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * Module dependencies. * @private */ var encodeUrl = require('encodeurl') var escapeHtml = require('escape-html') var parseUrl = require('parseurl') var resolve = require('path').resolve var send = require('send') var url = require('url') /** * Module exports. * @public */ module.exports = serveStatic /** * @param {string} root * @param {object} [options] * @return {function} * @public */ function serveStatic (root, options) { if (!root) { throw new TypeError('root path required') } if (typeof root !== 'string') { throw new TypeError('root path must be a string') } // copy options object var opts = Object.create(options || null) // fall-though var fallthrough = opts.fallthrough !== false // default redirect var redirect = opts.redirect !== false // headers listener var setHeaders = opts.setHeaders if (setHeaders && typeof setHeaders !== 'function') { throw new TypeError('option setHeaders must be function') } // setup options for send opts.maxage = opts.maxage || opts.maxAge || 0 opts.root = resolve(root) // construct directory listener var onDirectory = redirect ? createRedirectDirectoryListener() : createNotFoundDirectoryListener() return function serveStatic (req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { if (fallthrough) { return next() } // method not allowed res.statusCode = 405 res.setHeader('Allow', 'GET, HEAD') res.setHeader('Content-Length', '0') res.end() return } var forwardError = !fallthrough var originalUrl = parseUrl.original(req) var path = parseUrl(req).pathname // make sure redirect occurs at mount if (path === '/' && originalUrl.pathname.substr(-1) !== '/') { path = '' } // create send stream var stream = send(req, path, opts) // add directory handler stream.on('directory', onDirectory) // add headers listener if (setHeaders) { stream.on('headers', setHeaders) } // add file listener for fallthrough if (fallthrough) { stream.on('file', function onFile () { // once file is determined, always forward error forwardError = true }) } // forward errors stream.on('error', function error (err) { if (forwardError || !(err.statusCode < 500)) { next(err) return } next() }) // pipe stream.pipe(res) } } /** * Collapse all leading slashes into a single slash * @private */ function collapseLeadingSlashes (str) { for (var i = 0; i < str.length; i++) { if (str.charCodeAt(i) !== 0x2f /* / */) { break } } return i > 1 ? '/' + str.substr(i) : str } /** * Create a minimal HTML document. * * @param {string} title * @param {string} body * @private */ function createHtmlDocument (title, body) { return '\n' + '\n' + '\n' + '\n' + '' + title + '\n' + '\n' + '\n' + '
' + body + '
\n' + '\n' + '\n' } /** * Create a directory listener that just 404s. * @private */ function createNotFoundDirectoryListener () { return function notFound () { this.error(404) } } /** * Create a directory listener that performs a redirect. * @private */ function createRedirectDirectoryListener () { return function redirect (res) { if (this.hasTrailingSlash()) { this.error(404) return } // get original URL var originalUrl = parseUrl.original(this.req) // append trailing slash originalUrl.path = null originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/') // reformat the URL var loc = encodeUrl(url.format(originalUrl)) var doc = createHtmlDocument('Redirecting', 'Redirecting to ' + escapeHtml(loc)) // send redirect response res.statusCode = 301 res.setHeader('Content-Type', 'text/html; charset=UTF-8') res.setHeader('Content-Length', Buffer.byteLength(doc)) res.setHeader('Content-Security-Policy', "default-src 'none'") res.setHeader('X-Content-Type-Options', 'nosniff') res.setHeader('Location', loc) res.end(doc) } } expressjs-serve-static-83e72ac/package.json000066400000000000000000000021311477136712600210660ustar00rootroot00000000000000{ "name": "serve-static", "description": "Serve static files", "version": "2.2.0", "author": "Douglas Christopher Wilson ", "license": "MIT", "repository": "expressjs/serve-static", "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" }, "devDependencies": { "eslint": "7.32.0", "eslint-config-standard": "14.1.1", "eslint-plugin-import": "2.25.4", "eslint-plugin-markdown": "2.2.1", "eslint-plugin-node": "11.1.0", "eslint-plugin-promise": "5.2.0", "eslint-plugin-standard": "4.1.0", "mocha": "^10.7.0", "nyc": "^17.0.0", "supertest": "^6.3.4" }, "files": [ "LICENSE", "HISTORY.md", "index.js" ], "engines": { "node": ">= 18" }, "scripts": { "lint": "eslint .", "test": "mocha --reporter spec --bail --check-leaks test/", "test-ci": "nyc --reporter=lcov --reporter=text npm test", "test-cov": "nyc --reporter=html --reporter=text npm test", "version": "node scripts/version-history.js && git add HISTORY.md" } } expressjs-serve-static-83e72ac/scripts/000077500000000000000000000000001477136712600202725ustar00rootroot00000000000000expressjs-serve-static-83e72ac/scripts/version-history.js000066400000000000000000000025211477136712600240140ustar00rootroot00000000000000'use strict' var fs = require('fs') var path = require('path') var HISTORY_FILE_PATH = path.join(__dirname, '..', 'HISTORY.md') var MD_HEADER_REGEXP = /^====*$/ var VERSION = process.env.npm_package_version var VERSION_PLACEHOLDER_REGEXP = /^(?:unreleased|(\d+\.)+x)$/ var historyFileLines = fs.readFileSync(HISTORY_FILE_PATH, 'utf-8').split('\n') if (!MD_HEADER_REGEXP.test(historyFileLines[1])) { console.error('Missing header in HISTORY.md') process.exit(1) } if (!VERSION_PLACEHOLDER_REGEXP.test(historyFileLines[0])) { console.error('Missing placegolder version in HISTORY.md') process.exit(1) } if (historyFileLines[0].indexOf('x') !== -1) { var versionCheckRegExp = new RegExp('^' + historyFileLines[0].replace('x', '.+') + '$') if (!versionCheckRegExp.test(VERSION)) { console.error('Version %s does not match placeholder %s', VERSION, historyFileLines[0]) process.exit(1) } } historyFileLines[0] = VERSION + ' / ' + getLocaleDate() historyFileLines[1] = '='.repeat(historyFileLines[0].length) fs.writeFileSync(HISTORY_FILE_PATH, historyFileLines.join('\n')) function getLocaleDate () { var now = new Date() return zeroPad(now.getFullYear(), 4) + '-' + zeroPad(now.getMonth() + 1, 2) + '-' + zeroPad(now.getDate(), 2) } function zeroPad (number, length) { return number.toString().padStart(length, '0') } expressjs-serve-static-83e72ac/test/000077500000000000000000000000001477136712600175625ustar00rootroot00000000000000expressjs-serve-static-83e72ac/test/.eslintrc000066400000000000000000000000451477136712600214050ustar00rootroot00000000000000{ "env": { "mocha": true } } expressjs-serve-static-83e72ac/test/fixtures/000077500000000000000000000000001477136712600214335ustar00rootroot00000000000000expressjs-serve-static-83e72ac/test/fixtures/.hidden000066400000000000000000000000131477136712600226610ustar00rootroot00000000000000I am hiddenexpressjs-serve-static-83e72ac/test/fixtures/empty.txt000066400000000000000000000000001477136712600233200ustar00rootroot00000000000000expressjs-serve-static-83e72ac/test/fixtures/foo bar000066400000000000000000000000031477136712600226570ustar00rootroot00000000000000bazexpressjs-serve-static-83e72ac/test/fixtures/nums.txt000066400000000000000000000000111477136712600231460ustar00rootroot00000000000000123456789expressjs-serve-static-83e72ac/test/fixtures/pets/000077500000000000000000000000001477136712600224065ustar00rootroot00000000000000expressjs-serve-static-83e72ac/test/fixtures/pets/names.txt000066400000000000000000000000111477136712600242420ustar00rootroot00000000000000tobi,lokiexpressjs-serve-static-83e72ac/test/fixtures/snow ☃/000077500000000000000000000000001477136712600234565ustar00rootroot00000000000000expressjs-serve-static-83e72ac/test/fixtures/snow ☃/.gitkeep000066400000000000000000000000001477136712600250750ustar00rootroot00000000000000expressjs-serve-static-83e72ac/test/fixtures/todo.html000066400000000000000000000000221477136712600232600ustar00rootroot00000000000000
  • groceries
  • expressjs-serve-static-83e72ac/test/fixtures/todo.txt000066400000000000000000000000131477136712600231330ustar00rootroot00000000000000- groceriesexpressjs-serve-static-83e72ac/test/fixtures/users/000077500000000000000000000000001477136712600225745ustar00rootroot00000000000000expressjs-serve-static-83e72ac/test/fixtures/users/index.html000066400000000000000000000000271477136712600245700ustar00rootroot00000000000000

    tobi, loki, jane

    expressjs-serve-static-83e72ac/test/fixtures/users/tobi.txt000066400000000000000000000000061477136712600242660ustar00rootroot00000000000000ferretexpressjs-serve-static-83e72ac/test/test.js000066400000000000000000000610141477136712600211010ustar00rootroot00000000000000 var assert = require('assert') var http = require('http') var path = require('path') var request = require('supertest') var serveStatic = require('..') var fixtures = path.join(__dirname, '/fixtures') var relative = path.relative(process.cwd(), fixtures) var skipRelative = ~relative.indexOf('..') || path.resolve(relative) === relative describe('serveStatic()', function () { describe('basic operations', function () { var server before(function () { server = createServer() }) it('should require root path', function () { assert.throws(serveStatic.bind(), /root path required/) }) it('should require root path to be string', function () { assert.throws(serveStatic.bind(null, 42), /root path.*string/) }) it('should serve static files', function (done) { request(server) .get('/todo.txt') .expect(200, '- groceries', done) }) it('should support nesting', function (done) { request(server) .get('/users/tobi.txt') .expect(200, 'ferret', done) }) it('should set Content-Type', function (done) { request(server) .get('/todo.txt') .expect('Content-Type', 'text/plain; charset=utf-8') .expect(200, done) }) it('should set Last-Modified', function (done) { request(server) .get('/todo.txt') .expect('Last-Modified', /\d{2} \w{3} \d{4}/) .expect(200, done) }) it('should default max-age=0', function (done) { request(server) .get('/todo.txt') .expect('Cache-Control', 'public, max-age=0') .expect(200, done) }) it('should support urlencoded pathnames', function (done) { request(server) .get('/foo%20bar') .expect(200) .expect(shouldHaveBody(Buffer.from('baz'))) .end(done) }) it('should not choke on auth-looking URL', function (done) { request(server) .get('//todo@txt') .expect(404, done) }) it('should support index.html', function (done) { request(server) .get('/users/') .expect(200) .expect('Content-Type', /html/) .expect('

    tobi, loki, jane

    ', done) }) it('should support ../', function (done) { request(server) .get('/users/../todo.txt') .expect(200, '- groceries', done) }) it('should support HEAD', function (done) { request(server) .head('/todo.txt') .expect(200) .expect(shouldNotHaveBody()) .end(done) }) it('should skip POST requests', function (done) { request(server) .post('/todo.txt') .expect(404, 'sorry!', done) }) it('should support conditional requests', function (done) { request(server) .get('/todo.txt') .end(function (err, res) { if (err) throw err request(server) .get('/todo.txt') .set('If-None-Match', res.headers.etag) .expect(304, done) }) }) it('should support precondition checks', function (done) { request(server) .get('/todo.txt') .set('If-Match', '"foo"') .expect(412, done) }) it('should serve zero-length files', function (done) { request(server) .get('/empty.txt') .expect(200, '', done) }) it('should ignore hidden files', function (done) { request(server) .get('/.hidden') .expect(404, done) }) }); (skipRelative ? describe.skip : describe)('current dir', function () { var server before(function () { server = createServer('.') }) it('should be served with "."', function (done) { var dest = relative.split(path.sep).join('/') request(server) .get('/' + dest + '/todo.txt') .expect(200, '- groceries', done) }) }) describe('acceptRanges', function () { describe('when false', function () { it('should not include Accept-Ranges', function (done) { request(createServer(fixtures, { acceptRanges: false })) .get('/nums.txt') .expect(shouldNotHaveHeader('Accept-Ranges')) .expect(200, '123456789', done) }) it('should ignore Rage request header', function (done) { request(createServer(fixtures, { acceptRanges: false })) .get('/nums.txt') .set('Range', 'bytes=0-3') .expect(shouldNotHaveHeader('Accept-Ranges')) .expect(shouldNotHaveHeader('Content-Range')) .expect(200, '123456789', done) }) }) describe('when true', function () { it('should include Accept-Ranges', function (done) { request(createServer(fixtures, { acceptRanges: true })) .get('/nums.txt') .expect('Accept-Ranges', 'bytes') .expect(200, '123456789', done) }) it('should obey Rage request header', function (done) { request(createServer(fixtures, { acceptRanges: true })) .get('/nums.txt') .set('Range', 'bytes=0-3') .expect('Accept-Ranges', 'bytes') .expect('Content-Range', 'bytes 0-3/9') .expect(206, '1234', done) }) }) }) describe('cacheControl', function () { describe('when false', function () { it('should not include Cache-Control', function (done) { request(createServer(fixtures, { cacheControl: false })) .get('/nums.txt') .expect(shouldNotHaveHeader('Cache-Control')) .expect(200, '123456789', done) }) it('should ignore maxAge', function (done) { request(createServer(fixtures, { cacheControl: false, maxAge: 12000 })) .get('/nums.txt') .expect(shouldNotHaveHeader('Cache-Control')) .expect(200, '123456789', done) }) }) describe('when true', function () { it('should include Cache-Control', function (done) { request(createServer(fixtures, { cacheControl: true })) .get('/nums.txt') .expect('Cache-Control', 'public, max-age=0') .expect(200, '123456789', done) }) }) }) describe('extensions', function () { it('should be not be enabled by default', function (done) { var server = createServer(fixtures) request(server) .get('/todo') .expect(404, done) }) it('should be configurable', function (done) { var server = createServer(fixtures, { extensions: 'txt' }) request(server) .get('/todo') .expect(200, '- groceries', done) }) it('should support disabling extensions', function (done) { var server = createServer(fixtures, { extensions: false }) request(server) .get('/todo') .expect(404, done) }) it('should support fallbacks', function (done) { var server = createServer(fixtures, { extensions: ['htm', 'html', 'txt'] }) request(server) .get('/todo') .expect(200, '
  • groceries
  • ', done) }) it('should 404 if nothing found', function (done) { var server = createServer(fixtures, { extensions: ['htm', 'html', 'txt'] }) request(server) .get('/bob') .expect(404, done) }) }) describe('fallthrough', function () { it('should default to true', function (done) { request(createServer()) .get('/does-not-exist') .expect(404, 'sorry!', done) }) describe('when true', function () { before(function () { this.server = createServer(fixtures, { fallthrough: true }) }) it('should fall-through when OPTIONS request', function (done) { request(this.server) .options('/todo.txt') .expect(404, 'sorry!', done) }) it('should fall-through when URL malformed', function (done) { request(this.server) .get('/%') .expect(404, 'sorry!', done) }) it('should fall-through when traversing past root', function (done) { request(this.server) .get('/users/../../todo.txt') .expect(404, 'sorry!', done) }) it('should fall-through when URL too long', function (done) { var root = fixtures + Array(10000).join('/foobar') request(createServer(root, { fallthrough: true })) .get('/') .expect(404, 'sorry!', done) }) describe('with redirect: true', function () { before(function () { this.server = createServer(fixtures, { fallthrough: true, redirect: true }) }) it('should fall-through when directory', function (done) { request(this.server) .get('/pets/') .expect(404, 'sorry!', done) }) it('should redirect when directory without slash', function (done) { request(this.server) .get('/pets') .expect(301, /Redirecting/, done) }) }) describe('with redirect: false', function () { before(function () { this.server = createServer(fixtures, { fallthrough: true, redirect: false }) }) it('should fall-through when directory', function (done) { request(this.server) .get('/pets/') .expect(404, 'sorry!', done) }) it('should fall-through when directory without slash', function (done) { request(this.server) .get('/pets') .expect(404, 'sorry!', done) }) }) }) describe('when false', function () { before(function () { this.server = createServer(fixtures, { fallthrough: false }) }) it('should 405 when OPTIONS request', function (done) { request(this.server) .options('/todo.txt') .expect('Allow', 'GET, HEAD') .expect(405, done) }) it('should 400 when URL malformed', function (done) { request(this.server) .get('/%') .expect(400, /BadRequestError/, done) }) it('should 403 when traversing past root', function (done) { request(this.server) .get('/users/../../todo.txt') .expect(403, /ForbiddenError/, done) }) it('should 404 when URL too long', function (done) { var root = fixtures + Array(10000).join('/foobar') request(createServer(root, { fallthrough: false })) .get('/') .expect(404, /ENAMETOOLONG/, done) }) describe('with redirect: true', function () { before(function () { this.server = createServer(fixtures, { fallthrough: false, redirect: true }) }) it('should 404 when directory', function (done) { request(this.server) .get('/pets/') .expect(404, /NotFoundError|ENOENT/, done) }) it('should redirect when directory without slash', function (done) { request(this.server) .get('/pets') .expect(301, /Redirecting/, done) }) }) describe('with redirect: false', function () { before(function () { this.server = createServer(fixtures, { fallthrough: false, redirect: false }) }) it('should 404 when directory', function (done) { request(this.server) .get('/pets/') .expect(404, /NotFoundError|ENOENT/, done) }) it('should 404 when directory without slash', function (done) { request(this.server) .get('/pets') .expect(404, /NotFoundError|ENOENT/, done) }) }) }) }) describe('hidden files', function () { var server before(function () { server = createServer(fixtures, { dotfiles: 'allow' }) }) it('should be served when dotfiles: "allow" is given', function (done) { request(server) .get('/.hidden') .expect(200) .expect(shouldHaveBody(Buffer.from('I am hidden'))) .end(done) }) }) describe('immutable', function () { it('should default to false', function (done) { request(createServer(fixtures)) .get('/nums.txt') .expect('Cache-Control', 'public, max-age=0', done) }) it('should set immutable directive in Cache-Control', function (done) { request(createServer(fixtures, { immutable: true, maxAge: '1h' })) .get('/nums.txt') .expect('Cache-Control', 'public, max-age=3600, immutable', done) }) }) describe('lastModified', function () { describe('when false', function () { it('should not include Last-Modifed', function (done) { request(createServer(fixtures, { lastModified: false })) .get('/nums.txt') .expect(shouldNotHaveHeader('Last-Modified')) .expect(200, '123456789', done) }) }) describe('when true', function () { it('should include Last-Modifed', function (done) { request(createServer(fixtures, { lastModified: true })) .get('/nums.txt') .expect('Last-Modified', /^\w{3}, \d+ \w+ \d+ \d+:\d+:\d+ \w+$/) .expect(200, '123456789', done) }) }) }) describe('maxAge', function () { it('should accept string', function (done) { request(createServer(fixtures, { maxAge: '30d' })) .get('/todo.txt') .expect('cache-control', 'public, max-age=' + (60 * 60 * 24 * 30)) .expect(200, done) }) it('should be reasonable when infinite', function (done) { request(createServer(fixtures, { maxAge: Infinity })) .get('/todo.txt') .expect('cache-control', 'public, max-age=' + (60 * 60 * 24 * 365)) .expect(200, done) }) }) describe('redirect', function () { var server before(function () { server = createServer(fixtures, null, function (req, res) { req.url = req.url.replace(/\/snow(\/|$)/, '/snow \u2603$1') }) }) it('should redirect directories', function (done) { request(server) .get('/users') .expect('Location', '/users/') .expect(301, done) }) it('should include HTML link', function (done) { request(server) .get('/users') .expect('Location', '/users/') .expect(301, /\/users\//, done) }) it('should redirect directories with query string', function (done) { request(server) .get('/users?name=john') .expect('Location', '/users/?name=john') .expect(301, done) }) it('should not redirect to protocol-relative locations', function (done) { request(server) .get('//users') .expect('Location', '/users/') .expect(301, done) }) it('should ensure redirect URL is properly encoded', function (done) { request(server) .get('/snow') .expect('Location', '/snow%20%E2%98%83/') .expect('Content-Type', /html/) .expect(301, />Redirecting to \/snow%20%E2%98%83\/