Advertisement
Guest User

NodeJS proxy

a guest
Feb 17th, 2020
696
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env node
  2. 'use strict';
  3. var net = require('net'), tls = require('tls');
  4. var HTTPParser = process.binding('http_parser').HTTPParser;
  5. var http = require('http'), https = require('https');
  6. var url = require('url');
  7.  
  8. function main() {
  9.   //convert `-key value` to cfg[key]=value
  10.   var cfg = process.argv.slice(2/*skip ["node", "xxx.js"]*/).reduce(function (cfg, arg, i, argv) {
  11.     return (i % 2 === 0 && (arg.slice(0, 1) === '-' && (cfg[arg.slice(1)] = argv[i + 1])), cfg);
  12.   }, {local_host: '', local_port: 0, remote_host: '', remote_port: 0, usr: '', pwd: '', as_pac_server: 0});
  13.   cfg.local_host = cfg.local_host || 'localhost';
  14.   cfg.local_port = (cfg.local_port & 0xffff) || 8080;
  15.   cfg.remote_port = (cfg.remote_port & 0xffff) || 8080;
  16.   cfg.as_pac_server = cfg.as_pac_server === 'true';
  17.   cfg.is_remote_https = cfg.is_remote_https === 'true';
  18.   cfg.ignore_https_cert = cfg.ignore_https_cert === 'true';
  19.   cfg.are_remotes_in_pac_https = cfg.are_remotes_in_pac_https === 'true';
  20.  
  21.   if (!cfg.local_host || !cfg.local_port || !cfg.remote_host || !cfg.remote_port || !cfg.usr || !cfg.pwd)
  22.     return console.error('Usage of parameters:\n'
  23.       + '-local_host host\t' + 'Listening address. Default: localhost. (* means all interfaces)\n'
  24.       + '-local_port port\t' + 'Listening port. Default: 8080\n'
  25.       + '-remote_host host\t' + 'Real proxy/PAC server address\n'
  26.       + '-remote_port port\t' + 'Real proxy/PAC server port. Default: 8080\n'
  27.       + '-usr user\t\t' + 'Real proxy/PAC server user id\n'
  28.       + '-pwd password\t\t' + 'Real proxy/PAC user password\n'
  29.       + '-as_pac_server true/false\t' + 'Treat real proxy/PAC server as a PAC server. Default: false\n'
  30.       + '\n'
  31.       + '-is_remote_https true/false\t' + 'Talk to real proxy/PAC server with HTTPS. Default: false\n'
  32.       + '-ignore_https_cert true/false\t' + 'ignore error when verify certificate of real proxy/PAC server. Default: false\n'
  33.       + '-are_remotes_in_pac_https true/false\t' + 'Talk to proxy servers defined in PAC with HTTPS. Default: false\n'
  34.     );
  35.   if (cfg.as_pac_server && (cfg.local_host === '*' || cfg.local_host === '0.0.0.0' || cfg.local_host === '::')) {
  36.     return console.error('when use as a PAC server, the local_host parameter must be a definite address');
  37.   }
  38.   console.log('Using parameters: ' + JSON.stringify(cfg, null, '  '));
  39.   cfg.buf_proxy_basic_auth = new Buffer('Proxy-Authorization: Basic ' + new Buffer(cfg.usr + ':' + cfg.pwd).toString('base64'));
  40.  
  41.   if (cfg.as_pac_server) {
  42.     createPacServer(cfg.local_host, cfg.local_port, cfg.remote_host, cfg.remote_port, cfg.buf_proxy_basic_auth, cfg.is_remote_https, cfg.ignore_https_cert, cfg.are_remotes_in_pac_https);
  43.   } else {
  44.     createPortForwarder(cfg.local_host, cfg.local_port, cfg.remote_host, cfg.remote_port, cfg.buf_proxy_basic_auth, cfg.is_remote_https, cfg.ignore_https_cert);
  45.   }
  46. }
  47.  
  48. var CR = 0xd, LF = 0xa, BUF_CR = new Buffer([0xd]), BUF_CR_LF_CR_LF = new Buffer([0xd, 0xa, 0xd, 0xa]),
  49.   BUF_LF_LF = new Buffer([0xa, 0xa]), BUF_PROXY_CONNECTION_CLOSE = new Buffer('Proxy-Connection: close');
  50. var STATE_NONE = 0, STATE_FOUND_LF = 1, STATE_FOUND_LF_CR = 2;
  51.  
  52. function createPortForwarder(local_host, local_port, remote_host, remote_port, buf_proxy_basic_auth, is_remote_https, ignore_https_cert) {
  53.   net.createServer({allowHalfOpen: true}, function (socket) {
  54.     var realCon = (is_remote_https ? tls : net).connect({
  55.       port: remote_port, host: remote_host, allowHalfOpen: true,
  56.       rejectUnauthorized: !ignore_https_cert /*not used when is_remote_https false*/
  57.     });
  58.     realCon.on('data', function (buf) {
  59.       //console.log('<<<<' + (Date.t=new Date()) + '.' + Date.t.getMilliseconds() + '\n' + buf.toString('ascii'));
  60.       socket.write(buf);
  61.       realCon.__haveGotData = true;
  62.     }).on('end', function () {
  63.       socket.end();
  64.       if (!realCon.__haveGotData && !realCon.__haveShownError) {
  65.         console.error('[LocalProxy(:' + local_port + ')][Connection to ' + remote_host + ':' + remote_port + '] Error: ended by remote peer');
  66.         realCon.__haveShownError = true;
  67.       }
  68.     }).on('close', function () {
  69.       socket.end();
  70.       if (!realCon.__haveGotData && !realCon.__haveShownError) {
  71.         console.error('[LocalProxy(:' + local_port + ')][Connection to ' + remote_host + ':' + remote_port + '] Error: reset by remote peer');
  72.         realCon.__haveShownError = true;
  73.       }
  74.     }).on('error', function (err) {
  75.       console.error('[LocalProxy(:' + local_port + ')][Connection to ' + remote_host + ':' + remote_port + '] ' + err);
  76.       realCon.__haveShownError = true;
  77.     });
  78.  
  79.     var parser = new HTTPParser(HTTPParser.REQUEST);
  80.     parser[HTTPParser.kOnHeadersComplete] = function (versionMajor, versionMinor, headers, method,
  81.                                                       url, statusCode, statusMessage, upgrade,
  82.                                                       shouldKeepAlive) {
  83.       //console.log('---- kOnHeadersComplete----');
  84.       //console.log(arguments);
  85.       parser.__is_headers_complete = true;
  86.       parser.__upgrade = upgrade;
  87.       parser.__method = method;
  88.     };
  89.     //parser[HTTPParser.kOnMessageComplete] = function () {
  90.     //    console.log('---- kOnMessageComplete----');
  91.     //    console.log(arguments);
  92.     //};
  93.  
  94.     var state = STATE_NONE;
  95.  
  96.     socket.on('data', function (buf) {
  97.       if (!parser) {
  98.         realCon.write(buf);
  99.         return
  100.       }
  101.       //console.log('[' + remote_host + ':' + remote_port + ']>>>>' + (Date.t = new Date()) + '.' + Date.t.getMilliseconds() + '\n' + buf.toString('ascii'));
  102.       //var ret = parser.execute(buf);
  103.       //console.log('\n\n----parser result: ' + ret + ' buf len:' + buf.length);
  104.       //realCon.write(buf);
  105.       //return;
  106.  
  107.       var buf_ary = [], unsavedStart = 0, buf_len = buf.length;
  108.  
  109.       for (var i = 0; i < buf_len; i++) {
  110.         //find first LF
  111.         if (state === STATE_NONE) {
  112.           if (buf[i] === LF) {
  113.             state = STATE_FOUND_LF;
  114.           }
  115.           continue;
  116.         }
  117.  
  118.         //find second CR LF or LF
  119.         if (buf[i] === LF) {
  120.           parser.__is_headers_complete = false;
  121.           parser.execute(buf.slice(unsavedStart, i + 1));
  122.  
  123.           if (parser.__is_headers_complete) {
  124.             buf_ary.push(buf.slice(unsavedStart, buf[i - 1] === CR ? i - 1 : i));
  125.             //console.log('insert auth header');
  126.             buf_ary.push(buf_proxy_basic_auth);
  127.             buf_ary.push(state === STATE_FOUND_LF_CR ? BUF_CR_LF_CR_LF : BUF_LF_LF);
  128.  
  129.             // stop intercepting packets if encountered TLS and WebSocket handshake
  130.             if (parser.__method === 5 /*CONNECT*/ || parser.__upgrade) {
  131.               parser.close();
  132.               parser = null;
  133.  
  134.               buf_ary.push(buf.slice(i + 1));
  135.               realCon.write(Buffer.concat(buf_ary));
  136.  
  137.               state = STATE_NONE;
  138.               return;
  139.             }
  140.  
  141.             unsavedStart = i + 1;
  142.             state = STATE_NONE;
  143.           }
  144.           else {
  145.             state = STATE_FOUND_LF;
  146.           }
  147.         }
  148.         else if (buf[i] === CR && state === STATE_FOUND_LF) {
  149.           state = STATE_FOUND_LF_CR;
  150.         } else {
  151.           state = STATE_NONE;
  152.         }
  153.       }
  154.  
  155.       if (unsavedStart < buf_len) {
  156.         buf = buf.slice(unsavedStart, buf_len);
  157.         parser.execute(buf);
  158.         buf_ary.push(buf);
  159.       }
  160.  
  161.       realCon.write(Buffer.concat(buf_ary));
  162.  
  163.     }).on('end', cleanup).on('close', cleanup).on('error', function (err) {
  164.       if (!socket.__cleanup) {
  165.         console.error('[LocalProxy(:' + local_port + ')][Incoming connection] ' + err);
  166.       }
  167.     });
  168.  
  169.     function cleanup() {
  170.       socket.__cleanup = true;
  171.       if (parser) {
  172.         parser.close();
  173.         parser = null;
  174.       }
  175.       realCon.end();
  176.     }
  177.   }).on('error', function (err) {
  178.     console.error('[LocalProxy(:' + local_port + ')] ' + err);
  179.     process.exit(1);
  180.   }).listen(local_port, local_host === '*' ? undefined : local_host, function () {
  181.     console.log('[LocalProxy(:' + local_port + ')] OK: forward http://' + local_host + ':' + local_port + ' to ' + ' to http' + (is_remote_https ? 's' : '') + '://' + remote_host + ':' + remote_port);
  182.   });
  183. }
  184.  
  185. var proxyAddrMap = {};
  186.  
  187. function createPacServer(local_host, local_port, remote_host, remote_port, buf_proxy_basic_auth, is_remote_https, ignore_https_cert, are_remotes_in_pac_https) {
  188.   http.createServer(function (req, res) {
  189.  
  190.     var internal_req = url.parse(req.url);
  191.  
  192.     internal_req.host = remote_host;
  193.     internal_req.port = remote_port;
  194.     req.headers['host'] = remote_host + ':' + remote_port;
  195.     if (!req.headers['authorization']) {
  196.       req.headers['authorization'] = buf_proxy_basic_auth.slice('Proxy-Authorization: '.length).toString();
  197.     }
  198.     internal_req.headers = req.headers;
  199.     internal_req.rejectUnauthorized = !ignore_https_cert; //only used for SSL
  200.  
  201.     (is_remote_https ? https : http).get(internal_req, function (internal_res) {
  202.  
  203.       delete internal_res.headers['content-length'];
  204.       delete internal_res.headers['transfer-encoding'];
  205.  
  206.       res.writeHead(internal_res.statusCode, internal_res.headers);
  207.       res.__haveWrittenData = true;
  208.  
  209.       var buf_ary = [];
  210.       internal_res.on('data', function (buf) {
  211.         // console.log('<<<<' + (Date.t=new Date()) + '.' + Date.t.getMilliseconds() + '\n' + buf.toString('ascii'));
  212.         buf_ary.push(buf);
  213.       }).on('end', function () {
  214.         var s = Buffer.concat(buf_ary).toString();
  215.         buf_ary = [];
  216.         s = s.replace(/\bPROXY\s+([^'":;\s]+):(\d+)/g, function (_, host, port) {
  217.           var remoteAddr = host + ':' + port;
  218.           var _local_port = proxyAddrMap[remoteAddr];
  219.           if (!_local_port) {
  220.             _local_port = local_port + Object.keys(proxyAddrMap).length + 1;
  221.             proxyAddrMap[remoteAddr] = _local_port;
  222.             createPortForwarder(local_host, _local_port, host, Number(port), buf_proxy_basic_auth, are_remotes_in_pac_https, ignore_https_cert);
  223.           }
  224.           return 'PROXY ' + local_host + ':' + _local_port;
  225.         });
  226.         //console.log('return patched pac');
  227.         res.end(s);
  228.       }).on('error', function (err) {
  229.         res.end();
  230.         console.error('[LocalPAC][Reading response from ' + remote_host + ':' + remote_port + '] ' + err);
  231.       });
  232.     }).on('error', function (err) {
  233.       if (!res.__haveWrittenData) {
  234.         res.statusCode = 500;
  235.         res.end();
  236.       }
  237.       console.error('[LocalPAC][Connection to ' + remote_host + ':' + remote_port + '] ' + err);
  238.     });
  239.     res.on('error', function (err) {
  240.       console.error('[LocalPAC][Writing response] ' + err);
  241.     });
  242.   }).on('error', function (err) {
  243.     console.error('[LocalPAC] ' + err);
  244.     process.exit(1);
  245.   }).listen(local_port, local_host === '*' ? undefined : local_host, function () {
  246.     console.log('[LocalPAC] OK: forward http://' + local_host + ':' + local_port + ' to http' + (is_remote_https ? 's' : '') + '://' + remote_host + ':' + remote_port);
  247.   });
  248. }
  249.  
  250. main();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement