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