UNPKG

@quik-fe/clear-urls

Version:

ClearURLs extension to js package.

260 lines (225 loc) 6.6 kB
import ipRangeCheck from "ip-range-check"; import { URLHashParams } from "./utils/URLHashParams"; import { Logger } from "./types"; import { Provider } from "./Provider"; /** * Extract the host without port from an url. * @param {URL} url URL as String * @return {String} host as string */ function extractHost(url: URL) { return url.hostname; } /** * Returns true if the url has a local host. * @param {URL} url URL as object * @return {boolean} */ function checkLocalURL(url: URL) { let host = extractHost(url); if (!host.match(/^\d/) && host !== "localhost") { return false; } return ( ipRangeCheck(host, [ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10", "169.254.0.0/16", "127.0.0.1", ]) || host === "localhost" ); } /** * Decodes an URL, also one that is encoded multiple times. * * @see https://stackoverflow.com/a/38265168 * * @param url the url, that should be decoded */ function decodeURL(url) { let rtn = decodeURIComponent(url); while (isEncodedURI(rtn)) { rtn = decodeURIComponent(rtn); } // Required (e.g., to fix https://github.com/ClearURLs/Addon/issues/71) if (!rtn.startsWith("http")) { rtn = "http://" + rtn; } return rtn; } /** * Returns the given URL without searchParams and hash. * @param {URL} url the URL as object * @return {URL} the url without searchParams and hash */ function urlWithoutParamsAndHash(url: URL) { let newURL = url.toString(); if (url.search) { newURL = newURL.replace(url.search, ""); } if (url.hash) { newURL = newURL.replace(url.hash, ""); } return new URL(newURL); } /** * Returns an URLSearchParams as string. * Does handle spaces correctly. */ function urlSearchParamsToString(searchParams: URLSearchParams) { const rtn: string[] = []; searchParams.forEach((value, key) => { if (value) { rtn.push(key + "=" + encodeURIComponent(value)); } else { rtn.push(key); } }); return rtn.join("&"); } /** * Returns true, iff the given URI is encoded * @see https://stackoverflow.com/a/38265168 */ function isEncodedURI(uri) { return uri !== decodeURIComponent(uri || ""); } /** * Extract the fragments from an url. * @param {URL} url URL as object * @return {URLHashParams} fragments as URLSearchParams object */ function extractFragments(url) { return new URLHashParams(url); } /** * Helper function which remove the tracking fields * for each provider given as parameter. * * @param {Provider} provider Provider-Object * @param pureUrl URL as String * @param {boolean} quiet if the action should be displayed in log and statistics * @return {Object} Array with changes and url fields */ export function removeFieldsFormURL({ provider, pureUrl, localHostsSkipping = true, domainBlocking = true, loggingStatus = true, logger, }: { provider: Provider; pureUrl: string; localHostsSkipping?: boolean; domainBlocking?: boolean; loggingStatus?: boolean; logger?: Logger; }) { let url = pureUrl; let domain = ""; let rules = provider.getRules(); let changes = false; let rawRules = provider.getRawRules(); let urlObject = new URL(url); let fields: URLSearchParams = new URLSearchParams(); let fragments: URLHashParams = new URLHashParams(urlObject); const quiet = !logger; if (localHostsSkipping && checkLocalURL(urlObject)) { return { changes: false, url: url, cancel: false, }; } /* * Expand the url by provider redirections. So no tracking on * url redirections form sites to sites. */ let re = provider.getRedirection(url); if (re !== null) { url = decodeURL(re); //Log the action if (!quiet) { logger?.log(pureUrl, url, "log_redirect"); } return { redirect: true, url: url, }; } if (provider.isCaneling() && domainBlocking) { if (!quiet) logger?.log(pureUrl, pureUrl, "log_domain_blocked"); return { cancel: true, url: url, }; } /* * Apply raw rules to the URL. */ rawRules.forEach(function (rawRule: string | RegExp) { let beforeReplace = url; url = url.replace(new RegExp(rawRule, "gi"), ""); if (beforeReplace !== url) { //Log the action if (loggingStatus && !quiet) { logger?.log(beforeReplace, url, rawRule); } changes = true; } }); urlObject = new URL(url); fields = urlObject.searchParams; fragments = extractFragments(urlObject); domain = urlWithoutParamsAndHash(urlObject).toString(); /** * Only test for matches, if there are fields or fragments that can be cleaned. */ if (fields.toString() !== "" || fragments.toString() !== "") { rules.forEach((rule: string) => { const beforeFields = fields.toString(); const beforeFragments = fragments.toString(); let localChange = false; for (const field of fields.keys()) { if (new RegExp("^" + rule + "$", "gi").test(field)) { fields.delete(field); changes = true; localChange = true; } } for (const fragment of fragments.keys()) { if (new RegExp("^" + rule + "$", "gi").test(fragment)) { fragments.delete(fragment); changes = true; localChange = true; } } //Log the action if (localChange && loggingStatus) { let tempURL = domain; let tempBeforeURL = domain; if (fields.toString() !== "") tempURL += "?" + fields.toString(); if (fragments.toString() !== "") tempURL += "#" + fragments.toString(); if (beforeFields.toString() !== "") tempBeforeURL += "?" + beforeFields.toString(); if (beforeFragments.toString() !== "") tempBeforeURL += "#" + beforeFragments.toString(); if (!quiet) logger?.log(tempBeforeURL, tempURL, rule); } }); let finalURL = domain; if (fields.toString() !== "") finalURL += "?" + urlSearchParamsToString(fields); if (fragments.toString() !== "") finalURL += "#" + fragments.toString(); url = finalURL .replace(new RegExp("\\?&"), "?") .replace(new RegExp("#&"), "#"); } return { changes: changes, url: url, }; }