http-proxy-client.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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(
  53. options,
  54. () => {
  55. if (finished) {
  56. return;
  57. }
  58. let reqHeaders = {
  59. Host: destinationHost + ':' + destinationPort,
  60. Connection: 'close'
  61. };
  62. if (proxy.auth) {
  63. reqHeaders['Proxy-Authorization'] = 'Basic ' + Buffer.from(proxy.auth).toString('base64');
  64. }
  65. socket.write(
  66. // HTTP method
  67. 'CONNECT ' +
  68. destinationHost +
  69. ':' +
  70. destinationPort +
  71. ' HTTP/1.1\r\n' +
  72. // HTTP request headers
  73. Object.keys(reqHeaders)
  74. .map(key => key + ': ' + reqHeaders[key])
  75. .join('\r\n') +
  76. // End request
  77. '\r\n\r\n'
  78. );
  79. let headers = '';
  80. let onSocketData = chunk => {
  81. let match;
  82. let remainder;
  83. if (finished) {
  84. return;
  85. }
  86. headers += chunk.toString('binary');
  87. if ((match = headers.match(/\r\n\r\n/))) {
  88. socket.removeListener('data', onSocketData);
  89. remainder = headers.substr(match.index + match[0].length);
  90. headers = headers.substr(0, match.index);
  91. if (remainder) {
  92. socket.unshift(Buffer.from(remainder, 'binary'));
  93. }
  94. // proxy connection is now established
  95. finished = true;
  96. // check response code
  97. match = headers.match(/^HTTP\/\d+\.\d+ (\d+)/i);
  98. if (!match || (match[1] || '').charAt(0) !== '2') {
  99. try {
  100. socket.destroy();
  101. } catch (E) {
  102. // ignore
  103. }
  104. return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || '')));
  105. }
  106. socket.removeListener('error', tempSocketErr);
  107. return callback(null, socket);
  108. }
  109. };
  110. socket.on('data', onSocketData);
  111. }
  112. );
  113. socket.once('error', tempSocketErr);
  114. }
  115. module.exports = httpProxyClient;