├── .eslintignore ├── .eslintrc.yml ├── .github └── workflows │ ├── ci.yml │ └── scorecard.yml ├── .gitignore ├── HISTORY.md ├── LICENSE ├── README.md ├── benchmark └── index.js ├── index.js ├── package.json ├── scripts └── version-history.js └── test ├── .eslintrc.yml └── test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: standard 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-20.04 10 | strategy: 11 | matrix: 12 | name: 13 | - Node.js 0.6 14 | - Node.js 0.8 15 | - Node.js 0.10 16 | - Node.js 0.12 17 | - io.js 1.x 18 | - io.js 2.x 19 | - io.js 3.x 20 | - Node.js 4.x 21 | - Node.js 5.x 22 | - Node.js 6.x 23 | - Node.js 7.x 24 | - Node.js 8.x 25 | - Node.js 9.x 26 | - Node.js 10.x 27 | - Node.js 11.x 28 | - Node.js 12.x 29 | - Node.js 13.x 30 | - Node.js 14.x 31 | - Node.js 15.x 32 | - Node.js 16.x 33 | - Node.js 17.x 34 | - Node.js 18.x 35 | - Node.js 19.x 36 | - Node.js 20.x 37 | - Node.js 21.x 38 | 39 | include: 40 | - name: Node.js 0.6 41 | node-version: "0.6" 42 | npm-i: mocha@1.21.5 43 | npm-rm: beautify-benchmark benchmark nyc 44 | 45 | - name: Node.js 0.8 46 | node-version: "0.8" 47 | npm-i: mocha@2.5.3 48 | npm-rm: beautify-benchmark benchmark nyc 49 | 50 | - name: Node.js 0.10 51 | node-version: "0.10" 52 | npm-i: mocha@3.5.3 nyc@10.3.2 53 | npm-rm: beautify-benchmark benchmark 54 | 55 | - name: Node.js 0.12 56 | node-version: "0.12" 57 | npm-i: mocha@3.5.3 nyc@10.3.2 58 | npm-rm: beautify-benchmark benchmark 59 | 60 | - name: io.js 1.x 61 | node-version: "1.8" 62 | npm-i: mocha@3.5.3 nyc@10.3.2 63 | npm-rm: beautify-benchmark benchmark 64 | 65 | - name: io.js 2.x 66 | node-version: "2.5" 67 | npm-i: mocha@3.5.3 nyc@10.3.2 68 | npm-rm: beautify-benchmark benchmark 69 | 70 | - name: io.js 3.x 71 | node-version: "3.3" 72 | npm-i: mocha@3.5.3 nyc@10.3.2 73 | npm-rm: beautify-benchmark benchmark 74 | 75 | - name: Node.js 4.x 76 | node-version: "4.9" 77 | npm-i: mocha@5.2.0 nyc@11.9.0 78 | npm-rm: beautify-benchmark benchmark 79 | 80 | - name: Node.js 5.x 81 | node-version: "5.12" 82 | npm-i: mocha@5.2.0 nyc@11.9.0 83 | npm-rm: beautify-benchmark benchmark 84 | 85 | - name: Node.js 6.x 86 | node-version: "6.17" 87 | npm-i: mocha@6.2.2 nyc@14.1.1 88 | npm-rm: beautify-benchmark benchmark 89 | 90 | - name: Node.js 7.x 91 | node-version: "7.10" 92 | npm-i: mocha@6.2.2 nyc@14.1.1 93 | npm-rm: beautify-benchmark benchmark 94 | 95 | - name: Node.js 8.x 96 | node-version: "8.17" 97 | npm-i: mocha@7.2.0 nyc@14.1.1 98 | npm-rm: beautify-benchmark benchmark 99 | 100 | - name: Node.js 9.x 101 | node-version: "9.11" 102 | npm-i: mocha@7.2.0 nyc@14.1.1 103 | npm-rm: beautify-benchmark benchmark 104 | 105 | - name: Node.js 10.x 106 | node-version: "10.24" 107 | npm-i: mocha@8.4.0 108 | npm-rm: beautify-benchmark benchmark 109 | 110 | - name: Node.js 11.x 111 | node-version: "11.15" 112 | npm-i: mocha@8.4.0 113 | npm-rm: beautify-benchmark benchmark 114 | 115 | - name: Node.js 12.x 116 | node-version: "12.22" 117 | npm-i: mocha@9.2.2 118 | npm-rm: beautify-benchmark benchmark 119 | 120 | - name: Node.js 13.x 121 | node-version: "13.14" 122 | npm-i: mocha@9.2.2 123 | npm-rm: beautify-benchmark benchmark 124 | 125 | - name: Node.js 14.x 126 | node-version: "14.21" 127 | npm-rm: beautify-benchmark benchmark 128 | 129 | - name: Node.js 15.x 130 | node-version: "15.14" 131 | npm-rm: beautify-benchmark benchmark 132 | 133 | - name: Node.js 16.x 134 | node-version: "16.20" 135 | npm-rm: beautify-benchmark benchmark 136 | 137 | - name: Node.js 17.x 138 | node-version: "17.9" 139 | npm-rm: beautify-benchmark benchmark 140 | 141 | - name: Node.js 18.x 142 | node-version: "18.18" 143 | npm-rm: beautify-benchmark benchmark 144 | 145 | - name: Node.js 19.x 146 | node-version: "19.9" 147 | npm-rm: beautify-benchmark benchmark 148 | 149 | - name: Node.js 20.x 150 | node-version: "20.9" 151 | npm-rm: beautify-benchmark benchmark 152 | 153 | - name: Node.js 21.x 154 | node-version: "21.1" 155 | npm-rm: beautify-benchmark benchmark 156 | 157 | steps: 158 | - uses: actions/checkout@v3 159 | 160 | - name: Install Node.js ${{ matrix.node-version }} 161 | shell: bash -eo pipefail -l {0} 162 | run: | 163 | if [[ "${{ matrix.node-version }}" == 0.6* ]]; then 164 | sudo sh -c 'echo "deb http://us.archive.ubuntu.com/ubuntu/ bionic universe" >> /etc/apt/sources.list' 165 | sudo sh -c 'echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list' 166 | sudo apt-get update 167 | sudo apt-get install g++-4.8 gcc-4.8 libssl1.0-dev python2 python-is-python2 168 | export CC=/usr/bin/gcc-4.8 169 | export CXX=/usr/bin/g++-4.8 170 | fi 171 | nvm install --default ${{ matrix.node-version }} 172 | if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then 173 | nvm install --alias=npm 0.10 174 | nvm use ${{ matrix.node-version }} 175 | if [[ "$(npm -v)" == 1.1.* ]]; then 176 | nvm exec npm npm install -g npm@1.1 177 | ln -fs "$(which npm)" "$(dirname "$(nvm which npm)")/npm" 178 | else 179 | sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" 180 | fi 181 | npm config set strict-ssl false 182 | fi 183 | dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" 184 | 185 | - name: Configure npm 186 | run: | 187 | if [[ "$(npm config get package-lock)" == "true" ]]; then 188 | npm config set package-lock false 189 | else 190 | npm config set shrinkwrap false 191 | fi 192 | 193 | - name: Remove npm module(s) ${{ matrix.npm-rm }} 194 | run: npm rm --silent --save-dev ${{ matrix.npm-rm }} 195 | if: matrix.npm-rm != '' 196 | 197 | - name: Install npm module(s) ${{ matrix.npm-i }} 198 | run: npm install --save-dev ${{ matrix.npm-i }} 199 | if: matrix.npm-i != '' 200 | 201 | - name: Setup Node.js version-specific dependencies 202 | shell: bash 203 | run: | 204 | # eslint for linting 205 | # - remove on Node.js < 10 206 | if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then 207 | node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ 208 | grep -E '^eslint(-|$)' | \ 209 | sort -r | \ 210 | xargs -n1 npm rm --silent --save-dev 211 | fi 212 | 213 | - name: Install Node.js dependencies 214 | run: npm install 215 | 216 | - name: List environment 217 | id: list_env 218 | shell: bash 219 | run: | 220 | echo "node@$(node -v)" 221 | echo "npm@$(npm -v)" 222 | npm -s ls ||: 223 | (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print $2 "=" $3 }' >> "$GITHUB_OUTPUT" 224 | 225 | - name: Run tests 226 | shell: bash 227 | run: | 228 | if npm -ps ls nyc | grep -q nyc; then 229 | npm run test-ci 230 | else 231 | npm test 232 | fi 233 | 234 | - name: Lint code 235 | if: steps.list_env.outputs.eslint != '' 236 | run: npm run lint 237 | 238 | - name: Collect code coverage 239 | uses: coverallsapp/github-action@master 240 | if: steps.list_env.outputs.nyc != '' 241 | with: 242 | github-token: ${{ secrets.GITHUB_TOKEN }} 243 | flag-name: run-${{ matrix.test_number }} 244 | parallel: true 245 | 246 | coverage: 247 | needs: test 248 | runs-on: ubuntu-latest 249 | steps: 250 | - name: Upload code coverage 251 | uses: coverallsapp/github-action@master 252 | with: 253 | github-token: ${{ secrets.github_token }} 254 | parallel-finished: true 255 | -------------------------------------------------------------------------------- /.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 | npm-debug.log 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 0.2.0 / 2021-05-31 2 | ================== 3 | 4 | * Use `req.socket` over deprecated `req.connection` 5 | 6 | 0.1.2 / 2017-09-14 7 | ================== 8 | 9 | * perf: improve header parsing 10 | * perf: reduce overhead when no `X-Forwarded-For` header 11 | 12 | 0.1.1 / 2017-09-10 13 | ================== 14 | 15 | * Fix trimming leading / trailing OWS 16 | * perf: hoist regular expression 17 | 18 | 0.1.0 / 2014-09-21 19 | ================== 20 | 21 | * Initial release 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014-2017 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 | # forwarded 2 | 3 | [![NPM Version][npm-image]][npm-url] 4 | [![NPM Downloads][downloads-image]][downloads-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 | Parse HTTP X-Forwarded-For header 10 | 11 | ## Installation 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 forwarded 19 | ``` 20 | 21 | ## API 22 | 23 | ```js 24 | var forwarded = require('forwarded') 25 | ``` 26 | 27 | ### forwarded(req) 28 | 29 | ```js 30 | var addresses = forwarded(req) 31 | ``` 32 | 33 | Parse the `X-Forwarded-For` header from the given [Node.js `IncomingMessage` object][nodejs-http-incomingmessage]. 34 | Returns an array of the addresses, including the socket address for the `req`, in reverse 35 | order (i.e. index `0` is the socket address and the last index is the furthest address, 36 | typically the end-user). 37 | 38 | ## Testing 39 | 40 | ```sh 41 | $ npm test 42 | ``` 43 | 44 | ## License 45 | 46 | [MIT](LICENSE) 47 | 48 | [nodejs-http-incomingmessage]: https://nodejs.org/dist/latest/docs/api/http.html#class-httpincomingmessage 49 | 50 | [ci-image]: https://badgen.net/github/checks/jshttp/forwarded/master?label=ci 51 | [ci-url]: https://github.com/jshttp/forwarded/actions?query=workflow%3Aci 52 | [npm-image]: https://img.shields.io/npm/v/forwarded.svg 53 | [npm-url]: https://npmjs.org/package/forwarded 54 | [node-version-image]: https://img.shields.io/node/v/forwarded.svg 55 | [node-version-url]: https://nodejs.org/en/download/ 56 | [coveralls-image]: https://img.shields.io/coveralls/jshttp/forwarded/master.svg 57 | [coveralls-url]: https://coveralls.io/r/jshttp/forwarded?branch=master 58 | [downloads-image]: https://img.shields.io/npm/dm/forwarded.svg 59 | [downloads-url]: https://npmjs.org/package/forwarded 60 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var benchmark = require('benchmark') 7 | var benchmarks = require('beautify-benchmark') 8 | 9 | /** 10 | * Globals for benchmark.js 11 | */ 12 | 13 | global.forwarded = require('..') 14 | global.req0 = fakerequest({}) 15 | global.req1 = fakerequest({ 'x-forwarded-for': '192.168.0.10' }) 16 | global.req2 = fakerequest({ 'x-forwarded-for': '192.168.0.10, 192.168.1.20' }) 17 | global.req5 = fakerequest({ 'x-forwarded-for': '192.168.0.10, 192.168.1.20, 192.168.1.21, 192.168.1.22, 192.168.1.23' }) 18 | 19 | var suite = new benchmark.Suite() 20 | 21 | suite.add({ 22 | name: 'no header', 23 | minSamples: 100, 24 | fn: 'var addrs = forwarded(req0)' 25 | }) 26 | 27 | suite.add({ 28 | name: '1 address', 29 | minSamples: 100, 30 | fn: 'var addrs = forwarded(req1)' 31 | }) 32 | 33 | suite.add({ 34 | name: '2 addresses', 35 | minSamples: 100, 36 | fn: 'var addrs = forwarded(req2)' 37 | }) 38 | 39 | suite.add({ 40 | name: '5 addresses', 41 | minSamples: 100, 42 | fn: 'var addrs = forwarded(req5)' 43 | }) 44 | 45 | suite.on('cycle', function onCycle (event) { 46 | benchmarks.add(event.target) 47 | }) 48 | 49 | suite.on('complete', function onComplete () { 50 | benchmarks.log() 51 | }) 52 | 53 | suite.run({ async: false }) 54 | 55 | function fakerequest (headers) { 56 | return { 57 | headers: headers, 58 | connection: { 59 | remoteAddress: '10.0.0.1' 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * forwarded 3 | * Copyright(c) 2014-2017 Douglas Christopher Wilson 4 | * MIT Licensed 5 | */ 6 | 7 | 'use strict' 8 | 9 | /** 10 | * Module exports. 11 | * @public 12 | */ 13 | 14 | module.exports = forwarded 15 | 16 | /** 17 | * Get all addresses in the request, using the `X-Forwarded-For` header. 18 | * 19 | * @param {object} req 20 | * @return {array} 21 | * @public 22 | */ 23 | 24 | function forwarded (req) { 25 | if (!req) { 26 | throw new TypeError('argument req is required') 27 | } 28 | 29 | // simple header parsing 30 | var proxyAddrs = parse(req.headers['x-forwarded-for'] || '') 31 | var socketAddr = getSocketAddr(req) 32 | var addrs = [socketAddr].concat(proxyAddrs) 33 | 34 | // return all addresses 35 | return addrs 36 | } 37 | 38 | /** 39 | * Get the socket address for a request. 40 | * 41 | * @param {object} req 42 | * @return {string} 43 | * @private 44 | */ 45 | 46 | function getSocketAddr (req) { 47 | return req.socket 48 | ? req.socket.remoteAddress 49 | : req.connection.remoteAddress 50 | } 51 | 52 | /** 53 | * Parse the X-Forwarded-For header. 54 | * 55 | * @param {string} header 56 | * @private 57 | */ 58 | 59 | function parse (header) { 60 | var end = header.length 61 | var list = [] 62 | var start = header.length 63 | 64 | // gather addresses, backwards 65 | for (var i = header.length - 1; i >= 0; i--) { 66 | switch (header.charCodeAt(i)) { 67 | case 0x20: /* */ 68 | if (start === end) { 69 | start = end = i 70 | } 71 | break 72 | case 0x2c: /* , */ 73 | if (start !== end) { 74 | list.push(header.substring(start, end)) 75 | } 76 | start = end = i 77 | break 78 | default: 79 | start = i 80 | break 81 | } 82 | } 83 | 84 | // final address 85 | if (start !== end) { 86 | list.push(header.substring(start, end)) 87 | } 88 | 89 | return list 90 | } 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forwarded", 3 | "description": "Parse HTTP X-Forwarded-For header", 4 | "version": "0.2.0", 5 | "contributors": [ 6 | "Douglas Christopher Wilson " 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "x-forwarded-for", 11 | "http", 12 | "req" 13 | ], 14 | "repository": "jshttp/forwarded", 15 | "devDependencies": { 16 | "beautify-benchmark": "0.2.4", 17 | "benchmark": "2.1.4", 18 | "deep-equal": "1.0.1", 19 | "eslint": "7.32.0", 20 | "eslint-config-standard": "14.1.1", 21 | "eslint-plugin-import": "2.25.4", 22 | "eslint-plugin-node": "11.1.0", 23 | "eslint-plugin-promise": "5.2.0", 24 | "eslint-plugin-standard": "4.1.0", 25 | "mocha": "9.2.2", 26 | "nyc": "15.1.0" 27 | }, 28 | "files": [ 29 | "LICENSE", 30 | "HISTORY.md", 31 | "README.md", 32 | "index.js" 33 | ], 34 | "engines": { 35 | "node": ">= 0.6" 36 | }, 37 | "scripts": { 38 | "bench": "node benchmark/index.js", 39 | "lint": "eslint .", 40 | "test": "mocha --reporter spec --bail --check-leaks test/", 41 | "test-ci": "nyc --reporter=lcov --reporter=text npm test", 42 | "test-cov": "nyc --reporter=html --reporter=text npm test", 43 | "version": "node scripts/version-history.js && git add HISTORY.md" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /scripts/version-history.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | var HISTORY_FILE_PATH = path.join(__dirname, '..', 'HISTORY.md') 7 | var MD_HEADER_REGEXP = /^====*$/ 8 | var VERSION = process.env.npm_package_version 9 | var VERSION_PLACEHOLDER_REGEXP = /^(?:unreleased|(\d+\.)+x)$/ 10 | 11 | var historyFileLines = fs.readFileSync(HISTORY_FILE_PATH, 'utf-8').split('\n') 12 | 13 | if (!MD_HEADER_REGEXP.test(historyFileLines[1])) { 14 | console.error('Missing header in HISTORY.md') 15 | process.exit(1) 16 | } 17 | 18 | if (!VERSION_PLACEHOLDER_REGEXP.test(historyFileLines[0])) { 19 | console.error('Missing placeholder version in HISTORY.md') 20 | process.exit(1) 21 | } 22 | 23 | if (historyFileLines[0].indexOf('x') !== -1) { 24 | var versionCheckRegExp = new RegExp('^' + historyFileLines[0].replace('x', '.+') + '$') 25 | 26 | if (!versionCheckRegExp.test(VERSION)) { 27 | console.error('Version %s does not match placeholder %s', VERSION, historyFileLines[0]) 28 | process.exit(1) 29 | } 30 | } 31 | 32 | historyFileLines[0] = VERSION + ' / ' + getLocaleDate() 33 | historyFileLines[1] = repeat('=', historyFileLines[0].length) 34 | 35 | fs.writeFileSync(HISTORY_FILE_PATH, historyFileLines.join('\n')) 36 | 37 | function getLocaleDate () { 38 | var now = new Date() 39 | 40 | return zeroPad(now.getFullYear(), 4) + '-' + 41 | zeroPad(now.getMonth() + 1, 2) + '-' + 42 | zeroPad(now.getDate(), 2) 43 | } 44 | 45 | function repeat (str, length) { 46 | var out = '' 47 | 48 | for (var i = 0; i < length; i++) { 49 | out += str 50 | } 51 | 52 | return out 53 | } 54 | 55 | function zeroPad (number, length) { 56 | var num = number.toString() 57 | 58 | while (num.length < length) { 59 | num = '0' + num 60 | } 61 | 62 | return num 63 | } 64 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var assert = require('assert') 4 | var deepEqual = require('deep-equal') 5 | var forwarded = require('..') 6 | var http = require('http') 7 | 8 | describe('forwarded(req)', function () { 9 | it('should require req', function () { 10 | assert.throws(forwarded.bind(null), /argument req.*required/) 11 | }) 12 | 13 | it('should work with X-Forwarded-For header', function (done) { 14 | request(createServer()) 15 | .get('/') 16 | .expect(200, ['127.0.0.1'], done) 17 | }) 18 | 19 | it('should include entries from X-Forwarded-For', function (done) { 20 | request(createServer()) 21 | .get('/') 22 | .set('X-Forwarded-For', '10.0.0.2, 10.0.0.1') 23 | .expect(200, ['127.0.0.1', '10.0.0.1', '10.0.0.2'], done) 24 | }) 25 | 26 | it('should skip blank entries', function (done) { 27 | request(createServer()) 28 | .get('/') 29 | .set('X-Forwarded-For', '10.0.0.2,, 10.0.0.1') 30 | .expect(200, ['127.0.0.1', '10.0.0.1', '10.0.0.2'], done) 31 | }) 32 | 33 | it('should trim leading OWS', function (done) { 34 | request(createServer()) 35 | .get('/') 36 | .set('X-Forwarded-For', ' 10.0.0.2 , , 10.0.0.1 ') 37 | .expect(200, ['127.0.0.1', '10.0.0.1', '10.0.0.2'], done) 38 | }) 39 | 40 | describe('socket address', function () { 41 | it('should begin with socket address', function () { 42 | var req = createReq('socket', '127.0.0.1') 43 | assert.strictEqual(forwarded(req)[0], '127.0.0.1') 44 | }) 45 | 46 | it('should use address from req.socket', function () { 47 | var req = createReq('socket', '127.0.0.1') 48 | assert.strictEqual(forwarded(req)[0], req.socket.remoteAddress) 49 | }) 50 | 51 | it('should prefer req.socket', function () { 52 | var req = createReq('socket', '127.0.0.1') 53 | req.connection = { remoteAddress: '10.0.0.1' } 54 | assert.strictEqual(forwarded(req)[0], '127.0.0.1') 55 | }) 56 | 57 | it('should use fall back to req.connection', function () { 58 | var req = createReq('connection', '10.0.0.1') 59 | assert.strictEqual(forwarded(req)[0], '10.0.0.1') 60 | }) 61 | }) 62 | }) 63 | 64 | /** 65 | * Fake http.IncomingMessage to test socket/connection fallback 66 | */ 67 | 68 | function createReq (prop, socketAddr) { 69 | var req = {} 70 | 71 | req.headers = {} // http.IncomingMessage always has this 72 | req[prop] = { remoteAddress: socketAddr } // fake for test 73 | 74 | return req 75 | } 76 | 77 | /** 78 | * Create HTTP server to echo back results 79 | */ 80 | 81 | function createServer () { 82 | return http.createServer(function (req, res) { 83 | res.setHeader('Content-Type', 'application/json') 84 | res.end(JSON.stringify(forwarded(req)) || 'undefined') 85 | }) 86 | } 87 | 88 | /** 89 | * Fake supertest to support Node.js 0.6 90 | */ 91 | 92 | function request (server) { 93 | var headers = Object.create(null) 94 | var path = '/' 95 | 96 | function expect (statusCode, data, callback) { 97 | server.listen(0, '127.0.0.1', function () { 98 | var req = http.request({ 99 | headers: headers, 100 | host: '127.0.0.1', 101 | path: path, 102 | port: server.address().port 103 | }) 104 | 105 | req.on('response', function (res) { 106 | var body = '' 107 | res.setEncoding('utf8') 108 | res.on('data', function (c) { body += c }) 109 | res.on('end', function () { 110 | try { 111 | assert.strictEqual(res.statusCode, statusCode) 112 | assert.ok(deepEqual(JSON.parse(body), data)) 113 | callback(null) 114 | } catch (e) { 115 | callback(e) 116 | } finally { 117 | server.close() 118 | } 119 | }) 120 | }) 121 | 122 | req.end() 123 | }) 124 | } 125 | 126 | return { 127 | expect: expect, 128 | get: function (p) { path = p; return this }, 129 | set: function (k, v) { headers[k] = v; return this } 130 | } 131 | } 132 | --------------------------------------------------------------------------------