Skip to content

Commit

Permalink
Merge 8cf1117 into d125b19
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahmad Nassri committed Mar 26, 2016
2 parents d125b19 + 8cf1117 commit 165b74f
Show file tree
Hide file tree
Showing 27 changed files with 805 additions and 69 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -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
Expand Down
65 changes: 58 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -20,23 +22,62 @@ $ 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 | `['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 |
| --------- | --------- | ---------------------------------------------------------------------------------------- | -------------------------------------- |
| `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

```sh
$ npm test
```

## TODO

- [ ] extract ports from [`Forwarded`](http://tools.ietf.org/html/rfc7239#section-5.2) header: `Forwarded: for=x.x.x.x:yyyy`

## License

[MIT](LICENSE)
Expand All @@ -51,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
83 changes: 69 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*!
* forwarded
* Copyright(c) 2014 Douglas Christopher Wilson
* Copyright(c) 2015 Ahmad Nassri
* MIT Licensed
*/
*/

'use strict'

Expand All @@ -11,29 +12,83 @@
* @public
*/

module.exports = forwarded
var processor = require('./lib/processor')
var schemas = require('./lib/schemas')

/**
* Get all addresses in the request, using the `X-Forwarded-For` header.
*
* @param {object} req
* @return {array}
* @param {http.IncomingMessage} req
* @return {object}
* @public
*/

function forwarded(req) {
module.exports = function forwarded (req, options) {
options = options || {}

var opts = {
// default to only standard and x-forwarded for backward compatibility
schemas: options.schemas || [
'rfc7239',
'x-forwarded'
]
}

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 ? 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

return opts.schemas
// check if schemas exist
.map(function (name) {
// adjust case
name = name.toLowerCase()

if (!schemas[name]) {
throw new Error('invalid schema')
}

return schemas[name]
})

// process schemas
.reduce(function (forwarded, schema) {
var result = processor(req.headers, schema)

// ensure reverse order of addresses
if (typeof result.addrs === 'string') {
result.addrs = result.addrs
.split(/ *, */)
.filter(Boolean)
.reverse()
}

// 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)
}
30 changes: 30 additions & 0 deletions lib/processor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict'

var debug = require('debug-log')('forwarded')

module.exports = function processor (headers, schema) {
if (typeof schema === 'function') {
return schema(headers)
}

var result = {}

var fields = ['addrs', 'host', 'port']

fields.forEach(function (field) {
if (schema[field] && headers[schema[field]]) {
var value = headers[schema[field]]

debug('found header [%s = %s]', schema[field], value)

result[field] = value
}
})

// get protocol
if (typeof schema.proto === 'function') {
result.proto = schema.proto(headers)
}

return result
}
17 changes: 17 additions & 0 deletions lib/schemas/cloudflare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict'

var debug = require('debug-log')('forwarded')

module.exports = {
addrs: 'cf-connecting-ip',
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'])
}
}
}
9 changes: 9 additions & 0 deletions lib/schemas/fastly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'

module.exports = {
addrs: 'fastly-client-ip',
port: 'fastly-client-port',
proto: function protocol (headers) {
return headers['fastly-ssl'] ? 'https' : undefined
}
}
13 changes: 13 additions & 0 deletions lib/schemas/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'

module.exports = {
'cloudflare': require('./cloudflare'),
'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'),
'z-forwarded': require('./z-forwarded')
}
9 changes: 9 additions & 0 deletions lib/schemas/microsoft.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'

module.exports = {
proto: function protocol (headers) {
if (headers['front-end-https']) {
return headers['front-end-https'] === 'on' ? 'https' : 'http'
}
}
}
27 changes: 27 additions & 0 deletions lib/schemas/rfc7239.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict'

var parse = require('forwarded-parse')
var debug = require('debug-log')('forwarded')

module.exports = function (headers) {
if (!headers.forwarded) {
return {}
}

try {
var result = parse(headers.forwarded)

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)
}

return forwarded
}
5 changes: 5 additions & 0 deletions lib/schemas/weblogic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict'

module.exports = {
addrs: 'wl-proxy-client-ip'
}
5 changes: 5 additions & 0 deletions lib/schemas/x-cluster.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict'

module.exports = {
addrs: 'x-cluster-client-ip'
}
20 changes: 20 additions & 0 deletions lib/schemas/x-forwarded.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'

module.exports = {
addrs: 'x-forwarded-for',
host: 'x-forwarded-host',
port: 'x-forwarded-port',
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'
}
}
}
15 changes: 15 additions & 0 deletions lib/schemas/x-real.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict'

module.exports = {
addrs: 'x-real-ip',
port: 'x-real-port',
proto: function protocol (headers) {
if (headers['x-real-proto']) {
return headers['x-real-proto']
}

if (headers['x-url-scheme']) {
return headers['x-url-scheme']
}
}
}
16 changes: 16 additions & 0 deletions lib/schemas/z-forwarded.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict'

module.exports = {
addrs: 'z-forwarded-for',
host: 'z-forwarded-host',
port: 'z-forwarded-port',
proto: function protocol (headers) {
if (headers['x-forwarded-proto']) {
return headers['x-forwarded-proto']
}

if (headers['x-forwarded-protocol']) {
return headers['x-forwarded-protocol']
}
}
}

0 comments on commit 165b74f

Please sign in to comment.