From 0ea877f94bc7d02ec3609d43d4e13a4eebc355bc Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 19 Sep 2025 13:59:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintignore | 3 + .eslintrc.yml | 9 ++ .github/workflows/ci.yml | 205 +++++++++++++++++++++++++++++++++ .gitignore | 4 + HISTORY.md | 243 +++++++++++++++++++++++++++++++++++++++ LICENSE | 23 ++++ README.md | 140 ++++++++++++++++++++++ index.js | 238 ++++++++++++++++++++++++++++++++++++++ package.json | 47 ++++++++ test/.eslintrc.yml | 2 + test/charset.js | 76 ++++++++++++ test/encoding.js | 76 ++++++++++++ test/language.js | 76 ++++++++++++ test/type.js | 120 +++++++++++++++++++ 14 files changed, 1262 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 HISTORY.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 index.js create mode 100644 package.json create mode 100644 test/.eslintrc.yml create mode 100644 test/charset.js create mode 100644 test/encoding.js create mode 100644 test/language.js create mode 100644 test/type.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..76b1021 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +.nyc_output +coverage +node_modules diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..cf3015f --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,9 @@ +root: true +extends: + - standard + - plugin:markdown/recommended +plugins: + - markdown +overrides: + - files: '**/*.md' + processor: 'markdown/markdown' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..50b66c9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,205 @@ +name: ci + +on: +- pull_request +- push + +jobs: + test: + runs-on: ubuntu-18.04 + strategy: + matrix: + name: + - Node.js 0.6 + - 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 + + include: + - name: Node.js 0.6 + node-version: "0.6" + npm-i: mocha@1.21.5 + npm-rm: nyc + + - name: Node.js 0.8 + node-version: "0.8" + npm-i: mocha@2.5.3 + npm-rm: nyc + + - name: Node.js 0.10 + node-version: "0.10" + npm-i: mocha@3.5.3 nyc@10.3.2 + + - name: Node.js 0.12 + node-version: "0.12" + npm-i: mocha@3.5.3 nyc@10.3.2 + + - name: io.js 1.x + node-version: "1.8" + npm-i: mocha@3.5.3 nyc@10.3.2 + + - name: io.js 2.x + node-version: "2.5" + npm-i: mocha@3.5.3 nyc@10.3.2 + + - name: io.js 3.x + node-version: "3.3" + npm-i: mocha@3.5.3 nyc@10.3.2 + + - name: Node.js 4.x + node-version: "4.9" + npm-i: mocha@5.2.0 nyc@11.9.0 + + - name: Node.js 5.x + node-version: "5.12" + npm-i: mocha@5.2.0 nyc@11.9.0 + + - name: Node.js 6.x + node-version: "6.17" + npm-i: mocha@6.2.3 nyc@14.1.1 + + - name: Node.js 7.x + node-version: "7.10" + npm-i: mocha@6.2.3 nyc@14.1.1 + + - name: Node.js 8.x + node-version: "8.17" + npm-i: mocha@7.2.0 + + - name: Node.js 9.x + node-version: "9.11" + npm-i: mocha@7.2.0 + + - name: Node.js 10.x + node-version: "10.24" + npm-i: mocha@8.4.0 + + - name: Node.js 11.x + node-version: "11.15" + npm-i: mocha@8.4.0 + + - name: Node.js 12.x + node-version: "12.22" + + - name: Node.js 13.x + node-version: "13.14" + + - name: Node.js 14.x + node-version: "14.19" + + - name: Node.js 15.x + node-version: "15.14" + + - name: Node.js 16.x + node-version: "16.13" + + - name: Node.js 17.x + node-version: "17.4" + + steps: + - uses: actions/checkout@v2 + + - name: Install Node.js ${{ matrix.node-version }} + shell: bash -eo pipefail -l {0} + run: | + if [[ "${{ matrix.node-version }}" == 0.6* ]]; then + sudo apt-get install g++-4.8 gcc-4.8 libssl1.0-dev + export CC=/usr/bin/gcc-4.8 + export CXX=/usr/bin/g++-4.8 + fi + 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: npm config set shrinkwrap false + + - name: Remove npm module(s) ${{ matrix.npm-rm }} + run: npm rm --silent --save-dev ${{ matrix.npm-rm }} + if: matrix.npm-rm != '' + + - name: Install npm module(s) ${{ matrix.npm-i }} + run: npm install --save-dev ${{ matrix.npm-i }} + if: matrix.npm-i != '' + + - name: Setup Node.js version-specific dependencies + shell: bash + run: | + # eslint for linting + # - remove on Node.js < 10 + if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; 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 "::set-output name=" $2 "::" $3 }' + + - 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@master + if: steps.list_env.outputs.nyc != '' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + flag-name: run-${{ matrix.test_number }} + parallel: true + + coverage: + needs: test + runs-on: ubuntu-latest + steps: + - name: Upload code coverage + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel-finished: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba6c9a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.nyc_output +node_modules +coverage +package-lock.json diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..cb5990c --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,243 @@ +1.3.8 / 2022-02-02 +================== + + * deps: mime-types@~2.1.34 + - deps: mime-db@~1.51.0 + * deps: negotiator@0.6.3 + +1.3.7 / 2019-04-29 +================== + + * deps: negotiator@0.6.2 + - Fix sorting charset, encoding, and language with extra parameters + +1.3.6 / 2019-04-28 +================== + + * deps: mime-types@~2.1.24 + - deps: mime-db@~1.40.0 + +1.3.5 / 2018-02-28 +================== + + * deps: mime-types@~2.1.18 + - deps: mime-db@~1.33.0 + +1.3.4 / 2017-08-22 +================== + + * deps: mime-types@~2.1.16 + - deps: mime-db@~1.29.0 + +1.3.3 / 2016-05-02 +================== + + * deps: mime-types@~2.1.11 + - deps: mime-db@~1.23.0 + * deps: negotiator@0.6.1 + - perf: improve `Accept` parsing speed + - perf: improve `Accept-Charset` parsing speed + - perf: improve `Accept-Encoding` parsing speed + - perf: improve `Accept-Language` parsing speed + +1.3.2 / 2016-03-08 +================== + + * deps: mime-types@~2.1.10 + - Fix extension of `application/dash+xml` + - Update primary extension for `audio/mp4` + - deps: mime-db@~1.22.0 + +1.3.1 / 2016-01-19 +================== + + * deps: mime-types@~2.1.9 + - deps: mime-db@~1.21.0 + +1.3.0 / 2015-09-29 +================== + + * deps: mime-types@~2.1.7 + - deps: mime-db@~1.19.0 + * deps: negotiator@0.6.0 + - Fix including type extensions in parameters in `Accept` parsing + - Fix parsing `Accept` parameters with quoted equals + - Fix parsing `Accept` parameters with quoted semicolons + - Lazy-load modules from main entry point + - perf: delay type concatenation until needed + - perf: enable strict mode + - perf: hoist regular expressions + - perf: remove closures getting spec properties + - perf: remove a closure from media type parsing + - perf: remove property delete from media type parsing + +1.2.13 / 2015-09-06 +=================== + + * deps: mime-types@~2.1.6 + - deps: mime-db@~1.18.0 + +1.2.12 / 2015-07-30 +=================== + + * deps: mime-types@~2.1.4 + - deps: mime-db@~1.16.0 + +1.2.11 / 2015-07-16 +=================== + + * deps: mime-types@~2.1.3 + - deps: mime-db@~1.15.0 + +1.2.10 / 2015-07-01 +=================== + + * deps: mime-types@~2.1.2 + - deps: mime-db@~1.14.0 + +1.2.9 / 2015-06-08 +================== + + * deps: mime-types@~2.1.1 + - perf: fix deopt during mapping + +1.2.8 / 2015-06-07 +================== + + * deps: mime-types@~2.1.0 + - deps: mime-db@~1.13.0 + * perf: avoid argument reassignment & argument slice + * perf: avoid negotiator recursive construction + * perf: enable strict mode + * perf: remove unnecessary bitwise operator + +1.2.7 / 2015-05-10 +================== + + * deps: negotiator@0.5.3 + - Fix media type parameter matching to be case-insensitive + +1.2.6 / 2015-05-07 +================== + + * deps: mime-types@~2.0.11 + - deps: mime-db@~1.9.1 + * deps: negotiator@0.5.2 + - Fix comparing media types with quoted values + - Fix splitting media types with quoted commas + +1.2.5 / 2015-03-13 +================== + + * deps: mime-types@~2.0.10 + - deps: mime-db@~1.8.0 + +1.2.4 / 2015-02-14 +================== + + * Support Node.js 0.6 + * deps: mime-types@~2.0.9 + - deps: mime-db@~1.7.0 + * deps: negotiator@0.5.1 + - Fix preference sorting to be stable for long acceptable lists + +1.2.3 / 2015-01-31 +================== + + * deps: mime-types@~2.0.8 + - deps: mime-db@~1.6.0 + +1.2.2 / 2014-12-30 +================== + + * deps: mime-types@~2.0.7 + - deps: mime-db@~1.5.0 + +1.2.1 / 2014-12-30 +================== + + * deps: mime-types@~2.0.5 + - deps: mime-db@~1.3.1 + +1.2.0 / 2014-12-19 +================== + + * deps: negotiator@0.5.0 + - Fix list return order when large accepted list + - Fix missing identity encoding when q=0 exists + - Remove dynamic building of Negotiator class + +1.1.4 / 2014-12-10 +================== + + * deps: mime-types@~2.0.4 + - deps: mime-db@~1.3.0 + +1.1.3 / 2014-11-09 +================== + + * deps: mime-types@~2.0.3 + - deps: mime-db@~1.2.0 + +1.1.2 / 2014-10-14 +================== + + * deps: negotiator@0.4.9 + - Fix error when media type has invalid parameter + +1.1.1 / 2014-09-28 +================== + + * deps: mime-types@~2.0.2 + - deps: mime-db@~1.1.0 + * deps: negotiator@0.4.8 + - Fix all negotiations to be case-insensitive + - Stable sort preferences of same quality according to client order + +1.1.0 / 2014-09-02 +================== + + * update `mime-types` + +1.0.7 / 2014-07-04 +================== + + * Fix wrong type returned from `type` when match after unknown extension + +1.0.6 / 2014-06-24 +================== + + * deps: negotiator@0.4.7 + +1.0.5 / 2014-06-20 +================== + + * fix crash when unknown extension given + +1.0.4 / 2014-06-19 +================== + + * use `mime-types` + +1.0.3 / 2014-06-11 +================== + + * deps: negotiator@0.4.6 + - Order by specificity when quality is the same + +1.0.2 / 2014-05-29 +================== + + * Fix interpretation when header not in request + * deps: pin negotiator@0.4.5 + +1.0.1 / 2014-01-18 +================== + + * Identity encoding isn't always acceptable + * deps: negotiator@~0.4.0 + +1.0.0 / 2013-12-27 +================== + + * Genesis diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0616607 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2015 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..82680c5 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# accepts + +[![NPM Version][npm-version-image]][npm-url] +[![NPM Downloads][npm-downloads-image]][npm-url] +[![Node.js Version][node-version-image]][node-version-url] +[![Build Status][github-actions-ci-image]][github-actions-ci-url] +[![Test Coverage][coveralls-image]][coveralls-url] + +Higher level content negotiation based on [negotiator](https://www.npmjs.com/package/negotiator). +Extracted from [koa](https://www.npmjs.com/package/koa) for general use. + +In addition to negotiator, it allows: + +- Allows types as an array or arguments list, ie `(['text/html', 'application/json'])` + as well as `('text/html', 'application/json')`. +- Allows type shorthands such as `json`. +- Returns `false` when no types match +- Treats non-existent headers as `*` + +## Installation + +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 accepts +``` + +## API + +```js +var accepts = require('accepts') +``` + +### accepts(req) + +Create a new `Accepts` object for the given `req`. + +#### .charset(charsets) + +Return the first accepted charset. If nothing in `charsets` is accepted, +then `false` is returned. + +#### .charsets() + +Return the charsets that the request accepts, in the order of the client's +preference (most preferred first). + +#### .encoding(encodings) + +Return the first accepted encoding. If nothing in `encodings` is accepted, +then `false` is returned. + +#### .encodings() + +Return the encodings that the request accepts, in the order of the client's +preference (most preferred first). + +#### .language(languages) + +Return the first accepted language. If nothing in `languages` is accepted, +then `false` is returned. + +#### .languages() + +Return the languages that the request accepts, in the order of the client's +preference (most preferred first). + +#### .type(types) + +Return the first accepted type (and it is returned as the same text as what +appears in the `types` array). If nothing in `types` is accepted, then `false` +is returned. + +The `types` array can contain full MIME types or file extensions. Any value +that is not a full MIME types is passed to `require('mime-types').lookup`. + +#### .types() + +Return the types that the request accepts, in the order of the client's +preference (most preferred first). + +## Examples + +### Simple type negotiation + +This simple example shows how to use `accepts` to return a different typed +respond body based on what the client wants to accept. The server lists it's +preferences in order and will get back the best match between the client and +server. + +```js +var accepts = require('accepts') +var http = require('http') + +function app (req, res) { + var accept = accepts(req) + + // the order of this list is significant; should be server preferred order + switch (accept.type(['json', 'html'])) { + case 'json': + res.setHeader('Content-Type', 'application/json') + res.write('{"hello":"world!"}') + break + case 'html': + res.setHeader('Content-Type', 'text/html') + res.write('hello, world!') + break + default: + // the fallback is text/plain, so no need to specify it above + res.setHeader('Content-Type', 'text/plain') + res.write('hello, world!') + break + } + + res.end() +} + +http.createServer(app).listen(3000) +``` + +You can test this out with the cURL program: +```sh +curl -I -H'Accept: text/html' http://localhost:3000/ +``` + +## License + +[MIT](LICENSE) + +[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master +[coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master +[github-actions-ci-image]: https://badgen.net/github/checks/jshttp/accepts/master?label=ci +[github-actions-ci-url]: https://github.com/jshttp/accepts/actions/workflows/ci.yml +[node-version-image]: https://badgen.net/npm/node/accepts +[node-version-url]: https://nodejs.org/en/download +[npm-downloads-image]: https://badgen.net/npm/dm/accepts +[npm-url]: https://npmjs.org/package/accepts +[npm-version-image]: https://badgen.net/npm/v/accepts diff --git a/index.js b/index.js new file mode 100644 index 0000000..e9b2f63 --- /dev/null +++ b/index.js @@ -0,0 +1,238 @@ +/*! + * accepts + * Copyright(c) 2014 Jonathan Ong + * Copyright(c) 2015 Douglas Christopher Wilson + * MIT Licensed + */ + +'use strict' + +/** + * Module dependencies. + * @private + */ + +var Negotiator = require('negotiator') +var mime = require('mime-types') + +/** + * Module exports. + * @public + */ + +module.exports = Accepts + +/** + * Create a new Accepts object for the given req. + * + * @param {object} req + * @public + */ + +function Accepts (req) { + if (!(this instanceof Accepts)) { + return new Accepts(req) + } + + this.headers = req.headers + this.negotiator = new Negotiator(req) +} + +/** + * Check if the given `type(s)` is acceptable, returning + * the best match when true, otherwise `undefined`, in which + * case you should respond with 406 "Not Acceptable". + * + * The `type` value may be a single mime type string + * such as "application/json", the extension name + * such as "json" or an array `["json", "html", "text/plain"]`. When a list + * or array is given the _best_ match, if any is returned. + * + * Examples: + * + * // Accept: text/html + * this.types('html'); + * // => "html" + * + * // Accept: text/*, application/json + * this.types('html'); + * // => "html" + * this.types('text/html'); + * // => "text/html" + * this.types('json', 'text'); + * // => "json" + * this.types('application/json'); + * // => "application/json" + * + * // Accept: text/*, application/json + * this.types('image/png'); + * this.types('png'); + * // => undefined + * + * // Accept: text/*;q=.5, application/json + * this.types(['html', 'json']); + * this.types('html', 'json'); + * // => "json" + * + * @param {String|Array} types... + * @return {String|Array|Boolean} + * @public + */ + +Accepts.prototype.type = +Accepts.prototype.types = function (types_) { + var types = types_ + + // support flattened arguments + if (types && !Array.isArray(types)) { + types = new Array(arguments.length) + for (var i = 0; i < types.length; i++) { + types[i] = arguments[i] + } + } + + // no types, return all requested types + if (!types || types.length === 0) { + return this.negotiator.mediaTypes() + } + + // no accept header, return first given type + if (!this.headers.accept) { + return types[0] + } + + var mimes = types.map(extToMime) + var accepts = this.negotiator.mediaTypes(mimes.filter(validMime)) + var first = accepts[0] + + return first + ? types[mimes.indexOf(first)] + : false +} + +/** + * Return accepted encodings or best fit based on `encodings`. + * + * Given `Accept-Encoding: gzip, deflate` + * an array sorted by quality is returned: + * + * ['gzip', 'deflate'] + * + * @param {String|Array} encodings... + * @return {String|Array} + * @public + */ + +Accepts.prototype.encoding = +Accepts.prototype.encodings = function (encodings_) { + var encodings = encodings_ + + // support flattened arguments + if (encodings && !Array.isArray(encodings)) { + encodings = new Array(arguments.length) + for (var i = 0; i < encodings.length; i++) { + encodings[i] = arguments[i] + } + } + + // no encodings, return all requested encodings + if (!encodings || encodings.length === 0) { + return this.negotiator.encodings() + } + + return this.negotiator.encodings(encodings)[0] || false +} + +/** + * Return accepted charsets or best fit based on `charsets`. + * + * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5` + * an array sorted by quality is returned: + * + * ['utf-8', 'utf-7', 'iso-8859-1'] + * + * @param {String|Array} charsets... + * @return {String|Array} + * @public + */ + +Accepts.prototype.charset = +Accepts.prototype.charsets = function (charsets_) { + var charsets = charsets_ + + // support flattened arguments + if (charsets && !Array.isArray(charsets)) { + charsets = new Array(arguments.length) + for (var i = 0; i < charsets.length; i++) { + charsets[i] = arguments[i] + } + } + + // no charsets, return all requested charsets + if (!charsets || charsets.length === 0) { + return this.negotiator.charsets() + } + + return this.negotiator.charsets(charsets)[0] || false +} + +/** + * Return accepted languages or best fit based on `langs`. + * + * Given `Accept-Language: en;q=0.8, es, pt` + * an array sorted by quality is returned: + * + * ['es', 'pt', 'en'] + * + * @param {String|Array} langs... + * @return {Array|String} + * @public + */ + +Accepts.prototype.lang = +Accepts.prototype.langs = +Accepts.prototype.language = +Accepts.prototype.languages = function (languages_) { + var languages = languages_ + + // support flattened arguments + if (languages && !Array.isArray(languages)) { + languages = new Array(arguments.length) + for (var i = 0; i < languages.length; i++) { + languages[i] = arguments[i] + } + } + + // no languages, return all requested languages + if (!languages || languages.length === 0) { + return this.negotiator.languages() + } + + return this.negotiator.languages(languages)[0] || false +} + +/** + * Convert extnames to mime. + * + * @param {String} type + * @return {String} + * @private + */ + +function extToMime (type) { + return type.indexOf('/') === -1 + ? mime.lookup(type) + : type +} + +/** + * Check if mime is valid. + * + * @param {String} type + * @return {String} + * @private + */ + +function validMime (type) { + return typeof type === 'string' +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0f2d15d --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "accepts", + "description": "Higher-level content negotiation", + "version": "1.3.8", + "contributors": [ + "Douglas Christopher Wilson ", + "Jonathan Ong (http://jongleberry.com)" + ], + "license": "MIT", + "repository": "jshttp/accepts", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "devDependencies": { + "deep-equal": "1.0.1", + "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": "4.3.1", + "eslint-plugin-standard": "4.1.0", + "mocha": "9.2.0", + "nyc": "15.1.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "engines": { + "node": ">= 0.6" + }, + "scripts": { + "lint": "eslint .", + "test": "mocha --reporter spec --check-leaks --bail test/", + "test-ci": "nyc --reporter=lcov --reporter=text npm test", + "test-cov": "nyc --reporter=html --reporter=text npm test" + }, + "keywords": [ + "content", + "negotiation", + "accept", + "accepts" + ] +} diff --git a/test/.eslintrc.yml b/test/.eslintrc.yml new file mode 100644 index 0000000..9808c3b --- /dev/null +++ b/test/.eslintrc.yml @@ -0,0 +1,2 @@ +env: + mocha: true diff --git a/test/charset.js b/test/charset.js new file mode 100644 index 0000000..0bec5c9 --- /dev/null +++ b/test/charset.js @@ -0,0 +1,76 @@ + +var accepts = require('..') +var assert = require('assert') +var deepEqual = require('deep-equal') + +describe('accepts.charsets()', function () { + describe('with no arguments', function () { + describe('when Accept-Charset is populated', function () { + it('should return accepted types', function () { + var req = createRequest('utf-8, iso-8859-1;q=0.2, utf-7;q=0.5') + var accept = accepts(req) + assert.ok(deepEqual(accept.charsets(), ['utf-8', 'utf-7', 'iso-8859-1'])) + }) + }) + + describe('when Accept-Charset is not in request', function () { + it('should return *', function () { + var req = createRequest() + var accept = accepts(req) + assert.ok(deepEqual(accept.charsets(), ['*'])) + }) + }) + + describe('when Accept-Charset is empty', function () { + it('should return an empty array', function () { + var req = createRequest('') + var accept = accepts(req) + assert.ok(deepEqual(accept.charsets(), [])) + }) + }) + }) + + describe('with multiple arguments', function () { + describe('when Accept-Charset is populated', function () { + describe('if any types match', function () { + it('should return the best fit', function () { + var req = createRequest('utf-8, iso-8859-1;q=0.2, utf-7;q=0.5') + var accept = accepts(req) + assert.strictEqual(accept.charsets('utf-7', 'utf-8'), 'utf-8') + }) + }) + + describe('if no types match', function () { + it('should return false', function () { + var req = createRequest('utf-8, iso-8859-1;q=0.2, utf-7;q=0.5') + var accept = accepts(req) + assert.strictEqual(accept.charsets('utf-16'), false) + }) + }) + }) + + describe('when Accept-Charset is not populated', function () { + it('should return the first type', function () { + var req = createRequest() + var accept = accepts(req) + assert.strictEqual(accept.charsets('utf-7', 'utf-8'), 'utf-7') + }) + }) + }) + + describe('with an array', function () { + it('should return the best fit', function () { + var req = createRequest('utf-8, iso-8859-1;q=0.2, utf-7;q=0.5') + var accept = accepts(req) + assert.strictEqual(accept.charsets(['utf-7', 'utf-8']), 'utf-8') + }) + }) +}) + +function createRequest (charset) { + return { + headers: { + 'accept-charset': charset + } + } +} diff --git a/test/encoding.js b/test/encoding.js new file mode 100644 index 0000000..0e8585a --- /dev/null +++ b/test/encoding.js @@ -0,0 +1,76 @@ + +var accepts = require('..') +var assert = require('assert') +var deepEqual = require('deep-equal') + +describe('accepts.encodings()', function () { + describe('with no arguments', function () { + describe('when Accept-Encoding is populated', function () { + it('should return accepted types', function () { + var req = createRequest('gzip, compress;q=0.2') + var accept = accepts(req) + assert.ok(deepEqual(accept.encodings(), ['gzip', 'compress', 'identity'])) + assert.strictEqual(accept.encodings('gzip', 'compress'), 'gzip') + }) + }) + + describe('when Accept-Encoding is not in request', function () { + it('should return identity', function () { + var req = createRequest() + var accept = accepts(req) + assert.ok(deepEqual(accept.encodings(), ['identity'])) + assert.strictEqual(accept.encodings('gzip', 'deflate', 'identity'), 'identity') + }) + + describe('when identity is not included', function () { + it('should return false', function () { + var req = createRequest() + var accept = accepts(req) + assert.strictEqual(accept.encodings('gzip', 'deflate'), false) + }) + }) + }) + + describe('when Accept-Encoding is empty', function () { + it('should return identity', function () { + var req = createRequest('') + var accept = accepts(req) + assert.ok(deepEqual(accept.encodings(), ['identity'])) + assert.strictEqual(accept.encodings('gzip', 'deflate', 'identity'), 'identity') + }) + + describe('when identity is not included', function () { + it('should return false', function () { + var req = createRequest('') + var accept = accepts(req) + assert.strictEqual(accept.encodings('gzip', 'deflate'), false) + }) + }) + }) + }) + + describe('with multiple arguments', function () { + it('should return the best fit', function () { + var req = createRequest('gzip, compress;q=0.2') + var accept = accepts(req) + assert.strictEqual(accept.encodings('compress', 'gzip'), 'gzip') + assert.strictEqual(accept.encodings('gzip', 'compress'), 'gzip') + }) + }) + + describe('with an array', function () { + it('should return the best fit', function () { + var req = createRequest('gzip, compress;q=0.2') + var accept = accepts(req) + assert.strictEqual(accept.encodings(['compress', 'gzip']), 'gzip') + }) + }) +}) + +function createRequest (encoding) { + return { + headers: { + 'accept-encoding': encoding + } + } +} diff --git a/test/language.js b/test/language.js new file mode 100644 index 0000000..58a0ea1 --- /dev/null +++ b/test/language.js @@ -0,0 +1,76 @@ + +var accepts = require('..') +var assert = require('assert') +var deepEqual = require('deep-equal') + +describe('accepts.languages()', function () { + describe('with no arguments', function () { + describe('when Accept-Language is populated', function () { + it('should return accepted types', function () { + var req = createRequest('en;q=0.8, es, pt') + var accept = accepts(req) + assert.ok(deepEqual(accept.languages(), ['es', 'pt', 'en'])) + }) + }) + + describe('when Accept-Language is not in request', function () { + it('should return *', function () { + var req = createRequest() + var accept = accepts(req) + assert.ok(deepEqual(accept.languages(), ['*'])) + }) + }) + + describe('when Accept-Language is empty', function () { + it('should return an empty array', function () { + var req = createRequest('') + var accept = accepts(req) + assert.ok(deepEqual(accept.languages(), [])) + }) + }) + }) + + describe('with multiple arguments', function () { + describe('when Accept-Language is populated', function () { + describe('if any types types match', function () { + it('should return the best fit', function () { + var req = createRequest('en;q=0.8, es, pt') + var accept = accepts(req) + assert.strictEqual(accept.languages('es', 'en'), 'es') + }) + }) + + describe('if no types match', function () { + it('should return false', function () { + var req = createRequest('en;q=0.8, es, pt') + var accept = accepts(req) + assert.strictEqual(accept.languages('fr', 'au'), false) + }) + }) + }) + + describe('when Accept-Language is not populated', function () { + it('should return the first type', function () { + var req = createRequest() + var accept = accepts(req) + assert.strictEqual(accept.languages('es', 'en'), 'es') + }) + }) + }) + + describe('with an array', function () { + it('should return the best fit', function () { + var req = createRequest('en;q=0.8, es, pt') + var accept = accepts(req) + assert.strictEqual(accept.languages(['es', 'en']), 'es') + }) + }) +}) + +function createRequest (language) { + return { + headers: { + 'accept-language': language + } + } +} diff --git a/test/type.js b/test/type.js new file mode 100644 index 0000000..bbe91f3 --- /dev/null +++ b/test/type.js @@ -0,0 +1,120 @@ + +var accepts = require('..') +var assert = require('assert') +var deepEqual = require('deep-equal') + +describe('accepts.types()', function () { + describe('with no arguments', function () { + describe('when Accept is populated', function () { + it('should return all accepted types', function () { + var req = createRequest('application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain') + var accept = accepts(req) + assert.ok(deepEqual(accept.types(), ['text/html', 'text/plain', 'image/jpeg', 'application/*'])) + }) + }) + + describe('when Accept not in request', function () { + it('should return */*', function () { + var req = createRequest() + var accept = accepts(req) + assert.ok(deepEqual(accept.types(), ['*/*'])) + }) + }) + + describe('when Accept is empty', function () { + it('should return []', function () { + var req = createRequest('') + var accept = accepts(req) + assert.ok(deepEqual(accept.types(), [])) + }) + }) + }) + + describe('with no valid types', function () { + describe('when Accept is populated', function () { + it('should return false', function () { + var req = createRequest('application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain') + var accept = accepts(req) + assert.strictEqual(accept.types('image/png', 'image/tiff'), false) + }) + }) + + describe('when Accept is not populated', function () { + it('should return the first type', function () { + var req = createRequest() + var accept = accepts(req) + assert.strictEqual(accept.types('text/html', 'text/plain', 'image/jpeg', 'application/*'), 'text/html') + }) + }) + }) + + describe('when extensions are given', function () { + it('should convert to mime types', function () { + var req = createRequest('text/plain, text/html') + var accept = accepts(req) + assert.strictEqual(accept.types('html'), 'html') + assert.strictEqual(accept.types('.html'), '.html') + assert.strictEqual(accept.types('txt'), 'txt') + assert.strictEqual(accept.types('.txt'), '.txt') + assert.strictEqual(accept.types('png'), false) + assert.strictEqual(accept.types('bogus'), false) + }) + }) + + describe('when an array is given', function () { + it('should return the first match', function () { + var req = createRequest('text/plain, text/html') + var accept = accepts(req) + assert.strictEqual(accept.types(['png', 'text', 'html']), 'text') + assert.strictEqual(accept.types(['png', 'html']), 'html') + assert.strictEqual(accept.types(['bogus', 'html']), 'html') + }) + }) + + describe('when multiple arguments are given', function () { + it('should return the first match', function () { + var req = createRequest('text/plain, text/html') + var accept = accepts(req) + assert.strictEqual(accept.types('png', 'text', 'html'), 'text') + assert.strictEqual(accept.types('png', 'html'), 'html') + assert.strictEqual(accept.types('bogus', 'html'), 'html') + }) + }) + + describe('when present in Accept as an exact match', function () { + it('should return the type', function () { + var req = createRequest('text/plain, text/html') + var accept = accepts(req) + assert.strictEqual(accept.types('text/html'), 'text/html') + assert.strictEqual(accept.types('text/plain'), 'text/plain') + }) + }) + + describe('when present in Accept as a type match', function () { + it('should return the type', function () { + var req = createRequest('application/json, */*') + var accept = accepts(req) + assert.strictEqual(accept.types('text/html'), 'text/html') + assert.strictEqual(accept.types('text/plain'), 'text/plain') + assert.strictEqual(accept.types('image/png'), 'image/png') + }) + }) + + describe('when present in Accept as a subtype match', function () { + it('should return the type', function () { + var req = createRequest('application/json, text/*') + var accept = accepts(req) + assert.strictEqual(accept.types('text/html'), 'text/html') + assert.strictEqual(accept.types('text/plain'), 'text/plain') + assert.strictEqual(accept.types('image/png'), false) + }) + }) +}) + +function createRequest (type) { + return { + headers: { + accept: type + } + } +}