| 'use strict'; |
| |
| /** |
| * Minimal HTTP/S proxy client |
| */ |
| |
| const net = require('net'); |
| const tls = require('tls'); |
| const urllib = require('url'); |
| |
| /** |
| * Establishes proxied connection to destinationPort |
| * |
| * httpProxyClient("http://localhost:3128/", 80, "google.com", function(err, socket){ |
| * socket.write("GET / HTTP/1.0\r\n\r\n"); |
| * }); |
| * |
| * @param {String} proxyUrl proxy configuration, etg "http://proxy.host:3128/" |
| * @param {Number} destinationPort Port to open in destination host |
| * @param {String} destinationHost Destination hostname |
| * @param {Function} callback Callback to run with the rocket object once connection is established |
| */ |
| function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) { |
| let proxy = urllib.parse(proxyUrl); |
| |
| // create a socket connection to the proxy server |
| let options; |
| let connect; |
| let socket; |
| |
| options = { |
| host: proxy.hostname, |
| port: Number(proxy.port) ? Number(proxy.port) : (proxy.protocol === 'https:' ? 443 : 80) |
| }; |
| |
| if (proxy.protocol === 'https:') { |
| // we can use untrusted proxies as long as we verify actual SMTP certificates |
| options.rejectUnauthorized = false; |
| connect = tls.connect.bind(tls); |
| } else { |
| connect = net.connect.bind(net); |
| } |
| |
| // Error harness for initial connection. Once connection is established, the responsibility |
| // to handle errors is passed to whoever uses this socket |
| let finished = false; |
| let tempSocketErr = function (err) { |
| if (finished) { |
| return; |
| } |
| finished = true; |
| try { |
| socket.destroy(); |
| } catch (E) { |
| // ignore |
| } |
| callback(err); |
| }; |
| |
| socket = connect(options, () => { |
| if (finished) { |
| return; |
| } |
| |
| let reqHeaders = { |
| Host: destinationHost + ':' + destinationPort, |
| Connection: 'close' |
| }; |
| if (proxy.auth) { |
| reqHeaders['Proxy-Authorization'] = 'Basic ' + new Buffer(proxy.auth).toString('base64'); |
| } |
| |
| socket.write( |
| // HTTP method |
| 'CONNECT ' + destinationHost + ':' + destinationPort + ' HTTP/1.1\r\n' + |
| |
| // HTTP request headers |
| Object.keys(reqHeaders).map(key => key + ': ' + reqHeaders[key]).join('\r\n') + |
| |
| // End request |
| '\r\n\r\n'); |
| |
| let headers = ''; |
| let onSocketData = chunk => { |
| let match; |
| let remainder; |
| |
| if (finished) { |
| return; |
| } |
| |
| headers += chunk.toString('binary'); |
| if ((match = headers.match(/\r\n\r\n/))) { |
| socket.removeListener('data', onSocketData); |
| |
| remainder = headers.substr(match.index + match[0].length); |
| headers = headers.substr(0, match.index); |
| if (remainder) { |
| socket.unshift(new Buffer(remainder, 'binary')); |
| } |
| |
| // proxy connection is now established |
| finished = true; |
| |
| // check response code |
| match = headers.match(/^HTTP\/\d+\.\d+ (\d+)/i); |
| if (!match || (match[1] || '').charAt(0) !== '2') { |
| try { |
| socket.destroy(); |
| } catch (E) { |
| // ignore |
| } |
| return callback(new Error('Invalid response from proxy' + (match && ': ' + match[1] || ''))); |
| } |
| |
| socket.removeListener('error', tempSocketErr); |
| return callback(null, socket); |
| } |
| }; |
| socket.on('data', onSocketData); |
| }); |
| |
| socket.once('error', tempSocketErr); |
| } |
| |
| module.exports = httpProxyClient; |