"use strict"; /** * Creates new instance of `GlobalProxy`. * * @class * @name GlobalProxy * @classdesc - Contains methods to run and manage global transparent proxy. * @arg {object} [opts] - global proxy options * @arg {number} [opts.port=0] - global proxy port * @arg {number} [opts.timeout=0] - global proxy timeout * @arg {boolean} [opts.installCertificate=false] - flag to install global * proxy certificate as trusted in order to manage `https` connection or no * @arg {?string} [opts.rootPath] - Folder where proxy starts in order to * generate self-signed certificate. By default is `current work directory`. * @arg {boolean} [opts.useCache=false] - flag to cache and take from cache responses */ var fs = require("fs"); var path = require("path"); var util = require("util"); var expect = require("chai").expect; const getPort = require("get-port"); var MitmProxy = require("http-mitm-proxy").Proxy; var spawn = require("cross-spawn"); var U = require("glace-utils"); var BaseProxy = require("./baseProxy"); var cache = require("./middleware/cache"); var middleware = require("./middleware"); var LOG = U.logger; var _onError = MitmProxy.prototype._onError; /** * Patch mitm proxy error processing, in order to avoid default response * finalizing on reconnect. * * @ignore */ MitmProxy.prototype._onError = function (kind, ctx, err) { if (!ctx) return _onError.apply(this, arguments); var req = ctx.clientToProxyRequest; if (req._reconnect > 0 && !req.socket.destroyed) { this.onErrorHandlers.forEach(function(handler) { return handler(ctx, err, kind); }); } else { _onError.apply(this, arguments); }; }; var GlobalProxy = function (opts) { opts = U.defVal(opts, {}); BaseProxy.call(this, opts); this._installCertificate = U.defVal(opts.installCertificate, false); this._sslCaDir = U.defVal(opts.sslCaDir, path.resolve(U.cwd, ".http-mitm-proxy")); this._certificatePath = path.resolve(this._sslCaDir, "certs", "ca.pem"); this._proxy = new MitmProxy(); this._proxy.onError((ctx, err) => { if (!ctx) { LOG.error(err); return; } var req = ctx.clientToProxyRequest; if (req._reconnect > 0 && !req.socket.destroyed) { LOG.warn(util.format("Request reconnected", U.getReqKey(req))); req._reconnect--; this._proxy._onHttpServerRequest(ctx.isSSL, ctx.clientToProxyRequest, ctx.proxyToClientResponse); } else { LOG.error(util.format(U.getReqKey(req), err)); }; }); this._proxy.onRequest(async (ctx, callback) => { this.req = ctx.clientToProxyRequest; if (this.req._reconnect === undefined) { this.req._reconnect = this._reconnect; }; this.res = ctx.proxyToClientResponse; for (var mw of middleware) if (await mw.call(this)) return; delete this.req; delete this.res; return callback(); }); // TODO not sure that it works reliable this._proxy.onRequestData((ctx, chunk, callback) => { if (ctx.clientToProxyRequest.body) { chunk = new Buffer(""); }; return callback(null, chunk); }); this._proxy.onResponse((ctx, callback) => { if (ctx.clientToProxyRequest.body) { ctx.proxyToServerRequest.end(ctx.clientToProxyRequest.body); }; return callback(null); }); }; util.inherits(GlobalProxy, BaseProxy); module.exports = GlobalProxy; /** * Starts global proxy if it's not started yet. * * @async * @method */ GlobalProxy.prototype.start = async function () { if (this.isRunning) return; if (!this._port) this._port = await getPort(); return new Promise((resolve, reject) => { this._proxy.listen({ port: this._port, silent: true, sslCaDir: this._sslCaDir, timeout: this._timeout }, err => { if (err) reject(err); resolve(); }); }).then(() => cache.init()).then(() => { this.isRunning = true; if (this._installCertificate) { if (process.platform !== "win32") { throw new Error("For your platform certificate" + "installation isn't implemented"); }; expect(fs.existsSync(this._certificatePath), `Proxy certificate ${this._certificatePath} is absent`) .to.be.true; var proc = spawn.sync("certutil", [ "-addstore", "-enterprise", "-f", "Root", this._certificatePath ]); if (proc.status !== 0) { throw new Error( "Can't install proxy certificate as trusted:\n" + proc.stdout.toString()); }; }; }); }; /** * Stops global proxy if it's not stopped yet. * * @method */ GlobalProxy.prototype.stop = function () { if (!this.isRunning) return; this._proxy.close(); this.isRunning = false; };