UNPKG

wmod-proxy

Version:
402 lines (318 loc) 16.8 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ProxyError = exports.MANIFEST_FILE = void 0; exports["default"] = proxy; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapNativeSuper")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _fs = _interopRequireDefault(require("fs")); var _path = require("path"); var _mockttp = require("mockttp"); var _service = require("./service"); var _indicator = require("./indicator"); var _logger = require("./logger"); var _model = require("./model"); function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } var MANIFEST_FILE = 'manifest.js'; exports.MANIFEST_FILE = MANIFEST_FILE; function proxy(_x, _x2) { return _proxy.apply(this, arguments); } function _proxy() { _proxy = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(port, wmod) { var manifestPath, manifestSource, manifest, logger, keys, server, reqResMap, caFingerprint; return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: manifestPath = (0, _path.join)(wmod, MANIFEST_FILE); if ((0, _service.fileExists)(manifestPath)) { _context2.next = 3; break; } throw new ProxyError("Manifest file not found in: \"".concat(manifestPath, "\"")); case 3: manifestSource = require((0, _path.resolve)(manifestPath)); manifest = (0, _service.readManifest)(manifestSource); logger = (0, _logger.getLogger)(); // Get keys _context2.next = 8; return getSslKeys(); case 8: keys = _context2.sent; // Create server server = (0, _mockttp.getLocal)({ https: keys, http2: false }); // Configure server // An auxiliary map for tracking requests and responses reqResMap = new Map(); manifest.rules.forEach(function (_ref, ruleId) { var url = _ref.url, hostname = _ref.hostname, path = _ref.path, action = _ref.action, method = _ref.method; // TODO: remove this later when mockttp can handle RegExp hostnames if (hostname != null && hostname instanceof RegExp) { throw new ProxyError('Not implemented: hostname is RegExp (1)'); } logger.dbg("Action: method=".concat(method)); // Build rule var builder; builder = server.forAnyRequest(); if (path != null || url != null || hostname != null || method != null) { builder = builder.matching(function (req) { logger.dbg("".concat(ruleId, ": r------: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); if (method != null && req.method !== method) { logger.dbg("".concat(ruleId, ": -M-----: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); return false; } var reqUrl = new URL(req.url); if (hostname != null && reqUrl.hostname !== hostname) { logger.dbg("".concat(ruleId, ": -H-----: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); return false; } if (path != null) { if (path instanceof RegExp ? path.test(reqUrl.pathname) : path === reqUrl.pathname) { logger.dbg("".concat(ruleId, ": -p-----: [").concat($id(req), "] ").concat($method(req), " ").concat($path(req))); return true; } } if (url != null) { if (url instanceof RegExp ? url.test(req.url) : url === req.url) { logger.dbg("".concat(ruleId, ": -u-----: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); return true; } } logger.dbg("".concat(ruleId, ": -S-----: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); return false; }); } // Check what kind of rule we have if ((0, _model.isResolvedManifestRuleActionScripts)(action)) { // If there are files to inject... if (action.files.filter(function (_ref2) { var inject = _ref2.inject; return inject; }).length > 0) { // Try to inject builder.thenPassThrough({ beforeRequest: function beforeRequest(req) { reqResMap.set(req.id, req); logger.dbg("".concat(ruleId, ": --r----: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); }, beforeResponse: function () { var _beforeResponse = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(res) { var req, bodyText, headersAll, isReplaced; return _regenerator["default"].wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: logger.dbg("".concat(ruleId, ": ---r---: [").concat($id(res), "]")); if (reqResMap.has(res.id)) { _context.next = 4; break; } logger.dbg("".concat(ruleId, ": ----?--: [").concat($id(res), "]")); return _context.abrupt("return", undefined); case 4: req = reqResMap.get(res.id); if (isContentTypeHtml(res.headers)) { _context.next = 8; break; } logger.dbg("".concat(ruleId, ": ----T--: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); return _context.abrupt("return", undefined); case 8: // Remove used req-res map entry reqResMap["delete"](res.id); logger.dbg("".concat(ruleId, ": ----r--: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); _context.next = 12; return res.body.getText(); case 12: bodyText = _context.sent; // Removing Content-Security-Policy // TODO: ensure only object-src and script-src works headersAll = res.headers; if (headersAll['content-security-policy'] != null) { delete headersAll['content-security-policy']; } if (!(bodyText != null)) { _context.next = 20; break; } // For debugging // saveFile(`./${req.id}.html`, `${$method(req)} ${req.url}\n${bodyText}`); isReplaced = false; bodyText = bodyText.replace(/<body[^>]*>/, function (match) { isReplaced = true; var result = match + "<script>".concat((0, _indicator.getIndicatorScript)(manifest), "</script>"); action.files.filter(function (_ref3) { var inject = _ref3.inject; return inject; }).forEach(function (file) { var scriptName = (0, _path.basename)(file.path); var scriptSitePath = "/".concat(scriptName); logger.info("".concat(ruleId, ": -----i-: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req), " <== \"").concat(scriptSitePath, "\"")); result += "<script src=\"".concat(scriptSitePath, "\"></script>"); }); return result; }); if (isReplaced) { _context.next = 20; break; } throw new ProxyError('Cannot find the pattern for script injection'); case 20: logger.dbg("".concat(ruleId, ": ------r: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); return _context.abrupt("return", { body: bodyText, headers: headersAll }); case 22: case "end": return _context.stop(); } } }, _callee); })); function beforeResponse(_x3) { return _beforeResponse.apply(this, arguments); } return beforeResponse; }() }); } else { builder.thenPassThrough(); } } else if ((0, _model.isManifestRuleActionResponse)(action)) { builder.thenCallback(function (req) { logger.info("".concat(ruleId, ": --s----: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); return { statusCode: action.response }; }); } else if ((0, _model.isManifestRuleActionClose)(action) && action.close) { builder.thenCallback(function (req) { logger.info("".concat(ruleId, ": --c----: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req))); return 'close'; }); } // Serving GET file requests if ((0, _model.isResolvedManifestRuleActionScripts)(action)) { action.files.forEach(function (file) { var scriptName = (0, _path.basename)(file.path); var scriptSitePath = "/".concat(scriptName); // Serving our injected scripts var builder = server.forGet(scriptSitePath); if (hostname != null) { builder = builder.forHostname(hostname); } builder.thenCallback(function (req) { logger.info("".concat(ruleId, ": --f----: [").concat($id(req), "] ").concat($method(req), " ").concat($url(req), " <== ").concat((0, _path.join)(wmod, file.path))); return { statusCode: 200, headers: { 'content-type': 'application/javascript; charset=utf-8' }, body: _fs["default"].readFileSync((0, _path.join)(wmod, file.path)) }; }); }); } }); server.forUnmatchedRequest().thenPassThrough(); // Run server _context2.next = 15; return server.start(port); case 15: caFingerprint = (0, _mockttp.generateSPKIFingerprint)(keys.cert); // Print out the server details for manual configuration: logger.info("Server running on port ".concat(server.port)); logger.info("CA cert fingerprint ".concat(caFingerprint)); return _context2.abrupt("return", server); case 19: case "end": return _context2.stop(); } } }, _callee2); })); return _proxy.apply(this, arguments); } function truncate(str, n) { return str.length > n ? str.substring(0, n - 1) + '...' : str; } function $id(obj) { return obj.id.substring(0, 6); } function $url(obj) { return truncate(obj.url, 100); } function $path(obj) { return truncate(obj.path, 100); } function $method(obj) { return obj.method.padEnd(7, ' '); } function getSslKeys() { return _getSslKeys.apply(this, arguments); } function _getSslKeys() { _getSslKeys = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3() { var keyPemPath, certPemPath, _yield$generateCACert, key, cert; return _regenerator["default"].wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: keyPemPath = 'cert/key.pem'; certPemPath = 'cert/cert.pem'; if (!(!(0, _service.fileExists)(keyPemPath) || !(0, _service.fileExists)(certPemPath))) { _context3.next = 10; break; } _context3.next = 5; return (0, _mockttp.generateCACertificate)(); case 5: _yield$generateCACert = _context3.sent; key = _yield$generateCACert.key; cert = _yield$generateCACert.cert; (0, _service.saveFile)(keyPemPath, key); (0, _service.saveFile)(certPemPath, cert); case 10: return _context3.abrupt("return", { key: (0, _service.readFile)(keyPemPath), cert: (0, _service.readFile)(certPemPath) }); case 11: case "end": return _context3.stop(); } } }, _callee3); })); return _getSslKeys.apply(this, arguments); } function isContentTypeHtml(headers) { return headers['content-type'] != null && /^text\/html/.test(headers['content-type']); } var ProxyError = /*#__PURE__*/function (_Error) { (0, _inherits2["default"])(ProxyError, _Error); var _super = _createSuper(ProxyError); function ProxyError() { (0, _classCallCheck2["default"])(this, ProxyError); return _super.apply(this, arguments); } (0, _createClass2["default"])(ProxyError, [{ key: "toString", value: function toString() { return 'Proxy error:\n' + this.message; } }]); return ProxyError; }( /*#__PURE__*/(0, _wrapNativeSuper2["default"])(Error)); exports.ProxyError = ProxyError; //# sourceMappingURL=proxy.js.map