pax_global_header00006660000000000000000000000064150220011160014476gustar00rootroot0000000000000052 comment=83da8d55d6511c716f31d9ecd9bf96bdda61fc0a expressjs-serve-favicon-83da8d5/000077500000000000000000000000001502200111600167135ustar00rootroot00000000000000expressjs-serve-favicon-83da8d5/.eslintignore000066400000000000000000000000261502200111600214140ustar00rootroot00000000000000coverage node_modules expressjs-serve-favicon-83da8d5/.eslintrc000066400000000000000000000000341502200111600205340ustar00rootroot00000000000000{ "extends": "standard" } expressjs-serve-favicon-83da8d5/.github/000077500000000000000000000000001502200111600202535ustar00rootroot00000000000000expressjs-serve-favicon-83da8d5/.github/dependabot.yml000066400000000000000000000005041502200111600231020ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: monthly - package-ecosystem: npm directory: / schedule: interval: monthly open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-major"] expressjs-serve-favicon-83da8d5/.github/workflows/000077500000000000000000000000001502200111600223105ustar00rootroot00000000000000expressjs-serve-favicon-83da8d5/.github/workflows/ci.yml000066400000000000000000000146631502200111600234400ustar00rootroot00000000000000name: ci on: - pull_request - push permissions: contents: read jobs: test: permissions: checks: write # for coverallsapp/github-action to create new checks contents: read # for actions/checkout to fetch code runs-on: ubuntu-latest strategy: matrix: name: - Node.js 0.8 - Node.js 0.10 - Node.js 0.12 - io.js 1.x - io.js 2.x - io.js 3.x - Node.js 4.x - Node.js 5.x - Node.js 6.x - Node.js 7.x - Node.js 8.x - Node.js 9.x - Node.js 10.x - Node.js 11.x - Node.js 12.x - Node.js 13.x - Node.js 14.x - Node.js 15.x - Node.js 16.x - Node.js 17.x - Node.js 18.x - Node.js 19.x - Node.js 20.x - Node.js 21.x - Node.js 22.x - Node.js 23.x - Node.js 24.x include: - name: Node.js 0.8 node-version: "0.8" npm-i: mocha@2.5.3 supertest@1.1.0 npm-rm: nyc - name: Node.js 0.10 node-version: "0.10" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.1 - name: Node.js 0.12 node-version: "0.12" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.1 - name: io.js 1.x node-version: "1" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.1 - name: io.js 2.x node-version: "2" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.1 - name: io.js 3.x node-version: "3" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.1 - name: Node.js 4.x node-version: "4" npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - name: Node.js 5.x node-version: "5" npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - name: Node.js 6.x node-version: "6" npm-i: mocha@6.2.2 nyc@14.1.1 supertest@4.0.2 - name: Node.js 7.x node-version: "7" npm-i: mocha@6.2.2 nyc@14.1.1 supertest@4.0.2 - name: Node.js 8.x node-version: "8" npm-i: mocha@7.1.2 nyc@14.1.1 supertest@4.0.2 - name: Node.js 9.x node-version: "9" npm-i: mocha@7.1.2 nyc@14.1.1 supertest@4.0.2 - name: Node.js 10.x node-version: "10" npm-i: mocha@8.4.0 supertest@4.0.2 - name: Node.js 11.x node-version: "11" npm-i: mocha@8.4.0 supertest@4.0.2 - name: Node.js 12.x node-version: "12" npm-i: mocha@9.2.2 supertest@4.0.2 - name: Node.js 13.x node-version: "13" npm-i: mocha@9.2.2 supertest@4.0.2 - name: Node.js 14.x node-version: "14" npm-i: supertest@4.0.2 - name: Node.js 15.x node-version: "15" npm-i: supertest@4.0.2 - name: Node.js 16.x node-version: "16" npm-i: supertest@4.0.2 - name: Node.js 17.x node-version: "17" npm-i: supertest@4.0.2 - name: Node.js 18.x node-version: "18" - name: Node.js 19.x node-version: "19" - name: Node.js 20.x node-version: "20" - name: Node.js 21.x node-version: "21" - name: Node.js 22.x node-version: "22" - name: Node.js 23.x node-version: "23" - name: Node.js 24.x node-version: "24" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install Node.js ${{ matrix.node-version }} shell: bash -eo pipefail -l {0} run: | nvm install --default ${{ matrix.node-version }} if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then nvm install --alias=npm 0.10 nvm use ${{ matrix.node-version }} if [[ "$(npm -v)" == 1.1.* ]]; then nvm exec npm npm install -g npm@1.1 ln -fs "$(which npm)" "$(dirname "$(nvm which npm)")/npm" else sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" fi npm config set strict-ssl false fi dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" - name: Configure npm run: | if [[ "$(npm config get package-lock)" == "true" ]]; then npm config set package-lock false else npm config set shrinkwrap false fi - name: Remove npm module(s) ${{ matrix.npm-rm }} if: matrix.npm-rm != '' run: npm rm --silent --save-dev ${{ matrix.npm-rm }} - name: Install npm module(s) ${{ matrix.npm-i }} if: matrix.npm-i != '' run: npm install --save-dev ${{ matrix.npm-i }} - name: Setup Node.js version-specific dependencies shell: bash run: | # eslint for linting # - remove on Node.js < 12 if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 12 ]]; then node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ grep -E '^eslint(-|$)' | \ sort -r | \ xargs -n1 npm rm --silent --save-dev fi - name: Install Node.js dependencies run: npm install - name: List environment id: list_env shell: bash run: | echo "node@$(node -v)" echo "npm@$(npm -v)" npm -s ls ||: (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print $2 "=" $3 }' >> "$GITHUB_OUTPUT" - name: Run tests shell: bash run: | if npm -ps ls nyc | grep -q nyc; then npm run test-ci else npm test fi - name: Lint code if: steps.list_env.outputs.eslint != '' run: npm run lint - name: Collect code coverage uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # master if: steps.list_env.outputs.nyc != '' with: github-token: ${{ secrets.GITHUB_TOKEN }} flag-name: run-${{ matrix.test_number }} parallel: true coverage: permissions: checks: write # for coverallsapp/github-action to create new checks needs: test runs-on: ubuntu-latest steps: - name: Upload code coverage uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # master with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true expressjs-serve-favicon-83da8d5/.github/workflows/codeql.yml000066400000000000000000000055611502200111600243110ustar00rootroot00000000000000# 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 strategy: fail-fast: false matrix: language: ["javascript"] # CodeQL supports [ $supported-codeql-languages ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 with: languages: ${{ matrix.language }} # 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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 # ℹ️ 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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 with: category: "/language:${{matrix.language}}" expressjs-serve-favicon-83da8d5/.github/workflows/scorecard.yml000066400000000000000000000056471502200111600250140ustar00rootroot00000000000000# 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 # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 with: sarif_file: results.sarif expressjs-serve-favicon-83da8d5/.gitignore000066400000000000000000000000701502200111600207000ustar00rootroot00000000000000coverage/ node_modules/ npm-debug.log package-lock.json expressjs-serve-favicon-83da8d5/HISTORY.md000066400000000000000000000102001502200111600203670ustar00rootroot000000000000002.5.1 / 2025-06-10 ================== * deps: * safe-buffer@5.2.1 * ms@2.1.3 2.5.0 / 2018-03-29 ================== * Ignore requests without `url` property * deps: ms@2.1.1 - Add `week` - Add `w` 2.4.5 / 2017-09-26 ================== * deps: etag@~1.8.1 - perf: replace regular expression with substring * deps: fresh@0.5.2 - Fix regression matching multiple ETags in `If-None-Match` - perf: improve `If-None-Match` token parsing 2.4.4 / 2017-09-11 ================== * deps: fresh@0.5.1 - Fix handling of modified headers with invalid dates - perf: improve ETag match loop * deps: parseurl@~1.3.2 - perf: reduce overhead for full URLs - perf: unroll the "fast-path" `RegExp` * deps: safe-buffer@5.1.1 2.4.3 / 2017-05-16 ================== * Use `safe-buffer` for improved Buffer API * deps: ms@2.0.0 2.4.2 / 2017-03-24 ================== * deps: ms@1.0.0 2.4.1 / 2017-02-27 ================== * Remove usage of `res._headers` private field * deps: fresh@0.5.0 - Fix incorrect result when `If-None-Match` has both `*` and ETags - Fix weak `ETag` matching to match spec - perf: skip checking modified time if ETag check failed - perf: skip parsing `If-None-Match` when no `ETag` header - perf: use `Date.parse` instead of `new Date` 2.4.0 / 2017-02-19 ================== * deps: etag@~1.8.0 - Use SHA1 instead of MD5 for ETag hashing - Works with FIPS 140-2 OpenSSL configuration * deps: fresh@0.4.0 - Fix false detection of `no-cache` request directive - perf: enable strict mode - perf: hoist regular expressions - perf: remove duplicate conditional - perf: remove unnecessary boolean coercions * perf: simplify initial argument checking 2.3.2 / 2016-11-16 ================== * deps: ms@0.7.2 2.3.1 / 2016-01-23 ================== * deps: parseurl@~1.3.1 - perf: enable strict mode 2.3.0 / 2015-06-13 ================== * Send non-chunked response for `OPTIONS` * deps: etag@~1.7.0 - Always include entity length in ETags for hash length extensions - Generate non-Stats ETags using MD5 only (no longer CRC32) - Remove base64 padding in ETags to shorten * deps: fresh@0.3.0 - Add weak `ETag` matching support * perf: enable strict mode * perf: remove argument reassignment * perf: remove bitwise operations 2.2.1 / 2015-05-14 ================== * deps: etag@~1.6.0 - Improve support for JXcore - Support "fake" stats objects in environments without `fs` * deps: ms@0.7.1 - Prevent extraordinarily long inputs 2.2.0 / 2014-12-18 ================== * Support query string in the URL * deps: etag@~1.5.1 - deps: crc@3.2.1 * deps: ms@0.7.0 - Add `milliseconds` - Add `msecs` - Add `secs` - Add `mins` - Add `hrs` - Add `yrs` 2.1.7 / 2014-11-19 ================== * Avoid errors from enumerables on `Object.prototype` 2.1.6 / 2014-10-16 ================== * deps: etag@~1.5.0 2.1.5 / 2014-09-24 ================== * deps: etag@~1.4.0 2.1.4 / 2014-09-15 ================== * Fix content headers being sent in 304 response * deps: etag@~1.3.1 - Improve ETag generation speed 2.1.3 / 2014-09-07 ================== * deps: fresh@0.2.4 2.1.2 / 2014-09-05 ================== * deps: etag@~1.3.0 - Improve ETag generation speed 2.1.1 / 2014-08-25 ================== * Fix `ms` to be listed as a dependency 2.1.0 / 2014-08-24 ================== * Accept string for `maxAge` (converted by `ms`) * Use `etag` to generate `ETag` header 2.0.1 / 2014-06-05 ================== * Reduce byte size of `ETag` header 2.0.0 / 2014-05-02 ================== * `path` argument is required; there is no default icon. * Accept `Buffer` of icon as first argument. * Non-GET and HEAD requests are denied. * Send valid max-age value * Support conditional requests * Support max-age=0 * Support OPTIONS method * Throw if `path` argument is directory. 1.0.2 / 2014-03-16 ================== * Fixed content of default icon. 1.0.1 / 2014-03-11 ================== * Fixed path to default icon. 1.0.0 / 2014-02-15 ================== * Initial release expressjs-serve-favicon-83da8d5/LICENSE000066400000000000000000000022451502200111600177230ustar00rootroot00000000000000(The MIT License) Copyright (c) 2010 Sencha Inc. Copyright (c) 2011 LearnBoost Copyright (c) 2011 TJ Holowaychuk Copyright (c) 2014-2017 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-favicon-83da8d5/README.md000066400000000000000000000102161502200111600201720ustar00rootroot00000000000000# serve-favicon [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Linux Build Status][ci-image]][ci-url] [![Coverage Status][coveralls-image]][coveralls-url] [![OpenSSF Scorecard Badge][ossf-scorecard-badge]][ossf-scorecard-visualizer] Node.js middleware for serving a favicon. A favicon is a visual cue that client software, like browsers, use to identify a site. For an example and more information, please visit [the Wikipedia article on favicons](https://en.wikipedia.org/wiki/Favicon). Why use this module? - User agents request `favicon.ico` frequently and indiscriminately, so you may wish to exclude these requests from your logs by using this middleware before your logger middleware. - This module caches the icon in memory to improve performance by skipping disk access. - This module provides an `ETag` based on the contents of the icon, rather than file system properties. - This module will serve with the most compatible `Content-Type`. **Note** This module is exclusively for serving the "default, implicit favicon", which is `GET /favicon.ico`. For additional vendor-specific icons that require HTML markup, additional middleware is required to serve the relevant files, for example [serve-static](https://npmjs.org/package/serve-static). ## 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-favicon ``` ## API ### favicon(path, options) Create new middleware to serve a favicon from the given `path` to a favicon file. `path` may also be a `Buffer` of the icon to serve. #### Options Serve favicon accepts these properties in the options object. ##### maxAge The `cache-control` `max-age` directive in `ms`, defaulting to 1 year. This can also be a string accepted by the [ms](https://www.npmjs.org/package/ms#readme) module. ## Examples Typically this middleware will come very early in your stack (maybe even first) to avoid processing any other middleware if we already know the request is for `/favicon.ico`. ### express ```javascript var express = require('express') var favicon = require('serve-favicon') var path = require('path') var app = express() app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) // Add your routes here, etc. app.listen(3000) ``` ### connect ```javascript var connect = require('connect') var favicon = require('serve-favicon') var path = require('path') var app = connect() app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) // Add your middleware here, etc. app.listen(3000) ``` ### vanilla http server This middleware can be used anywhere, even outside express/connect. It takes `req`, `res`, and `callback`. ```javascript var http = require('http') var favicon = require('serve-favicon') var finalhandler = require('finalhandler') var path = require('path') var _favicon = favicon(path.join(__dirname, 'public', 'favicon.ico')) var server = http.createServer(function onRequest (req, res) { var done = finalhandler(req, res) _favicon(req, res, function onNext (err) { if (err) return done(err) // continue to process the request here, etc. res.statusCode = 404 res.end('oops') }) }) server.listen(3000) ``` ## License [MIT](LICENSE) [ci-image]: https://badgen.net/github/checks/expressjs/serve-favicon/master?label=ci [ci-url]: https://github.com/expressjs/serve-favicon/actions/workflows/ci.yml [coveralls-image]: https://img.shields.io/coveralls/expressjs/serve-favicon.svg [coveralls-url]: https://coveralls.io/r/expressjs/serve-favicon?branch=master [downloads-image]: https://img.shields.io/npm/dm/serve-favicon.svg [downloads-url]: https://npmjs.org/package/serve-favicon [npm-image]: https://img.shields.io/npm/v/serve-favicon.svg [npm-url]: https://npmjs.org/package/serve-favicon [ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/serve-favicon/badge [ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/serve-favicon expressjs-serve-favicon-83da8d5/index.js000066400000000000000000000101031502200111600203530ustar00rootroot00000000000000/*! * serve-favicon * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014-2017 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * Module dependencies. * @private */ var Buffer = require('safe-buffer').Buffer var etag = require('etag') var fresh = require('fresh') var fs = require('fs') var ms = require('ms') var parseUrl = require('parseurl') var path = require('path') var resolve = path.resolve /** * Module exports. * @public */ module.exports = favicon /** * Module variables. * @private */ var ONE_YEAR_MS = 60 * 60 * 24 * 365 * 1000 // 1 year /** * Serves the favicon located by the given `path`. * * @public * @param {String|Buffer} path * @param {Object} [options] * @return {Function} middleware */ function favicon (path, options) { var opts = options || {} var icon // favicon cache var maxAge = calcMaxAge(opts.maxAge) if (!path) { throw new TypeError('path to favicon.ico is required') } if (Buffer.isBuffer(path)) { icon = createIcon(Buffer.from(path), maxAge) } else if (typeof path === 'string') { path = resolveSync(path) } else { throw new TypeError('path to favicon.ico must be string or buffer') } return function favicon (req, res, next) { if (getPathname(req) !== '/favicon.ico') { next() return } if (req.method !== 'GET' && req.method !== 'HEAD') { res.statusCode = req.method === 'OPTIONS' ? 200 : 405 res.setHeader('Allow', 'GET, HEAD, OPTIONS') res.setHeader('Content-Length', '0') res.end() return } if (icon) { send(req, res, icon) return } fs.readFile(path, function (err, buf) { if (err) return next(err) icon = createIcon(buf, maxAge) send(req, res, icon) }) } } /** * Calculate the max-age from a configured value. * * @private * @param {string|number} val * @return {number} */ function calcMaxAge (val) { var num = typeof val === 'string' ? ms(val) : val return num != null ? Math.min(Math.max(0, num), ONE_YEAR_MS) : ONE_YEAR_MS } /** * Create icon data from Buffer and max-age. * * @private * @param {Buffer} buf * @param {number} maxAge * @return {object} */ function createIcon (buf, maxAge) { return { body: buf, headers: { 'Cache-Control': 'public, max-age=' + Math.floor(maxAge / 1000), 'ETag': etag(buf) } } } /** * Create EISDIR error. * * @private * @param {string} path * @return {Error} */ function createIsDirError (path) { var error = new Error('EISDIR, illegal operation on directory \'' + path + '\'') error.code = 'EISDIR' error.errno = 28 error.path = path error.syscall = 'open' return error } /** * Get the request pathname. * * @param {object} req * @return {string} */ function getPathname (req) { try { return parseUrl(req).pathname } catch (e) { return undefined } } /** * Determine if the cached representation is fresh. * * @param {object} req * @param {object} res * @return {boolean} * @private */ function isFresh (req, res) { return fresh(req.headers, { 'etag': res.getHeader('ETag'), 'last-modified': res.getHeader('Last-Modified') }) } /** * Resolve the path to icon. * * @param {string} iconPath * @private */ function resolveSync (iconPath) { var path = resolve(iconPath) var stat = fs.statSync(path) if (stat.isDirectory()) { throw createIsDirError(path) } return path } /** * Send icon data in response to a request. * * @private * @param {IncomingMessage} req * @param {OutgoingMessage} res * @param {object} icon */ function send (req, res, icon) { // Set headers var headers = icon.headers var keys = Object.keys(headers) for (var i = 0; i < keys.length; i++) { var key = keys[i] res.setHeader(key, headers[key]) } // Validate freshness if (isFresh(req, res)) { res.statusCode = 304 res.end() return } // Send icon res.statusCode = 200 res.setHeader('Content-Length', icon.body.length) res.setHeader('Content-Type', 'image/x-icon') res.end(icon.body) } expressjs-serve-favicon-83da8d5/package.json000066400000000000000000000022631502200111600212040ustar00rootroot00000000000000{ "name": "serve-favicon", "description": "favicon serving middleware with caching", "version": "2.5.1", "author": "Douglas Christopher Wilson ", "license": "MIT", "keywords": [ "express", "favicon", "middleware" ], "repository": "expressjs/serve-favicon", "dependencies": { "etag": "~1.8.1", "fresh": "~0.5.2", "ms": "~2.1.3", "parseurl": "~1.3.2", "safe-buffer": "~5.2.1" }, "devDependencies": { "eslint": "4.19.1", "eslint-config-standard": "11.0.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-markdown": "1.0.2", "eslint-plugin-node": "6.0.1", "eslint-plugin-promise": "3.8.0", "eslint-plugin-standard": "3.1.0", "mocha": "10.8.2", "nyc": "^15.1.0", "supertest": "7.1.1", "temp-path": "1.0.0" }, "files": [ "LICENSE", "HISTORY.md", "index.js" ], "engines": { "node": ">= 0.8.0" }, "scripts": { "lint": "eslint --plugin markdown --ext js,md .", "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" } }expressjs-serve-favicon-83da8d5/test/000077500000000000000000000000001502200111600176725ustar00rootroot00000000000000expressjs-serve-favicon-83da8d5/test/.eslintrc000066400000000000000000000000441502200111600215140ustar00rootroot00000000000000{ "env": { "mocha": true } } expressjs-serve-favicon-83da8d5/test/fixtures/000077500000000000000000000000001502200111600215435ustar00rootroot00000000000000expressjs-serve-favicon-83da8d5/test/fixtures/favicon.ico000066400000000000000000000025761502200111600236760ustar00rootroot00000000000000h( }@_UC˩~6Gcc<ep›lu4ơu            expressjs-serve-favicon-83da8d5/test/support/000077500000000000000000000000001502200111600214065ustar00rootroot00000000000000expressjs-serve-favicon-83da8d5/test/support/tempIcon.js000066400000000000000000000010741502200111600235240ustar00rootroot00000000000000 var crypto = require('crypto') var fs = require('fs') var tempPath = require('temp-path') module.exports = TempIcon function TempIcon () { this.data = crypto.pseudoRandomBytes(100) this.exists = false this.path = tempPath() } TempIcon.prototype.unlinkSync = function unlinkSync () { if (this.exists) { this.exists = false fs.unlinkSync(this.path) } } TempIcon.prototype.writeSync = function writeSync () { if (this.exists) { throw new Error('already written') } else { fs.writeFileSync(this.path, this.data) this.exists = true } } expressjs-serve-favicon-83da8d5/test/test.js000066400000000000000000000212451502200111600212130ustar00rootroot00000000000000 var assert = require('assert') var Buffer = require('safe-buffer').Buffer var favicon = require('..') var http = require('http') var path = require('path') var request = require('supertest') var TempIcon = require('./support/tempIcon') var FIXTURES_PATH = path.join(__dirname, 'fixtures') var ICON_PATH = path.join(FIXTURES_PATH, 'favicon.ico') describe('favicon()', function () { describe('arguments', function () { describe('path', function () { it('should be required', function () { assert.throws(favicon.bind(), /path.*required/) }) it('should accept file path', function () { assert.doesNotThrow(favicon.bind(null, path.join(FIXTURES_PATH, 'favicon.ico'))) }) it('should accept buffer', function () { assert.doesNotThrow(favicon.bind(null, Buffer.alloc(20))) }) it('should exist', function () { assert.throws(favicon.bind(null, path.join(FIXTURES_PATH, 'nothing')), /ENOENT.*nothing/) }) it('should not be dir', function () { assert.throws(favicon.bind(null, FIXTURES_PATH), /EISDIR.*fixtures/) }) it('should not be number', function () { assert.throws(favicon.bind(null, 12), /path.*must be.*string/) }) }) describe('options.maxAge', function () { it('should be in cache-control', function (done) { var server = createServer(null, {maxAge: 5000}) request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=5') .expect(200, done) }) it('should have a default', function (done) { var server = createServer() request(server) .get('/favicon.ico') .expect('Cache-Control', /public, max-age=[0-9]+/) .expect(200, done) }) it('should accept 0', function (done) { var server = createServer(null, {maxAge: 0}) request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=0') .expect(200, done) }) it('should accept string', function (done) { var server = createServer(null, {maxAge: '30d'}) request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=2592000') .expect(200, done) }) it('should be valid delta-seconds', function (done) { var server = createServer(null, {maxAge: 1234}) request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=1') .expect(200, done) }) it('should floor at 0', function (done) { var server = createServer(null, {maxAge: -4000}) request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=0') .expect(200, done) }) it('should ceil at 1 year', function (done) { var server = createServer(null, {maxAge: 900000000000}) request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=31536000') .expect(200, done) }) it('should accept Inifnity', function (done) { var server = createServer(null, {maxAge: Infinity}) request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=31536000') .expect(200, done) }) }) }) describe('requests', function () { before(function () { this.server = createServer() }) it('should serve icon', function (done) { request(this.server) .get('/favicon.ico') .expect('Content-Type', 'image/x-icon') .expect(200, done) }) it('should include cache-control', function (done) { request(this.server) .get('/favicon.ico') .expect('Cache-Control', /public/) .expect(200, done) }) it('should include strong etag', function (done) { request(this.server) .get('/favicon.ico') .expect('ETag', /^"[^"]+"$/) .expect(200, done) }) it('should deny POST', function (done) { request(this.server) .post('/favicon.ico') .expect('Allow', 'GET, HEAD, OPTIONS') .expect(405, done) }) it('should understand OPTIONS', function (done) { request(this.server) .options('/favicon.ico') .expect('Allow', 'GET, HEAD, OPTIONS') .expect(200, done) }) it('should 304 when If-None-Match matches', function (done) { var server = this.server request(server) .get('/favicon.ico') .expect(200, function (err, res) { if (err) return done(err) request(server) .get('/favicon.ico') .set('If-None-Match', res.headers.etag) .expect(304, done) }) }) it('should 304 when If-None-Match matches weakly', function (done) { var server = this.server request(server) .get('/favicon.ico') .expect(200, function (err, res) { if (err) return done(err) request(server) .get('/favicon.ico') .set('If-None-Match', 'W/' + res.headers.etag) .expect(304, done) }) }) it('should ignore non-favicon requests', function (done) { request(this.server) .get('/') .expect(404, 'oops', done) }) it('should work with query string', function (done) { request(this.server) .get('/favicon.ico?v=1') .expect('Content-Type', 'image/x-icon') .expect(200, done) }) describe('missing req.url', function () { it('should ignore the request', function (done) { var fn = favicon(ICON_PATH) fn({}, {}, done) }) }) }) describe('icon', function () { describe('file', function () { beforeEach(function () { this.icon = new TempIcon() this.icon.writeSync() }) afterEach(function () { this.icon.unlinkSync() this.icon = undefined }) it('should be read on first request', function (done) { var icon = this.icon var server = createServer(icon.path) request(server) .get('/favicon.ico') .expect(200, icon.data, done) }) it('should cache for second request', function (done) { var icon = this.icon var server = createServer(icon.path) request(server) .get('/favicon.ico') .expect(200, icon.data, function (err) { if (err) return done(err) icon.unlinkSync() request(server) .get('/favicon.ico') .expect(200, icon.data, done) }) }) }) describe('file error', function () { beforeEach(function () { this.icon = new TempIcon() this.icon.writeSync() }) afterEach(function () { this.icon.unlinkSync() this.icon = undefined }) it('should next() file read errors', function (done) { var icon = this.icon var server = createServer(icon.path) icon.unlinkSync() request(server) .get('/favicon.ico') .expect(500, /ENOENT/, done) }) it('should retry reading file after error', function (done) { var icon = this.icon var server = createServer(icon.path) icon.unlinkSync() request(server) .get('/favicon.ico') .expect(500, /ENOENT/, function (err) { if (err) return done(err) icon.writeSync() request(server) .get('/favicon.ico') .expect(200, icon.data, done) }) }) }) describe('buffer', function () { it('should be served from buffer', function (done) { var buffer = Buffer.alloc(20, '#') var server = createServer(buffer) request(server) .get('/favicon.ico') .expect('Content-Length', '20') .expect(200, buffer, done) }) it('should be copied', function (done) { var buffer = Buffer.alloc(20, '#') var server = createServer(buffer) assert.equal(buffer.toString(), '####################') buffer.fill('?') assert.equal(buffer.toString(), '????????????????????') request(server) .get('/favicon.ico') .expect('Content-Length', '20') .expect(200, Buffer.from('####################'), done) }) }) }) }) function createServer (path, opts) { var _path = path || ICON_PATH var _favicon = favicon(_path, opts) var server = http.createServer(function onRequest (req, res) { _favicon(req, res, function onNext (err) { res.statusCode = err ? (err.status || 500) : 404 res.end(err ? err.message : 'oops') }) }) return server }