allproxy
Version:
AllProxy: MITM HTTP Debugging Tool.
319 lines • 16.5 kB
JavaScript
;
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