Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move tunnel logic into separate module #1631

Merged
merged 3 commits into from Jun 15, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 0 additions & 10 deletions lib/copy.js

This file was deleted.

9 changes: 9 additions & 0 deletions lib/helpers.js
Expand Up @@ -46,10 +46,19 @@ function toBase64 (str) {
return (new Buffer(str || '', 'utf8')).toString('base64')
}

function copy (obj) {
var o = {}
Object.keys(obj).forEach(function (i) {
o[i] = obj[i]
})
return o
}

exports.isFunction = isFunction
exports.paramsHaveRequestBody = paramsHaveRequestBody
exports.safeStringify = safeStringify
exports.md5 = md5
exports.isReadStream = isReadStream
exports.toBase64 = toBase64
exports.copy = copy
exports.defer = deferMethod()
183 changes: 183 additions & 0 deletions lib/tunnel.js
@@ -0,0 +1,183 @@
'use strict'

var url = require('url')
, tunnel = require('tunnel-agent')

var defaultProxyHeaderWhiteList = [
'accept',
'accept-charset',
'accept-encoding',
'accept-language',
'accept-ranges',
'cache-control',
'content-encoding',
'content-language',
'content-length',
'content-location',
'content-md5',
'content-range',
'content-type',
'connection',
'date',
'expect',
'max-forwards',
'pragma',
'referer',
'te',
'transfer-encoding',
'user-agent',
'via'
]

var defaultProxyHeaderExclusiveList = [
'proxy-authorization'
]

function constructProxyHost(uriObject) {
var port = uriObject.portA
, protocol = uriObject.protocol
, proxyHost = uriObject.hostname + ':'

if (port) {
proxyHost += port
} else if (protocol === 'https:') {
proxyHost += '443'
} else {
proxyHost += '80'
}

return proxyHost
}

function constructProxyHeaderWhiteList(headers, proxyHeaderWhiteList) {
var whiteList = proxyHeaderWhiteList
.reduce(function (set, header) {
set[header.toLowerCase()] = true
return set
}, {})

return Object.keys(headers)
.filter(function (header) {
return whiteList[header.toLowerCase()]
})
.reduce(function (set, header) {
set[header] = headers[header]
return set
}, {})
}

function constructTunnelOptions (request, proxyHeaders) {
var proxy = request.proxy

var tunnelOptions = {
proxy : {
host : proxy.hostname,
port : +proxy.port,
proxyAuth : proxy.auth,
headers : proxyHeaders
},
headers : request.headers,
ca : request.ca,
cert : request.cert,
key : request.key,
passphrase : request.passphrase,
pfx : request.pfx,
ciphers : request.ciphers,
rejectUnauthorized : request.rejectUnauthorized,
secureOptions : request.secureOptions,
secureProtocol : request.secureProtocol
}

return tunnelOptions
}

function constructTunnelFnName(uri, proxy) {
var uriProtocol = (uri.protocol === 'https:' ? 'https' : 'http')
var proxyProtocol = (proxy.protocol === 'https:' ? 'Https' : 'Http')
return [uriProtocol, proxyProtocol].join('Over')
}

function getTunnelFn(request) {
var uri = request.uri
var proxy = request.proxy
var tunnelFnName = constructTunnelFnName(uri, proxy)
return tunnel[tunnelFnName]
}


function Tunnel (request) {
this.request = request
this.proxyHeaderWhiteList = defaultProxyHeaderWhiteList
this.proxyHeaderExclusiveList = []
}

Tunnel.prototype.isEnabled = function (options) {
var request = this.request
// Tunnel HTTPS by default, or if a previous request in the redirect chain
// was tunneled. Allow the user to override this setting.

// If self.tunnel is already set (because this is a redirect), use the
// existing value.
if (typeof request.tunnel !== 'undefined') {
return request.tunnel
}

// If options.tunnel is set (the user specified a value), use it.
if (typeof options.tunnel !== 'undefined') {
return options.tunnel
}

// If the destination is HTTPS, tunnel.
if (request.uri.protocol === 'https:') {
return true
}

// Otherwise, leave tunnel unset, because if a later request in the redirect
// chain is HTTPS then that request (and any subsequent ones) should be
// tunneled.
return undefined
}

Tunnel.prototype.setup = function (options) {
var self = this
, request = self.request

options = options || {}

if (typeof request.proxy === 'string') {
request.proxy = url.parse(request.proxy)
}

if (!request.proxy || !request.tunnel) {
return false
}

// Setup Proxy Header Exclusive List and White List
if (options.proxyHeaderWhiteList) {
self.proxyHeaderWhiteList = options.proxyHeaderWhiteList
}
if (options.proxyHeaderExclusiveList) {
self.proxyHeaderExclusiveList = options.proxyHeaderExclusiveList
}

var proxyHeaderExclusiveList = self.proxyHeaderExclusiveList.concat(defaultProxyHeaderExclusiveList)
var proxyHeaderWhiteList = self.proxyHeaderWhiteList.concat(proxyHeaderExclusiveList)

// Setup Proxy Headers and Proxy Headers Host
// Only send the Proxy White Listed Header names
var proxyHeaders = constructProxyHeaderWhiteList(request.headers, proxyHeaderWhiteList)
proxyHeaders.host = constructProxyHost(request.uri)

proxyHeaderExclusiveList.forEach(request.removeHeader, request)

// Set Agent from Tunnel Data
var tunnelFn = getTunnelFn(request)
var tunnelOptions = constructTunnelOptions(request, proxyHeaders)
request.agent = tunnelFn(tunnelOptions)

return true
}

Tunnel.defaultProxyHeaderWhiteList = defaultProxyHeaderWhiteList
Tunnel.defaultProxyHeaderExclusiveList = defaultProxyHeaderExclusiveList
exports.Tunnel = Tunnel