@quik-fe/clear-urls
Version:
ClearURLs extension to js package.
260 lines (225 loc) • 6.6 kB
text/typescript
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,
};
}