github.com/jfrog/frogbot@v1.1.1-0.20231221090046-821a26f50338/action/node_modules/tunnel/lib/tunnel.js (about) 1 'use strict'; 2 3 var net = require('net'); 4 var tls = require('tls'); 5 var http = require('http'); 6 var https = require('https'); 7 var events = require('events'); 8 var assert = require('assert'); 9 var util = require('util'); 10 11 12 exports.httpOverHttp = httpOverHttp; 13 exports.httpsOverHttp = httpsOverHttp; 14 exports.httpOverHttps = httpOverHttps; 15 exports.httpsOverHttps = httpsOverHttps; 16 17 18 function httpOverHttp(options) { 19 var agent = new TunnelingAgent(options); 20 agent.request = http.request; 21 return agent; 22 } 23 24 function httpsOverHttp(options) { 25 var agent = new TunnelingAgent(options); 26 agent.request = http.request; 27 agent.createSocket = createSecureSocket; 28 agent.defaultPort = 443; 29 return agent; 30 } 31 32 function httpOverHttps(options) { 33 var agent = new TunnelingAgent(options); 34 agent.request = https.request; 35 return agent; 36 } 37 38 function httpsOverHttps(options) { 39 var agent = new TunnelingAgent(options); 40 agent.request = https.request; 41 agent.createSocket = createSecureSocket; 42 agent.defaultPort = 443; 43 return agent; 44 } 45 46 47 function TunnelingAgent(options) { 48 var self = this; 49 self.options = options || {}; 50 self.proxyOptions = self.options.proxy || {}; 51 self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets; 52 self.requests = []; 53 self.sockets = []; 54 55 self.on('free', function onFree(socket, host, port, localAddress) { 56 var options = toOptions(host, port, localAddress); 57 for (var i = 0, len = self.requests.length; i < len; ++i) { 58 var pending = self.requests[i]; 59 if (pending.host === options.host && pending.port === options.port) { 60 // Detect the request to connect same origin server, 61 // reuse the connection. 62 self.requests.splice(i, 1); 63 pending.request.onSocket(socket); 64 return; 65 } 66 } 67 socket.destroy(); 68 self.removeSocket(socket); 69 }); 70 } 71 util.inherits(TunnelingAgent, events.EventEmitter); 72 73 TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) { 74 var self = this; 75 var options = mergeOptions({request: req}, self.options, toOptions(host, port, localAddress)); 76 77 if (self.sockets.length >= this.maxSockets) { 78 // We are over limit so we'll add it to the queue. 79 self.requests.push(options); 80 return; 81 } 82 83 // If we are under maxSockets create a new one. 84 self.createSocket(options, function(socket) { 85 socket.on('free', onFree); 86 socket.on('close', onCloseOrRemove); 87 socket.on('agentRemove', onCloseOrRemove); 88 req.onSocket(socket); 89 90 function onFree() { 91 self.emit('free', socket, options); 92 } 93 94 function onCloseOrRemove(err) { 95 self.removeSocket(socket); 96 socket.removeListener('free', onFree); 97 socket.removeListener('close', onCloseOrRemove); 98 socket.removeListener('agentRemove', onCloseOrRemove); 99 } 100 }); 101 }; 102 103 TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { 104 var self = this; 105 var placeholder = {}; 106 self.sockets.push(placeholder); 107 108 var connectOptions = mergeOptions({}, self.proxyOptions, { 109 method: 'CONNECT', 110 path: options.host + ':' + options.port, 111 agent: false, 112 headers: { 113 host: options.host + ':' + options.port 114 } 115 }); 116 if (options.localAddress) { 117 connectOptions.localAddress = options.localAddress; 118 } 119 if (connectOptions.proxyAuth) { 120 connectOptions.headers = connectOptions.headers || {}; 121 connectOptions.headers['Proxy-Authorization'] = 'Basic ' + 122 new Buffer(connectOptions.proxyAuth).toString('base64'); 123 } 124 125 debug('making CONNECT request'); 126 var connectReq = self.request(connectOptions); 127 connectReq.useChunkedEncodingByDefault = false; // for v0.6 128 connectReq.once('response', onResponse); // for v0.6 129 connectReq.once('upgrade', onUpgrade); // for v0.6 130 connectReq.once('connect', onConnect); // for v0.7 or later 131 connectReq.once('error', onError); 132 connectReq.end(); 133 134 function onResponse(res) { 135 // Very hacky. This is necessary to avoid http-parser leaks. 136 res.upgrade = true; 137 } 138 139 function onUpgrade(res, socket, head) { 140 // Hacky. 141 process.nextTick(function() { 142 onConnect(res, socket, head); 143 }); 144 } 145 146 function onConnect(res, socket, head) { 147 connectReq.removeAllListeners(); 148 socket.removeAllListeners(); 149 150 if (res.statusCode !== 200) { 151 debug('tunneling socket could not be established, statusCode=%d', 152 res.statusCode); 153 socket.destroy(); 154 var error = new Error('tunneling socket could not be established, ' + 155 'statusCode=' + res.statusCode); 156 error.code = 'ECONNRESET'; 157 options.request.emit('error', error); 158 self.removeSocket(placeholder); 159 return; 160 } 161 if (head.length > 0) { 162 debug('got illegal response body from proxy'); 163 socket.destroy(); 164 var error = new Error('got illegal response body from proxy'); 165 error.code = 'ECONNRESET'; 166 options.request.emit('error', error); 167 self.removeSocket(placeholder); 168 return; 169 } 170 debug('tunneling connection has established'); 171 self.sockets[self.sockets.indexOf(placeholder)] = socket; 172 return cb(socket); 173 } 174 175 function onError(cause) { 176 connectReq.removeAllListeners(); 177 178 debug('tunneling socket could not be established, cause=%s\n', 179 cause.message, cause.stack); 180 var error = new Error('tunneling socket could not be established, ' + 181 'cause=' + cause.message); 182 error.code = 'ECONNRESET'; 183 options.request.emit('error', error); 184 self.removeSocket(placeholder); 185 } 186 }; 187 188 TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { 189 var pos = this.sockets.indexOf(socket) 190 if (pos === -1) { 191 return; 192 } 193 this.sockets.splice(pos, 1); 194 195 var pending = this.requests.shift(); 196 if (pending) { 197 // If we have pending requests and a socket gets closed a new one 198 // needs to be created to take over in the pool for the one that closed. 199 this.createSocket(pending, function(socket) { 200 pending.request.onSocket(socket); 201 }); 202 } 203 }; 204 205 function createSecureSocket(options, cb) { 206 var self = this; 207 TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { 208 var hostHeader = options.request.getHeader('host'); 209 var tlsOptions = mergeOptions({}, self.options, { 210 socket: socket, 211 servername: hostHeader ? hostHeader.replace(/:.*$/, '') : options.host 212 }); 213 214 // 0 is dummy port for v0.6 215 var secureSocket = tls.connect(0, tlsOptions); 216 self.sockets[self.sockets.indexOf(socket)] = secureSocket; 217 cb(secureSocket); 218 }); 219 } 220 221 222 function toOptions(host, port, localAddress) { 223 if (typeof host === 'string') { // since v0.10 224 return { 225 host: host, 226 port: port, 227 localAddress: localAddress 228 }; 229 } 230 return host; // for v0.11 or later 231 } 232 233 function mergeOptions(target) { 234 for (var i = 1, len = arguments.length; i < len; ++i) { 235 var overrides = arguments[i]; 236 if (typeof overrides === 'object') { 237 var keys = Object.keys(overrides); 238 for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { 239 var k = keys[j]; 240 if (overrides[k] !== undefined) { 241 target[k] = overrides[k]; 242 } 243 } 244 } 245 } 246 return target; 247 } 248 249 250 var debug; 251 if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { 252 debug = function() { 253 var args = Array.prototype.slice.call(arguments); 254 if (typeof args[0] === 'string') { 255 args[0] = 'TUNNEL: ' + args[0]; 256 } else { 257 args.unshift('TUNNEL:'); 258 } 259 console.error.apply(console, args); 260 } 261 } else { 262 debug = function() {}; 263 } 264 exports.debug = debug; // for test