forwarded-for
Version:
Abstraction for retrieving ip address information from a Node.js connection. Searches for proxy headers before degrading req.address
194 lines (174 loc) • 5.73 kB
JavaScript
;
var net = require('net');
/**
* Forwarded instance.
*
* @constructor
* @param {String} ip The IP address.
* @param {Number} port The port number.
* @param {Boolean} secured The connection was secured.
* @api private
*/
function Forwarded(ip, port, secured) {
this.ip = ip || '127.0.0.1';
this.secure = !!secured;
this.port = +port || 0;
}
/**
* List of possible proxy headers that should be checked for the original client
* IP address and forwarded port.
*
* @type {Array}
* @private
*/
var proxies = [
{
ip: 'fastly-client-ip',
port: 'fastly-client-port', // Estimated guess, no standard header available.
proto: 'fastly-ssl'
},
{
ip: 'x-forwarded-for',
port: 'x-forwarded-port',
proto: 'x-forwarded-proto'
}, {
ip: 'z-forwarded-for',
port: 'z-forwarded-port', // Estimated guess, no standard header available.
proto: 'z-forwarded-proto' // Estimated guess, no standard header available.
}, {
ip: 'forwarded',
port: 'forwarded-port',
proto: 'forwarded-proto' // Estimated guess, no standard header available.
}, {
ip: 'x-real-ip',
port: 'x-real-port', // Estimated guess, no standard header available.
proto: 'x-real-proto' // Estimated guess, no standard header available.
}
];
/**
* Search the headers for a possible match against a known proxy header.
*
* @param {Object} headers The received HTTP headers.
* @param {Array} whitelist White list of proxies that should be checked.
* @returns {Forwarded|Undefined} A Forwarded address object or nothing.
* @api private
*/
function forwarded(headers, whitelist) {
var parts, ports, port, proto, ips, ip, length = proxies.length, i = 0;
for (; i < length; i++) {
if (!(proxies[i].ip in headers)) continue;
ports = (headers[proxies[i].port] || '').split(',');
ips = (headers[proxies[i].ip] || '')
.split(',')
.map((entry, j) => {
if (net.isIPv6(entry))
return entry.trim();
else {
parts = entry.split(':');
if (parts[1]) {
ports.length = Math.max(j+1, ports.length);
ports[j] = parts[1].trim();
}
return parts[0].trim();
}
});
proto = (headers[proxies[i].proto] || 'http');
//
// As these headers can potentially be set by a 1337H4X0R we need to ensure
// that all supplied values are valid IP addresses. If we receive a none
// IP value inside the IP header field we are going to assume that this
// header has been compromised and should be ignored
//
if (!ips || !ips.every(net.isIP)) return;
port = ports.shift(); // Extract the first port as it's the "source" port.
ip = ips.shift(); // Extract the first IP as it's the "source" IP.
//
// If we were given a white list, we need to ensure that the proxies that
// we're given are known and allowed.
//
if (whitelist && whitelist.length && !ips.every(function every(ip) {
return ~whitelist.indexOf(ip);
})) return;
//
// Shift the most recently found proxy header to the front of the proxies
// array. This optimizes future calls, placing the most commonly found headers
// near the front of the array.
//
if (i !== 0) {
proxies.unshift(proxies.splice(i, 1)[0]);
}
//
// We've gotten a match on a HTTP header, we need to parse it further as it
// could consist of multiple hops. The pattern for multiple hops is:
//
// client, proxy, proxy, proxy, etc.
//
// So extracting the first IP should be sufficient. There are SSL
// terminators like the once's that is used by `fastly.com` which set their
// HTTPS header to `1` as an indication that the connection was secure.
// (This reduces bandwidth)
//
return new Forwarded(ip, port, proto === '1' || proto === 'https');
}
}
/**
* Parse out the address information..
*
* @param {Object} obj A socket like object that could contain a `remoteAddress`.
* @param {Object} headers The received HTTP headers.
* @param {Array} whitelist White list
* @returns {Forwarded} A Forwarded address object.
* @api private
*/
function parse(obj, headers, whitelist) {
var proxied = forwarded(headers || {}, whitelist)
, connection = obj.connection
, socket = connection
? connection.socket
: obj.socket;
//
// We should always be testing for HTTP headers as remoteAddress would point
// to proxies.
//
if (proxied) return proxied;
// Check for the property on our given object.
if ('object' === typeof obj) {
if ('remoteAddress' in obj) {
return new Forwarded(
obj.remoteAddress,
obj.remotePort,
'secure' in obj ? obj.secure : obj.encrypted
);
}
// Edge case for Socket.IO 0.9
if ('object' === typeof obj.address && obj.address.address) {
return new Forwarded(
obj.address.address,
obj.address.port,
'secure' in obj ? obj.secure : obj.encrypted
);
}
}
if ('object' === typeof connection && 'remoteAddress' in connection) {
return new Forwarded(
connection.remoteAddress,
connection.remotePort,
'secure' in connection ? connection.secure : connection.encrypted
);
}
if ('object' === typeof socket && 'remoteAddress' in socket) {
return new Forwarded(
socket.remoteAddress,
socket.remoteAddress,
'secure' in socket ? socket.secure : socket.encrypted
);
}
return new Forwarded();
}
//
// Expose the module and all of it's interfaces.
//
parse.Forwarded = Forwarded;
parse.forwarded = forwarded;
parse.proxies = proxies;
module.exports = parse;