Files

250 lines
6.2 KiB
JavaScript

/**
* @fileoverview I2P Host Management Module
* Handles host verification, routing, and application paths for I2P browser extension
*/
// Constants for URL patterns and ports
const URL_PATTERNS = {
I2P_SUFFIX: ".i2p",
PROXY_HOST: "proxy.i2p",
LOCALHOST: ["localhost", "127.0.0.1"],
PROTOCOL_PREFIX: "ext+rc:",
};
const PORT_APPLICATIONS = {
TOR: "7695",
BLOG: "8084",
IRC: "7669",
};
const ROUTER_PATHS = {
TUNNEL_MGR: ["i2ptunnelmgr", "i2ptunnel"],
TORRENT: ["i2psnark", "torrents", "transmission", "tracker"],
MAIL: ["webmail", "susimail"],
MUWIRE: ["MuWire"],
BOTE: ["i2pbote"],
CONSOLE: ["home", "console", "dns", "susidns", "config", "sitemap", ""],
};
/**
* Validates and processes URLs for host checking
* @param {string|URL|Object} urlInput - URL to process
* @return {URL} Processed URL object
* @throws {Error} If URL is invalid
*/
function processURL(urlInput) {
try {
if (typeof urlInput === "string") {
if (!urlInput.startsWith("http")) {
urlInput = "http://" + urlInput;
}
return new URL(urlInput);
}
if (urlInput instanceof URL) {
return urlInput;
}
return new URL(urlInput.url);
} catch (error) {
console.error("Invalid URL processing:", error);
throw new Error("Invalid URL format");
}
}
/**
* Checks if request is for proxy host
* @param {Object} requestDetails - Request details object
* @return {boolean}
*/
function isProxyHost(requestDetails) {
try {
const requestUrl = processURL(requestDetails);
const isProxy = requestUrl.hostname === URL_PATTERNS.PROXY_HOST;
console.debug("(proxy) proxyinfo check:", requestUrl.hostname, isProxy);
return isProxy;
} catch (error) {
console.error("Proxy host check failed:", error);
return false;
}
}
/**
* Validates if URL points to localhost
* @param {string|URL} url
* @return {string|false} Host:port if local, false otherwise
*/
function isLocalHost(url) {
try {
const requestUrl = processURL(url);
if (URL_PATTERNS.LOCALHOST.includes(requestUrl.hostname)) {
return `${requestUrl.hostname}:${requestUrl.port}`;
}
return false;
} catch (error) {
console.error("Local host check failed:", error);
return false;
}
}
/**
* Standardizes localhost representation
* @param {string|URL} url
* @return {string}
*/
function tidyLocalHost(url) {
const hostPort = isLocalHost(url);
if (hostPort) {
return hostPort.replace("127.0.0.1", "localhost");
}
const processedUrl = processURL(url);
return `${processedUrl.hostname}:${processedUrl.port}`;
}
/**
* Service-specific host checks
* @param {string|URL} url
* @param {string} port
* @param {string} service
* @return {string|false}
*/
function checkServiceHost(url, port, service) {
const host = isLocalHost(url);
if (!host) {
return false;
}
return host.includes(` : ${port}`) ? service : false;
}
const isTorHost = (url) => checkServiceHost(url, PORT_APPLICATIONS.TOR, "tor");
const isBlogHost = (url) =>
checkServiceHost(url, PORT_APPLICATIONS.BLOG, "blog");
const isIRCHost = (url) => checkServiceHost(url, PORT_APPLICATIONS.IRC, "irc");
/**
* Verifies if request comes from extension
* @param {Object} url - Request URL object
* @return {boolean}
*/
function isExtensionHost(url) {
const extensionPrefix = browser.runtime
.getURL("")
.replace("moz-extension://", "")
.replace("/", "");
const checkUrl = (sourceUrl) => {
if (!sourceUrl) return false;
return sourceUrl
.replace("moz-extension://", "")
.replace("/", "")
.startsWith(extensionPrefix);
};
return checkUrl(url.originUrl) || checkUrl(url.documentUrl);
}
/**
* Extracts I2P hostname from URL
* @param {string|URL} url
* @returns {string|false}
*/
function i2pHostName(url) {
try {
const requestUrl = processURL(url);
return requestUrl.host.endsWith(URL_PATTERNS.I2P_SUFFIX)
? requestUrl.host
: false;
} catch (error) {
console.error("I2P hostname extraction failed:", error);
return false;
}
}
/**
* Validates I2P host
* @param {Object} url
* @returns {boolean}
*/
function i2pHost(url) {
if (isProxyHost(url)) {
console.warn("(host) proxy.i2p detected");
return false;
}
const requestUrl = processURL(url.url);
return requestUrl.hostname.endsWith(URL_PATTERNS.I2P_SUFFIX);
}
/**
* Gets first path element from URL
* @param {string|URL} url
* @returns {string}
*/
function getFirstPathElement(url) {
const requestUrl = processURL(url);
const path = requestUrl.pathname.replace(/^\/+/, "");
return path.split("/")[0];
}
/**
* Identifies application from path
* @param {string|URL} url
* @returns {string|boolean}
*/
function getPathApplication(url) {
const path = getFirstPathElement(url);
if (ROUTER_PATHS.TUNNEL_MGR.includes(path)) return "i2ptunnelmgr";
if (ROUTER_PATHS.TORRENT.includes(path)) return "i2psnark";
if (ROUTER_PATHS.MAIL.includes(path)) return "webmail";
if (path.startsWith("MuWire")) return "muwire";
if (path.startsWith("i2pbote")) return "i2pbote";
if (ROUTER_PATHS.CONSOLE.includes(path)) return "routerconsole";
console.warn("(host) unknown path:", path);
return true;
}
/**
* Verifies router host status
* @param {string|URL} url
* @returns {string|boolean}
*/
function isRouterHost(url) {
try {
const protocolUrl = identifyProtocolHandler(url);
if (protocolUrl) {
return isRouterHost(protocolUrl);
}
const requestUrl = processURL(url);
const { hostname, port } = requestUrl;
const controlHost = control_host();
const controlPort = control_port();
if (
tidyLocalHost(`${hostname}:${port}`) ===
tidyLocalHost(`${controlHost}:${controlPort}`)
) {
return getPathApplication(url);
}
return false;
} catch (error) {
console.error("Router host check failed:", error);
return false;
}
}
/**
* Identifies protocol handler in URL
* @param {string} url
* @returns {string|false}
*/
function identifyProtocolHandler(url) {
const encoded = encodeURIComponent(URL_PATTERNS.PROTOCOL_PREFIX);
if (url.includes(encoded)) {
return url.replace(encoded, "");
}
if (url.includes(URL_PATTERNS.PROTOCOL_PREFIX)) {
return url.replace(URL_PATTERNS.PROTOCOL_PREFIX, "");
}
return false;
}