UNPKG

allproxy

Version:

AllProxy: MITM HTTP Debugging Tool.

319 lines 16.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FreshFirefox = exports.NSS_DIR = void 0; const _ = __importStar(require("lodash")); const path = __importStar(require("path")); const constants_1 = require("./constants"); const browsers_1 = require("./browsers"); const promise_1 = require("./util/promise"); const error_1 = require("./util/error"); const fs_1 = require("./util/fs"); const process_management_1 = require("./util/process-management"); const message_server_1 = require("./message-server"); const cert_check_server_1 = require("./cert-check-server"); const FIREFOX_PREF_REGEX = /\w+_pref\("([^"]+)", (.*)\);/; let profileSetupBrowser; let browsers = {}; exports.NSS_DIR = path.join(constants_1.APP_ROOT, 'nss'); const testCertutil = (command, options) => { return (0, process_management_1.spawnToResult)(command, ['-h'], options) .then((output) => output.exitCode === 1 && output.stderr.includes("Utility to manipulate NSS certificate databases")) .catch((e) => { if (!(0, error_1.isErrorLike)(e) || e.code !== 'ENOENT') { console.error(`Failed to run ${command}`); console.error(e); } return false; }); }; const getCertutilCommand = _.memoize(() => __awaiter(void 0, void 0, void 0, function* () { // If a working certutil is available in our path, we're all good if (yield testCertutil('certutil')) return { command: 'certutil' }; // If not, try to use the relevant bundled version const bundledCertUtil = path.join(exports.NSS_DIR, process.platform, 'certutil'); if (process.platform !== 'linux') { if (yield testCertutil(bundledCertUtil)) { return { command: bundledCertUtil }; } else { //throw new Error("No certutil available"); console.log("No certutil available"); return; } } const certutilEnv = Object.assign(Object.assign({}, process.env), { // The linux bundle includes most required libs, but we need to make sure it's // in the search path so they get used, in case they're not installed elsewhere. LD_LIBRARY_PATH: process.env.LD_LIBRARY_PATH ? `${path.join(exports.NSS_DIR, process.platform)}:${process.env.LD_LIBRARY_PATH}` : path.join(exports.NSS_DIR, process.platform) }); if (yield testCertutil(bundledCertUtil, { env: certutilEnv })) { return { command: bundledCertUtil, options: { env: certutilEnv } }; } else { //throw new Error("No certutil available"); console.log("No certutil available"); return; } })); class FreshFirefox { constructor(config) { this.config = config; this.id = 'fresh-firefox'; this.version = '1.1.0'; this.firefoxProfilePath = path.join(this.config.configPath, 'firefox-profile'); } isActive(proxyPort) { return browsers[proxyPort] != null && !!browsers[proxyPort].pid; } isActivable() { return __awaiter(this, void 0, void 0, function* () { const availableBrowsers = yield (0, browsers_1.getAvailableBrowsers)(this.config.configPath); const firefoxBrowser = _.find(availableBrowsers, { name: 'firefox' }); return !!firefoxBrowser && // Must have Firefox installed parseInt(firefoxBrowser.version.split('.')[0], 0) >= 58 && // Must use cert9.db (yield getCertutilCommand().then(() => true).catch(() => false)); // Must have certutil available }); } startFirefox(initialServer, proxyPort, existingPrefs = {}) { return __awaiter(this, void 0, void 0, function* () { const initialUrl = initialServer.url; const browser = yield (0, browsers_1.launchBrowser)(initialUrl, { browser: 'firefox', profile: this.firefoxProfilePath, proxy: proxyPort ? `127.0.0.1:${proxyPort}` : undefined, prefs: _.assign(existingPrefs, proxyPort ? { // By default browser-launcher only configures HTTP, so we need to add HTTPS: // 'network.proxy.ssl': '"127.0.0.1"', // 'network.proxy.ssl_port': proxyPort, // The above browser-launcher proxy settings should do this, but don't seem to // reliably overwrite existing values, so we set them explicitly. 'network.proxy.http': '"127.0.0.1"', 'network.proxy.http_port': proxyPort, // Don't intercept our cert testing requests 'network.proxy.no_proxies_on': "\"" + initialServer.host + "\"", 'network.proxy.http.no_proxies_on': "\"" + initialServer.host + "\"", // Send localhost reqs via the proxy too 'network.proxy.allow_hijacking_localhost': true, } : {}, { // Disable the noisy captive portal check requests 'network.captive-portal-service.enabled': false, // Disable some annoying tip messages 'browser.chrome.toolbar_tips': false, // Ignore available updates: "app.update.auto": false, "browser.startup.homepage_override.mstone": "\"ignore\"", // Disable exit warnings: "browser.showQuitWarning": false, "browser.tabs.warnOnClose": false, "browser.tabs.warnOnCloseOtherTabs": false, // Disable various first-run things: "browser.uitour.enabled": false, 'browser.usedOnWindows10': true, "browser.usedOnWindows10.introURL": "\"\"", 'datareporting.healthreport.service.firstRun': false, 'toolkit.telemetry.reportingpolicy.firstRun': false, 'browser.reader.detectedFirstArticle': false, "datareporting.policy.dataSubmissionEnabled": false, "datareporting.policy.dataSubmissionPolicyAccepted": false, "datareporting.policy.dataSubmissionPolicyBypassNotification": true, "trailhead.firstrun.didSeeAboutWelcome": true, // Refresh all state on shutdown: "privacy.history.custom": true, "privacy.sanitize.sanitizeOnShutdown": true, "privacy.clearOnShutdown.cache": true, "privacy.clearOnShutdown.cookies": true, "privacy.clearOnShutdown.downloads": true, "privacy.clearOnShutdown.formdata": true, "privacy.clearOnShutdown.history": true, "privacy.clearOnShutdown.offlineApps": true, "privacy.clearOnShutdown.sessions": true, "privacy.clearOnShutdown.siteSettings": true, // Must be false, or 1st startup 2nd start opens a blank page, not the target URL: "privacy.clearOnShutdown.openWindows": false }) }, this.config.configPath); console.log('Firefox started'); if (browser.process.stdout) browser.process.stdout.pipe(process.stdout); if (browser.process.stderr) browser.process.stderr.pipe(process.stderr); const normalStop = browser.stop.bind(browser); browser.stop = function () { return __awaiter(this, void 0, void 0, function* () { if (process.platform === "win32") { // Firefox spawns a child process on Windows, and doesn't let us kill it at all. // To fix this, we kill all firefox instances that were started with this exact same URL. yield (0, process_management_1.windowsKill)(`CommandLine Like '%\\\\firefox.exe%${initialUrl}'`).catch(console.error); } else { normalStop(); } }); }; return browser; }); } // Create the profile. We need to run FF to do its setup, then close it & edit more ourselves. setupFirefoxProfile() { return __awaiter(this, void 0, void 0, function* () { const messageServer = new message_server_1.MessageServer(this.config, `HTTP Toolkit is preparing a Firefox profile, please wait...`); yield messageServer.start(); let messageShown = messageServer.waitForSuccess().catch(console.error); profileSetupBrowser = yield this.startFirefox(messageServer); profileSetupBrowser.process.once('close', (exitCode) => { console.log("Profile setup Firefox closed"); messageServer.stop(); profileSetupBrowser = undefined; if (messageShown !== true) { console.error(`Firefox profile setup failed with code ${exitCode}`); (0, fs_1.deleteFolder)(this.firefoxProfilePath).catch(console.warn); } }); yield messageShown; messageShown = true; yield (0, promise_1.delay)(200); // Tiny delay, so firefox can do initial setup tasks // Tell firefox to shutdown, and wait until it does. profileSetupBrowser.stop(); yield new Promise((resolve) => { if (!profileSetupBrowser) return resolve(); else profileSetupBrowser.process.once('close', resolve); }); // Once firefox has shut, rewrite the certificate database of the newly created profile: const certutil = yield getCertutilCommand(); if (certutil) { const certUtilResult = yield (0, process_management_1.spawnToResult)(certutil.command, [ '-A', '-d', `sql:${this.firefoxProfilePath}`, '-t', 'C,,', '-i', this.config.https.certPath, '-n', 'AllProxy' ], certutil.options || {}); if (certUtilResult.exitCode !== 0) { console.error(certUtilResult.stdout); console.error(certUtilResult.stderr); //throw new Error(`Certutil firefox profile setup failed with code ${certUtilResult.exitCode}`); console.log(`Certutil firefox profile setup failed with code ${certUtilResult.exitCode}`); return; } } }); } activate(proxyPort) { return __awaiter(this, void 0, void 0, function* () { if (this.isActive(proxyPort) || !!profileSetupBrowser) return; const firefoxPrefsFile = path.join(this.firefoxProfilePath, 'prefs.js'); let existingPrefs = {}; if ((yield (0, fs_1.canAccess)(firefoxPrefsFile)) === false) { /* First time, we do a separate pre-usage startup & stop, without the proxy, for certificate setup. This helps avoid initial Firefox profile setup request noise, and tidies up some awkward UX where firefox likes to open extra welcome windows/tabs on first run. */ yield this.setupFirefoxProfile(); } // We need to preserve & reuse any existing preferences, to avoid issues // where on pref setup firefox behaves badly (opening a 2nd window) on OSX. const prefContents = yield (0, fs_1.readFile)(firefoxPrefsFile, { encoding: 'utf8' }).catch(() => ''); existingPrefs = (prefContents) .split('\n') .reduce((prefs, line) => { const match = FIREFOX_PREF_REGEX.exec(line); if (match) { prefs[match[1]] = match[2]; } return prefs; }, {}); const certCheckServer = new cert_check_server_1.CertCheckServer(this.config); yield certCheckServer.start("https://google.com"); const browser = yield this.startFirefox(certCheckServer, proxyPort, existingPrefs); let certCheckSuccessful; certCheckServer.waitForSuccess().then(() => { certCheckSuccessful = true; }).catch((e) => { certCheckSuccessful = false; console.error(e); }); browsers[proxyPort] = browser; browser.process.once('close', (exitCode) => __awaiter(this, void 0, void 0, function* () { console.log('Firefox closed'); delete browsers[proxyPort]; // It seems maybe this can happen when firefox is just updated - it starts and // closes immediately, but loses some settings along the way. In that case, the 2nd // run will still try to load the cert check server. Keep it up for a sec so // that users get a clean error in this case. yield (0, promise_1.delay)(2000); certCheckServer.stop(); if (!certCheckSuccessful) { console.error(`Firefox certificate check ${certCheckSuccessful === false ? "failed" : "did not complete"} with FF exit code ${exitCode}`); (0, fs_1.deleteFolder)(this.firefoxProfilePath).catch(console.warn); } })); // Wait until the cert check works before reporting success to the UI yield certCheckServer.waitForSuccess(); }); } deactivate(proxyPort) { return __awaiter(this, void 0, void 0, function* () { if (this.isActive(proxyPort)) { const browser = browsers[proxyPort]; const closePromise = new Promise((resolve) => browser.process.once('close', resolve)); browser.stop(); yield closePromise; } }); } deactivateAll() { return __awaiter(this, void 0, void 0, function* () { yield Promise.all(Object.keys(browsers).map((proxyPort) => this.deactivate(proxyPort))); if (profileSetupBrowser) { profileSetupBrowser.stop(); return new Promise((resolve) => profileSetupBrowser.process.once('close', resolve)); } }); } } exports.FreshFirefox = FreshFirefox; ; //# sourceMappingURL=fresh-firefox.js.map