http-proxy-client.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use strict';
  2. /**
  3. * Minimal HTTP/S proxy client
  4. */
  5. const net = require('net');
  6. const tls = require('tls');
  7. const urllib = require('url');
  8. /**
  9. * Establishes proxied connection to destinationPort
  10. *
  11. * httpProxyClient("http://localhost:3128/", 80, "google.com", function(err, socket){
  12. * socket.write("GET / HTTP/1.0\r\n\r\n");
  13. * });
  14. *
  15. * @param {String} proxyUrl proxy configuration, etg "http://proxy.host:3128/"
  16. * @param {Number} destinationPort Port to open in destination host
  17. * @param {String} destinationHost Destination hostname
  18. * @param {Function} callback Callback to run with the rocket object once connection is established
  19. */
  20. function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
  21. let proxy = urllib.parse(proxyUrl);
  22. // create a socket connection to the proxy server
  23. let options;
  24. let connect;
  25. let socket;
  26. options = {
  27. host: proxy.hostname,
  28. port: Number(proxy.port) ? Number(proxy.port) : proxy.protocol === 'https:' ? 443 : 80
  29. };
  30. if (proxy.protocol === 'https:') {
  31. // we can use untrusted proxies as long as we verify actual SMTP certificates
  32. options.rejectUnauthorized = false;
  33. connect = tls.connect.bind(tls);
  34. } else {
  35. connect = net.connect.bind(net);
  36. }
  37. // Error harness for initial connection. Once connection is established, the responsibility
  38. // to handle errors is passed to whoever uses this socket
  39. let finished = false;
  40. let tempSocketErr = function(err) {
  41. if (finished) {
  42. return;
  43. }
  44. finished = true;
  45. try {
  46. socket.destroy();
  47. } catch (E) {
  48. // ignore
  49. }
  50. callback(err);
  51. };
  52. socket = connect(options, () => {
  53. if (finished) {
  54. return;
  55. }
  56. let reqHeaders = {
  57. Host: destinationHost + ':' + destinationPort,
  58. Connection: 'close'
  59. };
  60. if (proxy.auth) {
  61. reqHeaders['Proxy-Authorization'] = 'Basic ' + Buffer.from(proxy.auth).toString('base64');
  62. }
  63. socket.write(
  64. // HTTP method
  65. 'CONNECT ' +
  66. destinationHost +
  67. ':' +
  68. destinationPort +
  69. ' HTTP/1.1\r\n' +
  70. // HTTP request headers
  71. Object.keys(reqHeaders)
  72. .map(key => key + ': ' + reqHeaders[key])
  73. .join('\r\n') +
  74. // End request
  75. '\r\n\r\n'
  76. );
  77. let headers = '';
  78. let onSocketData = chunk => {
  79. let match;
  80. let remainder;
  81. if (finished) {
  82. return;
  83. }
  84. headers += chunk.toString('binary');
  85. if ((match = headers.match(/\r\n\r\n/))) {
  86. socket.removeListener('data', onSocketData);
  87. remainder = headers.substr(match.index + match[0].length);
  88. headers = headers.substr(0, match.index);
  89. if (remainder) {
  90. socket.unshift(Buffer.from(remainder, 'binary'));
  91. }
  92. // proxy connection is now established
  93. finished = true;
  94. // check response code
  95. match = headers.match(/^HTTP\/\d+\.\d+ (\d+)/i);
  96. if (!match || (match[1] || '').charAt(0) !== '2') {
  97. try {
  98. socket.destroy();
  99. } catch (E) {
  100. // ignore
  101. }
  102. return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || '')));
  103. }
  104. socket.removeListener('error', tempSocketErr);
  105. return callback(null, socket);
  106. }
  107. };
  108. socket.on('data', onSocketData);
  109. });
  110. socket.once('error', tempSocketErr);
  111. }
  112. module.exports = httpProxyClient;