From d5000758807bc8b06cc935f5153a67d198b79347 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 03:34:16 -0400 Subject: [PATCH 01/33] v1.0.0-rc.1 This is essentially a complete re-write and expansion on the original functionality of `v0.1.0` - modular schema support, allows for easy future expansions - support `RFC7239` & `X-Forwarded-*` as primary trusted schemas - allows for implementers to choose schemas to process by passing an `options` object - this *is* a breaking api change, where `v0.1.0` returns an array of IPs, `v1.0.0` returns an object representing parameters of `RFC 7239` - the version increment is required to allow dependants libraries/implementers to continue to operate safely with out version breakage *(assuming version resolving occurs with `npm`) - [`RFC 7239` Parameters](http://tools.ietf.org/html/rfc7239#section-5) - coupled with support for less common schemas, named in association with vendor usage: - cloudflare - fastly - microsoft - nginx - zscaler --- README.md | 41 +++++++++++++++---- index.js | 71 +++++++++++++++++++++++---------- lib/processor.js | 82 ++++++++++++++++++++++++++++++++++++++ lib/schemas/cloudflare.js | 19 +++++++++ lib/schemas/fastly.js | 11 +++++ lib/schemas/index.js | 12 ++++++ lib/schemas/microsoft.js | 7 ++++ lib/schemas/nginx.js | 7 ++++ lib/schemas/rackspace.js | 5 +++ lib/schemas/rfc7239.js | 54 +++++++++++++++++++++++++ lib/schemas/xff.js | 13 ++++++ lib/schemas/zscaler.js | 8 ++++ package.json | 42 +++++++++++++++---- test/helper.js | 13 ++++++ test/index.js | 69 ++++++++++++++++++++++++++++++++ test/schemas/cloudflare.js | 36 +++++++++++++++++ test/schemas/fastly.js | 38 ++++++++++++++++++ test/schemas/microsoft.js | 17 ++++++++ test/schemas/nginx.js | 45 +++++++++++++++++++++ test/schemas/rackspace.js | 21 ++++++++++ test/schemas/rfc7239.js | 35 ++++++++++++++++ test/schemas/xff.js | 62 ++++++++++++++++++++++++++++ test/schemas/zscaler.js | 53 ++++++++++++++++++++++++ test/test.js | 37 ----------------- 24 files changed, 725 insertions(+), 73 deletions(-) create mode 100644 lib/processor.js create mode 100644 lib/schemas/cloudflare.js create mode 100644 lib/schemas/fastly.js create mode 100644 lib/schemas/index.js create mode 100644 lib/schemas/microsoft.js create mode 100644 lib/schemas/nginx.js create mode 100644 lib/schemas/rackspace.js create mode 100644 lib/schemas/rfc7239.js create mode 100644 lib/schemas/xff.js create mode 100644 lib/schemas/zscaler.js create mode 100644 test/helper.js create mode 100644 test/index.js create mode 100644 test/schemas/cloudflare.js create mode 100644 test/schemas/fastly.js create mode 100644 test/schemas/microsoft.js create mode 100644 test/schemas/nginx.js create mode 100644 test/schemas/rackspace.js create mode 100644 test/schemas/rfc7239.js create mode 100644 test/schemas/xff.js create mode 100644 test/schemas/zscaler.js delete mode 100644 test/test.js diff --git a/README.md b/README.md index 2b4988f..64f5f00 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] -Parse HTTP X-Forwarded-For header +Parse *Forwarded* HTTP headers, using the standard: [RFC 7239](https://tools.ietf.org/html/rfc7239) *(Forwarded HTTP Extension)*, as well as commonly used none-standard headers: *e.g. `X-Forwarded-*`, `X-Real-*`, etc ...* + +review [`schemas` folder](lib/schemas) for a full list of supported headers schemas. ## Installation @@ -20,16 +22,37 @@ $ npm install forwarded var forwarded = require('forwarded') ``` -### forwarded(req) +### forwarded(req[, options]) + +returns an object who's properties represent [RFC 7239 Parameters (Section 5)](http://tools.ietf.org/html/rfc7239#section-5) ```js -var addresses = forwarded(req) +var result = forwarded(req) ``` -Parse the `X-Forwarded-For` header from the request. Returns an array -of the addresses, including the socket address for the `req`. In reverse -order (i.e. index `0` is the socket address and the last index is the -furthest address, typically the end-user). +#### options + +| name | type | description | required | default | +| --------- | ------- | ----------------------------------------- | -------- | -------------------- | +| `schemas` | `array` | ordered list of header schemas to process | no | `['xff', 'rfc7239']` | + +Parse appropriate headers from the request matching the selected [schemas](#options). + +### returned object + +| name | type | description | default | +| --------- | --------- | ---------------------------------------------------------------------------------------- | -------------------------------------- | +| `for` | `array` | alias of `addrs` | +| `by` | `string` | [RFC 7239 Section 5.1](http://tools.ietf.org/html/rfc7239#section-5.1) compatible result | `null` | +| `addrs` | `array` | [RFC 7239 Section 5.2](http://tools.ietf.org/html/rfc7239#section-5.2) compatible result | `[request.connection.remoteAddress]` | +| `host` | `string` | [RFC 7239 Section 5.3](http://tools.ietf.org/html/rfc7239#section-5.3) compatible result | `request.headers.host` | +| `proto` | `string` | [RFC 7239 Section 5.4](http://tools.ietf.org/html/rfc7239#section-5.4) compatible result | `request.connection.encrypted` | +| `port` | `string` | the last known port used by the client/proxy in chain of proxies | `request.connection.remotePort` | +| `ports` | `array` | ordered list of known ports in the chain of proxies | `[request.connection.remotePort]` | + +###### Notes + +- `forwarded().addrs` & `forwarded().ports`: return arrays of the addresses & ports respectively, including the socket address/port for the request. In reverse order (i.e. index `0` is the socket address/port and the last index is the furthest address/port, typically the end-user). ## Testing @@ -37,6 +60,10 @@ furthest address, typically the end-user). $ npm test ``` +## TODO +- [ ] process [`Via`](http://tools.ietf.org/html/rfc7230#section-5.7.1) header +- [ ] extract ports from [`Forwarded`](http://tools.ietf.org/html/rfc7239#section-5.2) header: `Forwarded: for=x.x.x.x:yyyy` + ## License [MIT](LICENSE) diff --git a/index.js b/index.js index 2f5c340..c99ac4b 100644 --- a/index.js +++ b/index.js @@ -1,35 +1,64 @@ -/*! - * forwarded - * Copyright(c) 2014 Douglas Christopher Wilson - * MIT Licensed - */ +'use strict' -/** - * Module exports. - */ - -module.exports = forwarded +var Processor = require('./lib/processor') +var schemas = require('./lib/schemas') +var util = require('util') /** * Get all addresses in the request, using the `X-Forwarded-For` header. * - * @param {Object} req + * @param {http.IncomingMessage} req * @api public */ -function forwarded(req) { +module.exports = function forwarded (req, options) { + var opts = util._extend({ + // default to only common + standard + // array order matters here + schemas: [ + 'xff', + 'rfc7239' + ] + }, options) + + // consistent case + opts.schemas.map(Function.prototype.call, String.prototype.toLowerCase) + if (!req) { throw new TypeError('argument req is required') } - // simple header parsing - var proxyAddrs = (req.headers['x-forwarded-for'] || '') - .split(/ *, */) - .filter(Boolean) - .reverse() - var socketAddr = req.connection.remoteAddress - var addrs = [socketAddr].concat(proxyAddrs) + // start with default values from socket connection + var forwarded = { + addrs: [req.connection.remoteAddress], + by: null, + host: req.headers && req.headers.host ? req.headers.host : undefined, + port: req.connection.remotePort.toString(), + ports: [req.connection.remotePort.toString()], + proto: req.connection.encrypted ? 'https' : 'http' + } + + // alias "for" to keep with RFC7239 naming + forwarded.for = forwarded.addrs + + return opts.schemas + // check if schemas exist + .filter(function (name) { + return schemas[name] + }) + + // process schemas + .reduce(function (forwarded, name) { + var result = new Processor(req, schemas[name]) - // return all addresses - return addrs + // update forwarded object + return { + addrs: forwarded.addrs.concat(result.addrs).filter(Boolean), + by: result.by ? result.by : forwarded.by, + host: result.host ? result.host : forwarded.host, + port: result.port ? result.port : forwarded.port, + ports: forwarded.ports.concat([result.port]).filter(Boolean), + proto: result.proto ? result.proto : forwarded.proto + } + }, forwarded) } diff --git a/lib/processor.js b/lib/processor.js new file mode 100644 index 0000000..32e5110 --- /dev/null +++ b/lib/processor.js @@ -0,0 +1,82 @@ +'use strict' + +var debug = require('debug')('forwarded') + +function processor (req, schema) { + if (typeof schema === 'function') { + return schema(req) + } + + this.req = req + this.schema = schema + + return { + addrs: this.addrs(), + host: this.host(), + port: this.port(), + proto: this.protocol() + } +} + +processor.prototype.host = function () { + if (this.schema.host && this.req.headers[this.schema.host]) { + var value = this.req.headers[this.schema.host] + + debug('found header [%s = %s]', this.schema.host, value) + + return this.req.headers[this.schema.host] + } +} + +processor.prototype.port = function () { + if (this.schema.port && this.req.headers[this.schema.port]) { + var value = this.req.headers[this.schema.port] + + debug('found header [%s = %s]', this.schema.port, value) + + return value + } +} + +processor.prototype.addrs = function () { + if (this.schema.addrs && this.req.headers[this.schema.addrs]) { + var value = this.req.headers[this.schema.addrs] + + debug('found header [%s = %s]', this.schema.addrs, value) + + return value + .split(/ *, */) + .filter(Boolean) + .reverse() + } +} + +processor.prototype.protocol = function () { + // utility + function runner (obj) { + // multiple possible values + if (Array.isArray(obj)) { + // get the last succesful item + return obj.map(runner.bind(this)).reduce(function (prev, curr) { + return curr ? curr : prev + }) + } + + if (typeof obj === 'function') { + return obj.call(this, this.req) + } + + if (this.req.headers[obj]) { + debug('found header [%s = %s]', obj, this.req.headers[obj]) + + return this.req.headers[obj] + } + } + + // actually run + if (this.schema.proto) { + return runner.call(this, this.schema.proto) + } +} + +module.exports = processor diff --git a/lib/schemas/cloudflare.js b/lib/schemas/cloudflare.js new file mode 100644 index 0000000..8227cac --- /dev/null +++ b/lib/schemas/cloudflare.js @@ -0,0 +1,19 @@ +'use strict' + +var debug = require('debug')('forwarded') + +function isSecure (req) { + try { + var cf = JSON.parse(req.headers['cf-visitor']) + return cf.scheme !== undefined + } catch (e) { + debug('could not parse "cf-visitor" header: %s', req.headers['cf-visitor']) + } + + return false +} + +module.exports = { + addrs: 'cf-connecting-ip', + proto: isSecure +} diff --git a/lib/schemas/fastly.js b/lib/schemas/fastly.js new file mode 100644 index 0000000..30a8140 --- /dev/null +++ b/lib/schemas/fastly.js @@ -0,0 +1,11 @@ +'use strict' + +function isSecure (req) { + return ~['1', 'true'].indexOf(req.headers['fastly-ssl']) +} + +module.exports = { + addrs: 'fastly-client-ip', + port: 'fastly-client-port', + proto: isSecure +} diff --git a/lib/schemas/index.js b/lib/schemas/index.js new file mode 100644 index 0000000..06a03ac --- /dev/null +++ b/lib/schemas/index.js @@ -0,0 +1,12 @@ +'use strict' + +module.exports = { + cloudflare: require('./cloudflare'), + fastly: require('./fastly'), + microsoft: require('./microsoft'), + nginx: require('./nginx'), + rackspace: require('./rackspace'), + rfc7239: require('./rfc7239'), + xff: require('./xff'), + zscaler: require('./zscaler') +} diff --git a/lib/schemas/microsoft.js b/lib/schemas/microsoft.js new file mode 100644 index 0000000..151824d --- /dev/null +++ b/lib/schemas/microsoft.js @@ -0,0 +1,7 @@ +'use strict' + +module.exports = { + proto: function isSecure (req) { + return req.headers['front-end-https'] === 'on' + } +} diff --git a/lib/schemas/nginx.js b/lib/schemas/nginx.js new file mode 100644 index 0000000..ef04873 --- /dev/null +++ b/lib/schemas/nginx.js @@ -0,0 +1,7 @@ +'use strict' + +module.exports = { + addrs: 'x-real-ip', + port: 'x-real-port', + proto: ['x-real-proto', 'x-url-scheme'] +} diff --git a/lib/schemas/rackspace.js b/lib/schemas/rackspace.js new file mode 100644 index 0000000..99ee065 --- /dev/null +++ b/lib/schemas/rackspace.js @@ -0,0 +1,5 @@ +'use strict' + +module.exports = { + addrs: 'x-cluster-client-ip' +} diff --git a/lib/schemas/rfc7239.js b/lib/schemas/rfc7239.js new file mode 100644 index 0000000..44d9600 --- /dev/null +++ b/lib/schemas/rfc7239.js @@ -0,0 +1,54 @@ +'use strict' + +function splitMap (string, separator, cb) { + // split into elements + return string.split(separator) + .filter(Boolean) + .forEach(cb) +} + +function parsePart (part) { + var pair = part.split(/ *= */) + + var name = pair[0].toLowerCase() + var value = pair[1] + + if (value) { + switch (typeof this[name]) { + case 'undefined': + this[name] = value + break + + // convert to array + case 'string': + this[name] = [this[name], value] + break + + // append to array + case 'object': + this[name].push(value) + break + } + } +} + +var ELEMENT_SEPARATOR = / *; */ +var PART_SEPARATOR = / *, */ + +module.exports = function (req) { + var forwarded = {} + var header = req.headers.forwarded + + if (!header) { + return forwarded + } + + splitMap(header, ELEMENT_SEPARATOR, function parseElement (el) { + return splitMap(el, PART_SEPARATOR, parsePart.bind(forwarded)) + }) + + // re-mapping + forwarded.addrs = forwarded.for + + return forwarded +} diff --git a/lib/schemas/xff.js b/lib/schemas/xff.js new file mode 100644 index 0000000..d551e39 --- /dev/null +++ b/lib/schemas/xff.js @@ -0,0 +1,13 @@ +'use strict' + +function isSecure (req) { + return req.headers['x-forwarded-ssl'] === 'on' +} + +module.exports = { + addrs: 'x-forwarded-for', + host: 'x-forwarded-host', + port: 'x-forwarded-port', + proto: ['x-forwarded-proto', 'x-forwarded-protocol', isSecure], + protoFn: isSecure +} diff --git a/lib/schemas/zscaler.js b/lib/schemas/zscaler.js new file mode 100644 index 0000000..5fc6807 --- /dev/null +++ b/lib/schemas/zscaler.js @@ -0,0 +1,8 @@ +'use strict' + +module.exports = { + addrs: 'z-forwarded-for', + host: 'z-forwarded-host', + port: 'z-forwarded-port', + proto: ['z-forwarded-proto', 'z-forwarded-protocol'] +} diff --git a/package.json b/package.json index d447523..32a0a7a 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,59 @@ { "name": "forwarded", "description": "Parse HTTP X-Forwarded-For header", - "version": "0.1.0", + "version": "1.0.0-rc.1", "contributors": [ + "Ahmad Nassri ", "Douglas Christopher Wilson " ], "license": "MIT", "keywords": [ - "x-forwarded-for", + "cf-connecting-ip", + "cf-visitor", + "fastly-client-ip", + "fastly-client-port", + "fastly-ssl", + "forwarded", + "front-end-https", "http", - "req" + "https", + "protocol", + "x-cluster-client-ip", + "x-forwarded-for", + "x-forwarded-host", + "x-forwarded-port", + "x-forwarded-proto", + "x-forwarded-protocol", + "x-forwarded-ssl", + "x-real-ip", + "x-real-port", + "x-real-proto", + "x-url-scheme", + "z-forwarded-for", + "z-forwarded-host", + "z-forwarded-port", + "z-forwarded-proto", + "z-forwarded-protocol" ], "repository": "jshttp/forwarded", "devDependencies": { - "istanbul": "0.3.2", - "mocha": "~1.21.4" + "debug": "^2.2.0", + "istanbul": "0.3.14", + "mocha": "~2.2.5" }, "files": [ "LICENSE", "HISTORY.md", "README.md", - "index.js" + "index.js", + "lib" ], "engines": { "node": ">= 0.6" }, "scripts": { - "test": "mocha --reporter spec --bail --check-leaks test/", - "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test": "mocha --recursive --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --recursive --reporter dot --check-leaks test/", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" } } diff --git a/test/helper.js b/test/helper.js new file mode 100644 index 0000000..2af814d --- /dev/null +++ b/test/helper.js @@ -0,0 +1,13 @@ +'use strict' + +module.exports = function createRequest (headers, connection) { + return { + connection: connection || { + encrypted: true, + remoteAddress: '127.0.0.1', + remotePort: 5000 + }, + + headers: headers || {} + } +} diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..5a1d0f8 --- /dev/null +++ b/test/index.js @@ -0,0 +1,69 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var forwarded = require('..') +var request = require('./helper') + +describe('forwarded(req)', function () { + it('should require http.IncomingMessage', function () { + assert.throws(forwarded.bind(null), 'argument req is required') + }) + + it('should get defaults', function () { + var result = forwarded(request({ + 'host': 'mockbin.com' + })) + + assert.equal(result.host, 'mockbin.com') + assert.equal(result.port, '5000') + assert.deepEqual(result.addrs, ['127.0.0.1']) + assert.deepEqual(result.proto, 'https') + }) + + it('should set proto=http when connection.encrypted=false', function () { + var result = forwarded(request({}, { + encrypted: false, + remotePort: 5000 + })) + + assert.deepEqual(result.proto, 'http') + }) + + it('should skip blank entries', function () { + var result = forwarded(request({ + 'x-forwarded-for': '10.0.0.2,, 10.0.0.1' + })) + + assert.deepEqual(result.addrs, ['127.0.0.1', '10.0.0.1', '10.0.0.2']) + }) + + it('should process all schemas in ordered sequence', function () { + var options = { + schemas: [ + 'fakeschema', + 'cloudflare', + 'fastly', + 'microsoft', + 'nginx', + 'rackspace', + 'xff', + 'zscaler', + 'rfc7239' + ] + } + + var result = forwarded(request({ + 'forwarded': 'for=0.0.0.7', + 'cf-connecting-ip': '0.0.0.1', + 'fastly-client-ip': '0.0.0.2', + 'x-real-ip': '0.0.0.3', + 'x-cluster-client-ip': '0.0.0.4', + 'x-forwarded-for': '0.0.0.5', + 'z-forwarded-for': '0.0.0.6' + }), options) + + assert.deepEqual(result.addrs, ['127.0.0.1', '0.0.0.1', '0.0.0.2', '0.0.0.3', '0.0.0.4', '0.0.0.5', '0.0.0.6', '0.0.0.7']) + }) +}) diff --git a/test/schemas/cloudflare.js b/test/schemas/cloudflare.js new file mode 100644 index 0000000..6e828d7 --- /dev/null +++ b/test/schemas/cloudflare.js @@ -0,0 +1,36 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var forwarded = require('../..') +var request = require('../helper') +var schemas = require('../../lib/schemas') + +var options = { + schemas: ['cloudflare'] +} + +describe('cloudflare', function () { + it('should parse [cf-connecting-ip]', function () { + var result = forwarded(request({ + 'cf-connecting-ip': '10.10.10.1' + }), options) + + assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) + }) + + it('should parse [cf-visitor]', function () { + var req = request({ + 'cf-visitor': '{"scheme": "https"}' + }) + + assert.ok(schemas.cloudflare.proto(req)) + + req = request({ + 'cf-visitor': '{malformed}' + }) + + assert.ok(!schemas.cloudflare.proto(req)) + }) +}) diff --git a/test/schemas/fastly.js b/test/schemas/fastly.js new file mode 100644 index 0000000..81414db --- /dev/null +++ b/test/schemas/fastly.js @@ -0,0 +1,38 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var forwarded = require('../..') +var request = require('../helper') +var schemas = require('../../lib/schemas') + +var options = { + schemas: ['fastly'] +} + +describe('fastly', function () { + it('should parse [fastly-client-ip]', function () { + var result = forwarded(request({ + 'fastly-client-ip': '10.10.10.1' + }), options) + + assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) + }) + + it('should parse [fastly-client-port]', function () { + var result = forwarded(request({ + 'fastly-client-port': '9000' + }), options) + + assert.deepEqual(result.port, '9000') + }) + + it('should parse [fastly-ssl]', function () { + var req = request({ + 'fastly-ssl': '1' + }) + + assert.ok(schemas.fastly.proto(req)) + }) +}) diff --git a/test/schemas/microsoft.js b/test/schemas/microsoft.js new file mode 100644 index 0000000..62385b8 --- /dev/null +++ b/test/schemas/microsoft.js @@ -0,0 +1,17 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var request = require('../helper') +var schemas = require('../../lib/schemas') + +describe('microsoft', function () { + it('should detect https protocol', function () { + var req = request({ + 'front-end-https': 'on' + }) + + assert.ok(schemas.microsoft.proto(req)) + }) +}) diff --git a/test/schemas/nginx.js b/test/schemas/nginx.js new file mode 100644 index 0000000..6db6a05 --- /dev/null +++ b/test/schemas/nginx.js @@ -0,0 +1,45 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var forwarded = require('../..') +var request = require('../helper') + +var options = { + schemas: ['nginx'] +} + +describe('nginx', function () { + it('should parse [x-real-ip]', function () { + var result = forwarded(request({ + 'x-real-ip': '10.10.10.1' + }), options) + + assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) + }) + + it('should parse [x-real-port]', function () { + var result = forwarded(request({ + 'x-real-port': '9000' + }), options) + + assert.deepEqual(result.port, '9000') + }) + + it('should parse [x-real-proto]', function () { + var result = forwarded(request({ + 'x-real-proto': 'https' + }), options) + + assert.deepEqual(result.proto, 'https') + }) + + it('should parse [x-url-scheme]', function () { + var result = forwarded(request({ + 'x-url-scheme': 'https' + }), options) + + assert.deepEqual(result.proto, 'https') + }) +}) diff --git a/test/schemas/rackspace.js b/test/schemas/rackspace.js new file mode 100644 index 0000000..63ba7bb --- /dev/null +++ b/test/schemas/rackspace.js @@ -0,0 +1,21 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var forwarded = require('../..') +var request = require('../helper') + +var options = { + schemas: ['rackspace'] +} + +describe('rackspace', function () { + it('should parse [x-cluster-client-ip]', function () { + var result = forwarded(request({ + 'x-cluster-client-ip': '10.10.10.1' + }), options) + + assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) + }) +}) diff --git a/test/schemas/rfc7239.js b/test/schemas/rfc7239.js new file mode 100644 index 0000000..6d6d054 --- /dev/null +++ b/test/schemas/rfc7239.js @@ -0,0 +1,35 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var forwarded = require('../..') +var request = require('../helper') +var schemas = require('../../lib/schemas') + +var options = { + schemas: ['rfc7239'] +} + +describe('rfc7239', function () { + it('should return empty object', function () { + var req = request() + + assert.deepEqual(schemas.rfc7239(req), {}) + }) + + it('should parse and process Forwarded header', function () { + var req = request({ + 'forwarded': 'host= mockbin.com, for=0.0.0.1, for=0.0.0.2, for=private, for=1::8, for=; port= 9000; proto=https; by=0.0.0.3' + }) + + assert.deepEqual(forwarded(req, options), { + addrs: ['127.0.0.1', '0.0.0.1', '0.0.0.2', 'private', '1::8'], + by: '0.0.0.3', + port: '9000', + ports: ['5000', '9000'], + host: 'mockbin.com', + proto: 'https' + }) + }) +}) diff --git a/test/schemas/xff.js b/test/schemas/xff.js new file mode 100644 index 0000000..cfbb4ab --- /dev/null +++ b/test/schemas/xff.js @@ -0,0 +1,62 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var forwarded = require('../..') +var request = require('../helper') +var schemas = require('../../lib/schemas') + +var options = { + schemas: ['xff'] +} + +describe('xff', function () { + it('should parse [x-forwarded-for]', function () { + var result = forwarded(request({ + 'x-forwarded-for': '10.10.10.1' + }), options) + + assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) + }) + + it('should parse [x-forwarded-host]', function () { + var result = forwarded(request({ + 'x-forwarded-host': 'mockbin.com' + }), options) + + assert.deepEqual(result.host, 'mockbin.com') + }) + + it('should parse [x-forwarded-port]', function () { + var result = forwarded(request({ + 'x-forwarded-port': '9000' + }), options) + + assert.deepEqual(result.port, '9000') + }) + + it('should parse [x-forwarded-proto]', function () { + var result = forwarded(request({ + 'x-forwarded-proto': 'https' + }), options) + + assert.deepEqual(result.proto, 'https') + }) + + it('should parse [x-forwarded-protocol]', function () { + var result = forwarded(request({ + 'x-forwarded-protocol': 'https' + }), options) + + assert.deepEqual(result.proto, 'https') + }) + + it('should parse [x-forwarded-ssl]', function () { + var req = request({ + 'x-forwarded-ssl': 'on' + }) + + assert.ok(schemas.xff.protoFn(req)) + }) +}) diff --git a/test/schemas/zscaler.js b/test/schemas/zscaler.js new file mode 100644 index 0000000..67265de --- /dev/null +++ b/test/schemas/zscaler.js @@ -0,0 +1,53 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var forwarded = require('../..') +var request = require('../helper') + +var options = { + schemas: ['zscaler'] +} + +describe('zscaler', function () { + it('should parse [z-forwarded-for]', function () { + var result = forwarded(request({ + 'z-forwarded-for': '10.10.10.1' + }), options) + + assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) + }) + + it('should parse [z-forwarded-host]', function () { + var result = forwarded(request({ + 'z-forwarded-host': 'mockbin.com' + }), options) + + assert.deepEqual(result.host, 'mockbin.com') + }) + + it('should parse [z-forwarded-port]', function () { + var result = forwarded(request({ + 'z-forwarded-port': '9000' + }), options) + + assert.deepEqual(result.port, '9000') + }) + + it('should parse [z-forwarded-proto]', function () { + var result = forwarded(request({ + 'z-forwarded-proto': 'https' + }), options) + + assert.deepEqual(result.proto, 'https') + }) + + it('should parse [z-forwarded-protocol]', function () { + var result = forwarded(request({ + 'z-forwarded-protocol': 'https' + }), options) + + assert.deepEqual(result.proto, 'https') + }) +}) diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 3c2dc19..0000000 --- a/test/test.js +++ /dev/null @@ -1,37 +0,0 @@ - -var assert = require('assert') -var forwarded = require('..') - -describe('forwarded(req)', function () { - it('should require req', function () { - assert.throws(forwarded.bind(null), /argument req.*required/) - }) - - it('should work with X-Forwarded-For header', function () { - var req = createReq('127.0.0.1') - assert.deepEqual(forwarded(req), ['127.0.0.1']) - }) - - it('should include entries from X-Forwarded-For', function () { - var req = createReq('127.0.0.1', { - 'x-forwarded-for': '10.0.0.2, 10.0.0.1' - }) - assert.deepEqual(forwarded(req), ['127.0.0.1', '10.0.0.1', '10.0.0.2']) - }) - - it('should skip blank entries', function () { - var req = createReq('127.0.0.1', { - 'x-forwarded-for': '10.0.0.2,, 10.0.0.1' - }) - assert.deepEqual(forwarded(req), ['127.0.0.1', '10.0.0.1', '10.0.0.2']) - }) -}) - -function createReq(socketAddr, headers) { - return { - connection: { - remoteAddress: socketAddr - }, - headers: headers || {} - }; -} From 7416d5a2c6dba3d452a83710330e3c5bb3551c59 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 03:44:00 -0400 Subject: [PATCH 02/33] resolving node <=0.8 issue ("debug" target not found) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 32a0a7a..c3ab1b2 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ ], "repository": "jshttp/forwarded", "devDependencies": { - "debug": "^2.2.0", + "debug": "~2.2.0", "istanbul": "0.3.14", "mocha": "~2.2.5" }, From 0b8bbc398ff0b53355ca1f5c1c229183061c6f5a Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 03:46:36 -0400 Subject: [PATCH 03/33] add node 0.12 to travis matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f034de0..4005a03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ node_js: - "0.8" - "0.10" - "0.11" + - "0.12" matrix: allow_failures: - node_js: "0.11" From e9badf71fb620b44c512cd1087ec89ee13b01f66 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 03:51:41 -0400 Subject: [PATCH 04/33] use node 0.6 safe versions of mocha + istanbul --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c3ab1b2..9ca6eab 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "repository": "jshttp/forwarded", "devDependencies": { "debug": "~2.2.0", - "istanbul": "0.3.14", - "mocha": "~2.2.5" + "istanbul": "0.3.9", + "mocha": "~1.21.5" }, "files": [ "LICENSE", @@ -54,6 +54,6 @@ "scripts": { "test": "mocha --recursive --reporter spec --bail --check-leaks test/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --recursive --reporter dot --check-leaks test/", - "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --recursive --reporter spec --check-leaks test/" } } From 5f36bf98b4aeb03d92f9c3394b29ed11b446219d Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 03:57:24 -0400 Subject: [PATCH 05/33] remove dependency on util._extend (node 0.6 support) --- index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index c99ac4b..e370f40 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,6 @@ var Processor = require('./lib/processor') var schemas = require('./lib/schemas') -var util = require('util') /** * Get all addresses in the request, using the `X-Forwarded-For` header. @@ -12,14 +11,16 @@ var util = require('util') */ module.exports = function forwarded (req, options) { - var opts = util._extend({ + options = options || {} + + var opts = { // default to only common + standard // array order matters here - schemas: [ + schemas: options.schemas || [ 'xff', 'rfc7239' ] - }, options) + } // consistent case opts.schemas.map(Function.prototype.call, String.prototype.toLowerCase) From 3e90938bf9fd143a995171e239dcd27d172c9af9 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 02:51:16 -0700 Subject: [PATCH 06/33] fix gh markdown rendering bug --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64f5f00..25fac41 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] -Parse *Forwarded* HTTP headers, using the standard: [RFC 7239](https://tools.ietf.org/html/rfc7239) *(Forwarded HTTP Extension)*, as well as commonly used none-standard headers: *e.g. `X-Forwarded-*`, `X-Real-*`, etc ...* +Parse *Forwarded* HTTP headers, using the standard: [RFC 7239](https://tools.ietf.org/html/rfc7239) *(Forwarded HTTP Extension)*, as well as commonly used none-standard headers (e.g. `X-Forwarded-*`, `X-Real-*`, etc ...) review [`schemas` folder](lib/schemas) for a full list of supported headers schemas. From 0962a47c60f2b5b1eb1d1ff2d65cf00282572c96 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 18:17:29 -0400 Subject: [PATCH 07/33] restore copyright notice --- index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/index.js b/index.js index e370f40..0ddbe9f 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,13 @@ +/*! + * forwarded + * Copyright(c) 2014 Douglas Christopher Wilson + * MIT Licensed +*/ + +/** + * Module exports. + */ + 'use strict' var Processor = require('./lib/processor') From 560dfa61e7271fc56bf3e2f52baeaa3346e5ced9 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 18:45:28 -0400 Subject: [PATCH 08/33] throw error on invalid schema name --- index.js | 10 +++++++--- test/index.js | 13 +++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 0ddbe9f..0215e39 100644 --- a/index.js +++ b/index.js @@ -54,13 +54,17 @@ module.exports = function forwarded (req, options) { return opts.schemas // check if schemas exist - .filter(function (name) { + .map(function (name) { + if (!schemas[name]) { + throw new Error('invalid schema') + } + return schemas[name] }) // process schemas - .reduce(function (forwarded, name) { - var result = new Processor(req, schemas[name]) + .reduce(function (forwarded, schema) { + var result = new Processor(req, schema) // update forwarded object return { diff --git a/test/index.js b/test/index.js index 5a1d0f8..49c7db8 100644 --- a/test/index.js +++ b/test/index.js @@ -8,7 +8,17 @@ var request = require('./helper') describe('forwarded(req)', function () { it('should require http.IncomingMessage', function () { - assert.throws(forwarded.bind(null), 'argument req is required') + assert.throws(forwarded.bind(null), TypeError) + }) + + it('should fail with invalid schemas', function () { + var options = { + schemas: [ + 'XFFFF' + ] + } + + assert.throws(function () { forwarded(request(), options) }, Error) }) it('should get defaults', function () { @@ -42,7 +52,6 @@ describe('forwarded(req)', function () { it('should process all schemas in ordered sequence', function () { var options = { schemas: [ - 'fakeschema', 'cloudflare', 'fastly', 'microsoft', From 34ad6cc1f0fc45fb2f15865dbee739c6d93e3850 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 18:45:56 -0400 Subject: [PATCH 09/33] lowecase schema names + test --- index.js | 2 +- test/index.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 0215e39..aad7ffd 100644 --- a/index.js +++ b/index.js @@ -33,7 +33,7 @@ module.exports = function forwarded (req, options) { } // consistent case - opts.schemas.map(Function.prototype.call, String.prototype.toLowerCase) + opts.schemas = opts.schemas.map(Function.prototype.call, String.prototype.toLowerCase) if (!req) { throw new TypeError('argument req is required') diff --git a/test/index.js b/test/index.js index 49c7db8..af185f7 100644 --- a/test/index.js +++ b/test/index.js @@ -21,6 +21,20 @@ describe('forwarded(req)', function () { assert.throws(function () { forwarded(request(), options) }, Error) }) + it('should convert schema names to lowercase', function () { + var options = { + schemas: [ + 'XFF' + ] + } + + var result = forwarded(request({ + 'x-forwarded-for': '10.0.0.1' + }), options) + + assert.deepEqual(result.addrs, ['127.0.0.1', '10.0.0.1']) + }) + it('should get defaults', function () { var result = forwarded(request({ 'host': 'mockbin.com' From 4e7e8f41e1058d4bf07efbd700822d62b3aef823 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 18:48:53 -0400 Subject: [PATCH 10/33] debug should be under dependencies, rather than devDependencies --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ca6eab..fec75d7 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,10 @@ "z-forwarded-protocol" ], "repository": "jshttp/forwarded", + "dependencies": { + "debug": "~2.2.0" + }, "devDependencies": { - "debug": "~2.2.0", "istanbul": "0.3.9", "mocha": "~1.21.5" }, From 2d937df832b81692c4339886d359243a92d9670c Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 18:56:51 -0400 Subject: [PATCH 11/33] On a disconnected socket, req.connection.remotePort is undefined --- index.js | 9 +++++++-- test/index.js | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index aad7ffd..49bf2d7 100644 --- a/index.js +++ b/index.js @@ -44,11 +44,16 @@ module.exports = function forwarded (req, options) { addrs: [req.connection.remoteAddress], by: null, host: req.headers && req.headers.host ? req.headers.host : undefined, - port: req.connection.remotePort.toString(), - ports: [req.connection.remotePort.toString()], + port: req.connection.remotePort ? req.connection.remotePort.toString() : undefined, + ports: [], proto: req.connection.encrypted ? 'https' : 'http' } + // add default port to ports array if present + if (forwarded.port) { + forwarded.ports.push(forwarded.port) + } + // alias "for" to keep with RFC7239 naming forwarded.for = forwarded.addrs diff --git a/test/index.js b/test/index.js index af185f7..9ab9ce2 100644 --- a/test/index.js +++ b/test/index.js @@ -21,6 +21,14 @@ describe('forwarded(req)', function () { assert.throws(function () { forwarded(request(), options) }, Error) }) + it('should not throw an error on a disconnected socket connection', function () { + var req = request({}, { + remoteAddress: '127.0.0.1' + }) + + assert.doesNotThrow(function () { forwarded(req) }, Error) + }) + it('should convert schema names to lowercase', function () { var options = { schemas: [ From 1520eae8c904e00cfb5526a7d677d027dbbe95d5 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 19:12:21 -0400 Subject: [PATCH 12/33] fix: address ordering in rfc2739 processor, fix: ensure processed 'for' property is an array --- index.js | 5 +++++ lib/schemas/rfc7239.js | 7 ++++++- test/index.js | 2 +- test/schemas/rfc7239.js | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 49bf2d7..f5af539 100644 --- a/index.js +++ b/index.js @@ -71,6 +71,11 @@ module.exports = function forwarded (req, options) { .reduce(function (forwarded, schema) { var result = new Processor(req, schema) + // ensure reverse order of addresses + if (result.addrs) { + result.addrs.reverse() + } + // update forwarded object return { addrs: forwarded.addrs.concat(result.addrs).filter(Boolean), diff --git a/lib/schemas/rfc7239.js b/lib/schemas/rfc7239.js index 44d9600..cfda0b3 100644 --- a/lib/schemas/rfc7239.js +++ b/lib/schemas/rfc7239.js @@ -47,7 +47,12 @@ module.exports = function (req) { return splitMap(el, PART_SEPARATOR, parsePart.bind(forwarded)) }) - // re-mapping + // ensure result is an array + if (forwarded.for && !Array.isArray(forwarded.for)) { + forwarded.for = [forwarded.for] + } + + // create alias forwarded.addrs = forwarded.for return forwarded diff --git a/test/index.js b/test/index.js index 9ab9ce2..861555c 100644 --- a/test/index.js +++ b/test/index.js @@ -65,7 +65,7 @@ describe('forwarded(req)', function () { it('should skip blank entries', function () { var result = forwarded(request({ - 'x-forwarded-for': '10.0.0.2,, 10.0.0.1' + 'forwarded': 'for=10.0.0.2,for,for=,for=10.0.0.1' })) assert.deepEqual(result.addrs, ['127.0.0.1', '10.0.0.1', '10.0.0.2']) diff --git a/test/schemas/rfc7239.js b/test/schemas/rfc7239.js index 6d6d054..f4ad20c 100644 --- a/test/schemas/rfc7239.js +++ b/test/schemas/rfc7239.js @@ -24,7 +24,7 @@ describe('rfc7239', function () { }) assert.deepEqual(forwarded(req, options), { - addrs: ['127.0.0.1', '0.0.0.1', '0.0.0.2', 'private', '1::8'], + addrs: ['127.0.0.1', '1::8', 'private', '0.0.0.2', '0.0.0.1'], by: '0.0.0.3', port: '9000', ports: ['5000', '9000'], From 8694d1c2e651eb7d64666224c96e91589278c7c5 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 19:13:39 -0400 Subject: [PATCH 13/33] default to rfc7239 schema --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index f5af539..9f45a48 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,6 @@ module.exports = function forwarded (req, options) { // default to only common + standard // array order matters here schemas: options.schemas || [ - 'xff', 'rfc7239' ] } From 5e778584b05a296d64a54a8e2c0378e8410a5e1c Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 19:23:57 -0400 Subject: [PATCH 14/33] fix: cloudflare https detection --- lib/schemas/cloudflare.js | 2 +- test/schemas/cloudflare.js | 34 +++++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/schemas/cloudflare.js b/lib/schemas/cloudflare.js index 8227cac..cda5e32 100644 --- a/lib/schemas/cloudflare.js +++ b/lib/schemas/cloudflare.js @@ -5,7 +5,7 @@ var debug = require('debug')('forwarded') function isSecure (req) { try { var cf = JSON.parse(req.headers['cf-visitor']) - return cf.scheme !== undefined + return !~[undefined, 'http'].indexOf(cf.scheme) } catch (e) { debug('could not parse "cf-visitor" header: %s', req.headers['cf-visitor']) } diff --git a/test/schemas/cloudflare.js b/test/schemas/cloudflare.js index 6e828d7..cc4f638 100644 --- a/test/schemas/cloudflare.js +++ b/test/schemas/cloudflare.js @@ -20,17 +20,37 @@ describe('cloudflare', function () { assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) }) - it('should parse [cf-visitor]', function () { - var req = request({ - 'cf-visitor': '{"scheme": "https"}' + describe('should parse [cf-visitor]', function () { + it('{"scheme": "https"}', function () { + var req = request({ + 'cf-visitor': '{"scheme": "https"}' + }) + + assert.ok(schemas.cloudflare.proto(req)) + }) + + it('{"scheme": "http"}', function () { + var req = request({ + 'cf-visitor': '{"scheme": "http"}' + }) + + assert.ok(!schemas.cloudflare.proto(req)) }) - assert.ok(schemas.cloudflare.proto(req)) + it('{}', function () { + var req = request({ + 'cf-visitor': '{}' + }) - req = request({ - 'cf-visitor': '{malformed}' + assert.ok(!schemas.cloudflare.proto(req)) }) - assert.ok(!schemas.cloudflare.proto(req)) + it('{}', function () { + var req = request({ + 'cf-visitor': '{malformed}' + }) + + assert.ok(!schemas.cloudflare.proto(req)) + }) }) }) From cbab68789c94ab654b56c6ff730dd7407aa79288 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 19:25:48 -0400 Subject: [PATCH 15/33] fix: fastly ssl header existance counts as HTTPS --- lib/schemas/fastly.js | 2 +- test/schemas/fastly.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/schemas/fastly.js b/lib/schemas/fastly.js index 30a8140..33f7dbd 100644 --- a/lib/schemas/fastly.js +++ b/lib/schemas/fastly.js @@ -1,7 +1,7 @@ 'use strict' function isSecure (req) { - return ~['1', 'true'].indexOf(req.headers['fastly-ssl']) + return req.headers['fastly-ssl'] !== undefined } module.exports = { diff --git a/test/schemas/fastly.js b/test/schemas/fastly.js index 81414db..0e1a292 100644 --- a/test/schemas/fastly.js +++ b/test/schemas/fastly.js @@ -34,5 +34,11 @@ describe('fastly', function () { }) assert.ok(schemas.fastly.proto(req)) + + req = request({ + 'fastly-ssl': undefined + }) + + assert.ok(!schemas.fastly.proto(req)) }) }) From 7a3cb41e03bbe2f64659ba74abfc790192f39e66 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 20:09:03 -0400 Subject: [PATCH 16/33] add author to LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index b7dce6c..714f163 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ (The MIT License) -Copyright (c) 2014 Douglas Christopher Wilson +Copyright (c) 2014 Douglas Christopher Wilson, Ahmad Nassri Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the From f39a24f466ca6067330127b0294be3956c32f403 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 20:57:27 -0400 Subject: [PATCH 17/33] update author info --- index.js | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index cdc0055..c58de40 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ /*! * forwarded * Copyright(c) 2014 Douglas Christopher Wilson + * Copyright(c) 2015 Ahmad Nassri * MIT Licensed */ diff --git a/package.json b/package.json index 0be5a66..580128b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Parse HTTP X-Forwarded-For header", "version": "1.0.0-rc.1", "contributors": [ - "Ahmad Nassri ", + "Ahmad Nassri (https://www.ahmadnassri.com)", "Douglas Christopher Wilson " ], "license": "MIT", From 33633288d9ebf15e9ce56863e3abe5f4def3d2cd Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 22:39:04 -0400 Subject: [PATCH 18/33] added test with real http server and request --- test/helper.js | 13 ---------- test/helpers.js | 52 ++++++++++++++++++++++++++++++++++++++ test/http.js | 35 +++++++++++++++++++++++++ test/index.js | 2 +- test/schemas/cloudflare.js | 2 +- test/schemas/fastly.js | 2 +- test/schemas/microsoft.js | 2 +- test/schemas/nginx.js | 2 +- test/schemas/rackspace.js | 2 +- test/schemas/rfc7239.js | 2 +- test/schemas/xff.js | 2 +- test/schemas/zscaler.js | 2 +- 12 files changed, 96 insertions(+), 22 deletions(-) delete mode 100644 test/helper.js create mode 100644 test/helpers.js create mode 100644 test/http.js diff --git a/test/helper.js b/test/helper.js deleted file mode 100644 index 2af814d..0000000 --- a/test/helper.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' - -module.exports = function createRequest (headers, connection) { - return { - connection: connection || { - encrypted: true, - remoteAddress: '127.0.0.1', - remotePort: 5000 - }, - - headers: headers || {} - } -} diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 0000000..c46c2a2 --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,52 @@ +'use strict' + +var forwarded = require('..') +var http = require('http') + +var createServer = function (done) { + var server = http.createServer(function (req, res) { + res.write(JSON.stringify(forwarded(req))) + res.end() + }) + + server.listen(0, 'localhost', function () { + done(server) + }) +} + +exports.createRequest = function (headers, done) { + createServer(function (server) { + + var options = { + headers: headers, + host: server.address().address, + port: server.address().port + } + + http.get(options, function (res) { + var body = '' + + res.on('data', function (chunk) { + body += chunk + }) + + res.on('end', function () { + server.close(function () { + done(JSON.parse(body)) + }) + }) + }) + }) +} + +exports.createRequestMock = function (headers, connection) { + return { + connection: connection || { + encrypted: true, + remoteAddress: '127.0.0.1', + remotePort: 5000 + }, + + headers: headers || {} + } +} diff --git a/test/http.js b/test/http.js new file mode 100644 index 0000000..e1ea7ae --- /dev/null +++ b/test/http.js @@ -0,0 +1,35 @@ +/* globals describe, it */ + +'use strict' + +var assert = require('assert') +var request = require('./helpers').createRequest + +describe('live server', function () { + it('should get defaults', function (done) { + request(null, function (result, address) { + assert.ok(/127\.0\.0\.1:\d+/.test(result.host)) + assert.deepEqual(result.addrs, ['127.0.0.1']) + assert.equal(result.proto, 'http') + + done() + }) + }) + + it('should get defaults', function (done) { + var headers = { + 'host': 'mockbin.com', + 'forwarded': 'host= mockbin.com, for=0.0.0.1, for=0.0.0.2, for=private, for=1::8, for=; port= 9000; proto=https; by=0.0.0.3' + } + + request(headers, function (result) { + assert.deepEqual(result.addrs, ['127.0.0.1', '1::8', 'private', '0.0.0.2', '0.0.0.1']) + assert.equal(result.by, '0.0.0.3') + assert.equal(result.host, 'mockbin.com') + assert.equal(result.proto, 'https') + + done() + }) + }) +}) + diff --git a/test/index.js b/test/index.js index 861555c..1fda101 100644 --- a/test/index.js +++ b/test/index.js @@ -4,7 +4,7 @@ var assert = require('assert') var forwarded = require('..') -var request = require('./helper') +var request = require('./helpers').createRequestMock describe('forwarded(req)', function () { it('should require http.IncomingMessage', function () { diff --git a/test/schemas/cloudflare.js b/test/schemas/cloudflare.js index cc4f638..0331af7 100644 --- a/test/schemas/cloudflare.js +++ b/test/schemas/cloudflare.js @@ -4,7 +4,7 @@ var assert = require('assert') var forwarded = require('../..') -var request = require('../helper') +var request = require('../helpers').createRequestMock var schemas = require('../../lib/schemas') var options = { diff --git a/test/schemas/fastly.js b/test/schemas/fastly.js index 0e1a292..782102e 100644 --- a/test/schemas/fastly.js +++ b/test/schemas/fastly.js @@ -4,7 +4,7 @@ var assert = require('assert') var forwarded = require('../..') -var request = require('../helper') +var request = require('../helpers').createRequestMock var schemas = require('../../lib/schemas') var options = { diff --git a/test/schemas/microsoft.js b/test/schemas/microsoft.js index 62385b8..0587f51 100644 --- a/test/schemas/microsoft.js +++ b/test/schemas/microsoft.js @@ -3,7 +3,7 @@ 'use strict' var assert = require('assert') -var request = require('../helper') +var request = require('../helpers').createRequestMock var schemas = require('../../lib/schemas') describe('microsoft', function () { diff --git a/test/schemas/nginx.js b/test/schemas/nginx.js index 6db6a05..e216aaf 100644 --- a/test/schemas/nginx.js +++ b/test/schemas/nginx.js @@ -4,7 +4,7 @@ var assert = require('assert') var forwarded = require('../..') -var request = require('../helper') +var request = require('../helpers').createRequestMock var options = { schemas: ['nginx'] diff --git a/test/schemas/rackspace.js b/test/schemas/rackspace.js index 63ba7bb..ee1b818 100644 --- a/test/schemas/rackspace.js +++ b/test/schemas/rackspace.js @@ -4,7 +4,7 @@ var assert = require('assert') var forwarded = require('../..') -var request = require('../helper') +var request = require('../helpers').createRequestMock var options = { schemas: ['rackspace'] diff --git a/test/schemas/rfc7239.js b/test/schemas/rfc7239.js index f4ad20c..9fa0f4a 100644 --- a/test/schemas/rfc7239.js +++ b/test/schemas/rfc7239.js @@ -4,7 +4,7 @@ var assert = require('assert') var forwarded = require('../..') -var request = require('../helper') +var request = require('../helpers').createRequestMock var schemas = require('../../lib/schemas') var options = { diff --git a/test/schemas/xff.js b/test/schemas/xff.js index cfbb4ab..9078429 100644 --- a/test/schemas/xff.js +++ b/test/schemas/xff.js @@ -4,7 +4,7 @@ var assert = require('assert') var forwarded = require('../..') -var request = require('../helper') +var request = require('../helpers').createRequestMock var schemas = require('../../lib/schemas') var options = { diff --git a/test/schemas/zscaler.js b/test/schemas/zscaler.js index 67265de..168376a 100644 --- a/test/schemas/zscaler.js +++ b/test/schemas/zscaler.js @@ -4,7 +4,7 @@ var assert = require('assert') var forwarded = require('../..') -var request = require('../helper') +var request = require('../helpers').createRequestMock var options = { schemas: ['zscaler'] From 553fb6f9e3c9063b190753d958222b8147315f86 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 30 May 2015 22:43:45 -0400 Subject: [PATCH 19/33] node 0.6 lacks a callback function on server.close --- test/helpers.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/helpers.js b/test/helpers.js index c46c2a2..82bb9f6 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -24,6 +24,7 @@ exports.createRequest = function (headers, done) { } http.get(options, function (res) { + var body = '' res.on('data', function (chunk) { @@ -31,7 +32,9 @@ exports.createRequest = function (headers, done) { }) res.on('end', function () { - server.close(function () { + server.close() + + server.on('close', function () { done(JSON.parse(body)) }) }) From e8f67946c79e78c333277f45d3a1fa82861a05ad Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 26 Mar 2016 16:48:35 -0400 Subject: [PATCH 20/33] chore(dependencies): update dependencies + replace debug for lighter debug-log --- lib/processor.js | 2 +- lib/schemas/cloudflare.js | 2 +- package.json | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/processor.js b/lib/processor.js index 32e5110..94dc3e7 100644 --- a/lib/processor.js +++ b/lib/processor.js @@ -1,6 +1,6 @@ 'use strict' -var debug = require('debug')('forwarded') +var debug = require('debug-log')('forwarded') function processor (req, schema) { if (typeof schema === 'function') { diff --git a/lib/schemas/cloudflare.js b/lib/schemas/cloudflare.js index cda5e32..1dfea50 100644 --- a/lib/schemas/cloudflare.js +++ b/lib/schemas/cloudflare.js @@ -1,6 +1,6 @@ 'use strict' -var debug = require('debug')('forwarded') +var debug = require('debug-log')('forwarded') function isSecure (req) { try { diff --git a/package.json b/package.json index 580128b..090adf6 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,11 @@ ], "repository": "jshttp/forwarded", "dependencies": { - "debug": "~2.2.0" + "debug-log": "^1.0.0" }, "devDependencies": { - "istanbul": "0.3.9", - "mocha": "1.21.5" + "istanbul": "0.4.2", + "mocha": "2.4.5" }, "files": [ "LICENSE", From 6ce460a2f3840510f5844c501b5b757ac7295460 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 26 Mar 2016 16:59:10 -0400 Subject: [PATCH 21/33] chore(rename): better use of header prefixes to reflect schema naming --- lib/schemas/index.js | 16 ++++++++-------- lib/schemas/{rackspace.js => x-cluster.js} | 0 lib/schemas/{xff.js => x-forwarded.js} | 0 lib/schemas/{nginx.js => x-real.js} | 0 lib/schemas/{zscaler.js => z-forwarded.js} | 0 test/index.js | 10 +++++----- test/schemas/{rackspace.js => x-cluster.js} | 4 ++-- test/schemas/{xff.js => x-forwarded.js} | 6 +++--- test/schemas/{nginx.js => x-real.js} | 4 ++-- test/schemas/{zscaler.js => z-forwarded.js} | 4 ++-- 10 files changed, 22 insertions(+), 22 deletions(-) rename lib/schemas/{rackspace.js => x-cluster.js} (100%) rename lib/schemas/{xff.js => x-forwarded.js} (100%) rename lib/schemas/{nginx.js => x-real.js} (100%) rename lib/schemas/{zscaler.js => z-forwarded.js} (100%) rename test/schemas/{rackspace.js => x-cluster.js} (86%) rename test/schemas/{xff.js => x-forwarded.js} (92%) rename test/schemas/{nginx.js => x-real.js} (94%) rename test/schemas/{zscaler.js => z-forwarded.js} (94%) diff --git a/lib/schemas/index.js b/lib/schemas/index.js index 06a03ac..3252df9 100644 --- a/lib/schemas/index.js +++ b/lib/schemas/index.js @@ -1,12 +1,12 @@ 'use strict' module.exports = { - cloudflare: require('./cloudflare'), - fastly: require('./fastly'), - microsoft: require('./microsoft'), - nginx: require('./nginx'), - rackspace: require('./rackspace'), - rfc7239: require('./rfc7239'), - xff: require('./xff'), - zscaler: require('./zscaler') + 'cloudflare': require('./cloudflare'), + 'fastly': require('./fastly'), + 'microsoft': require('./microsoft'), + 'rfc7239': require('./rfc7239'), + 'x-cluster': require('./x-cluster'), + 'x-forwarded': require('./x-forwarded'), + 'x-real': require('./x-real'), + 'z-forwarded': require('./z-forwarded') } diff --git a/lib/schemas/rackspace.js b/lib/schemas/x-cluster.js similarity index 100% rename from lib/schemas/rackspace.js rename to lib/schemas/x-cluster.js diff --git a/lib/schemas/xff.js b/lib/schemas/x-forwarded.js similarity index 100% rename from lib/schemas/xff.js rename to lib/schemas/x-forwarded.js diff --git a/lib/schemas/nginx.js b/lib/schemas/x-real.js similarity index 100% rename from lib/schemas/nginx.js rename to lib/schemas/x-real.js diff --git a/lib/schemas/zscaler.js b/lib/schemas/z-forwarded.js similarity index 100% rename from lib/schemas/zscaler.js rename to lib/schemas/z-forwarded.js diff --git a/test/index.js b/test/index.js index 1fda101..85ac9ec 100644 --- a/test/index.js +++ b/test/index.js @@ -32,7 +32,7 @@ describe('forwarded(req)', function () { it('should convert schema names to lowercase', function () { var options = { schemas: [ - 'XFF' + 'x-forwarded' ] } @@ -77,10 +77,10 @@ describe('forwarded(req)', function () { 'cloudflare', 'fastly', 'microsoft', - 'nginx', - 'rackspace', - 'xff', - 'zscaler', + 'x-real', + 'x-cluster', + 'x-forwarded', + 'z-forwarded', 'rfc7239' ] } diff --git a/test/schemas/rackspace.js b/test/schemas/x-cluster.js similarity index 86% rename from test/schemas/rackspace.js rename to test/schemas/x-cluster.js index ee1b818..ca9e7a8 100644 --- a/test/schemas/rackspace.js +++ b/test/schemas/x-cluster.js @@ -7,10 +7,10 @@ var forwarded = require('../..') var request = require('../helpers').createRequestMock var options = { - schemas: ['rackspace'] + schemas: ['x-cluster'] } -describe('rackspace', function () { +describe('x-cluster', function () { it('should parse [x-cluster-client-ip]', function () { var result = forwarded(request({ 'x-cluster-client-ip': '10.10.10.1' diff --git a/test/schemas/xff.js b/test/schemas/x-forwarded.js similarity index 92% rename from test/schemas/xff.js rename to test/schemas/x-forwarded.js index 9078429..73df075 100644 --- a/test/schemas/xff.js +++ b/test/schemas/x-forwarded.js @@ -8,10 +8,10 @@ var request = require('../helpers').createRequestMock var schemas = require('../../lib/schemas') var options = { - schemas: ['xff'] + schemas: ['x-forwarded'] } -describe('xff', function () { +describe('x-forwarded', function () { it('should parse [x-forwarded-for]', function () { var result = forwarded(request({ 'x-forwarded-for': '10.10.10.1' @@ -57,6 +57,6 @@ describe('xff', function () { 'x-forwarded-ssl': 'on' }) - assert.ok(schemas.xff.protoFn(req)) + assert.ok(schemas['x-forwarded'].protoFn(req)) }) }) diff --git a/test/schemas/nginx.js b/test/schemas/x-real.js similarity index 94% rename from test/schemas/nginx.js rename to test/schemas/x-real.js index e216aaf..4a03ce0 100644 --- a/test/schemas/nginx.js +++ b/test/schemas/x-real.js @@ -7,10 +7,10 @@ var forwarded = require('../..') var request = require('../helpers').createRequestMock var options = { - schemas: ['nginx'] + schemas: ['x-real'] } -describe('nginx', function () { +describe('x-real', function () { it('should parse [x-real-ip]', function () { var result = forwarded(request({ 'x-real-ip': '10.10.10.1' diff --git a/test/schemas/zscaler.js b/test/schemas/z-forwarded.js similarity index 94% rename from test/schemas/zscaler.js rename to test/schemas/z-forwarded.js index 168376a..79838a7 100644 --- a/test/schemas/zscaler.js +++ b/test/schemas/z-forwarded.js @@ -7,10 +7,10 @@ var forwarded = require('../..') var request = require('../helpers').createRequestMock var options = { - schemas: ['zscaler'] + schemas: ['z-forwarded'] } -describe('zscaler', function () { +describe('z-forwarded', function () { it('should parse [z-forwarded-for]', function () { var result = forwarded(request({ 'z-forwarded-for': '10.10.10.1' From 34599743ed5aeb525baf444013e8f12f8062d10a Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 26 Mar 2016 17:54:54 -0400 Subject: [PATCH 22/33] refactor(proto): simplified logic for protocol detection - should gain a minor performance boost with simpler logic for protocol detection - refactor tests for consistency --- index.js | 10 ++--- lib/processor.js | 80 +++++++++---------------------------- lib/schemas/cloudflare.js | 22 +++++----- lib/schemas/fastly.js | 8 ++-- lib/schemas/microsoft.js | 6 ++- lib/schemas/rfc7239.js | 4 +- lib/schemas/x-forwarded.js | 19 ++++++--- lib/schemas/x-real.js | 10 ++++- lib/schemas/z-forwarded.js | 10 ++++- test/helpers.js | 4 +- test/schemas/cloudflare.js | 49 ++++++++++------------- test/schemas/fastly.js | 28 ++++++------- test/schemas/microsoft.js | 7 ++-- test/schemas/rfc7239.js | 7 ++-- test/schemas/x-cluster.js | 5 +-- test/schemas/x-forwarded.js | 33 ++++++--------- test/schemas/x-real.js | 20 ++++------ test/schemas/z-forwarded.js | 25 +++++------- test/{http.js => server.js} | 2 +- 19 files changed, 148 insertions(+), 201 deletions(-) rename test/{http.js => server.js} (94%) diff --git a/index.js b/index.js index c58de40..e8a8d2e 100644 --- a/index.js +++ b/index.js @@ -12,7 +12,7 @@ * @public */ -var Processor = require('./lib/processor') +var processor = require('./lib/processor') var schemas = require('./lib/schemas') /** @@ -34,9 +34,6 @@ module.exports = function forwarded (req, options) { ] } - // consistent case - opts.schemas = opts.schemas.map(Function.prototype.call, String.prototype.toLowerCase) - if (!req) { throw new TypeError('argument req is required') } @@ -62,6 +59,9 @@ module.exports = function forwarded (req, options) { return opts.schemas // check if schemas exist .map(function (name) { + // adjust case + name = name.toLowerCase() + if (!schemas[name]) { throw new Error('invalid schema') } @@ -71,7 +71,7 @@ module.exports = function forwarded (req, options) { // process schemas .reduce(function (forwarded, schema) { - var result = new Processor(req, schema) + var result = processor(req.headers, schema) // ensure reverse order of addresses if (result.addrs) { diff --git a/lib/processor.js b/lib/processor.js index 94dc3e7..c4e550c 100644 --- a/lib/processor.js +++ b/lib/processor.js @@ -2,81 +2,37 @@ var debug = require('debug-log')('forwarded') -function processor (req, schema) { +module.exports = function processor (headers, schema) { if (typeof schema === 'function') { - return schema(req) + return schema(headers) } - this.req = req - this.schema = schema + var result = {} - return { - addrs: this.addrs(), - host: this.host(), - port: this.port(), - proto: this.protocol() - } -} - -processor.prototype.host = function () { - if (this.schema.host && this.req.headers[this.schema.host]) { - var value = this.req.headers[this.schema.host] + var fields = ['addrs', 'host', 'port'] - debug('found header [%s = %s]', this.schema.host, value) - - return this.req.headers[this.schema.host] - } -} - -processor.prototype.port = function () { - if (this.schema.port && this.req.headers[this.schema.port]) { - var value = this.req.headers[this.schema.port] - - debug('found header [%s = %s]', this.schema.port, value) - - return value - } -} + fields.forEach(function (field) { + if (schema[field] && headers[schema[field]]) { + var value = headers[schema[field]] -processor.prototype.addrs = function () { - if (this.schema.addrs && this.req.headers[this.schema.addrs]) { - var value = this.req.headers[this.schema.addrs] + debug('found header [%s = %s]', schema[field], value) - debug('found header [%s = %s]', this.schema.addrs, value) + result[field] = value + } + }) - return value + // sort addresses + if (result.addrs) { + result.addrs = result.addrs .split(/ *, */) .filter(Boolean) .reverse() } -} - -processor.prototype.protocol = function () { - // utility - function runner (obj) { - // multiple possible values - if (Array.isArray(obj)) { - // get the last succesful item - return obj.map(runner.bind(this)).reduce(function (prev, curr) { - return curr ? curr : prev - }) - } - - if (typeof obj === 'function') { - return obj.call(this, this.req) - } - - if (this.req.headers[obj]) { - debug('found header [%s = %s]', obj, this.req.headers[obj]) - return this.req.headers[obj] - } + // get protocol + if (typeof schema.proto === 'function') { + result.proto = schema.proto(headers) } - // actually run - if (this.schema.proto) { - return runner.call(this, this.schema.proto) - } + return result } - -module.exports = processor diff --git a/lib/schemas/cloudflare.js b/lib/schemas/cloudflare.js index 1dfea50..f5ee50d 100644 --- a/lib/schemas/cloudflare.js +++ b/lib/schemas/cloudflare.js @@ -2,18 +2,16 @@ var debug = require('debug-log')('forwarded') -function isSecure (req) { - try { - var cf = JSON.parse(req.headers['cf-visitor']) - return !~[undefined, 'http'].indexOf(cf.scheme) - } catch (e) { - debug('could not parse "cf-visitor" header: %s', req.headers['cf-visitor']) - } - - return false -} - module.exports = { addrs: 'cf-connecting-ip', - proto: isSecure + proto: function protocol (headers) { + try { + var cf = JSON.parse(headers['cf-visitor']) + if (cf.scheme) { + return cf.scheme + } + } catch (e) { + debug('could not parse "cf-visitor" header: %s', headers['cf-visitor']) + } + } } diff --git a/lib/schemas/fastly.js b/lib/schemas/fastly.js index 33f7dbd..ef3eb35 100644 --- a/lib/schemas/fastly.js +++ b/lib/schemas/fastly.js @@ -1,11 +1,9 @@ 'use strict' -function isSecure (req) { - return req.headers['fastly-ssl'] !== undefined -} - module.exports = { addrs: 'fastly-client-ip', port: 'fastly-client-port', - proto: isSecure + proto: function protocol (headers) { + return headers['fastly-ssl'] ? 'https' : undefined + } } diff --git a/lib/schemas/microsoft.js b/lib/schemas/microsoft.js index 151824d..2663355 100644 --- a/lib/schemas/microsoft.js +++ b/lib/schemas/microsoft.js @@ -1,7 +1,9 @@ 'use strict' module.exports = { - proto: function isSecure (req) { - return req.headers['front-end-https'] === 'on' + proto: function protocol (headers) { + if (headers['front-end-https']) { + return headers['front-end-https'] === 'on' ? 'https' : 'http' + } } } diff --git a/lib/schemas/rfc7239.js b/lib/schemas/rfc7239.js index cfda0b3..f3142bc 100644 --- a/lib/schemas/rfc7239.js +++ b/lib/schemas/rfc7239.js @@ -35,9 +35,9 @@ function parsePart (part) { var ELEMENT_SEPARATOR = / *; */ var PART_SEPARATOR = / *, */ -module.exports = function (req) { +module.exports = function (headers) { var forwarded = {} - var header = req.headers.forwarded + var header = headers.forwarded if (!header) { return forwarded diff --git a/lib/schemas/x-forwarded.js b/lib/schemas/x-forwarded.js index d551e39..70daa9c 100644 --- a/lib/schemas/x-forwarded.js +++ b/lib/schemas/x-forwarded.js @@ -1,13 +1,20 @@ 'use strict' -function isSecure (req) { - return req.headers['x-forwarded-ssl'] === 'on' -} - module.exports = { addrs: 'x-forwarded-for', host: 'x-forwarded-host', port: 'x-forwarded-port', - proto: ['x-forwarded-proto', 'x-forwarded-protocol', isSecure], - protoFn: isSecure + proto: function protocol (headers) { + if (headers['x-forwarded-proto']) { + return headers['x-forwarded-proto'] + } + + if (headers['x-forwarded-protocol']) { + return headers['x-forwarded-protocol'] + } + + if (headers['x-forwarded-ssl']) { + return headers['x-forwarded-ssl'] === 'on' ? 'https' : 'http' + } + } } diff --git a/lib/schemas/x-real.js b/lib/schemas/x-real.js index ef04873..b2c3f8c 100644 --- a/lib/schemas/x-real.js +++ b/lib/schemas/x-real.js @@ -3,5 +3,13 @@ module.exports = { addrs: 'x-real-ip', port: 'x-real-port', - proto: ['x-real-proto', 'x-url-scheme'] + proto: function protocol (headers) { + if (headers['x-real-proto']) { + return headers['x-real-proto'] + } + + if (headers['x-url-scheme']) { + return headers['x-url-scheme'] + } + } } diff --git a/lib/schemas/z-forwarded.js b/lib/schemas/z-forwarded.js index 5fc6807..5805cf8 100644 --- a/lib/schemas/z-forwarded.js +++ b/lib/schemas/z-forwarded.js @@ -4,5 +4,13 @@ module.exports = { addrs: 'z-forwarded-for', host: 'z-forwarded-host', port: 'z-forwarded-port', - proto: ['z-forwarded-proto', 'z-forwarded-protocol'] + proto: function protocol (headers) { + if (headers['x-forwarded-proto']) { + return headers['x-forwarded-proto'] + } + + if (headers['x-forwarded-protocol']) { + return headers['x-forwarded-protocol'] + } + } } diff --git a/test/helpers.js b/test/helpers.js index 82bb9f6..0473de1 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -14,9 +14,8 @@ var createServer = function (done) { }) } -exports.createRequest = function (headers, done) { +exports.createRequestServer = function (headers, done) { createServer(function (server) { - var options = { headers: headers, host: server.address().address, @@ -24,7 +23,6 @@ exports.createRequest = function (headers, done) { } http.get(options, function (res) { - var body = '' res.on('data', function (chunk) { diff --git a/test/schemas/cloudflare.js b/test/schemas/cloudflare.js index 0331af7..676469d 100644 --- a/test/schemas/cloudflare.js +++ b/test/schemas/cloudflare.js @@ -13,44 +13,37 @@ var options = { describe('cloudflare', function () { it('should parse [cf-connecting-ip]', function () { - var result = forwarded(request({ - 'cf-connecting-ip': '10.10.10.1' - }), options) + var req = request({'cf-connecting-ip': '10.10.10.1'}) + var result = forwarded(req, options) assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) }) - describe('should parse [cf-visitor]', function () { - it('{"scheme": "https"}', function () { - var req = request({ - 'cf-visitor': '{"scheme": "https"}' - }) + it('should parse [cf-visitor = {"scheme": "https"}]', function () { + var req = request({'cf-visitor': '{"scheme": "https"}'}) + var result = schemas.cloudflare.proto(req.headers) - assert.ok(schemas.cloudflare.proto(req)) - }) + assert.equal(result, 'https') + }) - it('{"scheme": "http"}', function () { - var req = request({ - 'cf-visitor': '{"scheme": "http"}' - }) + it('should parse [cf-visitor = {"scheme": "http"}]', function () { + var req = request({'cf-visitor': '{"scheme": "http"}'}) + var result = schemas.cloudflare.proto(req.headers) - assert.ok(!schemas.cloudflare.proto(req)) - }) + assert.equal(result, 'http') + }) - it('{}', function () { - var req = request({ - 'cf-visitor': '{}' - }) + it('should parse [cf-visitor = {}]', function () { + var req = request({'cf-visitor': '{}'}) + var result = schemas.cloudflare.proto(req.headers) - assert.ok(!schemas.cloudflare.proto(req)) - }) + assert.equal(result, undefined) + }) - it('{}', function () { - var req = request({ - 'cf-visitor': '{malformed}' - }) + it('should parse [cf-visitor = {malformed}]', function () { + var req = request({'cf-visitor': '{malformed}'}) + var result = schemas.cloudflare.proto(req.headers) - assert.ok(!schemas.cloudflare.proto(req)) - }) + assert.equal(result, undefined) }) }) diff --git a/test/schemas/fastly.js b/test/schemas/fastly.js index 782102e..623f4e1 100644 --- a/test/schemas/fastly.js +++ b/test/schemas/fastly.js @@ -13,32 +13,30 @@ var options = { describe('fastly', function () { it('should parse [fastly-client-ip]', function () { - var result = forwarded(request({ - 'fastly-client-ip': '10.10.10.1' - }), options) + var req = request({'fastly-client-ip': '10.10.10.1'}) + var result = forwarded(req, options) assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) }) it('should parse [fastly-client-port]', function () { - var result = forwarded(request({ - 'fastly-client-port': '9000' - }), options) + var req = request({'fastly-client-port': '9000'}) + var result = forwarded(req, options) assert.deepEqual(result.port, '9000') }) - it('should parse [fastly-ssl]', function () { - var req = request({ - 'fastly-ssl': '1' - }) + it('should parse [fastly-ssl = 1]', function () { + var req = request({'fastly-ssl': '1'}) + var result = schemas.fastly.proto(req.headers) - assert.ok(schemas.fastly.proto(req)) + assert.equal(result, 'https') + }) - req = request({ - 'fastly-ssl': undefined - }) + it('should parse [fastly-ssl = undefined]', function () { + var req = request() + var result = schemas.fastly.proto(req.headers) - assert.ok(!schemas.fastly.proto(req)) + assert.equal(result, undefined) }) }) diff --git a/test/schemas/microsoft.js b/test/schemas/microsoft.js index 0587f51..011a532 100644 --- a/test/schemas/microsoft.js +++ b/test/schemas/microsoft.js @@ -8,10 +8,9 @@ var schemas = require('../../lib/schemas') describe('microsoft', function () { it('should detect https protocol', function () { - var req = request({ - 'front-end-https': 'on' - }) + var req = request({'front-end-https': 'on'}) + var result = schemas.microsoft.proto(req.headers) - assert.ok(schemas.microsoft.proto(req)) + assert.equal(result, 'https') }) }) diff --git a/test/schemas/rfc7239.js b/test/schemas/rfc7239.js index 9fa0f4a..535976b 100644 --- a/test/schemas/rfc7239.js +++ b/test/schemas/rfc7239.js @@ -19,11 +19,10 @@ describe('rfc7239', function () { }) it('should parse and process Forwarded header', function () { - var req = request({ - 'forwarded': 'host= mockbin.com, for=0.0.0.1, for=0.0.0.2, for=private, for=1::8, for=; port= 9000; proto=https; by=0.0.0.3' - }) + var req = request({'forwarded': 'host= mockbin.com, for=0.0.0.1, for=0.0.0.2, for=private, for=1::8, for=; port= 9000; proto=https; by=0.0.0.3'}) + var result = forwarded(req, options) - assert.deepEqual(forwarded(req, options), { + assert.deepEqual(result, { addrs: ['127.0.0.1', '1::8', 'private', '0.0.0.2', '0.0.0.1'], by: '0.0.0.3', port: '9000', diff --git a/test/schemas/x-cluster.js b/test/schemas/x-cluster.js index ca9e7a8..640c487 100644 --- a/test/schemas/x-cluster.js +++ b/test/schemas/x-cluster.js @@ -12,9 +12,8 @@ var options = { describe('x-cluster', function () { it('should parse [x-cluster-client-ip]', function () { - var result = forwarded(request({ - 'x-cluster-client-ip': '10.10.10.1' - }), options) + var req = request({'x-cluster-client-ip': '10.10.10.1'}) + var result = forwarded(req, options) assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) }) diff --git a/test/schemas/x-forwarded.js b/test/schemas/x-forwarded.js index 73df075..9d00af5 100644 --- a/test/schemas/x-forwarded.js +++ b/test/schemas/x-forwarded.js @@ -5,7 +5,6 @@ var assert = require('assert') var forwarded = require('../..') var request = require('../helpers').createRequestMock -var schemas = require('../../lib/schemas') var options = { schemas: ['x-forwarded'] @@ -13,50 +12,44 @@ var options = { describe('x-forwarded', function () { it('should parse [x-forwarded-for]', function () { - var result = forwarded(request({ - 'x-forwarded-for': '10.10.10.1' - }), options) + var req = request({'x-forwarded-for': '10.10.10.1'}) + var result = forwarded(req, options) assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) }) it('should parse [x-forwarded-host]', function () { - var result = forwarded(request({ - 'x-forwarded-host': 'mockbin.com' - }), options) + var req = request({'x-forwarded-host': 'mockbin.com'}) + var result = forwarded(req, options) assert.deepEqual(result.host, 'mockbin.com') }) it('should parse [x-forwarded-port]', function () { - var result = forwarded(request({ - 'x-forwarded-port': '9000' - }), options) + var req = request({'x-forwarded-port': '9000'}) + var result = forwarded(req, options) assert.deepEqual(result.port, '9000') }) it('should parse [x-forwarded-proto]', function () { - var result = forwarded(request({ - 'x-forwarded-proto': 'https' - }), options) + var req = request({'x-forwarded-proto': 'https'}) + var result = forwarded(req, options) assert.deepEqual(result.proto, 'https') }) it('should parse [x-forwarded-protocol]', function () { - var result = forwarded(request({ - 'x-forwarded-protocol': 'https' - }), options) + var req = request({'x-forwarded-protocol': 'https'}) + var result = forwarded(req, options) assert.deepEqual(result.proto, 'https') }) it('should parse [x-forwarded-ssl]', function () { - var req = request({ - 'x-forwarded-ssl': 'on' - }) + var req = request({'x-forwarded-ssl': 'on'}) + var result = forwarded(req, options) - assert.ok(schemas['x-forwarded'].protoFn(req)) + assert.deepEqual(result.proto, 'https') }) }) diff --git a/test/schemas/x-real.js b/test/schemas/x-real.js index 4a03ce0..b88fb9b 100644 --- a/test/schemas/x-real.js +++ b/test/schemas/x-real.js @@ -12,33 +12,29 @@ var options = { describe('x-real', function () { it('should parse [x-real-ip]', function () { - var result = forwarded(request({ - 'x-real-ip': '10.10.10.1' - }), options) + var req = request({'x-real-ip': '10.10.10.1'}) + var result = forwarded(req, options) assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) }) it('should parse [x-real-port]', function () { - var result = forwarded(request({ - 'x-real-port': '9000' - }), options) + var req = request({'x-real-port': '9000'}) + var result = forwarded(req, options) assert.deepEqual(result.port, '9000') }) it('should parse [x-real-proto]', function () { - var result = forwarded(request({ - 'x-real-proto': 'https' - }), options) + var req = request({'x-real-proto': 'https'}) + var result = forwarded(req, options) assert.deepEqual(result.proto, 'https') }) it('should parse [x-url-scheme]', function () { - var result = forwarded(request({ - 'x-url-scheme': 'https' - }), options) + var req = request({'x-url-scheme': 'https'}) + var result = forwarded(req, options) assert.deepEqual(result.proto, 'https') }) diff --git a/test/schemas/z-forwarded.js b/test/schemas/z-forwarded.js index 79838a7..dd838a1 100644 --- a/test/schemas/z-forwarded.js +++ b/test/schemas/z-forwarded.js @@ -12,41 +12,36 @@ var options = { describe('z-forwarded', function () { it('should parse [z-forwarded-for]', function () { - var result = forwarded(request({ - 'z-forwarded-for': '10.10.10.1' - }), options) + var req = request({'z-forwarded-for': '10.10.10.1'}) + var result = forwarded(req, options) assert.deepEqual(result.addrs, ['127.0.0.1', '10.10.10.1']) }) it('should parse [z-forwarded-host]', function () { - var result = forwarded(request({ - 'z-forwarded-host': 'mockbin.com' - }), options) + var req = request({'z-forwarded-host': 'mockbin.com'}) + var result = forwarded(req, options) assert.deepEqual(result.host, 'mockbin.com') }) it('should parse [z-forwarded-port]', function () { - var result = forwarded(request({ - 'z-forwarded-port': '9000' - }), options) + var req = request({'z-forwarded-port': '9000'}) + var result = forwarded(req, options) assert.deepEqual(result.port, '9000') }) it('should parse [z-forwarded-proto]', function () { - var result = forwarded(request({ - 'z-forwarded-proto': 'https' - }), options) + var req = request({'z-forwarded-proto': 'https'}) + var result = forwarded(req, options) assert.deepEqual(result.proto, 'https') }) it('should parse [z-forwarded-protocol]', function () { - var result = forwarded(request({ - 'z-forwarded-protocol': 'https' - }), options) + var req = request({'z-forwarded-protocol': 'https'}) + var result = forwarded(req, options) assert.deepEqual(result.proto, 'https') }) diff --git a/test/http.js b/test/server.js similarity index 94% rename from test/http.js rename to test/server.js index e1ea7ae..4323b1d 100644 --- a/test/http.js +++ b/test/server.js @@ -3,7 +3,7 @@ 'use strict' var assert = require('assert') -var request = require('./helpers').createRequest +var request = require('./helpers').createRequestServer describe('live server', function () { it('should get defaults', function (done) { From e2c1292bdf1baedb3b95a0eb0b8343b29aaf03a4 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 26 Mar 2016 18:58:00 -0400 Subject: [PATCH 23/33] refactor(rfc7239): use the excellent forwarded-parse by @lpinca forwarded-parse provides an excellent parsing solution for RFC 7239 "Forwarded" header. however the spec also allows for values beyond the scope of this module (e.g. adding arbitrary fields) therefor it's best if forwarded-parse remains as a separate module whilte utilized here. --- lib/schemas/rfc7239.js | 64 +++++++++++------------------------------ package.json | 3 +- test/index.js | 40 ++++++++------------------ test/schemas/rfc7239.js | 16 ++++------- test/server.js | 2 +- 5 files changed, 37 insertions(+), 88 deletions(-) diff --git a/lib/schemas/rfc7239.js b/lib/schemas/rfc7239.js index f3142bc..ee5c728 100644 --- a/lib/schemas/rfc7239.js +++ b/lib/schemas/rfc7239.js @@ -1,59 +1,27 @@ 'use strict' -function splitMap (string, separator, cb) { - // split into elements - return string.split(separator) - .filter(Boolean) - .forEach(cb) -} - -function parsePart (part) { - var pair = part.split(/ *= */) - - var name = pair[0].toLowerCase() - var value = pair[1] - - if (value) { - switch (typeof this[name]) { - case 'undefined': - this[name] = value - break - - // convert to array - case 'string': - this[name] = [this[name], value] - break - - // append to array - case 'object': - this[name].push(value) - break - } - } -} - -var ELEMENT_SEPARATOR = / *; */ -var PART_SEPARATOR = / *, */ +var parse = require('forwarded-parse') +var debug = require('debug-log')('forwarded') module.exports = function (headers) { - var forwarded = {} - var header = headers.forwarded - - if (!header) { - return forwarded + if (!headers.forwarded) { + return {} } - splitMap(header, ELEMENT_SEPARATOR, function parseElement (el) { - return splitMap(el, PART_SEPARATOR, parsePart.bind(forwarded)) - }) + try { + var result = parse(headers.forwarded) - // ensure result is an array - if (forwarded.for && !Array.isArray(forwarded.for)) { - forwarded.for = [forwarded.for] + var forwarded = { + addrs: result.for ? result.for.reverse() : [], + by: result.by ? result.by[0] : undefined, + host: result.host ? result.host[0] : undefined, + port: result.port ? result.port[0] : undefined, + ports: result.port, + proto: result.proto ? result.proto[0] : undefined + } + } catch (e) { + debug(e) } - // create alias - forwarded.addrs = forwarded.for - return forwarded } diff --git a/package.json b/package.json index 090adf6..faeaa96 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ ], "repository": "jshttp/forwarded", "dependencies": { - "debug-log": "^1.0.0" + "debug-log": "^1.0.0", + "forwarded-parse": "^1.0.1" }, "devDependencies": { "istanbul": "0.4.2", diff --git a/test/index.js b/test/index.js index 85ac9ec..7da7cf2 100644 --- a/test/index.js +++ b/test/index.js @@ -22,31 +22,23 @@ describe('forwarded(req)', function () { }) it('should not throw an error on a disconnected socket connection', function () { - var req = request({}, { - remoteAddress: '127.0.0.1' - }) + var req = request({}, {remoteAddress: '127.0.0.1'}) assert.doesNotThrow(function () { forwarded(req) }, Error) }) it('should convert schema names to lowercase', function () { - var options = { - schemas: [ - 'x-forwarded' - ] - } + var options = { schemas: ['x-forwarded'] } - var result = forwarded(request({ - 'x-forwarded-for': '10.0.0.1' - }), options) + var req = request({'x-forwarded-for': '10.0.0.1'}) + var result = forwarded(req, options) assert.deepEqual(result.addrs, ['127.0.0.1', '10.0.0.1']) }) it('should get defaults', function () { - var result = forwarded(request({ - 'host': 'mockbin.com' - })) + var req = request({host: 'mockbin.com'}) + var result = forwarded(req) assert.equal(result.host, 'mockbin.com') assert.equal(result.port, '5000') @@ -55,22 +47,12 @@ describe('forwarded(req)', function () { }) it('should set proto=http when connection.encrypted=false', function () { - var result = forwarded(request({}, { - encrypted: false, - remotePort: 5000 - })) + var req = request({}, { encrypted: false, remotePort: 5000 }) + var result = forwarded(req) assert.deepEqual(result.proto, 'http') }) - it('should skip blank entries', function () { - var result = forwarded(request({ - 'forwarded': 'for=10.0.0.2,for,for=,for=10.0.0.1' - })) - - assert.deepEqual(result.addrs, ['127.0.0.1', '10.0.0.1', '10.0.0.2']) - }) - it('should process all schemas in ordered sequence', function () { var options = { schemas: [ @@ -85,7 +67,7 @@ describe('forwarded(req)', function () { ] } - var result = forwarded(request({ + var req = request({ 'forwarded': 'for=0.0.0.7', 'cf-connecting-ip': '0.0.0.1', 'fastly-client-ip': '0.0.0.2', @@ -93,7 +75,9 @@ describe('forwarded(req)', function () { 'x-cluster-client-ip': '0.0.0.4', 'x-forwarded-for': '0.0.0.5', 'z-forwarded-for': '0.0.0.6' - }), options) + }) + + var result = forwarded(req, options) assert.deepEqual(result.addrs, ['127.0.0.1', '0.0.0.1', '0.0.0.2', '0.0.0.3', '0.0.0.4', '0.0.0.5', '0.0.0.6', '0.0.0.7']) }) diff --git a/test/schemas/rfc7239.js b/test/schemas/rfc7239.js index 535976b..194a9ac 100644 --- a/test/schemas/rfc7239.js +++ b/test/schemas/rfc7239.js @@ -3,30 +3,26 @@ 'use strict' var assert = require('assert') -var forwarded = require('../..') var request = require('../helpers').createRequestMock var schemas = require('../../lib/schemas') -var options = { - schemas: ['rfc7239'] -} - describe('rfc7239', function () { it('should return empty object', function () { var req = request() + var result = schemas.rfc7239(req.headers) - assert.deepEqual(schemas.rfc7239(req), {}) + assert.deepEqual(result, {}) }) it('should parse and process Forwarded header', function () { - var req = request({'forwarded': 'host= mockbin.com, for=0.0.0.1, for=0.0.0.2, for=private, for=1::8, for=; port= 9000; proto=https; by=0.0.0.3'}) - var result = forwarded(req, options) + var req = request({'forwarded': 'host=mockbin.com, for=0.0.0.1, for=0.0.0.2, for=private, for="1::8", port=9000; proto=https; by=0.0.0.3'}) + var result = schemas.rfc7239(req.headers) assert.deepEqual(result, { - addrs: ['127.0.0.1', '1::8', 'private', '0.0.0.2', '0.0.0.1'], + addrs: ['1::8', 'private', '0.0.0.2', '0.0.0.1'], by: '0.0.0.3', port: '9000', - ports: ['5000', '9000'], + ports: ['9000'], host: 'mockbin.com', proto: 'https' }) diff --git a/test/server.js b/test/server.js index 4323b1d..1a4e011 100644 --- a/test/server.js +++ b/test/server.js @@ -19,7 +19,7 @@ describe('live server', function () { it('should get defaults', function (done) { var headers = { 'host': 'mockbin.com', - 'forwarded': 'host= mockbin.com, for=0.0.0.1, for=0.0.0.2, for=private, for=1::8, for=; port= 9000; proto=https; by=0.0.0.3' + 'forwarded': 'host=mockbin.com; for=0.0.0.1, for=0.0.0.2, for=private, for="1::8"; port=9000; proto=https; by=0.0.0.3' } request(headers, function (result) { From 9e54f7ab2ede578b73c7c9da97ef20643740f469 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 26 Mar 2016 19:01:49 -0400 Subject: [PATCH 24/33] feat(defaults): default schemas should be rfc7239 and x-forwarded --- index.js | 13 ++++++++----- lib/processor.js | 8 -------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index e8a8d2e..17057ea 100644 --- a/index.js +++ b/index.js @@ -27,10 +27,10 @@ module.exports = function forwarded (req, options) { options = options || {} var opts = { - // default to only common + standard - // array order matters here + // default to only standard and x-forwarded for backward compatibility schemas: options.schemas || [ - 'rfc7239' + 'rfc7239', + 'x-forwarded' ] } @@ -74,8 +74,11 @@ module.exports = function forwarded (req, options) { var result = processor(req.headers, schema) // ensure reverse order of addresses - if (result.addrs) { - result.addrs.reverse() + if (typeof result.addrs === 'string') { + result.addrs = result.addrs + .split(/ *, */) + .filter(Boolean) + .reverse() } // update forwarded object diff --git a/lib/processor.js b/lib/processor.js index c4e550c..830de47 100644 --- a/lib/processor.js +++ b/lib/processor.js @@ -21,14 +21,6 @@ module.exports = function processor (headers, schema) { } }) - // sort addresses - if (result.addrs) { - result.addrs = result.addrs - .split(/ *, */) - .filter(Boolean) - .reverse() - } - // get protocol if (typeof schema.proto === 'function') { result.proto = schema.proto(headers) From 7aa4ffe96619f8cc51bfc336008bb0eaad2f5e9d Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 26 Mar 2016 19:31:38 -0400 Subject: [PATCH 25/33] feat(schemas): add weblogic proxy support --- lib/schemas/index.js | 1 + lib/schemas/weblogic.js | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 lib/schemas/weblogic.js diff --git a/lib/schemas/index.js b/lib/schemas/index.js index 3252df9..9885322 100644 --- a/lib/schemas/index.js +++ b/lib/schemas/index.js @@ -5,6 +5,7 @@ module.exports = { 'fastly': require('./fastly'), 'microsoft': require('./microsoft'), 'rfc7239': require('./rfc7239'), + 'weblogic': require('./weblogic'), 'x-cluster': require('./x-cluster'), 'x-forwarded': require('./x-forwarded'), 'x-real': require('./x-real'), diff --git a/lib/schemas/weblogic.js b/lib/schemas/weblogic.js new file mode 100644 index 0000000..c57d58d --- /dev/null +++ b/lib/schemas/weblogic.js @@ -0,0 +1,5 @@ +'use strict' + +module.exports = { + addrs: 'wl-proxy-client-ip' +} From 8cf1117f3ced21892e596e80e544f28f760d0dff Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Sat, 26 Mar 2016 19:31:57 -0400 Subject: [PATCH 26/33] docs(readme): update docs with details on each schema --- README.md | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f443a20..1d20f74 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,26 @@ var result = forwarded(req) #### options -| name | type | description | required | default | -| --------- | ------- | ----------------------------------------- | -------- | -------------------- | -| `schemas` | `array` | ordered list of header schemas to process | no | `['xff', 'rfc7239']` | +| name | type | description | required | default | +| --------- | ------- | ----------------------------------------- | -------- | ---------------------------- | +| `schemas` | `array` | ordered list of header schemas to process | no | `['rfc7239', 'x-forwarded']` | Parse appropriate headers from the request matching the selected [schemas](#options). +###### available schemas + +| name | key | description | +| ------------------- | ------------- | ----------------------------------------------------------------------------------------- | +| RFC 7239 | `rfc7239` | [RFC 7239 Standard][rfc7239] | +| X-Forwarded-* | `x-forwarded` | Headers using the prefix [`X-Forwarded-*`][x-forwarded], a de facto standard | +| X-Real-* | `x-real` | Headers using the prefix [`X-Real-*`][x-real], mostly common in NGINX servers | +| Z-Forwarded-* | `z-forwarded` | less common version of `X-Forwarded-*` used by [Z Scaler][z-forwarded] | +| X-Cluster-Client-IP | `x-cluster` | used by [Rackspace][x-cluster], X-Ray servers | +| Cloudflare | `cloudflare` | Headers used by [Cloudflare][cloudflare] | +| Fastly | `fastly` | Headers used by Fastly, for [IP][fastly-ip], Port, and [SSL][fastly-ssl] info | +| Microsoft | `microsoft` | Non-standard header field used by [Microsoft][microsoft] applications and load-balancers | +| Weblogic | `weblogic` | Forwarded IP by Oracle's [Weblogic][weblogic] Proxy | + ### returned object | name | type | description | default | @@ -61,7 +75,7 @@ $ npm test ``` ## TODO -- [ ] process [`Via`](http://tools.ietf.org/html/rfc7230#section-5.7.1) header + - [ ] extract ports from [`Forwarded`](http://tools.ietf.org/html/rfc7239#section-5.2) header: `Forwarded: for=x.x.x.x:yyyy` ## License @@ -78,3 +92,13 @@ $ npm test [coveralls-url]: https://coveralls.io/r/jshttp/forwarded?branch=master [downloads-image]: https://img.shields.io/npm/dm/forwarded.svg [downloads-url]: https://npmjs.org/package/forwarded +[rfc7239]: https://tools.ietf.org/html/rfc7239 +[x-forwarded]: https://en.wikipedia.org/wiki/X-Forwarded-For +[z-forwarded]: https://en.wikipedia.org/wiki/X-Forwarded-For#Proxy_servers_and_caching_engines +[x-real]: http://nginx.org/en/docs/http/ngx_http_realip_module.html +[x-cluster]: https://support.rackspace.com/how-to/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address/ +[cloudflare]: https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-CloudFlare-handle-HTTP-Request-headers- +[fastly-ssl]: https://docs.fastly.com/guides/securing-communications/tls-termination +[fastly-ip]: https://docs.fastly.com/guides/basic-configuration/adding-or-modifying-headers-on-http-requests-and-responses +[weblogic]: https://blogs.oracle.com/wlscoherence/entry/obtaining_the_correct_client_ip +[microsoft]: http://technet.microsoft.com/en-us/library/aa997519(v=exchg.65).aspx From 0dc53a68db2d3db10268f27112c9e3040caa1910 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Mon, 23 May 2016 02:19:14 -0700 Subject: [PATCH 27/33] chore(package): update mocha to version 2.5.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index faeaa96..2deec6e 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "istanbul": "0.4.2", - "mocha": "2.4.5" + "mocha": "2.5.1" }, "files": [ "LICENSE", From 104f0a567c67de26b9978413193a0b97d485e097 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Wed, 25 May 2016 02:10:58 -0700 Subject: [PATCH 28/33] chore(package): update mocha to version 2.5.3 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2deec6e..993b949 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "istanbul": "0.4.2", - "mocha": "2.5.1" + "mocha": "2.5.3" }, "files": [ "LICENSE", From 9712f4d38ca896ab430b4fc7dfcf39ed697b2aad Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Wed, 8 Jun 2016 21:44:35 -0400 Subject: [PATCH 29/33] chore(package): update istanbul to version 0.4.3 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 993b949..0c2ac3d 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "forwarded-parse": "^1.0.1" }, "devDependencies": { - "istanbul": "0.4.2", + "istanbul": "0.4.3", "mocha": "2.5.3" }, "files": [ From 4b97e7101f40c46ffe660c60665a60a71b680769 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Thu, 4 Aug 2016 03:23:54 -0400 Subject: [PATCH 30/33] chore(package): update mocha to version 3.0.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c2ac3d..6c15ed0 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "istanbul": "0.4.3", - "mocha": "2.5.3" + "mocha": "3.0.1" }, "files": [ "LICENSE", From 8980fbfed381a0c2502946259467f4c5a00241ef Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Mon, 31 Oct 2016 19:55:57 -0400 Subject: [PATCH 31/33] chore: drop support for Node.js 0.10 BREAKING CHANGE: This module no longer supports Node.js 0.10 --- .travis.yml | 12 ++++-------- package.json | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2b2f440..5762380 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,9 @@ language: node_js node_js: - - "0.6" - - "0.8" - - "0.10" - - "0.12" - - "1.0" - - "1.8" - - "2.0" - - "2.1" + - '1.0' + - '1.8' + - '2.0' + - '2.1' sudo: false before_install: # Setup Node.js version-specific dependencies diff --git a/package.json b/package.json index 6c15ed0..405a03e 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "lib" ], "engines": { - "node": ">= 0.6" + "node": ">= 4" }, "scripts": { "test": "mocha --recursive --reporter spec --bail --check-leaks test/", From bbe1a0dbf78d33eb8f2f108474008f469494521c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 7 Nov 2016 18:37:01 +0000 Subject: [PATCH 32/33] chore(package): update dependencies https://greenkeeper.io/ --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 405a03e..401efb5 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,11 @@ "repository": "jshttp/forwarded", "dependencies": { "debug-log": "^1.0.0", - "forwarded-parse": "^1.0.1" + "forwarded-parse": "^2.0.0" }, "devDependencies": { - "istanbul": "0.4.3", - "mocha": "3.0.1" + "istanbul": "0.4.5", + "mocha": "3.1.2" }, "files": [ "LICENSE", From 7f03ddfd775a78418925d1b5726f1e756edaa720 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 25 Nov 2016 00:02:54 +0000 Subject: [PATCH 33/33] chore(package): update mocha to version 3.2.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 401efb5..5e74349 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "istanbul": "0.4.5", - "mocha": "3.1.2" + "mocha": "3.2.0" }, "files": [ "LICENSE",