├── .eslintignore ├── .eslintrc.yml ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ └── scorecard.yml ├── .gitignore ├── HISTORY.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .eslintrc.yml └── test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | -------------------------------------------------------------------------------- /.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/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: monthly 12 | time: "23:00" 13 | timezone: Europe/London 14 | open-pull-requests-limit: 10 15 | ignore: 16 | - dependency-name: "*" 17 | update-types: ["version-update:semver-major"] 18 | -------------------------------------------------------------------------------- /.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 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | test: 19 | permissions: 20 | checks: write # for coverallsapp/github-action to create new checks 21 | contents: read # for actions/checkout to fetch code 22 | runs-on: ubuntu-latest 23 | strategy: 24 | matrix: 25 | name: 26 | - Node.js 18.x 27 | - Node.js 19.x 28 | - Node.js 20.x 29 | - Node.js 21.x 30 | - Node.js 22.x 31 | 32 | include: 33 | - name: Node.js 18.x 34 | node-version: "18" 35 | 36 | - name: Node.js 19.x 37 | node-version: "19" 38 | 39 | - name: Node.js 20.x 40 | node-version: "20" 41 | 42 | - name: Node.js 21.x 43 | node-version: "21" 44 | 45 | - name: Node.js 22.x 46 | node-version: "22" 47 | 48 | steps: 49 | - uses: actions/checkout@v4.2.2 50 | 51 | - name: Install Node.js ${{ matrix.node-version }} 52 | shell: bash -eo pipefail -l {0} 53 | run: | 54 | nvm install --default ${{ matrix.node-version }} 55 | dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" 56 | 57 | - name: Configure npm 58 | run: | 59 | if [[ "$(npm config get package-lock)" == "true" ]]; then 60 | npm config set package-lock false 61 | else 62 | npm config set shrinkwrap false 63 | fi 64 | 65 | - name: Remove npm module(s) ${{ matrix.npm-rm }} 66 | run: npm rm --silent --save-dev ${{ matrix.npm-rm }} 67 | if: matrix.npm-rm != '' 68 | 69 | - name: Install npm module(s) ${{ matrix.npm-i }} 70 | run: npm install --save-dev ${{ matrix.npm-i }} 71 | if: matrix.npm-i != '' 72 | 73 | - name: Setup Node.js version-specific dependencies 74 | shell: bash 75 | run: | 76 | # eslint for linting 77 | # - remove on Node.js < 10 78 | if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then 79 | node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ 80 | grep -E '^eslint(-|$)' | \ 81 | sort -r | \ 82 | xargs -n1 npm rm --silent --save-dev 83 | fi 84 | 85 | - name: Install Node.js dependencies 86 | run: npm install 87 | 88 | - name: List environment 89 | id: list_env 90 | shell: bash 91 | run: | 92 | echo "node@$(node -v)" 93 | echo "npm@$(npm -v)" 94 | npm -s ls ||: 95 | (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' 96 | 97 | - name: Run tests 98 | shell: bash 99 | run: | 100 | if npm -ps ls nyc | grep -q nyc; then 101 | npm run test-ci 102 | else 103 | npm test 104 | fi 105 | 106 | - name: Lint code 107 | if: steps.list_env.outputs.eslint != '' 108 | run: npm run lint 109 | 110 | - name: Collect code coverage 111 | uses: coverallsapp/github-action@master 112 | if: steps.list_env.outputs.nyc != '' 113 | with: 114 | github-token: ${{ secrets.GITHUB_TOKEN }} 115 | flag-name: run-${{ matrix.test_number }} 116 | parallel: true 117 | 118 | coverage: 119 | permissions: 120 | checks: write # for coverallsapp/github-action to create new checks 121 | needs: test 122 | runs-on: ubuntu-latest 123 | steps: 124 | - name: Upload code coverage 125 | uses: coverallsapp/github-action@master 126 | with: 127 | github-token: ${{ secrets.GITHUB_TOKEN }} 128 | parallel-finished: true 129 | -------------------------------------------------------------------------------- /.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 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: ["javascript"] 39 | # CodeQL supports [ $supported-codeql-languages ] 40 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@v4.2.2 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v3.28.18 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@v3.28.18 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v3.28.18 72 | with: 73 | category: "/language:${{matrix.language}}" 74 | -------------------------------------------------------------------------------- /.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@85e6279cec87321a52edac9c87bce653a07cf6c2 # v3.6.0 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 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@dc138d4f519ecc58013d8fcef428272e2436cafd # v2.23.2 71 | with: 72 | sarif_file: results.sarif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | node_modules/ 4 | npm-debug.log 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 2.0.1 / 2025-03-27 2 | ========== 3 | 4 | 2.0.0 / 2024-08-31 5 | ========== 6 | 7 | * Drop node <18 8 | * Use `content-type@^1.0.5` and `media-typer@^1.0.0` for type validation 9 | - No behavior changes, upgrades `media-typer` 10 | * deps: mime-types@^3.0.0 11 | - Add `application/toml` with extension `.toml` 12 | - Add `application/ubjson` with extension `.ubj` 13 | - Add `application/x-keepass2` with extension `.kdbx` 14 | - Add deprecated iWorks mime types and extensions 15 | - Add extension `.amr` to `audio/amr` 16 | - Add extension `.cjs` to `application/node` 17 | - Add extension `.dbf` to `application/vnd.dbf` 18 | - Add extension `.m4s` to `video/iso.segment` 19 | - Add extension `.mvt` to `application/vnd.mapbox-vector-tile` 20 | - Add extension `.mxmf` to `audio/mobile-xmf` 21 | - Add extension `.opus` to `audio/ogg` 22 | - Add extension `.rar` to `application/vnd.rar` 23 | - Add extension `.td` to `application/urc-targetdesc+xml` 24 | - Add extension `.trig` to `application/trig` 25 | - Add extensions from IANA for `application/*+xml` types 26 | - Add `image/avif` with extension `.avif` 27 | - Add `image/ktx2` with extension `.ktx2` 28 | - Add `image/vnd.ms-dds` with extension `.dds` 29 | - Add new upstream MIME types 30 | - Fix extension of `application/vnd.apple.keynote` to be `.key` 31 | - Remove ambigious extensions from IANA for `application/*+xml` types 32 | - Update primary extension to `.es` for `application/ecmascript` 33 | 34 | 1.6.18 / 2019-04-26 35 | =================== 36 | 37 | * Fix regression passing request object to `typeis.is` 38 | 39 | 1.6.17 / 2019-04-25 40 | =================== 41 | 42 | * deps: mime-types@~2.1.24 43 | - Add Apple file extensions from IANA 44 | - Add extension `.csl` to `application/vnd.citationstyles.style+xml` 45 | - Add extension `.es` to `application/ecmascript` 46 | - Add extension `.nq` to `application/n-quads` 47 | - Add extension `.nt` to `application/n-triples` 48 | - Add extension `.owl` to `application/rdf+xml` 49 | - Add extensions `.siv` and `.sieve` to `application/sieve` 50 | - Add extensions from IANA for `image/*` types 51 | - Add extensions from IANA for `model/*` types 52 | - Add extensions to HEIC image types 53 | - Add new mime types 54 | - Add `text/mdx` with extension `.mdx` 55 | * perf: prevent internal `throw` on invalid type 56 | 57 | 1.6.16 / 2018-02-16 58 | =================== 59 | 60 | * deps: mime-types@~2.1.18 61 | - Add `application/raml+yaml` with extension `.raml` 62 | - Add `application/wasm` with extension `.wasm` 63 | - Add `text/shex` with extension `.shex` 64 | - Add extensions for JPEG-2000 images 65 | - Add extensions from IANA for `message/*` types 66 | - Add extension `.mjs` to `application/javascript` 67 | - Add extension `.wadl` to `application/vnd.sun.wadl+xml` 68 | - Add extension `.gz` to `application/gzip` 69 | - Add glTF types and extensions 70 | - Add new mime types 71 | - Update extensions `.md` and `.markdown` to be `text/markdown` 72 | - Update font MIME types 73 | - Update `text/hjson` to registered `application/hjson` 74 | 75 | 1.6.15 / 2017-03-31 76 | =================== 77 | 78 | * deps: mime-types@~2.1.15 79 | - Add new mime types 80 | 81 | 1.6.14 / 2016-11-18 82 | =================== 83 | 84 | * deps: mime-types@~2.1.13 85 | - Add new mime types 86 | 87 | 1.6.13 / 2016-05-18 88 | =================== 89 | 90 | * deps: mime-types@~2.1.11 91 | - Add new mime types 92 | 93 | 1.6.12 / 2016-02-28 94 | =================== 95 | 96 | * deps: mime-types@~2.1.10 97 | - Add new mime types 98 | - Fix extension of `application/dash+xml` 99 | - Update primary extension for `audio/mp4` 100 | 101 | 1.6.11 / 2016-01-29 102 | =================== 103 | 104 | * deps: mime-types@~2.1.9 105 | - Add new mime types 106 | 107 | 1.6.10 / 2015-12-01 108 | =================== 109 | 110 | * deps: mime-types@~2.1.8 111 | - Add new mime types 112 | 113 | 1.6.9 / 2015-09-27 114 | ================== 115 | 116 | * deps: mime-types@~2.1.7 117 | - Add new mime types 118 | 119 | 1.6.8 / 2015-09-04 120 | ================== 121 | 122 | * deps: mime-types@~2.1.6 123 | - Add new mime types 124 | 125 | 1.6.7 / 2015-08-20 126 | ================== 127 | 128 | * Fix type error when given invalid type to match against 129 | * deps: mime-types@~2.1.5 130 | - Add new mime types 131 | 132 | 1.6.6 / 2015-07-31 133 | ================== 134 | 135 | * deps: mime-types@~2.1.4 136 | - Add new mime types 137 | 138 | 1.6.5 / 2015-07-16 139 | ================== 140 | 141 | * deps: mime-types@~2.1.3 142 | - Add new mime types 143 | 144 | 1.6.4 / 2015-07-01 145 | ================== 146 | 147 | * deps: mime-types@~2.1.2 148 | - Add new mime types 149 | * perf: enable strict mode 150 | * perf: remove argument reassignment 151 | 152 | 1.6.3 / 2015-06-08 153 | ================== 154 | 155 | * deps: mime-types@~2.1.1 156 | - Add new mime types 157 | * perf: reduce try block size 158 | * perf: remove bitwise operations 159 | 160 | 1.6.2 / 2015-05-10 161 | ================== 162 | 163 | * deps: mime-types@~2.0.11 164 | - Add new mime types 165 | 166 | 1.6.1 / 2015-03-13 167 | ================== 168 | 169 | * deps: mime-types@~2.0.10 170 | - Add new mime types 171 | 172 | 1.6.0 / 2015-02-12 173 | ================== 174 | 175 | * fix false-positives in `hasBody` `Transfer-Encoding` check 176 | * support wildcard for both type and subtype (`*/*`) 177 | 178 | 1.5.7 / 2015-02-09 179 | ================== 180 | 181 | * fix argument reassignment 182 | * deps: mime-types@~2.0.9 183 | - Add new mime types 184 | 185 | 1.5.6 / 2015-01-29 186 | ================== 187 | 188 | * deps: mime-types@~2.0.8 189 | - Add new mime types 190 | 191 | 1.5.5 / 2014-12-30 192 | ================== 193 | 194 | * deps: mime-types@~2.0.7 195 | - Add new mime types 196 | - Fix missing extensions 197 | - Fix various invalid MIME type entries 198 | - Remove example template MIME types 199 | - deps: mime-db@~1.5.0 200 | 201 | 1.5.4 / 2014-12-10 202 | ================== 203 | 204 | * deps: mime-types@~2.0.4 205 | - Add new mime types 206 | - deps: mime-db@~1.3.0 207 | 208 | 1.5.3 / 2014-11-09 209 | ================== 210 | 211 | * deps: mime-types@~2.0.3 212 | - Add new mime types 213 | - deps: mime-db@~1.2.0 214 | 215 | 1.5.2 / 2014-09-28 216 | ================== 217 | 218 | * deps: mime-types@~2.0.2 219 | - Add new mime types 220 | - deps: mime-db@~1.1.0 221 | 222 | 1.5.1 / 2014-09-07 223 | ================== 224 | 225 | * Support Node.js 0.6 226 | * deps: media-typer@0.3.0 227 | * deps: mime-types@~2.0.1 228 | - Support Node.js 0.6 229 | 230 | 1.5.0 / 2014-09-05 231 | ================== 232 | 233 | * fix `hasbody` to be true for `content-length: 0` 234 | 235 | 1.4.0 / 2014-09-02 236 | ================== 237 | 238 | * update mime-types 239 | 240 | 1.3.2 / 2014-06-24 241 | ================== 242 | 243 | * use `~` range on mime-types 244 | 245 | 1.3.1 / 2014-06-19 246 | ================== 247 | 248 | * fix global variable leak 249 | 250 | 1.3.0 / 2014-06-19 251 | ================== 252 | 253 | * improve type parsing 254 | 255 | - invalid media type never matches 256 | - media type not case-sensitive 257 | - extra LWS does not affect results 258 | 259 | 1.2.2 / 2014-06-19 260 | ================== 261 | 262 | * fix behavior on unknown type argument 263 | 264 | 1.2.1 / 2014-06-03 265 | ================== 266 | 267 | * switch dependency from `mime` to `mime-types@1.0.0` 268 | 269 | 1.2.0 / 2014-05-11 270 | ================== 271 | 272 | * support suffix matching: 273 | 274 | - `+json` matches `application/vnd+json` 275 | - `*/vnd+json` matches `application/vnd+json` 276 | - `application/*+json` matches `application/vnd+json` 277 | 278 | 1.1.0 / 2014-04-12 279 | ================== 280 | 281 | * add non-array values support 282 | * expose internal utilities: 283 | 284 | - `.is()` 285 | - `.hasBody()` 286 | - `.normalize()` 287 | - `.match()` 288 | 289 | 1.0.1 / 2014-03-30 290 | ================== 291 | 292 | * add `multipart` as a shorthand 293 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 Jonathan Ong 4 | Copyright (c) 2014-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 | # type-is 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][ci-image]][ci-url] 7 | [![Test Coverage][coveralls-image]][coveralls-url] 8 | 9 | Infer the content-type of a request. 10 | 11 | ## Install 12 | 13 | This is a [Node.js](https://nodejs.org/en/) module available through the 14 | [npm registry](https://www.npmjs.com/). Installation is done using the 15 | [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): 16 | 17 | ```sh 18 | $ npm install type-is 19 | ``` 20 | 21 | ## API 22 | 23 | ```js 24 | var http = require('http') 25 | var typeis = require('type-is') 26 | 27 | http.createServer(function (req, res) { 28 | var istext = typeis(req, ['text/*']) 29 | res.end('you ' + (istext ? 'sent' : 'did not send') + ' me text') 30 | }) 31 | ``` 32 | 33 | ### typeis(request, types) 34 | 35 | Checks if the `request` is one of the `types`. If the request has no body, 36 | even if there is a `Content-Type` header, then `null` is returned. If the 37 | `Content-Type` header is invalid or does not matches any of the `types`, then 38 | `false` is returned. Otherwise, a string of the type that matched is returned. 39 | 40 | The `request` argument is expected to be a Node.js HTTP request. The `types` 41 | argument is an array of type strings. 42 | 43 | Each type in the `types` array can be one of the following: 44 | 45 | - A file extension name such as `json`. This name will be returned if matched. 46 | - A mime type such as `application/json`. 47 | - A mime type with a wildcard such as `*/*` or `*/json` or `application/*`. 48 | The full mime type will be returned if matched. 49 | - A suffix such as `+json`. This can be combined with a wildcard such as 50 | `*/vnd+json` or `application/*+json`. The full mime type will be returned 51 | if matched. 52 | 53 | Some examples to illustrate the inputs and returned value: 54 | 55 | ```js 56 | // req.headers.content-type = 'application/json' 57 | 58 | typeis(req, ['json']) // => 'json' 59 | typeis(req, ['html', 'json']) // => 'json' 60 | typeis(req, ['application/*']) // => 'application/json' 61 | typeis(req, ['application/json']) // => 'application/json' 62 | 63 | typeis(req, ['html']) // => false 64 | ``` 65 | 66 | ### typeis.hasBody(request) 67 | 68 | Returns a Boolean if the given `request` has a body, regardless of the 69 | `Content-Type` header. 70 | 71 | Having a body has no relation to how large the body is (it may be 0 bytes). 72 | This is similar to how file existence works. If a body does exist, then this 73 | indicates that there is data to read from the Node.js request stream. 74 | 75 | ```js 76 | if (typeis.hasBody(req)) { 77 | // read the body, since there is one 78 | 79 | req.on('data', function (chunk) { 80 | // ... 81 | }) 82 | } 83 | ``` 84 | 85 | ### typeis.is(mediaType, types) 86 | 87 | Checks if the `mediaType` is one of the `types`. If the `mediaType` is invalid 88 | or does not matches any of the `types`, then `false` is returned. Otherwise, a 89 | string of the type that matched is returned. 90 | 91 | The `mediaType` argument is expected to be a 92 | [media type](https://tools.ietf.org/html/rfc6838) string. The `types` argument 93 | is an array of type strings. 94 | 95 | Each type in the `types` array can be one of the following: 96 | 97 | - A file extension name such as `json`. This name will be returned if matched. 98 | - A mime type such as `application/json`. 99 | - A mime type with a wildcard such as `*/*` or `*/json` or `application/*`. 100 | The full mime type will be returned if matched. 101 | - A suffix such as `+json`. This can be combined with a wildcard such as 102 | `*/vnd+json` or `application/*+json`. The full mime type will be returned 103 | if matched. 104 | 105 | Some examples to illustrate the inputs and returned value: 106 | 107 | ```js 108 | var mediaType = 'application/json' 109 | 110 | typeis.is(mediaType, ['json']) // => 'json' 111 | typeis.is(mediaType, ['html', 'json']) // => 'json' 112 | typeis.is(mediaType, ['application/*']) // => 'application/json' 113 | typeis.is(mediaType, ['application/json']) // => 'application/json' 114 | 115 | typeis.is(mediaType, ['html']) // => false 116 | ``` 117 | 118 | ### typeis.match(expected, actual) 119 | 120 | Match the type string `expected` with `actual`, taking in to account wildcards. 121 | A wildcard can only be in the type of the subtype part of a media type and only 122 | in the `expected` value (as `actual` should be the real media type to match). A 123 | suffix can still be included even with a wildcard subtype. If an input is 124 | malformed, `false` will be returned. 125 | 126 | ```js 127 | typeis.match('text/html', 'text/html') // => true 128 | typeis.match('*/html', 'text/html') // => true 129 | typeis.match('text/*', 'text/html') // => true 130 | typeis.match('*/*', 'text/html') // => true 131 | typeis.match('*/*+json', 'application/x-custom+json') // => true 132 | ``` 133 | 134 | ### typeis.normalize(type) 135 | 136 | Normalize a `type` string. This works by performing the following: 137 | 138 | - If the `type` is not a string, `false` is returned. 139 | - If the string starts with `+` (so it is a `+suffix` shorthand like `+json`), 140 | then it is expanded to contain the complete wildcard notation of `*/*+suffix`. 141 | - If the string contains a `/`, then it is returned as the type. 142 | - Else the string is assumed to be a file extension and the mapped media type is 143 | returned, or `false` is there is no mapping. 144 | 145 | This includes two special mappings: 146 | 147 | - `'multipart'` -> `'multipart/*'` 148 | - `'urlencoded'` -> `'application/x-www-form-urlencoded'` 149 | 150 | ## Examples 151 | 152 | ### Example body parser 153 | 154 | ```js 155 | var express = require('express') 156 | var typeis = require('type-is') 157 | 158 | var app = express() 159 | 160 | app.use(function bodyParser (req, res, next) { 161 | if (!typeis.hasBody(req)) { 162 | return next() 163 | } 164 | 165 | switch (typeis(req, ['urlencoded', 'json', 'multipart'])) { 166 | case 'urlencoded': 167 | // parse urlencoded body 168 | throw new Error('implement urlencoded body parsing') 169 | case 'json': 170 | // parse json body 171 | throw new Error('implement json body parsing') 172 | case 'multipart': 173 | // parse multipart body 174 | throw new Error('implement multipart body parsing') 175 | default: 176 | // 415 error code 177 | res.statusCode = 415 178 | res.end() 179 | break 180 | } 181 | }) 182 | ``` 183 | 184 | ## License 185 | 186 | [MIT](LICENSE) 187 | 188 | [ci-image]: https://badgen.net/github/checks/jshttp/type-is/master?label=ci 189 | [ci-url]: https://github.com/jshttp/type-is/actions/workflows/ci.yml 190 | [coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/type-is/master 191 | [coveralls-url]: https://coveralls.io/r/jshttp/type-is?branch=master 192 | [node-version-image]: https://badgen.net/npm/node/type-is 193 | [node-version-url]: https://nodejs.org/en/download 194 | [npm-downloads-image]: https://badgen.net/npm/dm/type-is 195 | [npm-url]: https://npmjs.org/package/type-is 196 | [npm-version-image]: https://badgen.net/npm/v/type-is 197 | [travis-image]: https://badgen.net/travis/jshttp/type-is/master 198 | [travis-url]: https://travis-ci.org/jshttp/type-is 199 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * type-is 3 | * Copyright(c) 2014 Jonathan Ong 4 | * Copyright(c) 2014-2015 Douglas Christopher Wilson 5 | * MIT Licensed 6 | */ 7 | 8 | 'use strict' 9 | 10 | /** 11 | * Module dependencies. 12 | * @private 13 | */ 14 | 15 | var contentType = require('content-type') 16 | var mime = require('mime-types') 17 | var typer = require('media-typer') 18 | 19 | /** 20 | * Module exports. 21 | * @public 22 | */ 23 | 24 | module.exports = typeofrequest 25 | module.exports.is = typeis 26 | module.exports.hasBody = hasbody 27 | module.exports.normalize = normalize 28 | module.exports.match = mimeMatch 29 | 30 | /** 31 | * Compare a `value` content-type with `types`. 32 | * Each `type` can be an extension like `html`, 33 | * a special shortcut like `multipart` or `urlencoded`, 34 | * or a mime type. 35 | * 36 | * If no types match, `false` is returned. 37 | * Otherwise, the first `type` that matches is returned. 38 | * 39 | * @param {String} value 40 | * @param {Array} types 41 | * @public 42 | */ 43 | 44 | function typeis (value, types_) { 45 | var i 46 | var types = types_ 47 | 48 | // remove parameters and normalize 49 | var val = tryNormalizeType(value) 50 | 51 | // no type or invalid 52 | if (!val) { 53 | return false 54 | } 55 | 56 | // support flattened arguments 57 | if (types && !Array.isArray(types)) { 58 | types = new Array(arguments.length - 1) 59 | for (i = 0; i < types.length; i++) { 60 | types[i] = arguments[i + 1] 61 | } 62 | } 63 | 64 | // no types, return the content type 65 | if (!types || !types.length) { 66 | return val 67 | } 68 | 69 | var type 70 | for (i = 0; i < types.length; i++) { 71 | if (mimeMatch(normalize(type = types[i]), val)) { 72 | return type[0] === '+' || type.indexOf('*') !== -1 73 | ? val 74 | : type 75 | } 76 | } 77 | 78 | // no matches 79 | return false 80 | } 81 | 82 | /** 83 | * Check if a request has a request body. 84 | * A request with a body __must__ either have `transfer-encoding` 85 | * or `content-length` headers set. 86 | * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 87 | * 88 | * @param {Object} request 89 | * @return {Boolean} 90 | * @public 91 | */ 92 | 93 | function hasbody (req) { 94 | return req.headers['transfer-encoding'] !== undefined || 95 | !isNaN(req.headers['content-length']) 96 | } 97 | 98 | /** 99 | * Check if the incoming request contains the "Content-Type" 100 | * header field, and it contains any of the give mime `type`s. 101 | * If there is no request body, `null` is returned. 102 | * If there is no content type, `false` is returned. 103 | * Otherwise, it returns the first `type` that matches. 104 | * 105 | * Examples: 106 | * 107 | * // With Content-Type: text/html; charset=utf-8 108 | * this.is('html'); // => 'html' 109 | * this.is('text/html'); // => 'text/html' 110 | * this.is('text/*', 'application/json'); // => 'text/html' 111 | * 112 | * // When Content-Type is application/json 113 | * this.is('json', 'urlencoded'); // => 'json' 114 | * this.is('application/json'); // => 'application/json' 115 | * this.is('html', 'application/*'); // => 'application/json' 116 | * 117 | * this.is('html'); // => false 118 | * 119 | * @param {Object} req 120 | * @param {(String|Array)} types... 121 | * @return {(String|false|null)} 122 | * @public 123 | */ 124 | 125 | function typeofrequest (req, types_) { 126 | // no body 127 | if (!hasbody(req)) return null 128 | // support flattened arguments 129 | var types = arguments.length > 2 130 | ? Array.prototype.slice.call(arguments, 1) 131 | : types_ 132 | // request content type 133 | var value = req.headers['content-type'] 134 | 135 | return typeis(value, types) 136 | } 137 | 138 | /** 139 | * Normalize a mime type. 140 | * If it's a shorthand, expand it to a valid mime type. 141 | * 142 | * In general, you probably want: 143 | * 144 | * var type = is(req, ['urlencoded', 'json', 'multipart']); 145 | * 146 | * Then use the appropriate body parsers. 147 | * These three are the most common request body types 148 | * and are thus ensured to work. 149 | * 150 | * @param {String} type 151 | * @return {String|false|null} 152 | * @public 153 | */ 154 | 155 | function normalize (type) { 156 | if (typeof type !== 'string') { 157 | // invalid type 158 | return false 159 | } 160 | 161 | switch (type) { 162 | case 'urlencoded': 163 | return 'application/x-www-form-urlencoded' 164 | case 'multipart': 165 | return 'multipart/*' 166 | } 167 | 168 | if (type[0] === '+') { 169 | // "+json" -> "*/*+json" expando 170 | return '*/*' + type 171 | } 172 | 173 | return type.indexOf('/') === -1 174 | ? mime.lookup(type) 175 | : type 176 | } 177 | 178 | /** 179 | * Check if `expected` mime type 180 | * matches `actual` mime type with 181 | * wildcard and +suffix support. 182 | * 183 | * @param {String} expected 184 | * @param {String} actual 185 | * @return {Boolean} 186 | * @public 187 | */ 188 | 189 | function mimeMatch (expected, actual) { 190 | // invalid type 191 | if (expected === false) { 192 | return false 193 | } 194 | 195 | // split types 196 | var actualParts = actual.split('/') 197 | var expectedParts = expected.split('/') 198 | 199 | // invalid format 200 | if (actualParts.length !== 2 || expectedParts.length !== 2) { 201 | return false 202 | } 203 | 204 | // validate type 205 | if (expectedParts[0] !== '*' && expectedParts[0] !== actualParts[0]) { 206 | return false 207 | } 208 | 209 | // validate suffix wildcard 210 | if (expectedParts[1].slice(0, 2) === '*+') { 211 | return expectedParts[1].length <= actualParts[1].length + 1 && 212 | expectedParts[1].slice(1) === actualParts[1].slice(1 - expectedParts[1].length) 213 | } 214 | 215 | // validate subtype 216 | if (expectedParts[1] !== '*' && expectedParts[1] !== actualParts[1]) { 217 | return false 218 | } 219 | 220 | return true 221 | } 222 | 223 | /** 224 | * Normalize a type and remove parameters. 225 | * 226 | * @param {string} value 227 | * @return {(string|null)} 228 | * @private 229 | */ 230 | function normalizeType (value) { 231 | // Parse the type 232 | var type = contentType.parse(value).type 233 | 234 | return typer.test(type) ? type : null 235 | } 236 | 237 | /** 238 | * Try to normalize a type and remove parameters. 239 | * 240 | * @param {string} value 241 | * @return {(string|null)} 242 | * @private 243 | */ 244 | function tryNormalizeType (value) { 245 | try { 246 | return value ? normalizeType(value) : null 247 | } catch (err) { 248 | return null 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "type-is", 3 | "description": "Infer the content-type of a request.", 4 | "version": "2.0.1", 5 | "contributors": [ 6 | "Douglas Christopher Wilson ", 7 | "Jonathan Ong (http://jongleberry.com)" 8 | ], 9 | "license": "MIT", 10 | "repository": "jshttp/type-is", 11 | "dependencies": { 12 | "content-type": "^1.0.5", 13 | "media-typer": "^1.1.0", 14 | "mime-types": "^3.0.0" 15 | }, 16 | "devDependencies": { 17 | "eslint": "7.32.0", 18 | "eslint-config-standard": "14.1.1", 19 | "eslint-plugin-import": "2.31.0", 20 | "eslint-plugin-markdown": "2.2.1", 21 | "eslint-plugin-node": "11.1.0", 22 | "eslint-plugin-promise": "5.2.0", 23 | "eslint-plugin-standard": "4.1.0", 24 | "mocha": "9.2.2", 25 | "nyc": "15.1.0" 26 | }, 27 | "engines": { 28 | "node": ">= 0.6" 29 | }, 30 | "files": [ 31 | "LICENSE", 32 | "HISTORY.md", 33 | "index.js" 34 | ], 35 | "scripts": { 36 | "lint": "eslint .", 37 | "test": "mocha --reporter spec --check-leaks --bail test/", 38 | "test:debug": "mocha --reporter spec --check-leaks --inspect --inspect-brk test/", 39 | "test-ci": "nyc --reporter=lcovonly --reporter=text npm test", 40 | "test-cov": "nyc --reporter=html --reporter=text npm test" 41 | }, 42 | "keywords": [ 43 | "content", 44 | "type", 45 | "checking" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert') 3 | var typeis = require('..') 4 | 5 | describe('typeis(req, types)', function () { 6 | it('should ignore params', function () { 7 | var req = createRequest('text/html; charset=utf-8') 8 | assert.strictEqual(typeis(req, ['text/*']), 'text/html') 9 | }) 10 | 11 | it('should ignore params LWS', function () { 12 | var req = createRequest('text/html ; charset=utf-8') 13 | assert.strictEqual(typeis(req, ['text/*']), 'text/html') 14 | }) 15 | 16 | it('should ignore casing', function () { 17 | var req = createRequest('text/HTML') 18 | assert.strictEqual(typeis(req, ['text/*']), 'text/html') 19 | }) 20 | 21 | it('should fail invalid type', function () { 22 | var req = createRequest('text/html**') 23 | assert.strictEqual(typeis(req, ['text/*']), false) 24 | }) 25 | 26 | it('should not match invalid type', function () { 27 | var req = createRequest('text/html') 28 | assert.strictEqual(typeis(req, ['text/html/']), false) 29 | assert.strictEqual(typeis(req, [undefined, null, true, function () {}]), false) 30 | }) 31 | 32 | describe('when no body is given', function () { 33 | it('should return null', function () { 34 | var req = { headers: {} } 35 | 36 | assert.strictEqual(typeis(req), null) 37 | assert.strictEqual(typeis(req, ['image/*']), null) 38 | assert.strictEqual(typeis(req, 'image/*', 'text/*'), null) 39 | }) 40 | }) 41 | 42 | describe('when no content type is given', function () { 43 | it('should return false', function () { 44 | var req = createRequest() 45 | assert.strictEqual(typeis(req), false) 46 | assert.strictEqual(typeis(req, ['image/*']), false) 47 | assert.strictEqual(typeis(req, ['text/*', 'image/*']), false) 48 | }) 49 | }) 50 | 51 | describe('give no types', function () { 52 | it('should return the mime type', function () { 53 | var req = createRequest('image/png') 54 | assert.strictEqual(typeis(req), 'image/png') 55 | }) 56 | }) 57 | 58 | describe('given one type', function () { 59 | it('should return the type or false', function () { 60 | var req = createRequest('image/png') 61 | 62 | assert.strictEqual(typeis(req, ['png']), 'png') 63 | assert.strictEqual(typeis(req, ['.png']), '.png') 64 | assert.strictEqual(typeis(req, ['image/png']), 'image/png') 65 | assert.strictEqual(typeis(req, ['image/*']), 'image/png') 66 | assert.strictEqual(typeis(req, ['*/png']), 'image/png') 67 | 68 | assert.strictEqual(typeis(req, ['jpeg']), false) 69 | assert.strictEqual(typeis(req, ['.jpeg']), false) 70 | assert.strictEqual(typeis(req, ['image/jpeg']), false) 71 | assert.strictEqual(typeis(req, ['text/*']), false) 72 | assert.strictEqual(typeis(req, ['*/jpeg']), false) 73 | 74 | assert.strictEqual(typeis(req, ['bogus']), false) 75 | assert.strictEqual(typeis(req, ['something/bogus*']), false) 76 | }) 77 | }) 78 | 79 | describe('given multiple types', function () { 80 | it('should return the first match or false', function () { 81 | var req = createRequest('image/png') 82 | 83 | assert.strictEqual(typeis(req, ['png']), 'png') 84 | assert.strictEqual(typeis(req, '.png'), '.png') 85 | assert.strictEqual(typeis(req, ['text/*', 'image/*']), 'image/png') 86 | assert.strictEqual(typeis(req, ['image/*', 'text/*']), 'image/png') 87 | assert.strictEqual(typeis(req, ['image/*', 'image/png']), 'image/png') 88 | assert.strictEqual(typeis(req, 'image/png', 'image/*'), 'image/png') 89 | 90 | assert.strictEqual(typeis(req, ['jpeg']), false) 91 | assert.strictEqual(typeis(req, ['.jpeg']), false) 92 | assert.strictEqual(typeis(req, ['text/*', 'application/*']), false) 93 | assert.strictEqual(typeis(req, ['text/html', 'text/plain', 'application/json']), false) 94 | }) 95 | }) 96 | 97 | describe('given +suffix', function () { 98 | it('should match suffix types', function () { 99 | var req = createRequest('application/vnd+json') 100 | 101 | assert.strictEqual(typeis(req, '+json'), 'application/vnd+json') 102 | assert.strictEqual(typeis(req, 'application/vnd+json'), 'application/vnd+json') 103 | assert.strictEqual(typeis(req, 'application/*+json'), 'application/vnd+json') 104 | assert.strictEqual(typeis(req, '*/vnd+json'), 'application/vnd+json') 105 | assert.strictEqual(typeis(req, 'application/json'), false) 106 | assert.strictEqual(typeis(req, 'text/*+json'), false) 107 | }) 108 | }) 109 | 110 | describe('given "*/*"', function () { 111 | it('should match any content-type', function () { 112 | assert.strictEqual(typeis(createRequest('text/html'), '*/*'), 'text/html') 113 | assert.strictEqual(typeis(createRequest('text/xml'), '*/*'), 'text/xml') 114 | assert.strictEqual(typeis(createRequest('application/json'), '*/*'), 'application/json') 115 | assert.strictEqual(typeis(createRequest('application/vnd+json'), '*/*'), 'application/vnd+json') 116 | }) 117 | 118 | it('should not match invalid content-type', function () { 119 | assert.strictEqual(typeis(createRequest('bogus'), '*/*'), false) 120 | }) 121 | 122 | it('should not match body-less request', function () { 123 | var req = { headers: { 'content-type': 'text/html' } } 124 | assert.strictEqual(typeis(req, '*/*'), null) 125 | }) 126 | }) 127 | 128 | describe('when Content-Type: application/x-www-form-urlencoded', function () { 129 | it('should match "urlencoded"', function () { 130 | var req = createRequest('application/x-www-form-urlencoded') 131 | 132 | assert.strictEqual(typeis(req, ['urlencoded']), 'urlencoded') 133 | assert.strictEqual(typeis(req, ['json', 'urlencoded']), 'urlencoded') 134 | assert.strictEqual(typeis(req, ['urlencoded', 'json']), 'urlencoded') 135 | }) 136 | }) 137 | 138 | describe('when Content-Type: multipart/form-data', function () { 139 | it('should match "multipart/*"', function () { 140 | var req = createRequest('multipart/form-data') 141 | 142 | assert.strictEqual(typeis(req, ['multipart/*']), 'multipart/form-data') 143 | }) 144 | 145 | it('should match "multipart"', function () { 146 | var req = createRequest('multipart/form-data') 147 | 148 | assert.strictEqual(typeis(req, ['multipart']), 'multipart') 149 | }) 150 | }) 151 | }) 152 | 153 | describe('typeis.hasBody(req)', function () { 154 | describe('content-length', function () { 155 | it('should indicate body', function () { 156 | var req = { headers: { 'content-length': '1' } } 157 | assert.strictEqual(typeis.hasBody(req), true) 158 | }) 159 | 160 | it('should be true when 0', function () { 161 | var req = { headers: { 'content-length': '0' } } 162 | assert.strictEqual(typeis.hasBody(req), true) 163 | }) 164 | 165 | it('should be false when bogus', function () { 166 | var req = { headers: { 'content-length': 'bogus' } } 167 | assert.strictEqual(typeis.hasBody(req), false) 168 | }) 169 | }) 170 | 171 | describe('transfer-encoding', function () { 172 | it('should indicate body', function () { 173 | var req = { headers: { 'transfer-encoding': 'chunked' } } 174 | assert.strictEqual(typeis.hasBody(req), true) 175 | }) 176 | }) 177 | }) 178 | 179 | describe('typeis.is(mediaType, types)', function () { 180 | it('should ignore params', function () { 181 | assert.strictEqual(typeis.is('text/html; charset=utf-8', ['text/*']), 182 | 'text/html') 183 | }) 184 | 185 | it('should ignore casing', function () { 186 | assert.strictEqual(typeis.is('text/HTML', ['text/*']), 'text/html') 187 | }) 188 | 189 | it('should fail invalid type', function () { 190 | assert.strictEqual(typeis.is('text/html**', ['text/*']), false) 191 | }) 192 | 193 | it('should not match invalid type', function () { 194 | var req = createRequest('text/html') 195 | assert.strictEqual(typeis(req, ['text/html/']), false) 196 | assert.strictEqual(typeis(req, [undefined, null, true, function () {}]), false) 197 | }) 198 | 199 | it('should not match invalid type', function () { 200 | assert.strictEqual(typeis.is('text/html', ['text/html/']), false) 201 | assert.strictEqual(typeis.is('text/html', [undefined, null, true, function () {}]), false) 202 | }) 203 | 204 | describe('when no media type is given', function () { 205 | it('should return false', function () { 206 | assert.strictEqual(typeis.is(), false) 207 | assert.strictEqual(typeis.is('', ['application/json']), false) 208 | assert.strictEqual(typeis.is(null, ['image/*']), false) 209 | assert.strictEqual(typeis.is(undefined, ['text/*', 'image/*']), false) 210 | }) 211 | }) 212 | 213 | describe('given no types', function () { 214 | it('should return the mime type', function () { 215 | assert.strictEqual(typeis.is('image/png'), 'image/png') 216 | }) 217 | }) 218 | 219 | describe('given one type', function () { 220 | it('should return the type or false', function () { 221 | assert.strictEqual(typeis.is('image/png', ['png']), 'png') 222 | assert.strictEqual(typeis.is('image/png', ['.png']), '.png') 223 | assert.strictEqual(typeis.is('image/png', ['image/png']), 'image/png') 224 | assert.strictEqual(typeis.is('image/png', ['image/*']), 'image/png') 225 | assert.strictEqual(typeis.is('image/png', ['*/png']), 'image/png') 226 | 227 | assert.strictEqual(typeis.is('image/png', ['jpeg']), false) 228 | assert.strictEqual(typeis.is('image/png', ['.jpeg']), false) 229 | assert.strictEqual(typeis.is('image/png', ['image/jpeg']), false) 230 | assert.strictEqual(typeis.is('image/png', ['text/*']), false) 231 | assert.strictEqual(typeis.is('image/png', ['*/jpeg']), false) 232 | 233 | assert.strictEqual(typeis.is('image/png', ['bogus']), false) 234 | assert.strictEqual(typeis.is('image/png', ['something/bogus*']), false) 235 | }) 236 | }) 237 | 238 | describe('given multiple types', function () { 239 | it('should return the first match or false', function () { 240 | assert.strictEqual(typeis.is('image/png', ['png']), 'png') 241 | assert.strictEqual(typeis.is('image/png', '.png'), '.png') 242 | assert.strictEqual(typeis.is('image/png', ['text/*', 'image/*']), 'image/png') 243 | assert.strictEqual(typeis.is('image/png', ['image/*', 'text/*']), 'image/png') 244 | assert.strictEqual(typeis.is('image/png', ['image/*', 'image/png']), 'image/png') 245 | assert.strictEqual(typeis.is('image/png', 'image/png', 'image/*'), 'image/png') 246 | 247 | assert.strictEqual(typeis.is('image/png', ['jpeg']), false) 248 | assert.strictEqual(typeis.is('image/png', ['.jpeg']), false) 249 | assert.strictEqual(typeis.is('image/png', ['text/*', 'application/*']), false) 250 | assert.strictEqual(typeis.is('image/png', ['text/html', 'text/plain', 'application/json']), false) 251 | }) 252 | }) 253 | 254 | describe('given +suffix', function () { 255 | it('should match suffix types', function () { 256 | assert.strictEqual(typeis.is('application/vnd+json', '+json'), 'application/vnd+json') 257 | assert.strictEqual(typeis.is('application/vnd+json', 'application/vnd+json'), 'application/vnd+json') 258 | assert.strictEqual(typeis.is('application/vnd+json', 'application/*+json'), 'application/vnd+json') 259 | assert.strictEqual(typeis.is('application/vnd+json', '*/vnd+json'), 'application/vnd+json') 260 | assert.strictEqual(typeis.is('application/vnd+json', 'application/json'), false) 261 | assert.strictEqual(typeis.is('application/vnd+json', 'text/*+json'), false) 262 | }) 263 | }) 264 | 265 | describe('given "*/*"', function () { 266 | it('should match any media type', function () { 267 | assert.strictEqual(typeis.is('text/html', '*/*'), 'text/html') 268 | assert.strictEqual(typeis.is('text/xml', '*/*'), 'text/xml') 269 | assert.strictEqual(typeis.is('application/json', '*/*'), 'application/json') 270 | assert.strictEqual(typeis.is('application/vnd+json', '*/*'), 'application/vnd+json') 271 | }) 272 | 273 | it('should not match invalid media type', function () { 274 | assert.strictEqual(typeis.is('bogus', '*/*'), false) 275 | }) 276 | }) 277 | 278 | describe('when media type is application/x-www-form-urlencoded', function () { 279 | it('should match "urlencoded"', function () { 280 | assert.strictEqual(typeis.is('application/x-www-form-urlencoded', ['urlencoded']), 'urlencoded') 281 | assert.strictEqual(typeis.is('application/x-www-form-urlencoded', ['json', 'urlencoded']), 'urlencoded') 282 | assert.strictEqual(typeis.is('application/x-www-form-urlencoded', ['urlencoded', 'json']), 'urlencoded') 283 | }) 284 | }) 285 | 286 | describe('when media type is multipart/form-data', function () { 287 | it('should match "multipart/*"', function () { 288 | assert.strictEqual(typeis.is('multipart/form-data', ['multipart/*']), 'multipart/form-data') 289 | }) 290 | 291 | it('should match "multipart"', function () { 292 | assert.strictEqual(typeis.is('multipart/form-data', ['multipart']), 'multipart') 293 | }) 294 | }) 295 | 296 | describe('when give request object', function () { 297 | it('should use the content-type header', function () { 298 | var req = createRequest('image/png') 299 | 300 | assert.strictEqual(typeis.is(req, ['png']), 'png') 301 | assert.strictEqual(typeis.is(req, ['jpeg']), false) 302 | }) 303 | 304 | it('should not check for body', function () { 305 | var req = { headers: { 'content-type': 'text/html' } } 306 | 307 | assert.strictEqual(typeis.is(req, ['html']), 'html') 308 | assert.strictEqual(typeis.is(req, ['jpeg']), false) 309 | }) 310 | }) 311 | }) 312 | 313 | describe('typeis.match(expected, actual)', function () { 314 | it('should return false when expected is false', function () { 315 | assert.strictEqual(typeis.match(false, 'text/html'), false) 316 | }) 317 | 318 | it('should perform exact matching', function () { 319 | assert.strictEqual(typeis.match('text/html', 'text/html'), true) 320 | assert.strictEqual(typeis.match('text/html', 'text/plain'), false) 321 | assert.strictEqual(typeis.match('text/html', 'text/xml'), false) 322 | assert.strictEqual(typeis.match('text/html', 'application/html'), false) 323 | assert.strictEqual(typeis.match('text/html', 'text/html+xml'), false) 324 | }) 325 | 326 | it('should perform type wildcard matching', function () { 327 | assert.strictEqual(typeis.match('*/html', 'text/html'), true) 328 | assert.strictEqual(typeis.match('*/html', 'application/html'), true) 329 | assert.strictEqual(typeis.match('*/html', 'text/xml'), false) 330 | assert.strictEqual(typeis.match('*/html', 'text/html+xml'), false) 331 | }) 332 | 333 | it('should perform subtype wildcard matching', function () { 334 | assert.strictEqual(typeis.match('text/*', 'text/html'), true) 335 | assert.strictEqual(typeis.match('text/*', 'text/xml'), true) 336 | assert.strictEqual(typeis.match('text/*', 'text/html+xml'), true) 337 | assert.strictEqual(typeis.match('text/*', 'application/xml'), false) 338 | }) 339 | 340 | it('should perform full wildcard matching', function () { 341 | assert.strictEqual(typeis.match('*/*', 'text/html'), true) 342 | assert.strictEqual(typeis.match('*/*', 'text/html+xml'), true) 343 | assert.strictEqual(typeis.match('*/*+xml', 'text/html+xml'), true) 344 | }) 345 | 346 | it('should perform full wildcard matching with specific suffix', function () { 347 | assert.strictEqual(typeis.match('*/*+xml', 'text/html+xml'), true) 348 | assert.strictEqual(typeis.match('*/*+xml', 'text/html'), false) 349 | }) 350 | }) 351 | 352 | describe('typeis.normalize(type)', function () { 353 | it('should return false for non-strings', function () { 354 | assert.strictEqual(typeis.normalize({}), false) 355 | assert.strictEqual(typeis.normalize([]), false) 356 | assert.strictEqual(typeis.normalize(42), false) 357 | assert.strictEqual(typeis.normalize(null), false) 358 | assert.strictEqual(typeis.normalize(function () {}), false) 359 | }) 360 | 361 | it('should return media type for extension', function () { 362 | assert.strictEqual(typeis.normalize('json'), 'application/json') 363 | }) 364 | 365 | it('should return expanded wildcard for suffix', function () { 366 | assert.strictEqual(typeis.normalize('+json'), '*/*+json') 367 | }) 368 | 369 | it('should pass through media type', function () { 370 | assert.strictEqual(typeis.normalize('application/json'), 'application/json') 371 | }) 372 | 373 | it('should pass through wildcard', function () { 374 | assert.strictEqual(typeis.normalize('*/*'), '*/*') 375 | assert.strictEqual(typeis.normalize('image/*'), 'image/*') 376 | }) 377 | 378 | it('should return false for unmapped extension', function () { 379 | assert.strictEqual(typeis.normalize('unknown'), false) 380 | }) 381 | 382 | it('should expand special "urlencoded"', function () { 383 | assert.strictEqual(typeis.normalize('urlencoded'), 'application/x-www-form-urlencoded') 384 | }) 385 | 386 | it('should expand special "multipart"', function () { 387 | assert.strictEqual(typeis.normalize('multipart'), 'multipart/*') 388 | }) 389 | }) 390 | 391 | function createRequest (type) { 392 | return { 393 | headers: { 394 | 'content-type': type || undefined, 395 | 'transfer-encoding': 'chunked' 396 | } 397 | } 398 | } 399 | --------------------------------------------------------------------------------