UNPKG

catproxy

Version:

a node proxy or host change tools

395 lines (356 loc) 11.6 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.requestUpgradeHandler = exports.requestConnectHandler = exports.requestHandler = undefined; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; var _url = require('url'); var _url2 = _interopRequireDefault(_url); var _promise = require('promise'); var _promise2 = _interopRequireDefault(_promise); var _buffer = require('buffer'); var _log = require('./log'); var _log2 = _interopRequireDefault(_log); var _net = require('net'); var _net2 = _interopRequireDefault(_net); var _serverManager = require('./serverManager'); var _serverManager2 = _interopRequireDefault(_serverManager); var _defCfg = require('./config/defCfg'); var _config = require('./config/config'); var config = _interopRequireWildcard(_config); var _http = require('http'); var _http2 = _interopRequireDefault(_http); var _https = require('https'); var _https2 = _interopRequireDefault(_https); var _changeHost = require('./changeHost'); var _changeHost2 = _interopRequireDefault(_changeHost); var _cert = require('./cert/cert.js'); var _responseService = require('./responseService'); var _responseService2 = _interopRequireDefault(_responseService); var _evt = require('./evt'); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var headerWsTest = /upgrade\s*:\s*websocket\s*\n/i; // 升级到 ws wss var upgradeToWebSocket = function upgradeToWebSocket(req, cltSocket, head) { var com = this; // 不是upgrade websocket请求 直接放弃 if (req.headers.upgrade.toLowerCase() !== 'websocket') { cltSocket.destroy(); return; } // 禁止超时 cltSocket.setTimeout(0); // 禁止纳格(Nagle)算法。默认情况下TCP连接使用纳格算法,这些连接在发送数据之前对数据进行缓冲处理。 将noDelay设成true会在每次socket.write()被调用时立刻发送数据。noDelay默认为true。 cltSocket.setNoDelay(true); // 启用长连接 cltSocket.setKeepAlive(true, 0); var isSecure = req.connection.encrypted || req.connection.pai; var url = req.url; var hostname = req.headers.host.split(':'); var port = hostname[1] ? hostname[1] : isSecure ? 443 : 80; hostname = hostname[0]; var options = { port: port, path: url, method: req.method, headers: req.headers }; if (isSecure) { var _getCert = (0, _cert.getCert)(hostname); var key = _getCert.privateKey; var cert = _getCert.cert; options.key = key; options.cert = cert; options.rejectUnauthorized = false; } var _config$get = config.get(); var p = _config$get.port; var hp = _config$get.httpsPort; var isServerPort = +port === +p; if (isSecure) { isServerPort = +hp === +port; } (0, _changeHost2.default)(hostname, isServerPort).then(function (ip) { options.hostname = ip; var proxyReq = (isSecure ? _https2.default : _http2.default).request(options, function (proxyRes) { if (!proxyRes.upgrade) { proxyRes.end && proxyRes.end(); } }); proxyReq.on('upgrade', function (proxyRes, proxySocket, proxyHead) { var result = {}; Object.defineProperties(result, { host: { value: req.headers.host, enumerable: true }, port: { value: port, enumerable: true }, headers: req.headers, protocol: { value: isSecure ? "wss" : "ws", enumerable: true } }); _evt.pipeRequest.call(com, result); proxySocket.on('error', function (err) { return _log2.default.error(err); }); proxySocket.on('end', function () { cltSocket.end(); }); proxySocket.setTimeout(0); proxySocket.setNoDelay(true); proxySocket.setKeepAlive(true, 0); cltSocket.on('error', function (err) { proxySocket.end(); _log2.default.error(err); }); if (proxyHead && proxyHead.length) { proxySocket.unshift(proxyHead); } var headers = Object.keys(proxyRes.headers).reduce(function (head, key) { var value = proxyRes.headers[key]; if (!Array.isArray(value)) { head.push(key + ': ' + value); return head; } for (var i = 0; i < value.length; i++) { head.push(key + ': ' + value[i]); } return head; }, ['HTTP/1.1 101 Switching Protocols']).join('\r\n') + '\r\n\r\n'; // 写入头文件 cltSocket.write(headers); proxySocket.pipe(cltSocket).pipe(proxySocket); }); proxyReq.on('error', function (err) { _log2.default.error(err); cltSocket.end(); }); req.pipe(proxyReq); }, function (error) { _log2.default.error(error); cltSocket.end(); }); }; // 请求到后的解析 var requestHandler = exports.requestHandler = function requestHandler(req, res) { var com = this; var isSecure = req.connection.encrypted || req.connection.pai, headers = req.headers, method = req.method, host = headers.host, protocol = !!isSecure ? "https" : 'http', fullUrl = /^http.*/.test(req.url) ? req.url : protocol + '://' + host + req.url, urlObject = _url2.default.parse(fullUrl), port = urlObject.port || (protocol === "http" ? '80' : '443'), pathStr = urlObject.path, pathname = urlObject.pathname, visitUrl = protocol + "://" + host + pathname; _log2.default.verbose("request url: " + fullUrl); // 请求信息 var reqInfo = { headers: headers, host: host, method: method, protocol: protocol, port: port, path: pathStr }; Object.defineProperties(reqInfo, { req: { writable: false, value: req, enumerable: true }, originalFullUrl: { writable: false, value: fullUrl, enumerable: true }, originalUrl: { writable: false, value: visitUrl, enumerable: true }, startTime: { writable: false, value: new Date().getTime(), enumerable: true } }); // 响应信息 var resInfo = { headers: {} }; Object.defineProperties(resInfo, { res: { writable: false, value: res, enumerable: true }, originalFullUrl: { writable: false, value: fullUrl, enumerable: true }, originalUrl: { writable: false, value: visitUrl, enumerable: true } }); // 调用相应模块 _responseService2.default.call(com, reqInfo, resInfo); var reqBodyData = []; var l = 0; var end = function end() { req.emit('reqBodyDataReady', null, _buffer.Buffer.concat(reqBodyData)); }; var data = function data(buffer) { l = l + buffer.length; reqBodyData.push(buffer); // 超过长度了 if (l > _defCfg.LIMIT_SIZE) { req.pause(); req.unpipe(); req.removeListener('data', data); req.removeListener('end', end); req.emit('reqBodyDataReady', { message: 'request entity too large', status: _defCfg.STATUS.LIMIT_ERROR }, _buffer.Buffer.concat(reqBodyData)); } }; req.on('data', data).on('end', end).on('error', function (err) { _log2.default.error('error req', err); }); }; /** * connect转发请求处理 * @param req * @param cltSocket * @param head */ var requestConnectHandler = exports.requestConnectHandler = function requestConnectHandler(req, cltSocket, head) { var com = this; return new _promise2.default(function (resolve, reject) { if (!head || head.length === 0) { cltSocket.once('data', function (chunk) { resolve(chunk); }); } else { resolve(head); } cltSocket.write('HTTP/' + req.httpVersion + ' 200 Connection Established\r\n' + 'Proxy-agent: Node-CatProxy\r\n'); if (req.headers['proxy-connection'] === 'keep-alive') { cltSocket.write('Proxy-Connection: keep-alive\r\n'); cltSocket.write('Connection: keep-alive\r\n'); } cltSocket.write('\r\n'); }).then(function (first) { cltSocket.pause(); // log.debug("first data", first[0]); var opt = config.get(); var reqUrl = 'http://' + req.url; var srvUrl = _url2.default.parse(reqUrl); var crackHttps = void 0; if (typeof opt.breakHttps === 'boolean') { crackHttps = opt.breakHttps; } else if (_typeof(opt.breakHttps) === 'object' && opt.breakHttps.length) { crackHttps = opt.breakHttps.some(function (current) { return new RegExp(current).test(srvUrl.hostname); }); } // 如果当前状态是 破解状态 并且有排除列表 if (crackHttps && _typeof(opt.excludeHttps) === 'object' && opt.excludeHttps) { crackHttps = !opt.excludeHttps.some(function (current) { return new RegExp(current).test(srvUrl.hostname); }); } // * - an incoming connection using SSLv3/TLSv1 records should start with 0x16 // * - an incoming connection using SSLv2 records should start with the record size // * and as the first record should not be very big we can expect 0x80 or 0x00 (the MSB is a flag) // 如果需要捕获https的请求 // 访问地址直接是ip,跳过不代理 if (crackHttps && (first[0] == 0x16 || first[0] == 0x80 || first[0] == 0x00)) { _log2.default.verbose('crack https ' + reqUrl); (0, _serverManager2.default)(opt.sni === 1 ? "" : srvUrl.hostname).then(function (_ref) { var port = _ref.port; var server = _ref.server; // 与服务器绑定 server.catProxy = com.catProxy; var srvSocket = _net2.default.connect(port, "localhost", function () { srvSocket.pipe(cltSocket).pipe(srvSocket); cltSocket.emit('data', first); cltSocket.resume(); }); srvSocket.on('error', function (err) { cltSocket.end(); _log2.default.error('crack https请求出现错误: ' + err); }); cltSocket.on('error', function (err) { _log2.default.error('crack https请求出现错误: ' + err); srvSocket.end(); }); }); } else { (function () { // 不认识的协议或者 不破解的https直接连接对应的服务器 _log2.default.verbose('pipe request ' + reqUrl); var result = {}; Object.defineProperties(result, { host: { value: srvUrl.host, enumerable: true }, headers: req.headers, port: { value: srvUrl.port, enumerable: true }, protocol: { value: headerWsTest.test(first.toString()) ? "ws" : "http", enumerable: true } }); _evt.pipeRequest.call(com, result); var srvSocket = _net2.default.connect(srvUrl.port, srvUrl.hostname, function () { srvSocket.pipe(cltSocket).pipe(srvSocket); cltSocket.emit('data', first); cltSocket.resume(); }); cltSocket.on('error', function (err) { _log2.default.error('转发请求出现错误: ' + err); srvSocket.end(); }); srvSocket.on('error', function (err) { cltSocket.end(); _log2.default.error('转发请求出现错误: ' + err); }); })(); } }).then(null, function (err) { return _log2.default.error(err); }); }; /** * upgrade ws转发请求处理 * @param req * @param socket */ var requestUpgradeHandler = exports.requestUpgradeHandler = function requestUpgradeHandler(req, cltSocket, head) { // 不是get 取不到 upgrade就放弃 if (req.method === 'GET' && req.headers.upgrade) { upgradeToWebSocket.call(this, req, cltSocket, head); } else { cltSocket.destroy(); } }; exports.default = { requestHandler: requestHandler, requestConnectHandler: requestConnectHandler, requestUpgradeHandler: requestUpgradeHandler };