├── .eslintignore ├── .eslintrc.yml ├── .github └── workflows │ ├── ci.yml │ ├── codeql.yml │ └── scorecard.yml ├── .gitignore ├── HISTORY.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .eslintrc.yml ├── charset.js ├── encoding.js ├── language.js └── type.js /.eslintignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - standard 4 | - plugin:markdown/recommended 5 | plugins: 6 | - markdown 7 | overrides: 8 | - files: '**/*.md' 9 | processor: 'markdown/markdown' 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - '2.x' 8 | paths-ignore: 9 | - '*.md' 10 | pull_request: 11 | paths-ignore: 12 | - '*.md' 13 | 14 | jobs: 15 | test: 16 | runs-on: ubuntu-20.04 17 | strategy: 18 | matrix: 19 | name: 20 | - Node.js 18.x 21 | - Node.js 19.x 22 | - Node.js 20.x 23 | - Node.js 21.x 24 | - Node.js 22.x 25 | 26 | include: 27 | - name: Node.js 18.x 28 | node-version: "18" 29 | 30 | - name: Node.js 19.x 31 | node-version: "19" 32 | 33 | - name: Node.js 20.x 34 | node-version: "20" 35 | 36 | - name: Node.js 21.x 37 | node-version: "21" 38 | 39 | - name: Node.js 22.x 40 | node-version: "22" 41 | 42 | steps: 43 | - uses: actions/checkout@v4 44 | 45 | - name: Install Node.js ${{ matrix.node-version }} 46 | shell: bash -eo pipefail -l {0} 47 | run: | 48 | nvm install --default ${{ matrix.node-version }} 49 | dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" 50 | 51 | - name: Configure npm 52 | run: | 53 | if [[ "$(npm config get package-lock)" == "true" ]]; then 54 | npm config set package-lock false 55 | else 56 | npm config set shrinkwrap false 57 | fi 58 | 59 | - name: Install Node.js dependencies 60 | run: npm install 61 | 62 | - name: List environment 63 | id: list_env 64 | shell: bash 65 | run: | 66 | echo "node@$(node -v)" 67 | echo "npm@$(npm -v)" 68 | npm -s ls ||: 69 | (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' 70 | 71 | - name: Run tests 72 | shell: bash 73 | run: | 74 | if npm -ps ls nyc | grep -q nyc; then 75 | npm run test-ci 76 | else 77 | npm test 78 | fi 79 | 80 | - name: Lint code 81 | if: steps.list_env.outputs.eslint != '' 82 | run: npm run lint 83 | 84 | - name: Collect code coverage 85 | uses: coverallsapp/github-action@master 86 | if: steps.list_env.outputs.nyc != '' 87 | with: 88 | github-token: ${{ secrets.GITHUB_TOKEN }} 89 | flag-name: run-${{ matrix.test_number }} 90 | parallel: true 91 | 92 | coverage: 93 | needs: test 94 | runs-on: ubuntu-latest 95 | steps: 96 | - name: Upload code coverage 97 | uses: coverallsapp/github-action@master 98 | with: 99 | github-token: ${{ secrets.GITHUB_TOKEN }} 100 | parallel-finished: true 101 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["master"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["master"] 20 | schedule: 21 | - cron: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 42 | with: 43 | languages: javascript 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | 48 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 49 | # If this step fails, then you should remove it and run the build manually (see below) 50 | # - name: Autobuild 51 | # uses: github/codeql-action/autobuild@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 52 | 53 | # ℹ️ Command-line programs to run using the OS shell. 54 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 55 | 56 | # If the Autobuild fails above, remove it and uncomment the following three lines. 57 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 58 | 59 | # - run: | 60 | # echo "Run, Build Application using script" 61 | # ./location_of_script_within_repo/buildscript.sh 62 | 63 | - name: Perform CodeQL Analysis 64 | uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 65 | with: 66 | category: "/language:javascript" -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '16 21 * * 1' 14 | push: 15 | branches: [ "master" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: "Checkout code" 35 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 41 | with: 42 | results_file: results.sarif 43 | results_format: sarif 44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 45 | # - you want to enable the Branch-Protection check on a *public* repository, or 46 | # - you are installing Scorecard on a *private* repository 47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 49 | 50 | # Public repositories: 51 | # - Publish results to OpenSSF REST API for easy access by consumers 52 | # - Allows the repository to include the Scorecard badge. 53 | # - See https://github.com/ossf/scorecard-action#publishing-results. 54 | # For private repositories: 55 | # - `publish_results` will always be set to `false`, regardless 56 | # of the value entered here. 57 | publish_results: true 58 | 59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 60 | # format to the repository Actions tab. 61 | - name: "Upload artifact" 62 | uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 63 | with: 64 | name: SARIF file 65 | path: results.sarif 66 | retention-days: 5 67 | 68 | # Upload the results to GitHub's code scanning dashboard. 69 | - name: "Upload to code-scanning" 70 | uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 71 | with: 72 | sarif_file: results.sarif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | node_modules 3 | coverage 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | unreleased 2 | ================== 3 | 4 | * deps: mime-types@^3.0.1 5 | 6 | 2.0.0 / 2024-08-31 7 | ================== 8 | 9 | * Drop node <18 support 10 | * deps: mime-types@^3.0.0 11 | * deps: negotiator@^1.0.0 12 | 13 | 1.3.8 / 2022-02-02 14 | ================== 15 | 16 | * deps: mime-types@~2.1.34 17 | - deps: mime-db@~1.51.0 18 | * deps: negotiator@0.6.3 19 | 20 | 1.3.7 / 2019-04-29 21 | ================== 22 | 23 | * deps: negotiator@0.6.2 24 | - Fix sorting charset, encoding, and language with extra parameters 25 | 26 | 1.3.6 / 2019-04-28 27 | ================== 28 | 29 | * deps: mime-types@~2.1.24 30 | - deps: mime-db@~1.40.0 31 | 32 | 1.3.5 / 2018-02-28 33 | ================== 34 | 35 | * deps: mime-types@~2.1.18 36 | - deps: mime-db@~1.33.0 37 | 38 | 1.3.4 / 2017-08-22 39 | ================== 40 | 41 | * deps: mime-types@~2.1.16 42 | - deps: mime-db@~1.29.0 43 | 44 | 1.3.3 / 2016-05-02 45 | ================== 46 | 47 | * deps: mime-types@~2.1.11 48 | - deps: mime-db@~1.23.0 49 | * deps: negotiator@0.6.1 50 | - perf: improve `Accept` parsing speed 51 | - perf: improve `Accept-Charset` parsing speed 52 | - perf: improve `Accept-Encoding` parsing speed 53 | - perf: improve `Accept-Language` parsing speed 54 | 55 | 1.3.2 / 2016-03-08 56 | ================== 57 | 58 | * deps: mime-types@~2.1.10 59 | - Fix extension of `application/dash+xml` 60 | - Update primary extension for `audio/mp4` 61 | - deps: mime-db@~1.22.0 62 | 63 | 1.3.1 / 2016-01-19 64 | ================== 65 | 66 | * deps: mime-types@~2.1.9 67 | - deps: mime-db@~1.21.0 68 | 69 | 1.3.0 / 2015-09-29 70 | ================== 71 | 72 | * deps: mime-types@~2.1.7 73 | - deps: mime-db@~1.19.0 74 | * deps: negotiator@0.6.0 75 | - Fix including type extensions in parameters in `Accept` parsing 76 | - Fix parsing `Accept` parameters with quoted equals 77 | - Fix parsing `Accept` parameters with quoted semicolons 78 | - Lazy-load modules from main entry point 79 | - perf: delay type concatenation until needed 80 | - perf: enable strict mode 81 | - perf: hoist regular expressions 82 | - perf: remove closures getting spec properties 83 | - perf: remove a closure from media type parsing 84 | - perf: remove property delete from media type parsing 85 | 86 | 1.2.13 / 2015-09-06 87 | =================== 88 | 89 | * deps: mime-types@~2.1.6 90 | - deps: mime-db@~1.18.0 91 | 92 | 1.2.12 / 2015-07-30 93 | =================== 94 | 95 | * deps: mime-types@~2.1.4 96 | - deps: mime-db@~1.16.0 97 | 98 | 1.2.11 / 2015-07-16 99 | =================== 100 | 101 | * deps: mime-types@~2.1.3 102 | - deps: mime-db@~1.15.0 103 | 104 | 1.2.10 / 2015-07-01 105 | =================== 106 | 107 | * deps: mime-types@~2.1.2 108 | - deps: mime-db@~1.14.0 109 | 110 | 1.2.9 / 2015-06-08 111 | ================== 112 | 113 | * deps: mime-types@~2.1.1 114 | - perf: fix deopt during mapping 115 | 116 | 1.2.8 / 2015-06-07 117 | ================== 118 | 119 | * deps: mime-types@~2.1.0 120 | - deps: mime-db@~1.13.0 121 | * perf: avoid argument reassignment & argument slice 122 | * perf: avoid negotiator recursive construction 123 | * perf: enable strict mode 124 | * perf: remove unnecessary bitwise operator 125 | 126 | 1.2.7 / 2015-05-10 127 | ================== 128 | 129 | * deps: negotiator@0.5.3 130 | - Fix media type parameter matching to be case-insensitive 131 | 132 | 1.2.6 / 2015-05-07 133 | ================== 134 | 135 | * deps: mime-types@~2.0.11 136 | - deps: mime-db@~1.9.1 137 | * deps: negotiator@0.5.2 138 | - Fix comparing media types with quoted values 139 | - Fix splitting media types with quoted commas 140 | 141 | 1.2.5 / 2015-03-13 142 | ================== 143 | 144 | * deps: mime-types@~2.0.10 145 | - deps: mime-db@~1.8.0 146 | 147 | 1.2.4 / 2015-02-14 148 | ================== 149 | 150 | * Support Node.js 0.6 151 | * deps: mime-types@~2.0.9 152 | - deps: mime-db@~1.7.0 153 | * deps: negotiator@0.5.1 154 | - Fix preference sorting to be stable for long acceptable lists 155 | 156 | 1.2.3 / 2015-01-31 157 | ================== 158 | 159 | * deps: mime-types@~2.0.8 160 | - deps: mime-db@~1.6.0 161 | 162 | 1.2.2 / 2014-12-30 163 | ================== 164 | 165 | * deps: mime-types@~2.0.7 166 | - deps: mime-db@~1.5.0 167 | 168 | 1.2.1 / 2014-12-30 169 | ================== 170 | 171 | * deps: mime-types@~2.0.5 172 | - deps: mime-db@~1.3.1 173 | 174 | 1.2.0 / 2014-12-19 175 | ================== 176 | 177 | * deps: negotiator@0.5.0 178 | - Fix list return order when large accepted list 179 | - Fix missing identity encoding when q=0 exists 180 | - Remove dynamic building of Negotiator class 181 | 182 | 1.1.4 / 2014-12-10 183 | ================== 184 | 185 | * deps: mime-types@~2.0.4 186 | - deps: mime-db@~1.3.0 187 | 188 | 1.1.3 / 2014-11-09 189 | ================== 190 | 191 | * deps: mime-types@~2.0.3 192 | - deps: mime-db@~1.2.0 193 | 194 | 1.1.2 / 2014-10-14 195 | ================== 196 | 197 | * deps: negotiator@0.4.9 198 | - Fix error when media type has invalid parameter 199 | 200 | 1.1.1 / 2014-09-28 201 | ================== 202 | 203 | * deps: mime-types@~2.0.2 204 | - deps: mime-db@~1.1.0 205 | * deps: negotiator@0.4.8 206 | - Fix all negotiations to be case-insensitive 207 | - Stable sort preferences of same quality according to client order 208 | 209 | 1.1.0 / 2014-09-02 210 | ================== 211 | 212 | * update `mime-types` 213 | 214 | 1.0.7 / 2014-07-04 215 | ================== 216 | 217 | * Fix wrong type returned from `type` when match after unknown extension 218 | 219 | 1.0.6 / 2014-06-24 220 | ================== 221 | 222 | * deps: negotiator@0.4.7 223 | 224 | 1.0.5 / 2014-06-20 225 | ================== 226 | 227 | * fix crash when unknown extension given 228 | 229 | 1.0.4 / 2014-06-19 230 | ================== 231 | 232 | * use `mime-types` 233 | 234 | 1.0.3 / 2014-06-11 235 | ================== 236 | 237 | * deps: negotiator@0.4.6 238 | - Order by specificity when quality is the same 239 | 240 | 1.0.2 / 2014-05-29 241 | ================== 242 | 243 | * Fix interpretation when header not in request 244 | * deps: pin negotiator@0.4.5 245 | 246 | 1.0.1 / 2014-01-18 247 | ================== 248 | 249 | * Identity encoding isn't always acceptable 250 | * deps: negotiator@~0.4.0 251 | 252 | 1.0.0 / 2013-12-27 253 | ================== 254 | 255 | * Genesis 256 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 Jonathan Ong 4 | Copyright (c) 2015 Douglas Christopher Wilson 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # accepts 2 | 3 | [![NPM Version][npm-version-image]][npm-url] 4 | [![NPM Downloads][npm-downloads-image]][npm-url] 5 | [![Node.js Version][node-version-image]][node-version-url] 6 | [![Build Status][github-actions-ci-image]][github-actions-ci-url] 7 | [![Test Coverage][coveralls-image]][coveralls-url] 8 | 9 | Higher level content negotiation based on [negotiator](https://www.npmjs.com/package/negotiator). 10 | Extracted from [koa](https://www.npmjs.com/package/koa) for general use. 11 | 12 | In addition to negotiator, it allows: 13 | 14 | - Allows types as an array or arguments list, ie `(['text/html', 'application/json'])` 15 | as well as `('text/html', 'application/json')`. 16 | - Allows type shorthands such as `json`. 17 | - Returns `false` when no types match 18 | - Treats non-existent headers as `*` 19 | 20 | ## Installation 21 | 22 | This is a [Node.js](https://nodejs.org/en/) module available through the 23 | [npm registry](https://www.npmjs.com/). Installation is done using the 24 | [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): 25 | 26 | ```sh 27 | $ npm install accepts 28 | ``` 29 | 30 | ## API 31 | 32 | ```js 33 | var accepts = require('accepts') 34 | ``` 35 | 36 | ### accepts(req) 37 | 38 | Create a new `Accepts` object for the given `req`. 39 | 40 | #### .charset(charsets) 41 | 42 | Return the first accepted charset. If nothing in `charsets` is accepted, 43 | then `false` is returned. 44 | 45 | #### .charsets() 46 | 47 | Return the charsets that the request accepts, in the order of the client's 48 | preference (most preferred first). 49 | 50 | #### .encoding(encodings) 51 | 52 | Return the first accepted encoding. If nothing in `encodings` is accepted, 53 | then `false` is returned. 54 | 55 | #### .encodings() 56 | 57 | Return the encodings that the request accepts, in the order of the client's 58 | preference (most preferred first). 59 | 60 | #### .language(languages) 61 | 62 | Return the first accepted language. If nothing in `languages` is accepted, 63 | then `false` is returned. 64 | 65 | #### .languages() 66 | 67 | Return the languages that the request accepts, in the order of the client's 68 | preference (most preferred first). 69 | 70 | #### .type(types) 71 | 72 | Return the first accepted type (and it is returned as the same text as what 73 | appears in the `types` array). If nothing in `types` is accepted, then `false` 74 | is returned. 75 | 76 | The `types` array can contain full MIME types or file extensions. Any value 77 | that is not a full MIME type is passed to `require('mime-types').lookup`. 78 | 79 | #### .types() 80 | 81 | Return the types that the request accepts, in the order of the client's 82 | preference (most preferred first). 83 | 84 | ## Examples 85 | 86 | ### Simple type negotiation 87 | 88 | This simple example shows how to use `accepts` to return a different typed 89 | respond body based on what the client wants to accept. The server lists it's 90 | preferences in order and will get back the best match between the client and 91 | server. 92 | 93 | ```js 94 | var accepts = require('accepts') 95 | var http = require('http') 96 | 97 | function app (req, res) { 98 | var accept = accepts(req) 99 | 100 | // the order of this list is significant; should be server preferred order 101 | switch (accept.type(['json', 'html'])) { 102 | case 'json': 103 | res.setHeader('Content-Type', 'application/json') 104 | res.write('{"hello":"world!"}') 105 | break 106 | case 'html': 107 | res.setHeader('Content-Type', 'text/html') 108 | res.write('hello, world!') 109 | break 110 | default: 111 | // the fallback is text/plain, so no need to specify it above 112 | res.setHeader('Content-Type', 'text/plain') 113 | res.write('hello, world!') 114 | break 115 | } 116 | 117 | res.end() 118 | } 119 | 120 | http.createServer(app).listen(3000) 121 | ``` 122 | 123 | You can test this out with the cURL program: 124 | ```sh 125 | curl -I -H'Accept: text/html' http://localhost:3000/ 126 | ``` 127 | 128 | ## License 129 | 130 | [MIT](LICENSE) 131 | 132 | [coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master 133 | [coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master 134 | [github-actions-ci-image]: https://badgen.net/github/checks/jshttp/accepts/master?label=ci 135 | [github-actions-ci-url]: https://github.com/jshttp/accepts/actions/workflows/ci.yml 136 | [node-version-image]: https://badgen.net/npm/node/accepts 137 | [node-version-url]: https://nodejs.org/en/download 138 | [npm-downloads-image]: https://badgen.net/npm/dm/accepts 139 | [npm-url]: https://npmjs.org/package/accepts 140 | [npm-version-image]: https://badgen.net/npm/v/accepts 141 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * accepts 3 | * Copyright(c) 2014 Jonathan Ong 4 | * Copyright(c) 2015 Douglas Christopher Wilson 5 | * MIT Licensed 6 | */ 7 | 8 | 'use strict' 9 | 10 | /** 11 | * Module dependencies. 12 | * @private 13 | */ 14 | 15 | var Negotiator = require('negotiator') 16 | var mime = require('mime-types') 17 | 18 | /** 19 | * Module exports. 20 | * @public 21 | */ 22 | 23 | module.exports = Accepts 24 | 25 | /** 26 | * Create a new Accepts object for the given req. 27 | * 28 | * @param {object} req 29 | * @public 30 | */ 31 | 32 | function Accepts (req) { 33 | if (!(this instanceof Accepts)) { 34 | return new Accepts(req) 35 | } 36 | 37 | this.headers = req.headers 38 | this.negotiator = new Negotiator(req) 39 | } 40 | 41 | /** 42 | * Check if the given `type(s)` is acceptable, returning 43 | * the best match when true, otherwise `undefined`, in which 44 | * case you should respond with 406 "Not Acceptable". 45 | * 46 | * The `type` value may be a single mime type string 47 | * such as "application/json", the extension name 48 | * such as "json" or an array `["json", "html", "text/plain"]`. When a list 49 | * or array is given the _best_ match, if any is returned. 50 | * 51 | * Examples: 52 | * 53 | * // Accept: text/html 54 | * this.types('html'); 55 | * // => "html" 56 | * 57 | * // Accept: text/*, application/json 58 | * this.types('html'); 59 | * // => "html" 60 | * this.types('text/html'); 61 | * // => "text/html" 62 | * this.types('json', 'text'); 63 | * // => "json" 64 | * this.types('application/json'); 65 | * // => "application/json" 66 | * 67 | * // Accept: text/*, application/json 68 | * this.types('image/png'); 69 | * this.types('png'); 70 | * // => undefined 71 | * 72 | * // Accept: text/*;q=.5, application/json 73 | * this.types(['html', 'json']); 74 | * this.types('html', 'json'); 75 | * // => "json" 76 | * 77 | * @param {String|Array} types... 78 | * @return {String|Array|Boolean} 79 | * @public 80 | */ 81 | 82 | Accepts.prototype.type = 83 | Accepts.prototype.types = function (types_) { 84 | var types = types_ 85 | 86 | // support flattened arguments 87 | if (types && !Array.isArray(types)) { 88 | types = new Array(arguments.length) 89 | for (var i = 0; i < types.length; i++) { 90 | types[i] = arguments[i] 91 | } 92 | } 93 | 94 | // no types, return all requested types 95 | if (!types || types.length === 0) { 96 | return this.negotiator.mediaTypes() 97 | } 98 | 99 | // no accept header, return first given type 100 | if (!this.headers.accept) { 101 | return types[0] 102 | } 103 | 104 | var mimes = types.map(extToMime) 105 | var accepts = this.negotiator.mediaTypes(mimes.filter(validMime)) 106 | var first = accepts[0] 107 | 108 | return first 109 | ? types[mimes.indexOf(first)] 110 | : false 111 | } 112 | 113 | /** 114 | * Return accepted encodings or best fit based on `encodings`. 115 | * 116 | * Given `Accept-Encoding: gzip, deflate` 117 | * an array sorted by quality is returned: 118 | * 119 | * ['gzip', 'deflate'] 120 | * 121 | * @param {String|Array} encodings... 122 | * @return {String|Array} 123 | * @public 124 | */ 125 | 126 | Accepts.prototype.encoding = 127 | Accepts.prototype.encodings = function (encodings_) { 128 | var encodings = encodings_ 129 | 130 | // support flattened arguments 131 | if (encodings && !Array.isArray(encodings)) { 132 | encodings = new Array(arguments.length) 133 | for (var i = 0; i < encodings.length; i++) { 134 | encodings[i] = arguments[i] 135 | } 136 | } 137 | 138 | // no encodings, return all requested encodings 139 | if (!encodings || encodings.length === 0) { 140 | return this.negotiator.encodings() 141 | } 142 | 143 | return this.negotiator.encodings(encodings)[0] || false 144 | } 145 | 146 | /** 147 | * Return accepted charsets or best fit based on `charsets`. 148 | * 149 | * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5` 150 | * an array sorted by quality is returned: 151 | * 152 | * ['utf-8', 'utf-7', 'iso-8859-1'] 153 | * 154 | * @param {String|Array} charsets... 155 | * @return {String|Array} 156 | * @public 157 | */ 158 | 159 | Accepts.prototype.charset = 160 | Accepts.prototype.charsets = function (charsets_) { 161 | var charsets = charsets_ 162 | 163 | // support flattened arguments 164 | if (charsets && !Array.isArray(charsets)) { 165 | charsets = new Array(arguments.length) 166 | for (var i = 0; i < charsets.length; i++) { 167 | charsets[i] = arguments[i] 168 | } 169 | } 170 | 171 | // no charsets, return all requested charsets 172 | if (!charsets || charsets.length === 0) { 173 | return this.negotiator.charsets() 174 | } 175 | 176 | return this.negotiator.charsets(charsets)[0] || false 177 | } 178 | 179 | /** 180 | * Return accepted languages or best fit based on `langs`. 181 | * 182 | * Given `Accept-Language: en;q=0.8, es, pt` 183 | * an array sorted by quality is returned: 184 | * 185 | * ['es', 'pt', 'en'] 186 | * 187 | * @param {String|Array} langs... 188 | * @return {Array|String} 189 | * @public 190 | */ 191 | 192 | Accepts.prototype.lang = 193 | Accepts.prototype.langs = 194 | Accepts.prototype.language = 195 | Accepts.prototype.languages = function (languages_) { 196 | var languages = languages_ 197 | 198 | // support flattened arguments 199 | if (languages && !Array.isArray(languages)) { 200 | languages = new Array(arguments.length) 201 | for (var i = 0; i < languages.length; i++) { 202 | languages[i] = arguments[i] 203 | } 204 | } 205 | 206 | // no languages, return all requested languages 207 | if (!languages || languages.length === 0) { 208 | return this.negotiator.languages() 209 | } 210 | 211 | return this.negotiator.languages(languages)[0] || false 212 | } 213 | 214 | /** 215 | * Convert extnames to mime. 216 | * 217 | * @param {String} type 218 | * @return {String} 219 | * @private 220 | */ 221 | 222 | function extToMime (type) { 223 | return type.indexOf('/') === -1 224 | ? mime.lookup(type) 225 | : type 226 | } 227 | 228 | /** 229 | * Check if mime is valid. 230 | * 231 | * @param {String} type 232 | * @return {Boolean} 233 | * @private 234 | */ 235 | 236 | function validMime (type) { 237 | return typeof type === 'string' 238 | } 239 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "accepts", 3 | "description": "Higher-level content negotiation", 4 | "version": "2.0.0", 5 | "contributors": [ 6 | "Douglas Christopher Wilson ", 7 | "Jonathan Ong (http://jongleberry.com)" 8 | ], 9 | "license": "MIT", 10 | "repository": "jshttp/accepts", 11 | "dependencies": { 12 | "mime-types": "^3.0.1", 13 | "negotiator": "^1.0.0" 14 | }, 15 | "devDependencies": { 16 | "deep-equal": "1.0.1", 17 | "eslint": "7.32.0", 18 | "eslint-config-standard": "14.1.1", 19 | "eslint-plugin-import": "2.25.4", 20 | "eslint-plugin-markdown": "2.2.1", 21 | "eslint-plugin-node": "11.1.0", 22 | "eslint-plugin-promise": "4.3.1", 23 | "eslint-plugin-standard": "4.1.0", 24 | "mocha": "9.2.0", 25 | "nyc": "15.1.0" 26 | }, 27 | "files": [ 28 | "LICENSE", 29 | "HISTORY.md", 30 | "index.js" 31 | ], 32 | "engines": { 33 | "node": ">= 18" 34 | }, 35 | "scripts": { 36 | "lint": "eslint .", 37 | "test": "mocha --reporter spec --check-leaks --bail test/", 38 | "test-ci": "nyc --reporter=lcov --reporter=text npm test", 39 | "test-cov": "nyc --reporter=html --reporter=text npm test" 40 | }, 41 | "keywords": [ 42 | "content", 43 | "negotiation", 44 | "accept", 45 | "accepts" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | -------------------------------------------------------------------------------- /test/charset.js: -------------------------------------------------------------------------------- 1 | 2 | var accepts = require('..') 3 | var assert = require('assert') 4 | var deepEqual = require('deep-equal') 5 | 6 | describe('accepts.charsets()', function () { 7 | describe('with no arguments', function () { 8 | describe('when Accept-Charset is populated', function () { 9 | it('should return accepted types', function () { 10 | var req = createRequest('utf-8, iso-8859-1;q=0.2, utf-7;q=0.5') 11 | var accept = accepts(req) 12 | assert.ok(deepEqual(accept.charsets(), ['utf-8', 'utf-7', 'iso-8859-1'])) 13 | }) 14 | }) 15 | 16 | describe('when Accept-Charset is not in request', function () { 17 | it('should return *', function () { 18 | var req = createRequest() 19 | var accept = accepts(req) 20 | assert.ok(deepEqual(accept.charsets(), ['*'])) 21 | }) 22 | }) 23 | 24 | describe('when Accept-Charset is empty', function () { 25 | it('should return an empty array', function () { 26 | var req = createRequest('') 27 | var accept = accepts(req) 28 | assert.ok(deepEqual(accept.charsets(), [])) 29 | }) 30 | }) 31 | }) 32 | 33 | describe('with multiple arguments', function () { 34 | describe('when Accept-Charset is populated', function () { 35 | describe('if any types match', function () { 36 | it('should return the best fit', function () { 37 | var req = createRequest('utf-8, iso-8859-1;q=0.2, utf-7;q=0.5') 38 | var accept = accepts(req) 39 | assert.strictEqual(accept.charsets('utf-7', 'utf-8'), 'utf-8') 40 | }) 41 | }) 42 | 43 | describe('if no types match', function () { 44 | it('should return false', function () { 45 | var req = createRequest('utf-8, iso-8859-1;q=0.2, utf-7;q=0.5') 46 | var accept = accepts(req) 47 | assert.strictEqual(accept.charsets('utf-16'), false) 48 | }) 49 | }) 50 | }) 51 | 52 | describe('when Accept-Charset is not populated', function () { 53 | it('should return the first type', function () { 54 | var req = createRequest() 55 | var accept = accepts(req) 56 | assert.strictEqual(accept.charsets('utf-7', 'utf-8'), 'utf-7') 57 | }) 58 | }) 59 | }) 60 | 61 | describe('with an array', function () { 62 | it('should return the best fit', function () { 63 | var req = createRequest('utf-8, iso-8859-1;q=0.2, utf-7;q=0.5') 64 | var accept = accepts(req) 65 | assert.strictEqual(accept.charsets(['utf-7', 'utf-8']), 'utf-8') 66 | }) 67 | }) 68 | }) 69 | 70 | function createRequest (charset) { 71 | return { 72 | headers: { 73 | 'accept-charset': charset 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/encoding.js: -------------------------------------------------------------------------------- 1 | 2 | var accepts = require('..') 3 | var assert = require('assert') 4 | var deepEqual = require('deep-equal') 5 | 6 | describe('accepts.encodings()', function () { 7 | describe('with no arguments', function () { 8 | describe('when Accept-Encoding is populated', function () { 9 | it('should return accepted types', function () { 10 | var req = createRequest('gzip, compress;q=0.2') 11 | var accept = accepts(req) 12 | assert.ok(deepEqual(accept.encodings(), ['gzip', 'compress', 'identity'])) 13 | assert.strictEqual(accept.encodings('gzip', 'compress'), 'gzip') 14 | }) 15 | }) 16 | 17 | describe('when Accept-Encoding is not in request', function () { 18 | it('should return identity', function () { 19 | var req = createRequest() 20 | var accept = accepts(req) 21 | assert.ok(deepEqual(accept.encodings(), ['identity'])) 22 | assert.strictEqual(accept.encodings('gzip', 'deflate', 'identity'), 'identity') 23 | }) 24 | 25 | describe('when identity is not included', function () { 26 | it('should return false', function () { 27 | var req = createRequest() 28 | var accept = accepts(req) 29 | assert.strictEqual(accept.encodings('gzip', 'deflate'), false) 30 | }) 31 | }) 32 | }) 33 | 34 | describe('when Accept-Encoding is empty', function () { 35 | it('should return identity', function () { 36 | var req = createRequest('') 37 | var accept = accepts(req) 38 | assert.ok(deepEqual(accept.encodings(), ['identity'])) 39 | assert.strictEqual(accept.encodings('gzip', 'deflate', 'identity'), 'identity') 40 | }) 41 | 42 | describe('when identity is not included', function () { 43 | it('should return false', function () { 44 | var req = createRequest('') 45 | var accept = accepts(req) 46 | assert.strictEqual(accept.encodings('gzip', 'deflate'), false) 47 | }) 48 | }) 49 | }) 50 | }) 51 | 52 | describe('with multiple arguments', function () { 53 | it('should return the best fit', function () { 54 | var req = createRequest('gzip, compress;q=0.2') 55 | var accept = accepts(req) 56 | assert.strictEqual(accept.encodings('compress', 'gzip'), 'gzip') 57 | assert.strictEqual(accept.encodings('gzip', 'compress'), 'gzip') 58 | }) 59 | }) 60 | 61 | describe('with an array', function () { 62 | it('should return the best fit', function () { 63 | var req = createRequest('gzip, compress;q=0.2') 64 | var accept = accepts(req) 65 | assert.strictEqual(accept.encodings(['compress', 'gzip']), 'gzip') 66 | }) 67 | }) 68 | }) 69 | 70 | function createRequest (encoding) { 71 | return { 72 | headers: { 73 | 'accept-encoding': encoding 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/language.js: -------------------------------------------------------------------------------- 1 | 2 | var accepts = require('..') 3 | var assert = require('assert') 4 | var deepEqual = require('deep-equal') 5 | 6 | describe('accepts.languages()', function () { 7 | describe('with no arguments', function () { 8 | describe('when Accept-Language is populated', function () { 9 | it('should return accepted types', function () { 10 | var req = createRequest('en;q=0.8, es, pt') 11 | var accept = accepts(req) 12 | assert.ok(deepEqual(accept.languages(), ['es', 'pt', 'en'])) 13 | }) 14 | }) 15 | 16 | describe('when Accept-Language is not in request', function () { 17 | it('should return *', function () { 18 | var req = createRequest() 19 | var accept = accepts(req) 20 | assert.ok(deepEqual(accept.languages(), ['*'])) 21 | }) 22 | }) 23 | 24 | describe('when Accept-Language is empty', function () { 25 | it('should return an empty array', function () { 26 | var req = createRequest('') 27 | var accept = accepts(req) 28 | assert.ok(deepEqual(accept.languages(), [])) 29 | }) 30 | }) 31 | }) 32 | 33 | describe('with multiple arguments', function () { 34 | describe('when Accept-Language is populated', function () { 35 | describe('if any types types match', function () { 36 | it('should return the best fit', function () { 37 | var req = createRequest('en;q=0.8, es, pt') 38 | var accept = accepts(req) 39 | assert.strictEqual(accept.languages('es', 'en'), 'es') 40 | }) 41 | }) 42 | 43 | describe('if no types match', function () { 44 | it('should return false', function () { 45 | var req = createRequest('en;q=0.8, es, pt') 46 | var accept = accepts(req) 47 | assert.strictEqual(accept.languages('fr', 'au'), false) 48 | }) 49 | }) 50 | }) 51 | 52 | describe('when Accept-Language is not populated', function () { 53 | it('should return the first type', function () { 54 | var req = createRequest() 55 | var accept = accepts(req) 56 | assert.strictEqual(accept.languages('es', 'en'), 'es') 57 | }) 58 | }) 59 | }) 60 | 61 | describe('with an array', function () { 62 | it('should return the best fit', function () { 63 | var req = createRequest('en;q=0.8, es, pt') 64 | var accept = accepts(req) 65 | assert.strictEqual(accept.languages(['es', 'en']), 'es') 66 | }) 67 | }) 68 | }) 69 | 70 | function createRequest (language) { 71 | return { 72 | headers: { 73 | 'accept-language': language 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/type.js: -------------------------------------------------------------------------------- 1 | 2 | var accepts = require('..') 3 | var assert = require('assert') 4 | var deepEqual = require('deep-equal') 5 | 6 | describe('accepts.types()', function () { 7 | describe('with no arguments', function () { 8 | describe('when Accept is populated', function () { 9 | it('should return all accepted types', function () { 10 | var req = createRequest('application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain') 11 | var accept = accepts(req) 12 | assert.ok(deepEqual(accept.types(), ['text/html', 'text/plain', 'image/jpeg', 'application/*'])) 13 | }) 14 | }) 15 | 16 | describe('when Accept not in request', function () { 17 | it('should return */*', function () { 18 | var req = createRequest() 19 | var accept = accepts(req) 20 | assert.ok(deepEqual(accept.types(), ['*/*'])) 21 | }) 22 | }) 23 | 24 | describe('when Accept is empty', function () { 25 | it('should return []', function () { 26 | var req = createRequest('') 27 | var accept = accepts(req) 28 | assert.ok(deepEqual(accept.types(), [])) 29 | }) 30 | }) 31 | }) 32 | 33 | describe('with no valid types', function () { 34 | describe('when Accept is populated', function () { 35 | it('should return false', function () { 36 | var req = createRequest('application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain') 37 | var accept = accepts(req) 38 | assert.strictEqual(accept.types('image/png', 'image/tiff'), false) 39 | }) 40 | }) 41 | 42 | describe('when Accept is not populated', function () { 43 | it('should return the first type', function () { 44 | var req = createRequest() 45 | var accept = accepts(req) 46 | assert.strictEqual(accept.types('text/html', 'text/plain', 'image/jpeg', 'application/*'), 'text/html') 47 | }) 48 | }) 49 | }) 50 | 51 | describe('when extensions are given', function () { 52 | it('should convert to mime types', function () { 53 | var req = createRequest('text/plain, text/html') 54 | var accept = accepts(req) 55 | assert.strictEqual(accept.types('html'), 'html') 56 | assert.strictEqual(accept.types('.html'), '.html') 57 | assert.strictEqual(accept.types('txt'), 'txt') 58 | assert.strictEqual(accept.types('.txt'), '.txt') 59 | assert.strictEqual(accept.types('png'), false) 60 | assert.strictEqual(accept.types('bogus'), false) 61 | }) 62 | }) 63 | 64 | describe('when an array is given', function () { 65 | it('should return the first match', function () { 66 | var req = createRequest('text/plain, text/html') 67 | var accept = accepts(req) 68 | assert.strictEqual(accept.types(['png', 'text', 'html']), 'text') 69 | assert.strictEqual(accept.types(['png', 'html']), 'html') 70 | assert.strictEqual(accept.types(['bogus', 'html']), 'html') 71 | }) 72 | }) 73 | 74 | describe('when multiple arguments are given', function () { 75 | it('should return the first match', function () { 76 | var req = createRequest('text/plain, text/html') 77 | var accept = accepts(req) 78 | assert.strictEqual(accept.types('png', 'text', 'html'), 'text') 79 | assert.strictEqual(accept.types('png', 'html'), 'html') 80 | assert.strictEqual(accept.types('bogus', 'html'), 'html') 81 | }) 82 | }) 83 | 84 | describe('when present in Accept as an exact match', function () { 85 | it('should return the type', function () { 86 | var req = createRequest('text/plain, text/html') 87 | var accept = accepts(req) 88 | assert.strictEqual(accept.types('text/html'), 'text/html') 89 | assert.strictEqual(accept.types('text/plain'), 'text/plain') 90 | }) 91 | }) 92 | 93 | describe('when present in Accept as a type match', function () { 94 | it('should return the type', function () { 95 | var req = createRequest('application/json, */*') 96 | var accept = accepts(req) 97 | assert.strictEqual(accept.types('text/html'), 'text/html') 98 | assert.strictEqual(accept.types('text/plain'), 'text/plain') 99 | assert.strictEqual(accept.types('image/png'), 'image/png') 100 | }) 101 | }) 102 | 103 | describe('when present in Accept as a subtype match', function () { 104 | it('should return the type', function () { 105 | var req = createRequest('application/json, text/*') 106 | var accept = accepts(req) 107 | assert.strictEqual(accept.types('text/html'), 'text/html') 108 | assert.strictEqual(accept.types('text/plain'), 'text/plain') 109 | assert.strictEqual(accept.types('image/png'), false) 110 | }) 111 | }) 112 | }) 113 | 114 | function createRequest (type) { 115 | return { 116 | headers: { 117 | accept: type 118 | } 119 | } 120 | } 121 | --------------------------------------------------------------------------------