tunnel.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. 'use strict'
  2. var url = require('url')
  3. , tunnel = require('tunnel-agent')
  4. var defaultProxyHeaderWhiteList = [
  5. 'accept',
  6. 'accept-charset',
  7. 'accept-encoding',
  8. 'accept-language',
  9. 'accept-ranges',
  10. 'cache-control',
  11. 'content-encoding',
  12. 'content-language',
  13. 'content-length',
  14. 'content-location',
  15. 'content-md5',
  16. 'content-range',
  17. 'content-type',
  18. 'connection',
  19. 'date',
  20. 'expect',
  21. 'max-forwards',
  22. 'pragma',
  23. 'referer',
  24. 'te',
  25. 'transfer-encoding',
  26. 'user-agent',
  27. 'via'
  28. ]
  29. var defaultProxyHeaderExclusiveList = [
  30. 'proxy-authorization'
  31. ]
  32. function constructProxyHost(uriObject) {
  33. var port = uriObject.portA
  34. , protocol = uriObject.protocol
  35. , proxyHost = uriObject.hostname + ':'
  36. if (port) {
  37. proxyHost += port
  38. } else if (protocol === 'https:') {
  39. proxyHost += '443'
  40. } else {
  41. proxyHost += '80'
  42. }
  43. return proxyHost
  44. }
  45. function constructProxyHeaderWhiteList(headers, proxyHeaderWhiteList) {
  46. var whiteList = proxyHeaderWhiteList
  47. .reduce(function (set, header) {
  48. set[header.toLowerCase()] = true
  49. return set
  50. }, {})
  51. return Object.keys(headers)
  52. .filter(function (header) {
  53. return whiteList[header.toLowerCase()]
  54. })
  55. .reduce(function (set, header) {
  56. set[header] = headers[header]
  57. return set
  58. }, {})
  59. }
  60. function constructTunnelOptions (request, proxyHeaders) {
  61. var proxy = request.proxy
  62. var tunnelOptions = {
  63. proxy : {
  64. host : proxy.hostname,
  65. port : +proxy.port,
  66. proxyAuth : proxy.auth,
  67. headers : proxyHeaders
  68. },
  69. headers : request.headers,
  70. ca : request.ca,
  71. cert : request.cert,
  72. key : request.key,
  73. passphrase : request.passphrase,
  74. pfx : request.pfx,
  75. ciphers : request.ciphers,
  76. rejectUnauthorized : request.rejectUnauthorized,
  77. secureOptions : request.secureOptions,
  78. secureProtocol : request.secureProtocol
  79. }
  80. return tunnelOptions
  81. }
  82. function constructTunnelFnName(uri, proxy) {
  83. var uriProtocol = (uri.protocol === 'https:' ? 'https' : 'http')
  84. var proxyProtocol = (proxy.protocol === 'https:' ? 'Https' : 'Http')
  85. return [uriProtocol, proxyProtocol].join('Over')
  86. }
  87. function getTunnelFn(request) {
  88. var uri = request.uri
  89. var proxy = request.proxy
  90. var tunnelFnName = constructTunnelFnName(uri, proxy)
  91. return tunnel[tunnelFnName]
  92. }
  93. function Tunnel (request) {
  94. this.request = request
  95. this.proxyHeaderWhiteList = defaultProxyHeaderWhiteList
  96. this.proxyHeaderExclusiveList = []
  97. }
  98. Tunnel.prototype.isEnabled = function (options) {
  99. var request = this.request
  100. // Tunnel HTTPS by default, or if a previous request in the redirect chain
  101. // was tunneled. Allow the user to override this setting.
  102. // If self.tunnel is already set (because this is a redirect), use the
  103. // existing value.
  104. if (typeof request.tunnel !== 'undefined') {
  105. return request.tunnel
  106. }
  107. // If options.tunnel is set (the user specified a value), use it.
  108. if (typeof options.tunnel !== 'undefined') {
  109. return options.tunnel
  110. }
  111. // If the destination is HTTPS, tunnel.
  112. if (request.uri.protocol === 'https:') {
  113. return true
  114. }
  115. // Otherwise, leave tunnel unset, because if a later request in the redirect
  116. // chain is HTTPS then that request (and any subsequent ones) should be
  117. // tunneled.
  118. return undefined
  119. }
  120. Tunnel.prototype.setup = function (options) {
  121. var self = this
  122. , request = self.request
  123. options = options || {}
  124. if (typeof request.proxy === 'string') {
  125. request.proxy = url.parse(request.proxy)
  126. }
  127. if (!request.proxy || !request.tunnel) {
  128. return false
  129. }
  130. // Setup Proxy Header Exclusive List and White List
  131. if (options.proxyHeaderWhiteList) {
  132. self.proxyHeaderWhiteList = options.proxyHeaderWhiteList
  133. }
  134. if (options.proxyHeaderExclusiveList) {
  135. self.proxyHeaderExclusiveList = options.proxyHeaderExclusiveList
  136. }
  137. var proxyHeaderExclusiveList = self.proxyHeaderExclusiveList.concat(defaultProxyHeaderExclusiveList)
  138. var proxyHeaderWhiteList = self.proxyHeaderWhiteList.concat(proxyHeaderExclusiveList)
  139. // Setup Proxy Headers and Proxy Headers Host
  140. // Only send the Proxy White Listed Header names
  141. var proxyHeaders = constructProxyHeaderWhiteList(request.headers, proxyHeaderWhiteList)
  142. proxyHeaders.host = constructProxyHost(request.uri)
  143. proxyHeaderExclusiveList.forEach(request.removeHeader, request)
  144. // Set Agent from Tunnel Data
  145. var tunnelFn = getTunnelFn(request)
  146. var tunnelOptions = constructTunnelOptions(request, proxyHeaders)
  147. request.agent = tunnelFn(tunnelOptions)
  148. return true
  149. }
  150. Tunnel.defaultProxyHeaderWhiteList = defaultProxyHeaderWhiteList
  151. Tunnel.defaultProxyHeaderExclusiveList = defaultProxyHeaderExclusiveList
  152. exports.Tunnel = Tunnel