├── .eslintignore ├── .eslintrc.yml ├── .github └── workflows │ ├── ci.yml │ └── scorecard.yml ├── .gitignore ├── HISTORY.md ├── LICENSE ├── README.md ├── benchmark ├── compiling.js ├── index.js ├── kind.js └── matching.js ├── index.js ├── package.json └── test ├── .eslintrc.yml └── test.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 | - pull_request 5 | - push 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | name: 13 | - Node.js 0.10 14 | - Node.js 0.12 15 | - io.js 1.x 16 | - io.js 2.x 17 | - io.js 3.x 18 | - Node.js 4.x 19 | - Node.js 5.x 20 | - Node.js 6.x 21 | - Node.js 7.x 22 | - Node.js 8.x 23 | - Node.js 9.x 24 | - Node.js 10.x 25 | - Node.js 11.x 26 | - Node.js 12.x 27 | - Node.js 13.x 28 | - Node.js 14.x 29 | - Node.js 15.x 30 | - Node.js 16.x 31 | - Node.js 17.x 32 | - Node.js 18.x 33 | - Node.js 19.x 34 | - Node.js 20.x 35 | - Node.js 21.x 36 | - Node.js 22.x 37 | 38 | include: 39 | - name: Node.js 0.10 40 | node-version: "0.10" 41 | npm-i: mocha@3.5.3 nyc@10.3.2 42 | npm-rm: beautify-benchmark benchmark 43 | 44 | - name: Node.js 0.12 45 | node-version: "0.12" 46 | npm-i: mocha@3.5.3 nyc@10.3.2 47 | npm-rm: beautify-benchmark benchmark 48 | 49 | - name: io.js 1.x 50 | node-version: "1.8" 51 | npm-i: mocha@3.5.3 nyc@10.3.2 52 | npm-rm: beautify-benchmark benchmark 53 | 54 | - name: io.js 2.x 55 | node-version: "2.5" 56 | npm-i: mocha@3.5.3 nyc@10.3.2 57 | npm-rm: beautify-benchmark benchmark 58 | 59 | - name: io.js 3.x 60 | node-version: "3.3" 61 | npm-i: mocha@3.5.3 nyc@10.3.2 62 | npm-rm: beautify-benchmark benchmark 63 | 64 | - name: Node.js 4.x 65 | node-version: "4.9" 66 | npm-i: mocha@5.2.0 nyc@11.9.0 67 | npm-rm: beautify-benchmark benchmark 68 | 69 | - name: Node.js 5.x 70 | node-version: "5.12" 71 | npm-i: mocha@5.2.0 nyc@11.9.0 72 | npm-rm: beautify-benchmark benchmark 73 | 74 | - name: Node.js 6.x 75 | node-version: "6.17" 76 | npm-i: mocha@6.2.2 nyc@14.1.1 77 | npm-rm: beautify-benchmark benchmark 78 | 79 | - name: Node.js 7.x 80 | node-version: "7.10" 81 | npm-i: mocha@6.2.2 nyc@14.1.1 82 | npm-rm: beautify-benchmark benchmark 83 | 84 | - name: Node.js 8.x 85 | node-version: "8.17" 86 | npm-i: mocha@7.2.0 nyc@14.1.1 87 | npm-rm: beautify-benchmark benchmark 88 | 89 | - name: Node.js 9.x 90 | node-version: "9.11" 91 | npm-i: mocha@7.2.0 nyc@14.1.1 92 | npm-rm: beautify-benchmark benchmark 93 | 94 | - name: Node.js 10.x 95 | node-version: "10.24" 96 | npm-rm: beautify-benchmark benchmark 97 | 98 | - name: Node.js 11.x 99 | node-version: "11.15" 100 | npm-rm: beautify-benchmark benchmark 101 | 102 | - name: Node.js 12.x 103 | node-version: "12.22" 104 | npm-rm: beautify-benchmark benchmark 105 | 106 | - name: Node.js 13.x 107 | node-version: "13.14" 108 | npm-rm: beautify-benchmark benchmark 109 | 110 | - name: Node.js 14.x 111 | node-version: "14.17" 112 | npm-rm: beautify-benchmark benchmark 113 | 114 | - name: Node.js 15.x 115 | node-version: "15.14" 116 | npm-rm: beautify-benchmark benchmark 117 | 118 | - name: Node.js 16.x 119 | node-version: "16.2" 120 | npm-rm: beautify-benchmark benchmark 121 | 122 | - name: Node.js 17.x 123 | node-version: "17.9" 124 | npm-rm: beautify-benchmark benchmark 125 | 126 | - name: Node.js 18.x 127 | node-version: "18.18" 128 | npm-rm: beautify-benchmark benchmark 129 | 130 | - name: Node.js 19.x 131 | node-version: "19.9" 132 | npm-rm: beautify-benchmark benchmark 133 | 134 | - name: Node.js 20.x 135 | node-version: "20.9" 136 | npm-rm: beautify-benchmark benchmark 137 | 138 | - name: Node.js 21.x 139 | node-version: "21.7" 140 | npm-rm: beautify-benchmark benchmark 141 | 142 | - name: Node.js 22.x 143 | node-version: "22.0" 144 | npm-rm: beautify-benchmark benchmark 145 | 146 | steps: 147 | - uses: actions/checkout@v4 148 | 149 | - name: Install Node.js ${{ matrix.node-version }} 150 | shell: bash -eo pipefail -l {0} 151 | run: | 152 | nvm install --default ${{ matrix.node-version }} 153 | if [[ "${{ matrix.node-version }}" == 0.* ]]; then 154 | npm config set strict-ssl false 155 | fi 156 | dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" 157 | 158 | - name: Configure npm 159 | run: | 160 | if [[ "$(npm config get package-lock)" == "true" ]]; then 161 | npm config set package-lock false 162 | else 163 | npm config set shrinkwrap false 164 | fi 165 | 166 | - name: Remove npm module(s) ${{ matrix.npm-rm }} 167 | run: npm rm --silent --save-dev ${{ matrix.npm-rm }} 168 | if: matrix.npm-rm != '' 169 | 170 | - name: Install npm module(s) ${{ matrix.npm-i }} 171 | run: npm install --save-dev ${{ matrix.npm-i }} 172 | if: matrix.npm-i != '' 173 | 174 | - name: Setup Node.js version-specific dependencies 175 | shell: bash 176 | run: | 177 | # eslint for linting 178 | # - remove on Node.js < 10 179 | if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then 180 | node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ 181 | grep -E '^eslint(-|$)' | \ 182 | sort -r | \ 183 | xargs -n1 npm rm --silent --save-dev 184 | fi 185 | 186 | - name: Install Node.js dependencies 187 | run: npm install 188 | 189 | - name: List environment 190 | id: list_env 191 | shell: bash 192 | run: | 193 | echo "node@$(node -v)" 194 | echo "npm@$(npm -v)" 195 | npm -s ls ||: 196 | (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' 197 | 198 | - name: Run tests 199 | shell: bash 200 | run: | 201 | if npm -ps ls nyc | grep -q nyc; then 202 | npm run test-ci 203 | else 204 | npm test 205 | fi 206 | 207 | - name: Lint code 208 | if: steps.list_env.outputs.eslint != '' 209 | run: npm run lint 210 | 211 | - name: Collect code coverage 212 | uses: coverallsapp/github-action@master 213 | if: steps.list_env.outputs.nyc != '' 214 | with: 215 | github-token: ${{ secrets.GITHUB_TOKEN }} 216 | flag-name: run-${{ matrix.test_number }} 217 | parallel: true 218 | 219 | coverage: 220 | needs: test 221 | runs-on: ubuntu-latest 222 | steps: 223 | - name: Upload code coverage 224 | uses: coverallsapp/github-action@master 225 | with: 226 | github-token: ${{ secrets.GITHUB_TOKEN }} 227 | parallel-finished: true 228 | -------------------------------------------------------------------------------- /.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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # v2.0.6 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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 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@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2.23.2 71 | with: 72 | sarif_file: results.sarif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | node_modules/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 2.0.7 / 2021-05-31 2 | ================== 3 | 4 | * deps: forwarded@0.2.0 5 | - Use `req.socket` over deprecated `req.connection` 6 | 7 | 2.0.6 / 2020-02-24 8 | ================== 9 | 10 | * deps: ipaddr.js@1.9.1 11 | 12 | 2.0.5 / 2019-04-16 13 | ================== 14 | 15 | * deps: ipaddr.js@1.9.0 16 | 17 | 2.0.4 / 2018-07-26 18 | ================== 19 | 20 | * deps: ipaddr.js@1.8.0 21 | 22 | 2.0.3 / 2018-02-19 23 | ================== 24 | 25 | * deps: ipaddr.js@1.6.0 26 | 27 | 2.0.2 / 2017-09-24 28 | ================== 29 | 30 | * deps: forwarded@~0.1.2 31 | - perf: improve header parsing 32 | - perf: reduce overhead when no `X-Forwarded-For` header 33 | 34 | 2.0.1 / 2017-09-10 35 | ================== 36 | 37 | * deps: forwarded@~0.1.1 38 | - Fix trimming leading / trailing OWS 39 | - perf: hoist regular expression 40 | * deps: ipaddr.js@1.5.2 41 | 42 | 2.0.0 / 2017-08-08 43 | ================== 44 | 45 | * Drop support for Node.js below 0.10 46 | 47 | 1.1.5 / 2017-07-25 48 | ================== 49 | 50 | * Fix array argument being altered 51 | * deps: ipaddr.js@1.4.0 52 | 53 | 1.1.4 / 2017-03-24 54 | ================== 55 | 56 | * deps: ipaddr.js@1.3.0 57 | 58 | 1.1.3 / 2017-01-14 59 | ================== 60 | 61 | * deps: ipaddr.js@1.2.0 62 | 63 | 1.1.2 / 2016-05-29 64 | ================== 65 | 66 | * deps: ipaddr.js@1.1.1 67 | - Fix IPv6-mapped IPv4 validation edge cases 68 | 69 | 1.1.1 / 2016-05-03 70 | ================== 71 | 72 | * Fix regression matching mixed versions against multiple subnets 73 | 74 | 1.1.0 / 2016-05-01 75 | ================== 76 | 77 | * Fix accepting various invalid netmasks 78 | - IPv4 netmasks must be contingous 79 | - IPv6 addresses cannot be used as a netmask 80 | * deps: ipaddr.js@1.1.0 81 | 82 | 1.0.10 / 2015-12-09 83 | =================== 84 | 85 | * deps: ipaddr.js@1.0.5 86 | - Fix regression in `isValid` with non-string arguments 87 | 88 | 1.0.9 / 2015-12-01 89 | ================== 90 | 91 | * deps: ipaddr.js@1.0.4 92 | - Fix accepting some invalid IPv6 addresses 93 | - Reject CIDRs with negative or overlong masks 94 | * perf: enable strict mode 95 | 96 | 1.0.8 / 2015-05-10 97 | ================== 98 | 99 | * deps: ipaddr.js@1.0.1 100 | 101 | 1.0.7 / 2015-03-16 102 | ================== 103 | 104 | * deps: ipaddr.js@0.1.9 105 | - Fix OOM on certain inputs to `isValid` 106 | 107 | 1.0.6 / 2015-02-01 108 | ================== 109 | 110 | * deps: ipaddr.js@0.1.8 111 | 112 | 1.0.5 / 2015-01-08 113 | ================== 114 | 115 | * deps: ipaddr.js@0.1.6 116 | 117 | 1.0.4 / 2014-11-23 118 | ================== 119 | 120 | * deps: ipaddr.js@0.1.5 121 | - Fix edge cases with `isValid` 122 | 123 | 1.0.3 / 2014-09-21 124 | ================== 125 | 126 | * Use `forwarded` npm module 127 | 128 | 1.0.2 / 2014-09-18 129 | ================== 130 | 131 | * Fix a global leak when multiple subnets are trusted 132 | * Support Node.js 0.6 133 | * deps: ipaddr.js@0.1.3 134 | 135 | 1.0.1 / 2014-06-03 136 | ================== 137 | 138 | * Fix links in npm package 139 | 140 | 1.0.0 / 2014-05-08 141 | ================== 142 | 143 | * Add `trust` argument to determine proxy trust on 144 | * Accepts custom function 145 | * Accepts IPv4/IPv6 address(es) 146 | * Accepts subnets 147 | * Accepts pre-defined names 148 | * Add optional `trust` argument to `proxyaddr.all` to 149 | stop at first untrusted 150 | * Add `proxyaddr.compile` to pre-compile `trust` function 151 | to make subsequent calls faster 152 | 153 | 0.0.1 / 2014-05-04 154 | ================== 155 | 156 | * Fix bad npm publish 157 | 158 | 0.0.0 / 2014-05-04 159 | ================== 160 | 161 | * Initial release 162 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014-2016 Douglas Christopher Wilson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proxy-addr 2 | 3 | [![NPM Version][npm-version-image]][npm-url] 4 | [![NPM Downloads][npm-downloads-image]][npm-url] 5 | [![Node.js Version][node-image]][node-url] 6 | [![Build Status][ci-image]][ci-url] 7 | [![Test Coverage][coveralls-image]][coveralls-url] 8 | 9 | Determine address of proxied 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 proxy-addr 19 | ``` 20 | 21 | ## API 22 | 23 | ```js 24 | var proxyaddr = require('proxy-addr') 25 | ``` 26 | 27 | ### proxyaddr(req, trust) 28 | 29 | Return the address of the request, using the given `trust` parameter. 30 | 31 | The `trust` argument is a function that returns `true` if you trust 32 | the address, `false` if you don't. The closest untrusted address is 33 | returned. 34 | 35 | ```js 36 | proxyaddr(req, function (addr) { return addr === '127.0.0.1' }) 37 | proxyaddr(req, function (addr, i) { return i < 1 }) 38 | ``` 39 | 40 | The `trust` arugment may also be a single IP address string or an 41 | array of trusted addresses, as plain IP addresses, CIDR-formatted 42 | strings, or IP/netmask strings. 43 | 44 | ```js 45 | proxyaddr(req, '127.0.0.1') 46 | proxyaddr(req, ['127.0.0.0/8', '10.0.0.0/8']) 47 | proxyaddr(req, ['127.0.0.0/255.0.0.0', '192.168.0.0/255.255.0.0']) 48 | ``` 49 | 50 | This module also supports IPv6. Your IPv6 addresses will be normalized 51 | automatically (i.e. `fe80::00ed:1` equals `fe80:0:0:0:0:0:ed:1`). 52 | 53 | ```js 54 | proxyaddr(req, '::1') 55 | proxyaddr(req, ['::1/128', 'fe80::/10']) 56 | ``` 57 | 58 | This module will automatically work with IPv4-mapped IPv6 addresses 59 | as well to support node.js in IPv6-only mode. This means that you do 60 | not have to specify both `::ffff:a00:1` and `10.0.0.1`. 61 | 62 | As a convenience, this module also takes certain pre-defined names 63 | in addition to IP addresses, which expand into IP addresses: 64 | 65 | ```js 66 | proxyaddr(req, 'loopback') 67 | proxyaddr(req, ['loopback', 'fc00:ac:1ab5:fff::1/64']) 68 | ``` 69 | 70 | * `loopback`: IPv4 and IPv6 loopback addresses (like `::1` and 71 | `127.0.0.1`). 72 | * `linklocal`: IPv4 and IPv6 link-local addresses (like 73 | `fe80::1:1:1:1` and `169.254.0.1`). 74 | * `uniquelocal`: IPv4 private addresses and IPv6 unique-local 75 | addresses (like `fc00:ac:1ab5:fff::1` and `192.168.0.1`). 76 | 77 | When `trust` is specified as a function, it will be called for each 78 | address to determine if it is a trusted address. The function is 79 | given two arguments: `addr` and `i`, where `addr` is a string of 80 | the address to check and `i` is a number that represents the distance 81 | from the socket address. 82 | 83 | ### proxyaddr.all(req, [trust]) 84 | 85 | Return all the addresses of the request, optionally stopping at the 86 | first untrusted. This array is ordered from closest to furthest 87 | (i.e. `arr[0] === req.connection.remoteAddress`). 88 | 89 | ```js 90 | proxyaddr.all(req) 91 | ``` 92 | 93 | The optional `trust` argument takes the same arguments as `trust` 94 | does in `proxyaddr(req, trust)`. 95 | 96 | ```js 97 | proxyaddr.all(req, 'loopback') 98 | ``` 99 | 100 | ### proxyaddr.compile(val) 101 | 102 | Compiles argument `val` into a `trust` function. This function takes 103 | the same arguments as `trust` does in `proxyaddr(req, trust)` and 104 | returns a function suitable for `proxyaddr(req, trust)`. 105 | 106 | ```js 107 | var trust = proxyaddr.compile('loopback') 108 | var addr = proxyaddr(req, trust) 109 | ``` 110 | 111 | This function is meant to be optimized for use against every request. 112 | It is recommend to compile a trust function up-front for the trusted 113 | configuration and pass that to `proxyaddr(req, trust)` for each request. 114 | 115 | ## Testing 116 | 117 | ```sh 118 | $ npm test 119 | ``` 120 | 121 | ## Benchmarks 122 | 123 | ```sh 124 | $ npm run-script bench 125 | ``` 126 | 127 | ## License 128 | 129 | [MIT](LICENSE) 130 | 131 | [ci-image]: https://badgen.net/github/checks/jshttp/proxy-addr/master?label=ci 132 | [ci-url]: https://github.com/jshttp/proxy-addr/actions?query=workflow%3Aci 133 | [coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/proxy-addr/master 134 | [coveralls-url]: https://coveralls.io/r/jshttp/proxy-addr?branch=master 135 | [node-image]: https://badgen.net/npm/node/proxy-addr 136 | [node-url]: https://nodejs.org/en/download 137 | [npm-downloads-image]: https://badgen.net/npm/dm/proxy-addr 138 | [npm-url]: https://npmjs.org/package/proxy-addr 139 | [npm-version-image]: https://badgen.net/npm/v/proxy-addr 140 | -------------------------------------------------------------------------------- /benchmark/compiling.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Globals for benchmark.js 4 | */ 5 | global.proxyaddr = require('..') 6 | global.createReq = createReq 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | var benchmark = require('benchmark') 12 | var benchmarks = require('beautify-benchmark') 13 | 14 | var suite = new benchmark.Suite() 15 | 16 | suite.add({ 17 | name: 're-compiling', 18 | minSamples: 100, 19 | fn: 'proxyaddr(req, "loopback")', 20 | setup: 'req = createReq("127.0.0.1", "10.0.0.1")' 21 | }) 22 | 23 | suite.add({ 24 | name: 'pre-compiling', 25 | minSamples: 100, 26 | fn: 'proxyaddr(req, trust)', 27 | setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile("loopback")' 28 | }) 29 | 30 | suite.on('cycle', function onCycle (event) { 31 | benchmarks.add(event.target) 32 | }) 33 | 34 | suite.on('complete', function onComplete () { 35 | benchmarks.log() 36 | }) 37 | 38 | suite.run({ async: false }) 39 | 40 | function createReq (socketAddr, forwardedFor) { 41 | return { 42 | connection: { 43 | remoteAddress: socketAddr 44 | }, 45 | headers: { 46 | 'x-forwarded-for': (forwardedFor || '') 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var spawn = require('child_process').spawn 4 | 5 | var exe = process.argv[0] 6 | var cwd = process.cwd() 7 | 8 | runScripts(fs.readdirSync(__dirname)) 9 | 10 | function runScripts (fileNames) { 11 | var fileName = fileNames.shift() 12 | 13 | if (!fileName) return 14 | if (!/\.js$/i.test(fileName)) return runScripts(fileNames) 15 | if (fileName.toLowerCase() === 'index.js') return runScripts(fileNames) 16 | 17 | var fullPath = path.join(__dirname, fileName) 18 | 19 | console.log('> %s %s', exe, path.relative(cwd, fullPath)) 20 | 21 | var proc = spawn(exe, [fullPath], { 22 | stdio: 'inherit' 23 | }) 24 | 25 | proc.on('exit', function () { 26 | runScripts(fileNames) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /benchmark/kind.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Globals for benchmark.js 4 | */ 5 | global.proxyaddr = require('..') 6 | global.createReq = createReq 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | var benchmark = require('benchmark') 12 | var benchmarks = require('beautify-benchmark') 13 | 14 | var suite = new benchmark.Suite() 15 | 16 | suite.add({ 17 | name: 'ipv4', 18 | minSamples: 100, 19 | fn: 'proxyaddr(req, trust)', 20 | setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile("127.0.0.1")' 21 | }) 22 | 23 | suite.add({ 24 | name: 'ipv4-mapped', 25 | minSamples: 100, 26 | fn: 'proxyaddr(req, trust)', 27 | setup: 'req = createReq("::ffff:7f00:1", "10.0.0.1"); trust = proxyaddr.compile("127.0.0.1")' 28 | }) 29 | 30 | suite.add({ 31 | name: 'ipv6', 32 | minSamples: 100, 33 | fn: 'proxyaddr(req, trust)', 34 | setup: 'req = createReq("::1", "10.0.0.1"); trust = proxyaddr.compile("::1")' 35 | }) 36 | 37 | suite.on('cycle', function onCycle (event) { 38 | benchmarks.add(event.target) 39 | }) 40 | 41 | suite.on('complete', function onComplete () { 42 | benchmarks.log() 43 | }) 44 | 45 | suite.run({ async: false }) 46 | 47 | function createReq (socketAddr, forwardedFor) { 48 | return { 49 | connection: { 50 | remoteAddress: socketAddr 51 | }, 52 | headers: { 53 | 'x-forwarded-for': (forwardedFor || '') 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /benchmark/matching.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Globals for benchmark.js 4 | */ 5 | global.proxyaddr = require('..') 6 | global.createReq = createReq 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | var benchmark = require('benchmark') 12 | var benchmarks = require('beautify-benchmark') 13 | 14 | var suite = new benchmark.Suite() 15 | 16 | suite.add({ 17 | name: 'trust none', 18 | minSamples: 100, 19 | fn: 'proxyaddr(req, trust)', 20 | setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile([])' 21 | }) 22 | 23 | suite.add({ 24 | name: 'trust all', 25 | minSamples: 100, 26 | fn: 'proxyaddr(req, trust)', 27 | setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = function() {return true}' 28 | }) 29 | 30 | suite.add({ 31 | name: 'trust single', 32 | minSamples: 100, 33 | fn: 'proxyaddr(req, trust)', 34 | setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile("127.0.0.1")' 35 | }) 36 | 37 | suite.add({ 38 | name: 'trust first', 39 | minSamples: 100, 40 | fn: 'proxyaddr(req, trust)', 41 | setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = function(a, i) {return i<1}' 42 | }) 43 | 44 | suite.add({ 45 | name: 'trust subnet', 46 | minSamples: 100, 47 | fn: 'proxyaddr(req, trust)', 48 | setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile("127.0.0.1/8")' 49 | }) 50 | 51 | suite.add({ 52 | name: 'trust multiple', 53 | minSamples: 100, 54 | fn: 'proxyaddr(req, trust)', 55 | setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile(["127.0.0.1", "10.0.0.1"])' 56 | }) 57 | 58 | suite.on('cycle', function onCycle (event) { 59 | benchmarks.add(event.target) 60 | }) 61 | 62 | suite.on('complete', function onComplete () { 63 | benchmarks.log() 64 | }) 65 | 66 | suite.run({ async: false }) 67 | 68 | function createReq (socketAddr, forwardedFor) { 69 | return { 70 | connection: { 71 | remoteAddress: socketAddr 72 | }, 73 | headers: { 74 | 'x-forwarded-for': (forwardedFor || '') 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * proxy-addr 3 | * Copyright(c) 2014-2016 Douglas Christopher Wilson 4 | * MIT Licensed 5 | */ 6 | 7 | 'use strict' 8 | 9 | /** 10 | * Module exports. 11 | * @public 12 | */ 13 | 14 | module.exports = proxyaddr 15 | module.exports.all = alladdrs 16 | module.exports.compile = compile 17 | 18 | /** 19 | * Module dependencies. 20 | * @private 21 | */ 22 | 23 | var forwarded = require('forwarded') 24 | var ipaddr = require('ipaddr.js') 25 | 26 | /** 27 | * Variables. 28 | * @private 29 | */ 30 | 31 | var DIGIT_REGEXP = /^[0-9]+$/ 32 | var isip = ipaddr.isValid 33 | var parseip = ipaddr.parse 34 | 35 | /** 36 | * Pre-defined IP ranges. 37 | * @private 38 | */ 39 | 40 | var IP_RANGES = { 41 | linklocal: ['169.254.0.0/16', 'fe80::/10'], 42 | loopback: ['127.0.0.1/8', '::1/128'], 43 | uniquelocal: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7'] 44 | } 45 | 46 | /** 47 | * Get all addresses in the request, optionally stopping 48 | * at the first untrusted. 49 | * 50 | * @param {Object} request 51 | * @param {Function|Array|String} [trust] 52 | * @public 53 | */ 54 | 55 | function alladdrs (req, trust) { 56 | // get addresses 57 | var addrs = forwarded(req) 58 | 59 | if (!trust) { 60 | // Return all addresses 61 | return addrs 62 | } 63 | 64 | if (typeof trust !== 'function') { 65 | trust = compile(trust) 66 | } 67 | 68 | for (var i = 0; i < addrs.length - 1; i++) { 69 | if (trust(addrs[i], i)) continue 70 | 71 | addrs.length = i + 1 72 | } 73 | 74 | return addrs 75 | } 76 | 77 | /** 78 | * Compile argument into trust function. 79 | * 80 | * @param {Array|String} val 81 | * @private 82 | */ 83 | 84 | function compile (val) { 85 | if (!val) { 86 | throw new TypeError('argument is required') 87 | } 88 | 89 | var trust 90 | 91 | if (typeof val === 'string') { 92 | trust = [val] 93 | } else if (Array.isArray(val)) { 94 | trust = val.slice() 95 | } else { 96 | throw new TypeError('unsupported trust argument') 97 | } 98 | 99 | for (var i = 0; i < trust.length; i++) { 100 | val = trust[i] 101 | 102 | if (!Object.prototype.hasOwnProperty.call(IP_RANGES, val)) { 103 | continue 104 | } 105 | 106 | // Splice in pre-defined range 107 | val = IP_RANGES[val] 108 | trust.splice.apply(trust, [i, 1].concat(val)) 109 | i += val.length - 1 110 | } 111 | 112 | return compileTrust(compileRangeSubnets(trust)) 113 | } 114 | 115 | /** 116 | * Compile `arr` elements into range subnets. 117 | * 118 | * @param {Array} arr 119 | * @private 120 | */ 121 | 122 | function compileRangeSubnets (arr) { 123 | var rangeSubnets = new Array(arr.length) 124 | 125 | for (var i = 0; i < arr.length; i++) { 126 | rangeSubnets[i] = parseipNotation(arr[i]) 127 | } 128 | 129 | return rangeSubnets 130 | } 131 | 132 | /** 133 | * Compile range subnet array into trust function. 134 | * 135 | * @param {Array} rangeSubnets 136 | * @private 137 | */ 138 | 139 | function compileTrust (rangeSubnets) { 140 | // Return optimized function based on length 141 | var len = rangeSubnets.length 142 | return len === 0 143 | ? trustNone 144 | : len === 1 145 | ? trustSingle(rangeSubnets[0]) 146 | : trustMulti(rangeSubnets) 147 | } 148 | 149 | /** 150 | * Parse IP notation string into range subnet. 151 | * 152 | * @param {String} note 153 | * @private 154 | */ 155 | 156 | function parseipNotation (note) { 157 | var pos = note.lastIndexOf('/') 158 | var str = pos !== -1 159 | ? note.substring(0, pos) 160 | : note 161 | 162 | if (!isip(str)) { 163 | throw new TypeError('invalid IP address: ' + str) 164 | } 165 | 166 | var ip = parseip(str) 167 | 168 | if (pos === -1 && ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) { 169 | // Store as IPv4 170 | ip = ip.toIPv4Address() 171 | } 172 | 173 | var max = ip.kind() === 'ipv6' 174 | ? 128 175 | : 32 176 | 177 | var range = pos !== -1 178 | ? note.substring(pos + 1, note.length) 179 | : null 180 | 181 | if (range === null) { 182 | range = max 183 | } else if (DIGIT_REGEXP.test(range)) { 184 | range = parseInt(range, 10) 185 | } else if (ip.kind() === 'ipv4' && isip(range)) { 186 | range = parseNetmask(range) 187 | } else { 188 | range = null 189 | } 190 | 191 | if (range <= 0 || range > max) { 192 | throw new TypeError('invalid range on address: ' + note) 193 | } 194 | 195 | return [ip, range] 196 | } 197 | 198 | /** 199 | * Parse netmask string into CIDR range. 200 | * 201 | * @param {String} netmask 202 | * @private 203 | */ 204 | 205 | function parseNetmask (netmask) { 206 | var ip = parseip(netmask) 207 | var kind = ip.kind() 208 | 209 | return kind === 'ipv4' 210 | ? ip.prefixLengthFromSubnetMask() 211 | : null 212 | } 213 | 214 | /** 215 | * Determine address of proxied request. 216 | * 217 | * @param {Object} request 218 | * @param {Function|Array|String} trust 219 | * @public 220 | */ 221 | 222 | function proxyaddr (req, trust) { 223 | if (!req) { 224 | throw new TypeError('req argument is required') 225 | } 226 | 227 | if (!trust) { 228 | throw new TypeError('trust argument is required') 229 | } 230 | 231 | var addrs = alladdrs(req, trust) 232 | var addr = addrs[addrs.length - 1] 233 | 234 | return addr 235 | } 236 | 237 | /** 238 | * Static trust function to trust nothing. 239 | * 240 | * @private 241 | */ 242 | 243 | function trustNone () { 244 | return false 245 | } 246 | 247 | /** 248 | * Compile trust function for multiple subnets. 249 | * 250 | * @param {Array} subnets 251 | * @private 252 | */ 253 | 254 | function trustMulti (subnets) { 255 | return function trust (addr) { 256 | if (!isip(addr)) return false 257 | 258 | var ip = parseip(addr) 259 | var ipconv 260 | var kind = ip.kind() 261 | 262 | for (var i = 0; i < subnets.length; i++) { 263 | var subnet = subnets[i] 264 | var subnetip = subnet[0] 265 | var subnetkind = subnetip.kind() 266 | var subnetrange = subnet[1] 267 | var trusted = ip 268 | 269 | if (kind !== subnetkind) { 270 | if (subnetkind === 'ipv4' && !ip.isIPv4MappedAddress()) { 271 | // Incompatible IP addresses 272 | continue 273 | } 274 | 275 | if (!ipconv) { 276 | // Convert IP to match subnet IP kind 277 | ipconv = subnetkind === 'ipv4' 278 | ? ip.toIPv4Address() 279 | : ip.toIPv4MappedAddress() 280 | } 281 | 282 | trusted = ipconv 283 | } 284 | 285 | if (trusted.match(subnetip, subnetrange)) { 286 | return true 287 | } 288 | } 289 | 290 | return false 291 | } 292 | } 293 | 294 | /** 295 | * Compile trust function for single subnet. 296 | * 297 | * @param {Object} subnet 298 | * @private 299 | */ 300 | 301 | function trustSingle (subnet) { 302 | var subnetip = subnet[0] 303 | var subnetkind = subnetip.kind() 304 | var subnetisipv4 = subnetkind === 'ipv4' 305 | var subnetrange = subnet[1] 306 | 307 | return function trust (addr) { 308 | if (!isip(addr)) return false 309 | 310 | var ip = parseip(addr) 311 | var kind = ip.kind() 312 | 313 | if (kind !== subnetkind) { 314 | if (subnetisipv4 && !ip.isIPv4MappedAddress()) { 315 | // Incompatible IP addresses 316 | return false 317 | } 318 | 319 | // Convert IP to match subnet IP kind 320 | ip = subnetisipv4 321 | ? ip.toIPv4Address() 322 | : ip.toIPv4MappedAddress() 323 | } 324 | 325 | return ip.match(subnetip, subnetrange) 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proxy-addr", 3 | "description": "Determine address of proxied request", 4 | "version": "2.0.7", 5 | "author": "Douglas Christopher Wilson ", 6 | "license": "MIT", 7 | "keywords": [ 8 | "ip", 9 | "proxy", 10 | "x-forwarded-for" 11 | ], 12 | "repository": "jshttp/proxy-addr", 13 | "dependencies": { 14 | "forwarded": "0.2.0", 15 | "ipaddr.js": "1.9.1" 16 | }, 17 | "devDependencies": { 18 | "benchmark": "2.1.4", 19 | "beautify-benchmark": "0.2.4", 20 | "deep-equal": "1.0.1", 21 | "eslint": "7.26.0", 22 | "eslint-config-standard": "14.1.1", 23 | "eslint-plugin-import": "2.23.4", 24 | "eslint-plugin-markdown": "2.2.0", 25 | "eslint-plugin-node": "11.1.0", 26 | "eslint-plugin-promise": "4.3.1", 27 | "eslint-plugin-standard": "4.1.0", 28 | "mocha": "8.4.0", 29 | "nyc": "15.1.0" 30 | }, 31 | "files": [ 32 | "LICENSE", 33 | "HISTORY.md", 34 | "README.md", 35 | "index.js" 36 | ], 37 | "engines": { 38 | "node": ">= 0.10" 39 | }, 40 | "scripts": { 41 | "bench": "node benchmark/index.js", 42 | "lint": "eslint .", 43 | "test": "mocha --reporter spec --bail --check-leaks test/", 44 | "test-ci": "nyc --reporter=lcov --reporter=text npm test", 45 | "test-cov": "nyc --reporter=html --reporter=text npm test" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert') 3 | var deepEqual = require('deep-equal') 4 | var proxyaddr = require('..') 5 | 6 | describe('proxyaddr(req, trust)', function () { 7 | describe('arguments', function () { 8 | describe('req', function () { 9 | it('should be required', function () { 10 | assert.throws(proxyaddr, /req.*required/) 11 | }) 12 | }) 13 | 14 | describe('trust', function () { 15 | it('should be required', function () { 16 | var req = createReq('127.0.0.1') 17 | assert.throws(proxyaddr.bind(null, req), /trust.*required/) 18 | }) 19 | 20 | it('should accept a function', function () { 21 | var req = createReq('127.0.0.1') 22 | assert.doesNotThrow(proxyaddr.bind(null, req, all)) 23 | }) 24 | 25 | it('should accept an array', function () { 26 | var req = createReq('127.0.0.1') 27 | assert.doesNotThrow(proxyaddr.bind(null, req, [])) 28 | }) 29 | 30 | it('should accept a string', function () { 31 | var req = createReq('127.0.0.1') 32 | assert.doesNotThrow(proxyaddr.bind(null, req, '127.0.0.1')) 33 | }) 34 | 35 | it('should reject a number', function () { 36 | var req = createReq('127.0.0.1') 37 | assert.throws(proxyaddr.bind(null, req, 42), /unsupported trust argument/) 38 | }) 39 | 40 | it('should accept IPv4', function () { 41 | var req = createReq('127.0.0.1') 42 | assert.doesNotThrow(proxyaddr.bind(null, req, '127.0.0.1')) 43 | }) 44 | 45 | it('should accept IPv6', function () { 46 | var req = createReq('127.0.0.1') 47 | assert.doesNotThrow(proxyaddr.bind(null, req, '::1')) 48 | }) 49 | 50 | it('should accept IPv4-style IPv6', function () { 51 | var req = createReq('127.0.0.1') 52 | assert.doesNotThrow(proxyaddr.bind(null, req, '::ffff:127.0.0.1')) 53 | }) 54 | 55 | it('should accept pre-defined names', function () { 56 | var req = createReq('127.0.0.1') 57 | assert.doesNotThrow(proxyaddr.bind(null, req, 'loopback')) 58 | }) 59 | 60 | it('should accept pre-defined names in array', function () { 61 | var req = createReq('127.0.0.1') 62 | assert.doesNotThrow(proxyaddr.bind(null, req, ['loopback', '10.0.0.1'])) 63 | }) 64 | 65 | it('should not alter input array', function () { 66 | var arr = ['loopback', '10.0.0.1'] 67 | var req = createReq('127.0.0.1') 68 | assert.doesNotThrow(proxyaddr.bind(null, req, arr)) 69 | strictDeepEqual(arr, ['loopback', '10.0.0.1']) 70 | }) 71 | 72 | it('should reject non-IP', function () { 73 | var req = createReq('127.0.0.1') 74 | assert.throws(proxyaddr.bind(null, req, 'blargh'), /invalid IP address/) 75 | assert.throws(proxyaddr.bind(null, req, '10.0.300.1'), /invalid IP address/) 76 | assert.throws(proxyaddr.bind(null, req, '::ffff:30.168.1.9000'), /invalid IP address/) 77 | assert.throws(proxyaddr.bind(null, req, '-1'), /invalid IP address/) 78 | }) 79 | 80 | it('should reject bad CIDR', function () { 81 | var req = createReq('127.0.0.1') 82 | assert.throws(proxyaddr.bind(null, req, '10.0.0.1/internet'), /invalid range on address/) 83 | assert.throws(proxyaddr.bind(null, req, '10.0.0.1/6000'), /invalid range on address/) 84 | assert.throws(proxyaddr.bind(null, req, '::1/6000'), /invalid range on address/) 85 | assert.throws(proxyaddr.bind(null, req, '::ffff:a00:2/136'), /invalid range on address/) 86 | assert.throws(proxyaddr.bind(null, req, '::ffff:a00:2/-1'), /invalid range on address/) 87 | }) 88 | 89 | it('should reject bad netmask', function () { 90 | var req = createReq('127.0.0.1') 91 | assert.throws(proxyaddr.bind(null, req, '10.0.0.1/255.0.255.0'), /invalid range on address/) 92 | assert.throws(proxyaddr.bind(null, req, '10.0.0.1/ffc0::'), /invalid range on address/) 93 | assert.throws(proxyaddr.bind(null, req, 'fe80::/ffc0::'), /invalid range on address/) 94 | assert.throws(proxyaddr.bind(null, req, 'fe80::/255.255.255.0'), /invalid range on address/) 95 | assert.throws(proxyaddr.bind(null, req, '::ffff:a00:2/255.255.255.0'), /invalid range on address/) 96 | }) 97 | 98 | it('should be invoked as trust(addr, i)', function () { 99 | var log = [] 100 | var req = createReq('127.0.0.1', { 101 | 'x-forwarded-for': '192.168.0.1, 10.0.0.1' 102 | }) 103 | 104 | proxyaddr(req, function (addr, i) { 105 | return log.push(Array.prototype.slice.call(arguments)) 106 | }) 107 | 108 | strictDeepEqual(log, [ 109 | ['127.0.0.1', 0], 110 | ['10.0.0.1', 1] 111 | ]) 112 | }) 113 | }) 114 | }) 115 | 116 | describe('with all trusted', function () { 117 | it('should return socket address with no headers', function () { 118 | var req = createReq('127.0.0.1') 119 | assert.strictEqual(proxyaddr(req, all), '127.0.0.1') 120 | }) 121 | 122 | it('should return header value', function () { 123 | var req = createReq('127.0.0.1', { 124 | 'x-forwarded-for': '10.0.0.1' 125 | }) 126 | assert.strictEqual(proxyaddr(req, all), '10.0.0.1') 127 | }) 128 | 129 | it('should return furthest header value', function () { 130 | var req = createReq('127.0.0.1', { 131 | 'x-forwarded-for': '10.0.0.1, 10.0.0.2' 132 | }) 133 | assert.strictEqual(proxyaddr(req, all), '10.0.0.1') 134 | }) 135 | }) 136 | 137 | describe('with none trusted', function () { 138 | it('should return socket address with no headers', function () { 139 | var req = createReq('127.0.0.1') 140 | assert.strictEqual(proxyaddr(req, none), '127.0.0.1') 141 | }) 142 | 143 | it('should return socket address with headers', function () { 144 | var req = createReq('127.0.0.1', { 145 | 'x-forwarded-for': '10.0.0.1, 10.0.0.2' 146 | }) 147 | assert.strictEqual(proxyaddr(req, none), '127.0.0.1') 148 | }) 149 | }) 150 | 151 | describe('with some trusted', function () { 152 | it('should return socket address with no headers', function () { 153 | var req = createReq('127.0.0.1') 154 | assert.strictEqual(proxyaddr(req, trust10x), '127.0.0.1') 155 | }) 156 | 157 | it('should return socket address when not trusted', function () { 158 | var req = createReq('127.0.0.1', { 159 | 'x-forwarded-for': '10.0.0.1, 10.0.0.2' 160 | }) 161 | assert.strictEqual(proxyaddr(req, trust10x), '127.0.0.1') 162 | }) 163 | 164 | it('should return header when socket trusted', function () { 165 | var req = createReq('10.0.0.1', { 166 | 'x-forwarded-for': '192.168.0.1' 167 | }) 168 | assert.strictEqual(proxyaddr(req, trust10x), '192.168.0.1') 169 | }) 170 | 171 | it('should return first untrusted after trusted', function () { 172 | var req = createReq('10.0.0.1', { 173 | 'x-forwarded-for': '192.168.0.1, 10.0.0.2' 174 | }) 175 | assert.strictEqual(proxyaddr(req, trust10x), '192.168.0.1') 176 | }) 177 | 178 | it('should not skip untrusted', function () { 179 | var req = createReq('10.0.0.1', { 180 | 'x-forwarded-for': '10.0.0.3, 192.168.0.1, 10.0.0.2' 181 | }) 182 | assert.strictEqual(proxyaddr(req, trust10x), '192.168.0.1') 183 | }) 184 | }) 185 | 186 | describe('when given array', function () { 187 | it('should accept literal IP addresses', function () { 188 | var req = createReq('10.0.0.1', { 189 | 'x-forwarded-for': '192.168.0.1, 10.0.0.2' 190 | }) 191 | assert.strictEqual(proxyaddr(req, ['10.0.0.1', '10.0.0.2']), '192.168.0.1') 192 | }) 193 | 194 | it('should not trust non-IP addresses', function () { 195 | var req = createReq('10.0.0.1', { 196 | 'x-forwarded-for': '192.168.0.1, 10.0.0.2, localhost' 197 | }) 198 | assert.strictEqual(proxyaddr(req, ['10.0.0.1', '10.0.0.2']), 'localhost') 199 | }) 200 | 201 | it('should return socket address if none match', function () { 202 | var req = createReq('10.0.0.1', { 203 | 'x-forwarded-for': '192.168.0.1, 10.0.0.2' 204 | }) 205 | assert.strictEqual(proxyaddr(req, ['127.0.0.1', '192.168.0.100']), '10.0.0.1') 206 | }) 207 | 208 | describe('when array empty', function () { 209 | it('should return socket address ', function () { 210 | var req = createReq('127.0.0.1') 211 | assert.strictEqual(proxyaddr(req, []), '127.0.0.1') 212 | }) 213 | 214 | it('should return socket address with headers', function () { 215 | var req = createReq('127.0.0.1', { 216 | 'x-forwarded-for': '10.0.0.1, 10.0.0.2' 217 | }) 218 | assert.strictEqual(proxyaddr(req, []), '127.0.0.1') 219 | }) 220 | }) 221 | }) 222 | 223 | describe('when given IPv4 addresses', function () { 224 | it('should accept literal IP addresses', function () { 225 | var req = createReq('10.0.0.1', { 226 | 'x-forwarded-for': '192.168.0.1, 10.0.0.2' 227 | }) 228 | assert.strictEqual(proxyaddr(req, ['10.0.0.1', '10.0.0.2']), '192.168.0.1') 229 | }) 230 | 231 | it('should accept CIDR notation', function () { 232 | var req = createReq('10.0.0.1', { 233 | 'x-forwarded-for': '192.168.0.1, 10.0.0.200' 234 | }) 235 | assert.strictEqual(proxyaddr(req, '10.0.0.2/26'), '10.0.0.200') 236 | }) 237 | 238 | it('should accept netmask notation', function () { 239 | var req = createReq('10.0.0.1', { 240 | 'x-forwarded-for': '192.168.0.1, 10.0.0.200' 241 | }) 242 | assert.strictEqual(proxyaddr(req, '10.0.0.2/255.255.255.192'), '10.0.0.200') 243 | }) 244 | }) 245 | 246 | describe('when given IPv6 addresses', function () { 247 | it('should accept literal IP addresses', function () { 248 | var req = createReq('fe80::1', { 249 | 'x-forwarded-for': '2002:c000:203::1, fe80::2' 250 | }) 251 | assert.strictEqual(proxyaddr(req, ['fe80::1', 'fe80::2']), '2002:c000:203::1') 252 | }) 253 | 254 | it('should accept CIDR notation', function () { 255 | var req = createReq('fe80::1', { 256 | 'x-forwarded-for': '2002:c000:203::1, fe80::ff00' 257 | }) 258 | assert.strictEqual(proxyaddr(req, 'fe80::/125'), 'fe80::ff00') 259 | }) 260 | }) 261 | 262 | describe('when IP versions mixed', function () { 263 | it('should match respective versions', function () { 264 | var req = createReq('::1', { 265 | 'x-forwarded-for': '2002:c000:203::1' 266 | }) 267 | assert.strictEqual(proxyaddr(req, ['127.0.0.1', '::1']), '2002:c000:203::1') 268 | }) 269 | 270 | it('should not match IPv4 to IPv6', function () { 271 | var req = createReq('::1', { 272 | 'x-forwarded-for': '2002:c000:203::1' 273 | }) 274 | assert.strictEqual(proxyaddr(req, '127.0.0.1'), '::1') 275 | }) 276 | }) 277 | 278 | describe('when IPv4-mapped IPv6 addresses', function () { 279 | it('should match IPv4 trust to IPv6 request', function () { 280 | var req = createReq('::ffff:a00:1', { 281 | 'x-forwarded-for': '192.168.0.1, 10.0.0.2' 282 | }) 283 | assert.strictEqual(proxyaddr(req, ['10.0.0.1', '10.0.0.2']), '192.168.0.1') 284 | }) 285 | 286 | it('should match IPv4 netmask trust to IPv6 request', function () { 287 | var req = createReq('::ffff:a00:1', { 288 | 'x-forwarded-for': '192.168.0.1, 10.0.0.2' 289 | }) 290 | assert.strictEqual(proxyaddr(req, ['10.0.0.1/16']), '192.168.0.1') 291 | }) 292 | 293 | it('should match IPv6 trust to IPv4 request', function () { 294 | var req = createReq('10.0.0.1', { 295 | 'x-forwarded-for': '192.168.0.1, 10.0.0.2' 296 | }) 297 | assert.strictEqual(proxyaddr(req, ['::ffff:a00:1', '::ffff:a00:2']), '192.168.0.1') 298 | }) 299 | 300 | it('should match CIDR notation for IPv4-mapped address', function () { 301 | var req = createReq('10.0.0.1', { 302 | 'x-forwarded-for': '192.168.0.1, 10.0.0.200' 303 | }) 304 | assert.strictEqual(proxyaddr(req, '::ffff:a00:2/122'), '10.0.0.200') 305 | }) 306 | 307 | it('should match CIDR notation for IPv4-mapped address mixed with IPv6 CIDR', function () { 308 | var req = createReq('10.0.0.1', { 309 | 'x-forwarded-for': '192.168.0.1, 10.0.0.200' 310 | }) 311 | assert.strictEqual(proxyaddr(req, ['::ffff:a00:2/122', 'fe80::/125']), '10.0.0.200') 312 | }) 313 | 314 | it('should match CIDR notation for IPv4-mapped address mixed with IPv4 addresses', function () { 315 | var req = createReq('10.0.0.1', { 316 | 'x-forwarded-for': '192.168.0.1, 10.0.0.200' 317 | }) 318 | assert.strictEqual(proxyaddr(req, ['::ffff:a00:2/122', '127.0.0.1']), '10.0.0.200') 319 | }) 320 | }) 321 | 322 | describe('when given pre-defined names', function () { 323 | it('should accept single pre-defined name', function () { 324 | var req = createReq('fe80::1', { 325 | 'x-forwarded-for': '2002:c000:203::1, fe80::2' 326 | }) 327 | assert.strictEqual(proxyaddr(req, 'linklocal'), '2002:c000:203::1') 328 | }) 329 | 330 | it('should accept multiple pre-defined names', function () { 331 | var req = createReq('::1', { 332 | 'x-forwarded-for': '2002:c000:203::1, fe80::2' 333 | }) 334 | assert.strictEqual(proxyaddr(req, ['loopback', 'linklocal']), '2002:c000:203::1') 335 | }) 336 | }) 337 | 338 | describe('when header contains non-ip addresses', function () { 339 | it('should stop at first non-ip after trusted', function () { 340 | var req = createReq('127.0.0.1', { 341 | 'x-forwarded-for': 'myrouter, 127.0.0.1, proxy' 342 | }) 343 | assert.strictEqual(proxyaddr(req, '127.0.0.1'), 'proxy') 344 | }) 345 | 346 | it('should stop at first malformed ip after trusted', function () { 347 | var req = createReq('127.0.0.1', { 348 | 'x-forwarded-for': 'myrouter, 127.0.0.1, ::8:8:8:8:8:8:8:8:8' 349 | }) 350 | assert.strictEqual(proxyaddr(req, '127.0.0.1'), '::8:8:8:8:8:8:8:8:8') 351 | }) 352 | 353 | it('should provide all values to function', function () { 354 | var log = [] 355 | var req = createReq('127.0.0.1', { 356 | 'x-forwarded-for': 'myrouter, 127.0.0.1, proxy' 357 | }) 358 | 359 | proxyaddr(req, function (addr, i) { 360 | return log.push(Array.prototype.slice.call(arguments)) 361 | }) 362 | 363 | strictDeepEqual(log, [ 364 | ['127.0.0.1', 0], 365 | ['proxy', 1], 366 | ['127.0.0.1', 2] 367 | ]) 368 | }) 369 | }) 370 | 371 | describe('when socket address undefined', function () { 372 | it('should return undefined as address', function () { 373 | var req = createReq(undefined) 374 | assert.strictEqual(proxyaddr(req, '127.0.0.1'), undefined) 375 | }) 376 | 377 | it('should return undefined even with trusted headers', function () { 378 | var req = createReq(undefined, { 379 | 'x-forwarded-for': '127.0.0.1, 10.0.0.1' 380 | }) 381 | assert.strictEqual(proxyaddr(req, '127.0.0.1'), undefined) 382 | }) 383 | }) 384 | }) 385 | 386 | describe('proxyaddr.all(req, [trust])', function () { 387 | describe('arguments', function () { 388 | describe('req', function () { 389 | it('should be required', function () { 390 | assert.throws(proxyaddr.all, /req.*required/) 391 | }) 392 | }) 393 | 394 | describe('trust', function () { 395 | it('should be optional', function () { 396 | var req = createReq('127.0.0.1') 397 | assert.doesNotThrow(proxyaddr.all.bind(null, req)) 398 | }) 399 | }) 400 | }) 401 | 402 | describe('with no headers', function () { 403 | it('should return socket address', function () { 404 | var req = createReq('127.0.0.1') 405 | strictDeepEqual(proxyaddr.all(req), ['127.0.0.1']) 406 | }) 407 | }) 408 | 409 | describe('with x-forwarded-for header', function () { 410 | it('should include x-forwarded-for', function () { 411 | var req = createReq('127.0.0.1', { 412 | 'x-forwarded-for': '10.0.0.1' 413 | }) 414 | strictDeepEqual(proxyaddr.all(req), ['127.0.0.1', '10.0.0.1']) 415 | }) 416 | 417 | it('should include x-forwarded-for in correct order', function () { 418 | var req = createReq('127.0.0.1', { 419 | 'x-forwarded-for': '10.0.0.1, 10.0.0.2' 420 | }) 421 | strictDeepEqual(proxyaddr.all(req), ['127.0.0.1', '10.0.0.2', '10.0.0.1']) 422 | }) 423 | }) 424 | 425 | describe('with trust argument', function () { 426 | it('should stop at first untrusted', function () { 427 | var req = createReq('127.0.0.1', { 428 | 'x-forwarded-for': '10.0.0.1, 10.0.0.2' 429 | }) 430 | strictDeepEqual(proxyaddr.all(req, '127.0.0.1'), ['127.0.0.1', '10.0.0.2']) 431 | }) 432 | 433 | it('should be only socket address for no trust', function () { 434 | var req = createReq('127.0.0.1', { 435 | 'x-forwarded-for': '10.0.0.1, 10.0.0.2' 436 | }) 437 | strictDeepEqual(proxyaddr.all(req, []), ['127.0.0.1']) 438 | }) 439 | }) 440 | }) 441 | 442 | describe('proxyaddr.compile(trust)', function () { 443 | describe('arguments', function () { 444 | describe('trust', function () { 445 | it('should be required', function () { 446 | assert.throws(proxyaddr.compile, /argument.*required/) 447 | }) 448 | 449 | it('should accept an array', function () { 450 | assert.strictEqual(typeof proxyaddr.compile([]), 'function') 451 | }) 452 | 453 | it('should accept a string', function () { 454 | assert.strictEqual(typeof proxyaddr.compile('127.0.0.1'), 'function') 455 | }) 456 | 457 | it('should reject a number', function () { 458 | assert.throws(proxyaddr.compile.bind(null, 42), /unsupported trust argument/) 459 | }) 460 | 461 | it('should accept IPv4', function () { 462 | assert.strictEqual(typeof proxyaddr.compile('127.0.0.1'), 'function') 463 | }) 464 | 465 | it('should accept IPv6', function () { 466 | assert.strictEqual(typeof proxyaddr.compile('::1'), 'function') 467 | }) 468 | 469 | it('should accept IPv4-style IPv6', function () { 470 | assert.strictEqual(typeof proxyaddr.compile('::ffff:127.0.0.1'), 'function') 471 | }) 472 | 473 | it('should accept pre-defined names', function () { 474 | assert.strictEqual(typeof proxyaddr.compile('loopback'), 'function') 475 | }) 476 | 477 | it('should accept pre-defined names in array', function () { 478 | assert.strictEqual(typeof proxyaddr.compile(['loopback', '10.0.0.1']), 'function') 479 | }) 480 | 481 | it('should reject non-IP', function () { 482 | assert.throws(proxyaddr.compile.bind(null, 'blargh'), /invalid IP address/) 483 | assert.throws(proxyaddr.compile.bind(null, '-1'), /invalid IP address/) 484 | }) 485 | 486 | it('should reject bad CIDR', function () { 487 | assert.throws(proxyaddr.compile.bind(null, '10.0.0.1/6000'), /invalid range on address/) 488 | assert.throws(proxyaddr.compile.bind(null, '::1/6000'), /invalid range on address/) 489 | assert.throws(proxyaddr.compile.bind(null, '::ffff:a00:2/136'), /invalid range on address/) 490 | assert.throws(proxyaddr.compile.bind(null, '::ffff:a00:2/-46'), /invalid range on address/) 491 | }) 492 | 493 | it('should not alter input array', function () { 494 | var arr = ['loopback', '10.0.0.1'] 495 | assert.strictEqual(typeof proxyaddr.compile(arr), 'function') 496 | strictDeepEqual(arr, ['loopback', '10.0.0.1']) 497 | }) 498 | }) 499 | }) 500 | }) 501 | 502 | function createReq (socketAddr, headers) { 503 | return { 504 | connection: { 505 | remoteAddress: socketAddr 506 | }, 507 | headers: headers || {} 508 | } 509 | } 510 | 511 | function strictDeepEqual (actual, expected, message) { 512 | if (assert.deepStrictEqual) { 513 | assert.deepStrictEqual(actual, expected, message) 514 | } else { 515 | assert.ok(deepEqual(actual, expected, { strict: true })) 516 | } 517 | } 518 | 519 | function all () { return true } 520 | function none () { return false } 521 | function trust10x (addr) { return /^10\./.test(addr) } 522 | --------------------------------------------------------------------------------