import { sizeOfNetworkCSP, sizeOfNetworkFilter, sizeOfNetworkHostname, sizeOfNetworkRedirect, sizeOfRawNetwork, sizeOfUTF8 } from "../data-view.js";
import { TOKENS_BUFFER } from "../tokens-buffer.js";
import { HASH_SEED, bitCount, clearBit, fastHash, findLastIndexOfUnescapedCharacter, getBit, hasUnicode, isAlpha, isDigit, setBit, tokenizeInPlace, tokenizeRegexInPlace, tokenizeWithWildcardsInPlace } from "../utils.js";
import { NORMALIZED_TYPE_TOKEN } from "../request.js";
import { toASCII } from "../punycode.js";
import { Domains } from "../engine/domains.js";
//#region node_modules/@ghostery/adblocker/dist/esm/filters/network.js
/*!
* Copyright (c) 2017-present Ghostery GmbH. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
var HTTP_HASH = fastHash("http");
var HTTPS_HASH = fastHash("https");
function isAllowedHostname(ch) {
	return isDigit(ch) || isAlpha(ch) || ch === 95 || ch === 45 || ch === 46;
}
/**
* Masks used to store options of network filters in a bitmask.
*/
var NETWORK_FILTER_MASK;
(function(NETWORK_FILTER_MASK) {
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromDocument"] = 1] = "fromDocument";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromFont"] = 2] = "fromFont";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromHttp"] = 4] = "fromHttp";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromHttps"] = 8] = "fromHttps";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromImage"] = 16] = "fromImage";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromMedia"] = 32] = "fromMedia";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromObject"] = 64] = "fromObject";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromOther"] = 128] = "fromOther";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromPing"] = 256] = "fromPing";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromScript"] = 512] = "fromScript";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromStylesheet"] = 1024] = "fromStylesheet";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromSubdocument"] = 2048] = "fromSubdocument";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromWebsocket"] = 4096] = "fromWebsocket";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["fromXmlHttpRequest"] = 8192] = "fromXmlHttpRequest";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["firstParty"] = 16384] = "firstParty";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["thirdParty"] = 32768] = "thirdParty";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isReplace"] = 65536] = "isReplace";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isBadFilter"] = 131072] = "isBadFilter";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isCSP"] = 262144] = "isCSP";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isGenericHide"] = 524288] = "isGenericHide";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isImportant"] = 1048576] = "isImportant";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isSpecificHide"] = 2097152] = "isSpecificHide";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isFullRegex"] = 4194304] = "isFullRegex";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isRegex"] = 8388608] = "isRegex";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isUnicode"] = 16777216] = "isUnicode";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isLeftAnchor"] = 33554432] = "isLeftAnchor";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isRightAnchor"] = 67108864] = "isRightAnchor";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isException"] = 134217728] = "isException";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isHostnameAnchor"] = 268435456] = "isHostnameAnchor";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isRedirectRule"] = 536870912] = "isRedirectRule";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isRedirect"] = 1073741824] = "isRedirect";
	NETWORK_FILTER_MASK[NETWORK_FILTER_MASK["isRemoveParam"] = -2147483648] = "isRemoveParam";
})(NETWORK_FILTER_MASK || (NETWORK_FILTER_MASK = {}));
var NETWORK_FILTER_OPTIONAL_PARTS_MASK;
(function(NETWORK_FILTER_OPTIONAL_PARTS_MASK) {
	NETWORK_FILTER_OPTIONAL_PARTS_MASK[NETWORK_FILTER_OPTIONAL_PARTS_MASK["hasFilter"] = 1] = "hasFilter";
	NETWORK_FILTER_OPTIONAL_PARTS_MASK[NETWORK_FILTER_OPTIONAL_PARTS_MASK["hasHostname"] = 2] = "hasHostname";
	NETWORK_FILTER_OPTIONAL_PARTS_MASK[NETWORK_FILTER_OPTIONAL_PARTS_MASK["hasDomains"] = 4] = "hasDomains";
	NETWORK_FILTER_OPTIONAL_PARTS_MASK[NETWORK_FILTER_OPTIONAL_PARTS_MASK["hasRawLine"] = 8] = "hasRawLine";
	NETWORK_FILTER_OPTIONAL_PARTS_MASK[NETWORK_FILTER_OPTIONAL_PARTS_MASK["hasDenyallow"] = 16] = "hasDenyallow";
	NETWORK_FILTER_OPTIONAL_PARTS_MASK[NETWORK_FILTER_OPTIONAL_PARTS_MASK["hasOptionValue"] = 32] = "hasOptionValue";
	NETWORK_FILTER_OPTIONAL_PARTS_MASK[NETWORK_FILTER_OPTIONAL_PARTS_MASK["isCaseSensitive"] = 64] = "isCaseSensitive";
})(NETWORK_FILTER_OPTIONAL_PARTS_MASK || (NETWORK_FILTER_OPTIONAL_PARTS_MASK = {}));
/**
* Mask used when a network filter can be applied on any content type.
*/
var FROM_ANY = NETWORK_FILTER_MASK.fromDocument | NETWORK_FILTER_MASK.fromFont | NETWORK_FILTER_MASK.fromImage | NETWORK_FILTER_MASK.fromMedia | NETWORK_FILTER_MASK.fromObject | NETWORK_FILTER_MASK.fromOther | NETWORK_FILTER_MASK.fromPing | NETWORK_FILTER_MASK.fromScript | NETWORK_FILTER_MASK.fromStylesheet | NETWORK_FILTER_MASK.fromSubdocument | NETWORK_FILTER_MASK.fromWebsocket | NETWORK_FILTER_MASK.fromXmlHttpRequest;
/**
* Map content type value to mask the corresponding mask.
* ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy
*/
var REQUEST_TYPE_TO_MASK = {
	beacon: NETWORK_FILTER_MASK.fromPing,
	document: NETWORK_FILTER_MASK.fromDocument,
	cspviolationreport: NETWORK_FILTER_MASK.fromOther,
	fedcm: NETWORK_FILTER_MASK.fromScript,
	fetch: NETWORK_FILTER_MASK.fromXmlHttpRequest,
	font: NETWORK_FILTER_MASK.fromFont,
	image: NETWORK_FILTER_MASK.fromImage,
	imageset: NETWORK_FILTER_MASK.fromImage,
	json: NETWORK_FILTER_MASK.fromScript,
	mainFrame: NETWORK_FILTER_MASK.fromDocument,
	main_frame: NETWORK_FILTER_MASK.fromDocument,
	media: NETWORK_FILTER_MASK.fromMedia,
	object: NETWORK_FILTER_MASK.fromObject,
	object_subrequest: NETWORK_FILTER_MASK.fromObject,
	ping: NETWORK_FILTER_MASK.fromPing,
	script: NETWORK_FILTER_MASK.fromScript,
	stylesheet: NETWORK_FILTER_MASK.fromStylesheet,
	subFrame: NETWORK_FILTER_MASK.fromSubdocument,
	sub_frame: NETWORK_FILTER_MASK.fromSubdocument,
	webSocket: NETWORK_FILTER_MASK.fromWebsocket,
	websocket: NETWORK_FILTER_MASK.fromWebsocket,
	xhr: NETWORK_FILTER_MASK.fromXmlHttpRequest,
	xmlhttprequest: NETWORK_FILTER_MASK.fromXmlHttpRequest,
	cspReport: NETWORK_FILTER_MASK.fromOther,
	csp_report: NETWORK_FILTER_MASK.fromOther,
	eventsource: NETWORK_FILTER_MASK.fromOther,
	manifest: NETWORK_FILTER_MASK.fromOther,
	other: NETWORK_FILTER_MASK.fromOther,
	prefetch: NETWORK_FILTER_MASK.fromOther,
	preflight: NETWORK_FILTER_MASK.fromOther,
	signedexchange: NETWORK_FILTER_MASK.fromOther,
	speculative: NETWORK_FILTER_MASK.fromOther,
	texttrack: NETWORK_FILTER_MASK.fromOther,
	web_manifest: NETWORK_FILTER_MASK.fromOther,
	xml_dtd: NETWORK_FILTER_MASK.fromOther,
	xslt: NETWORK_FILTER_MASK.fromOther
};
function getListOfRequestTypesNegated(filter) {
	const types = [];
	if (filter.fromDocument() === false) types.push("document");
	if (filter.fromImage() === false) types.push("image");
	if (filter.fromMedia() === false) types.push("media");
	if (filter.fromObject() === false) types.push("object");
	if (filter.fromOther() === false) types.push("other");
	if (filter.fromPing() === false) types.push("ping");
	if (filter.fromScript() === false) types.push("script");
	if (filter.fromStylesheet() === false) types.push("stylesheet");
	if (filter.fromSubdocument() === false) types.push("sub_frame");
	if (filter.fromWebsocket() === false) types.push("websocket");
	if (filter.fromXmlHttpRequest() === false) types.push("xhr");
	if (filter.fromFont() === false) types.push("font");
	return types;
}
function getListOfRequestTypes(filter) {
	const types = [];
	if (filter.fromDocument()) types.push("document");
	if (filter.fromImage()) types.push("image");
	if (filter.fromMedia()) types.push("media");
	if (filter.fromObject()) types.push("object");
	if (filter.fromOther()) types.push("other");
	if (filter.fromPing()) types.push("ping");
	if (filter.fromScript()) types.push("script");
	if (filter.fromStylesheet()) types.push("stylesheet");
	if (filter.fromSubdocument()) types.push("sub_frame");
	if (filter.fromWebsocket()) types.push("websocket");
	if (filter.fromXmlHttpRequest()) types.push("xhr");
	if (filter.fromFont()) types.push("font");
	return types;
}
function computeFilterId(mask, filter, hostname, domains, denyallow, optionValue, isCaseSensitive) {
	let hash = HASH_SEED * 37 ^ mask;
	if (domains !== void 0) hash = domains.updateId(hash);
	if (denyallow !== void 0) hash = denyallow.updateId(hash);
	if (filter !== void 0) for (let i = 0; i < filter.length; i += 1) hash = hash * 37 ^ filter.charCodeAt(i);
	if (hostname !== void 0) for (let i = 0; i < hostname.length; i += 1) hash = hash * 37 ^ hostname.charCodeAt(i);
	if (optionValue !== void 0) for (let i = 0; i < optionValue.length; i += 1) hash = hash * 37 ^ optionValue.charCodeAt(i);
	if (isCaseSensitive !== void 0) hash = hash * 37 ^ 1 << +isCaseSensitive;
	return hash >>> 0;
}
/**
* Compiles a filter pattern to a regex. This is only performed *lazily* for
* filters containing at least a * or ^ symbol. Because Regexes are expansive,
* we try to convert some patterns to plain filters.
*/
function compileRegex(filter, isLeftAnchor, isRightAnchor, isFullRegex, isCaseSensitive) {
	if (isFullRegex === true) return new RegExp(filter.slice(1, filter.length - 1), isCaseSensitive ? "" : "i");
	filter = filter.replace(/([|.$+?{}()[\]\\])/g, "\\$1");
	filter = filter.replace(/\*/g, ".*");
	filter = filter.replace(/\^/g, "(?:[^\\w\\d_.%-]|$)");
	if (isRightAnchor) filter = `${filter}$`;
	if (isLeftAnchor) filter = `^${filter}`;
	return new RegExp(filter, isCaseSensitive ? "" : "i");
}
/**
* Collects a filter option key until the function sees the special character.
* This function will stop iterating over the given string if it sees equal sign or comma sign.
* If there's an equal sign, it means that we'll see the value.
* Otherwise, if there's a comma sign, it means that the option doesn't have any values.
*/
function getFilterOptionName(line, pos, end) {
	const start = pos;
	for (; pos < end; pos++) {
		const code = line.charCodeAt(pos);
		if (code === 61 || code === 44) {
			end = pos;
			break;
		}
	}
	return [pos, line.slice(start, end)];
}
/**
* Collects a filter option value until the function sees the special character.
* This function respects the escaping characters, so we can safely collect the full value
* including the special characters which are not allowed normally.
* This function will stop if it sees a comma sign.
*/
function getFilterOptionValue(line, pos, end) {
	let start = pos;
	let value = "";
	for (; pos < end; pos++) {
		const code = line.charCodeAt(pos);
		if (code === 92) {
			value += line.slice(start, pos);
			start = ++pos;
		} else if (code === 44) break;
	}
	if (start - pos !== 0) value += line.slice(start, pos);
	return [pos, value];
}
var REGEXP_CHARACTER_ESCAPES = new Set([
	102,
	110,
	114,
	116,
	118,
	48,
	94,
	36,
	92,
	46,
	42,
	43,
	63,
	40,
	41,
	91,
	93,
	123,
	125,
	124,
	47,
	100,
	68,
	119,
	87,
	115,
	83,
	98,
	66
]);
function isHexLiteral(code) {
	return code >= 48 && code <= 57 || code <= 65 && code <= 70 || code >= 97 && code <= 102;
}
/**
* Checks if the character(s) after the escape character should be preserved.
* @returns The end index of search range and boolean indicating whether the character escape was found or not.
*/
function isCharacterEscapeInRegExp(line, pos, _end) {
	const code = line.charCodeAt(pos + 1);
	if (code === 44 || REGEXP_CHARACTER_ESCAPES.has(code)) return [pos + 1, true];
	if (code === 99) {
		const next = line.charCodeAt(pos + 2);
		if (next >= 65 && next <= 90 || next >= 97 && next <= 122) return [pos + 2, true];
	}
	if (code === 120 && isHexLiteral(line.charCodeAt(pos + 2)) && isHexLiteral(line.charCodeAt(pos + 3))) return [pos + 3, true];
	if (code === 117) {
		if (line.charCodeAt(pos + 2) === 123) {
			const close = line.indexOf("}", pos + 3);
			const hexLiteralLength = close - pos + 3;
			if (hexLiteralLength >= 1 && hexLiteralLength <= 6) return [close, true];
		} else if (isHexLiteral(line.charCodeAt(pos + 2)) && isHexLiteral(line.charCodeAt(pos + 3)) && isHexLiteral(line.charCodeAt(pos + 4)) && isHexLiteral(line.charCodeAt(pos + 5))) return [pos + 5, true];
	}
	return [pos + 1, false];
}
/**
* Comma and slash are expected to come with escape character by the spec.
* https://adguard.com/kb/general/ad-filtering/create-own-filters/#replace-modifier
*/
function isCharacterEscapeInReplace(line, pos, end) {
	const code = line.charCodeAt(pos + 1);
	if (code === 44 || code === 47) return [pos + 1, false];
	return isCharacterEscapeInRegExp(line, pos, end);
}
/**
* Collects a filter option value of the replace modifier.
* This function respects the escaping character with the allowed characters of the replace modifier.
* In the replace modifier, it can include the any sign allowed in the regular expression.
* Therefore, a comma sign can interfere the `getFilterOptionValue` function.
* This function will not stop unless it collects all the parts of the replace modifier option value.
*/
function getFilterReplaceOptionValue(line, pos, end) {
	if (line.charCodeAt(pos++) !== 47) return [end, void 0];
	const parts = [
		"",
		"",
		""
	];
	let start = pos;
	let slashes = 0;
	for (; pos < end; pos++) {
		const code = line.charCodeAt(pos);
		if (code === 92) {
			parts[slashes] += line.slice(start, pos);
			const [posAfterCharacterEscape, isCharacterEscape] = isCharacterEscapeInReplace(line, pos, end);
			if (isCharacterEscape === false) ++pos;
			start = pos;
			pos = posAfterCharacterEscape;
		} else if (code === 47) {
			if (pos - start !== 0) parts[slashes] += line.slice(start, pos);
			start = pos + 1;
			if (++slashes === 2) break;
		}
	}
	const valueEnd = line.indexOf(",", pos);
	if (valueEnd !== -1) end = valueEnd;
	parts[2] = line.slice(start, end);
	pos = end;
	return [pos, parts];
}
/**
* Collects an array of filter options from the given index.
* This function leverages `getFilterOptionKey`, `getFilterOptionValue`, and every extension functions.
* Depending on the filter option key, the function to collect filter option value can vary.
* For the generic filter option value, it'll use `getFilterOptionValue` function to get the value.
*/
function getFilterOptions(line, pos, end) {
	const options = [];
	let name;
	let value;
	for (; pos < end; pos++) {
		[pos, name] = getFilterOptionName(line, pos, end);
		if (name !== void 0) {
			if (line.charCodeAt(pos) === 61) pos++;
			if (name === "replace") {
				const result = getFilterReplaceOptionValue(line, pos, end);
				if (result[1] === void 0) value = "";
				else value = line.slice(pos, result[0]);
				pos = result[0];
			} else [pos, value] = getFilterOptionValue(line, pos, end);
			options.push([name, value]);
		}
	}
	return options;
}
/**
* Transforms the replace modifier option value into the regular expression with its replacement.
* This function takes a fixed length array from `getFilterReplaceOptionValue`
* then try to build the regular expression.
* This function will return `null` if the array format or the given regular expression components are not valid.
*/
function replaceOptionValueToRegexp(value) {
	const [, values] = getFilterReplaceOptionValue(value, 0, value.length);
	if (values === void 0) return null;
	try {
		return [new RegExp(values[0], values[2]), values[1]];
	} catch (error) {
		return null;
	}
}
var MATCH_ALL = /* @__PURE__ */ new RegExp("");
var NetworkFilter = class NetworkFilter {
	static parse(line, debug = false) {
		let mask = NETWORK_FILTER_MASK.thirdParty | NETWORK_FILTER_MASK.firstParty | NETWORK_FILTER_MASK.fromHttps | NETWORK_FILTER_MASK.fromHttp;
		let cptMaskPositive = 0;
		let cptMaskNegative = FROM_ANY;
		let hostname;
		let domains;
		let denyallow;
		let optionValue;
		let isCaseSensitive = false;
		let filterIndexStart = 0;
		let filterIndexEnd = line.length;
		if (line.charCodeAt(0) === 64 && line.charCodeAt(1) === 64) {
			filterIndexStart += 2;
			mask = setBit(mask, NETWORK_FILTER_MASK.isException);
		}
		const optionsIndex = findLastIndexOfUnescapedCharacter(line, "$");
		if (optionsIndex !== -1 && line.charCodeAt(optionsIndex + 1) !== 47) {
			filterIndexEnd = optionsIndex;
			let domainsList;
			let denyallowList;
			for (const rawOption of getFilterOptions(line, optionsIndex + 1, line.length)) {
				const negation = rawOption[0].charCodeAt(0) === 126;
				const option = negation === true ? rawOption[0].slice(1) : rawOption[0];
				const value = rawOption[1];
				switch (option) {
					case "to":
						domainsList ?? (domainsList = /* @__PURE__ */ new Set());
						denyallowList ?? (denyallowList = /* @__PURE__ */ new Set());
						for (const hostname of value.split("|")) if (hostname.startsWith("~")) denyallowList.add(hostname.slice(1));
						else domainsList.add(hostname);
						break;
					case "denyallow":
						denyallowList ?? (denyallowList = /* @__PURE__ */ new Set());
						for (const domain of value.split("|")) denyallowList.add(domain);
						break;
					case "domain":
					case "from":
						domainsList ?? (domainsList = /* @__PURE__ */ new Set());
						for (const domain of value.split("|")) domainsList.add(domain);
						break;
					case "badfilter":
						mask = setBit(mask, NETWORK_FILTER_MASK.isBadFilter);
						break;
					case "important":
						if (negation) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isImportant);
						break;
					case "match-case":
						if (negation) return null;
						isCaseSensitive = true;
						break;
					case "3p":
					case "third-party":
						if (negation) mask = clearBit(mask, NETWORK_FILTER_MASK.thirdParty);
						else mask = clearBit(mask, NETWORK_FILTER_MASK.firstParty);
						break;
					case "1p":
					case "first-party":
						if (negation) mask = clearBit(mask, NETWORK_FILTER_MASK.firstParty);
						else mask = clearBit(mask, NETWORK_FILTER_MASK.thirdParty);
						break;
					case "redirect-rule":
					case "redirect": {
						if (negation) return null;
						if (value.length === 0) return null;
						const priorityIndex = value.lastIndexOf(":");
						if (priorityIndex === 0) return null;
						else if (priorityIndex !== -1 && (isNaN(Number(value.slice(priorityIndex + 1))) === true || priorityIndex + 1 === value.length)) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isRedirect);
						if (option === "redirect-rule") mask = setBit(mask, NETWORK_FILTER_MASK.isRedirectRule);
						if (optionValue !== void 0) return null;
						optionValue = value;
						break;
					}
					case "csp":
						if (negation) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isCSP);
						if (value.length > 0) {
							if (optionValue !== void 0) return null;
							optionValue = value;
						}
						break;
					case "ehide":
					case "elemhide":
						if (negation) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isGenericHide);
						mask = setBit(mask, NETWORK_FILTER_MASK.isSpecificHide);
						break;
					case "shide":
					case "specifichide":
						if (negation) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isSpecificHide);
						break;
					case "ghide":
					case "generichide":
						if (negation) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isGenericHide);
						break;
					case "inline-script":
						if (negation || optionValue !== void 0) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isCSP);
						optionValue = "script-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:";
						break;
					case "inline-font":
						if (negation || optionValue !== void 0) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isCSP);
						optionValue = "font-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:";
						break;
					case "replace":
					case "content":
						if (negation || (value.length === 0 ? getBit(mask, NETWORK_FILTER_MASK.isException) === false : replaceOptionValueToRegexp(value) === null) || optionValue !== void 0) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isReplace);
						optionValue = value;
						break;
					case "removeparam":
						if (negation || value.startsWith("/") || optionValue !== void 0) return null;
						mask = setBit(mask, NETWORK_FILTER_MASK.isRemoveParam);
						optionValue = value;
						break;
					default: {
						let optionMask = 0;
						switch (option) {
							case "all":
								if (negation) return null;
								break;
							case "image":
								optionMask = NETWORK_FILTER_MASK.fromImage;
								break;
							case "media":
								optionMask = NETWORK_FILTER_MASK.fromMedia;
								break;
							case "object":
							case "object-subrequest":
								optionMask = NETWORK_FILTER_MASK.fromObject;
								break;
							case "other":
								optionMask = NETWORK_FILTER_MASK.fromOther;
								break;
							case "ping":
							case "beacon":
								optionMask = NETWORK_FILTER_MASK.fromPing;
								break;
							case "script":
								optionMask = NETWORK_FILTER_MASK.fromScript;
								break;
							case "css":
							case "stylesheet":
								optionMask = NETWORK_FILTER_MASK.fromStylesheet;
								break;
							case "frame":
							case "subdocument":
								optionMask = NETWORK_FILTER_MASK.fromSubdocument;
								break;
							case "xhr":
							case "xmlhttprequest":
								optionMask = NETWORK_FILTER_MASK.fromXmlHttpRequest;
								break;
							case "websocket":
								optionMask = NETWORK_FILTER_MASK.fromWebsocket;
								break;
							case "font":
								optionMask = NETWORK_FILTER_MASK.fromFont;
								break;
							case "doc":
							case "document":
								optionMask = NETWORK_FILTER_MASK.fromDocument;
								break;
							default: return null;
						}
						if (negation) cptMaskNegative = clearBit(cptMaskNegative, optionMask);
						else cptMaskPositive = setBit(cptMaskPositive, optionMask);
						break;
					}
				}
			}
			if (domainsList !== void 0 && domainsList.size !== 0) {
				domains = Domains.parse(domainsList, {
					delimiter: "|",
					debug
				});
				if (domains === void 0) return null;
			}
			if (denyallowList !== void 0 && denyallowList.size !== 0) {
				if (domainsList === void 0 || domainsList.size === 0) return null;
				denyallow = Domains.parse(denyallowList, {
					delimiter: "|",
					debug
				});
				if (denyallow === void 0) return null;
			}
		}
		if (cptMaskPositive === 0) mask = setBit(mask, cptMaskNegative);
		else if (cptMaskNegative === FROM_ANY) mask = setBit(mask, cptMaskPositive);
		else mask = setBit(mask, cptMaskPositive & cptMaskNegative);
		let filter;
		if (filterIndexEnd - filterIndexStart >= 2 && line.charCodeAt(filterIndexStart) === 47 && line.charCodeAt(filterIndexEnd - 1) === 47) {
			filter = line.slice(filterIndexStart, filterIndexEnd);
			try {
				compileRegex(filter, false, false, true, isCaseSensitive);
			} catch (ex) {
				return null;
			}
			mask = setBit(mask, NETWORK_FILTER_MASK.isFullRegex);
		} else {
			if (isCaseSensitive === true) return null;
			if (filterIndexEnd > 0 && line.charCodeAt(filterIndexEnd - 1) === 124) {
				mask = setBit(mask, NETWORK_FILTER_MASK.isRightAnchor);
				filterIndexEnd -= 1;
			}
			if (filterIndexStart < filterIndexEnd && line.charCodeAt(filterIndexStart) === 124) if (filterIndexStart < filterIndexEnd - 1 && line.charCodeAt(filterIndexStart + 1) === 124) {
				mask = setBit(mask, NETWORK_FILTER_MASK.isHostnameAnchor);
				filterIndexStart += 2;
			} else {
				mask = setBit(mask, NETWORK_FILTER_MASK.isLeftAnchor);
				filterIndexStart += 1;
			}
			if (getBit(mask, NETWORK_FILTER_MASK.isHostnameAnchor)) {
				let firstSeparator = filterIndexStart;
				while (firstSeparator < filterIndexEnd && isAllowedHostname(line.charCodeAt(firstSeparator)) === true) firstSeparator += 1;
				if (firstSeparator === filterIndexEnd) {
					hostname = line.slice(filterIndexStart, filterIndexEnd);
					filterIndexStart = filterIndexEnd;
				} else {
					hostname = line.slice(filterIndexStart, firstSeparator);
					filterIndexStart = firstSeparator;
					const separatorCode = line.charCodeAt(firstSeparator);
					if (separatorCode === 94) if (filterIndexEnd - filterIndexStart === 1) {
						filterIndexStart = filterIndexEnd;
						mask = setBit(mask, NETWORK_FILTER_MASK.isRightAnchor);
					} else {
						mask = setBit(mask, NETWORK_FILTER_MASK.isRegex);
						mask = setBit(mask, NETWORK_FILTER_MASK.isLeftAnchor);
					}
					else if (separatorCode === 42) mask = setBit(mask, NETWORK_FILTER_MASK.isRegex);
					else mask = setBit(mask, NETWORK_FILTER_MASK.isLeftAnchor);
				}
			}
			if (filterIndexEnd - filterIndexStart > 0 && line.charCodeAt(filterIndexEnd - 1) === 42) filterIndexEnd -= 1;
			if (getBit(mask, NETWORK_FILTER_MASK.isHostnameAnchor) === false && filterIndexEnd - filterIndexStart > 0 && line.charCodeAt(filterIndexStart) === 42) {
				mask = clearBit(mask, NETWORK_FILTER_MASK.isLeftAnchor);
				filterIndexStart += 1;
			}
			if (getBit(mask, NETWORK_FILTER_MASK.isLeftAnchor)) {
				if (filterIndexEnd - filterIndexStart === 5 && line.startsWith("ws://", filterIndexStart)) {
					mask = setBit(mask, NETWORK_FILTER_MASK.fromWebsocket);
					mask = clearBit(mask, NETWORK_FILTER_MASK.isLeftAnchor);
					mask = clearBit(mask, NETWORK_FILTER_MASK.fromHttp);
					mask = clearBit(mask, NETWORK_FILTER_MASK.fromHttps);
					filterIndexStart = filterIndexEnd;
				} else if (filterIndexEnd - filterIndexStart === 7 && line.startsWith("http://", filterIndexStart)) {
					mask = setBit(mask, NETWORK_FILTER_MASK.fromHttp);
					mask = clearBit(mask, NETWORK_FILTER_MASK.fromHttps);
					mask = clearBit(mask, NETWORK_FILTER_MASK.isLeftAnchor);
					filterIndexStart = filterIndexEnd;
				} else if (filterIndexEnd - filterIndexStart === 8 && line.startsWith("https://", filterIndexStart)) {
					mask = setBit(mask, NETWORK_FILTER_MASK.fromHttps);
					mask = clearBit(mask, NETWORK_FILTER_MASK.fromHttp);
					mask = clearBit(mask, NETWORK_FILTER_MASK.isLeftAnchor);
					filterIndexStart = filterIndexEnd;
				} else if (filterIndexEnd - filterIndexStart === 8 && line.startsWith("http*://", filterIndexStart)) {
					mask = setBit(mask, NETWORK_FILTER_MASK.fromHttps);
					mask = setBit(mask, NETWORK_FILTER_MASK.fromHttp);
					mask = clearBit(mask, NETWORK_FILTER_MASK.isLeftAnchor);
					filterIndexStart = filterIndexEnd;
				}
			}
			if (filterIndexEnd - filterIndexStart > 0) {
				filter = line.slice(filterIndexStart, filterIndexEnd);
				mask = setNetworkMask(mask, NETWORK_FILTER_MASK.isUnicode, hasUnicode(filter));
				if (getBit(mask, NETWORK_FILTER_MASK.isRegex) === false && checkIsRegex(filter, 0, filter.length)) mask = setNetworkMask(mask, NETWORK_FILTER_MASK.isRegex, true);
				else filter = filter.toLowerCase();
			}
			if (hostname !== void 0) {
				hostname = hostname.toLowerCase();
				if (hasUnicode(hostname)) {
					mask = setNetworkMask(mask, NETWORK_FILTER_MASK.isUnicode, true);
					hostname = toASCII(hostname);
				}
			}
		}
		return new NetworkFilter({
			filter,
			hostname,
			mask,
			domains,
			denyallow,
			optionValue,
			rawLine: debug === true ? line : void 0,
			regex: void 0,
			isCaseSensitive
		});
	}
	/**
	* Deserialize network filters. The code accessing the buffer should be
	* symetrical to the one in `serializeNetworkFilter`.
	*/
	static deserialize(buffer) {
		const mask = buffer.getUint32();
		const optionalParts = buffer.getUint8();
		const isUnicode = getBit(mask, NETWORK_FILTER_MASK.isUnicode);
		return new NetworkFilter({
			mask,
			filter: getBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasFilter) ? isUnicode ? buffer.getUTF8() : buffer.getNetworkFilter() : void 0,
			hostname: getBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasHostname) ? buffer.getNetworkHostname() : void 0,
			domains: getBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasDomains) ? Domains.deserialize(buffer) : void 0,
			rawLine: getBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasRawLine) ? buffer.getRawNetwork() : void 0,
			denyallow: getBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasDenyallow) ? Domains.deserialize(buffer) : void 0,
			optionValue: getBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasOptionValue) ? getBit(mask, NETWORK_FILTER_MASK.isCSP) ? buffer.getNetworkCSP() : getBit(mask, NETWORK_FILTER_MASK.isRedirect) ? buffer.getNetworkRedirect() : buffer.getUTF8() : void 0,
			regex: void 0,
			isCaseSensitive: getBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.isCaseSensitive)
		});
	}
	constructor({ filter, hostname, mask, domains, denyallow, optionValue, rawLine, regex, isCaseSensitive = false }) {
		this.filter = filter;
		this.hostname = hostname;
		this.mask = setBit(mask, 0);
		this.domains = domains;
		this.denyallow = denyallow;
		this.optionValue = optionValue;
		this.isCaseSensitive = isCaseSensitive;
		this.rawLine = rawLine;
		this.id = void 0;
		this.regex = regex;
	}
	get csp() {
		if (!this.isCSP()) return;
		return this.optionValue;
	}
	get redirect() {
		if (!this.isRedirect()) return;
		return this.optionValue;
	}
	get removeparam() {
		if (!this.isRemoveParam()) return;
		return this.optionValue;
	}
	isCosmeticFilter() {
		return false;
	}
	isNetworkFilter() {
		return true;
	}
	match(request) {
		return checkOptions(this, request) && checkPattern(this, request);
	}
	/**
	* To allow for a more compact representation of network filters, the
	* representation is composed of a mandatory header, and some optional
	*
	* Header:
	* =======
	*
	*  | opt | mask
	*     8     32
	*
	* For an empty filter having no pattern, hostname, the minimum size is: 42 bits.
	*
	* Then for each optional part (filter, hostname optDomains, optNotDomains,
	* redirect), it takes 16 bits for the length of the string + the length of the
	* string in bytes.
	*
	* The optional parts are written in order of there number of occurrence in the
	* filter list used by the adblocker. The most common being `hostname`, then
	* `filter`, `optDomains`, `optNotDomains`, `redirect`.
	*
	* Example:
	* ========
	*
	* @@||cliqz.com would result in a serialized version:
	*
	* | 1 | mask | 9 | c | l | i | q | z | . | c | o | m  (16 bytes)
	*
	* In this case, the serialized version is actually bigger than the original
	* filter, but faster to deserialize. In the future, we could optimize the
	* representation to compact small filters better.
	*
	* Ideas:
	*  * variable length encoding for the mask (if not option, take max 1 byte).
	*  * first byte could contain the mask as well if small enough.
	*  * when packing ascii string, store several of them in each byte.
	*/
	serialize(buffer) {
		buffer.pushUint32(this.mask);
		const index = buffer.getPos();
		buffer.pushUint8(0);
		let optionalParts = 0;
		if (this.filter !== void 0) {
			optionalParts = setBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasFilter);
			if (this.isUnicode()) buffer.pushUTF8(this.filter);
			else buffer.pushNetworkFilter(this.filter);
		}
		if (this.hostname !== void 0) {
			optionalParts = setBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasHostname);
			buffer.pushNetworkHostname(this.hostname);
		}
		if (this.domains !== void 0) {
			optionalParts = setBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasDomains);
			this.domains.serialize(buffer);
		}
		if (this.rawLine !== void 0) {
			optionalParts = setBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasRawLine);
			buffer.pushRawNetwork(this.rawLine);
		}
		if (this.denyallow !== void 0) {
			optionalParts = setBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasDenyallow);
			this.denyallow.serialize(buffer);
		}
		if (this.optionValue !== void 0) {
			optionalParts = setBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.hasOptionValue);
			if (this.isCSP()) buffer.pushNetworkCSP(this.optionValue);
			else if (this.isRedirect()) buffer.pushNetworkRedirect(this.optionValue);
			else buffer.pushUTF8(this.optionValue);
		}
		if (this.isCaseSensitive) optionalParts = setBit(optionalParts, NETWORK_FILTER_OPTIONAL_PARTS_MASK.isCaseSensitive);
		buffer.setByte(index, optionalParts);
	}
	getSerializedSize(compression) {
		let estimate = 5;
		if (this.filter !== void 0) if (this.isUnicode() === true) estimate += sizeOfUTF8(this.filter);
		else estimate += sizeOfNetworkFilter(this.filter, compression);
		if (this.hostname !== void 0) estimate += sizeOfNetworkHostname(this.hostname, compression);
		if (this.domains !== void 0) estimate += this.domains.getSerializedSize();
		if (this.rawLine !== void 0) estimate += sizeOfRawNetwork(this.rawLine, compression);
		if (this.denyallow !== void 0) estimate += this.denyallow.getSerializedSize();
		if (this.optionValue !== void 0) if (this.isCSP()) estimate += sizeOfNetworkCSP(this.optionValue, compression);
		else if (this.isRedirect()) estimate += sizeOfNetworkRedirect(this.optionValue, compression);
		else estimate += sizeOfUTF8(this.optionValue);
		return estimate;
	}
	/**
	* Tries to recreate the original representation of the filter (adblock
	* syntax) from the internal representation. If `rawLine` is set (when filters
	* are parsed in `debug` mode for example), then it is returned directly.
	* Otherwise, we try to stick as closely as possible to the original form;
	* there are things which cannot be recovered though, like domains options
	* of which only hashes are stored.
	*/
	toString(modifierReplacer) {
		if (this.rawLine !== void 0) return this.rawLine;
		let filter = "";
		if (this.isException()) filter += "@@";
		if (this.isHostnameAnchor()) filter += "||";
		else if (this.fromHttp() !== this.fromHttps()) if (this.fromHttp()) filter += "|http://";
		else filter += "|https://";
		else if (this.isLeftAnchor()) filter += "|";
		if (this.hasHostname()) {
			filter += this.getHostname();
			filter += "^";
		}
		if (this.isFullRegex()) filter += `/${this.getRegex().source}/`;
		else if (this.isRegex()) filter += this.getRegex().source;
		else filter += this.getFilter();
		if (this.isRightAnchor() && filter[filter.length - 1] !== "^") filter += "|";
		const options = [];
		if (this.fromAny() === false) {
			const numberOfCptOptions = bitCount(this.getCptMask());
			if (bitCount(FROM_ANY) - numberOfCptOptions < numberOfCptOptions) for (const type of getListOfRequestTypesNegated(this)) if (type === "sub_frame") options.push("~subdocument");
			else options.push(`~${type}`);
			else for (const type of getListOfRequestTypes(this)) if (type === "sub_frame") options.push("subdocument");
			else options.push(type);
		}
		if (this.isImportant()) options.push("important");
		if (this.isRedirectRule()) if (this.optionValue === "") options.push("redirect-rule");
		else options.push(`redirect-rule=${this.optionValue}`);
		else if (this.isRedirect()) if (this.optionValue === "") options.push("redirect");
		else options.push(`redirect=${this.optionValue}`);
		if (this.isCSP()) options.push(`csp=${this.optionValue}`);
		if (this.isElemHide()) options.push("elemhide");
		if (this.isSpecificHide()) options.push("specifichide");
		if (this.isGenericHide()) options.push("generichide");
		if (this.firstParty() !== this.thirdParty()) {
			if (this.firstParty()) options.push("1p");
			if (this.thirdParty()) options.push("3p");
		}
		if (this.domains !== void 0) if (this.domains.parts !== void 0) options.push(`domain=${this.domains.parts}`);
		else options.push("domain=<hashed>");
		if (this.denyallow !== void 0) if (this.denyallow.parts !== void 0) options.push(`denyallow=${this.denyallow.parts}`);
		else options.push("denyallow=<hashed>");
		if (this.isBadFilter()) options.push("badfilter");
		const removeparam = this.removeparam;
		if (removeparam !== void 0) if (removeparam.length > 0) options.push(`removeparam=${removeparam}`);
		else options.push("removeparam");
		if (this.isCaseSensitive) options.push("match-case");
		if (options.length > 0) if (typeof modifierReplacer === "function") filter += `$${options.map(modifierReplacer).join(",")}`;
		else filter += `$${options.join(",")}`;
		return filter;
	}
	getIdWithoutBadFilter() {
		return computeFilterId(this.mask & ~NETWORK_FILTER_MASK.isBadFilter, this.filter, this.hostname, this.domains, this.denyallow, this.optionValue, this.isCaseSensitive);
	}
	getId() {
		if (this.id === void 0) this.id = computeFilterId(this.mask, this.filter, this.hostname, this.domains, this.denyallow, this.optionValue, this.isCaseSensitive);
		return this.id;
	}
	hasFilter() {
		return this.filter !== void 0;
	}
	hasDomains() {
		return this.domains !== void 0;
	}
	getMask() {
		return this.mask;
	}
	getCptMask() {
		return this.getMask() & FROM_ANY;
	}
	isRedirect() {
		return getBit(this.getMask(), NETWORK_FILTER_MASK.isRedirect);
	}
	isRedirectRule() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isRedirectRule);
	}
	getRedirect() {
		return this.optionValue ?? "";
	}
	isReplace() {
		return getBit(this.getMask(), NETWORK_FILTER_MASK.isReplace);
	}
	isRemoveParam() {
		return getBit(this.getMask(), NETWORK_FILTER_MASK.isRemoveParam);
	}
	getHtmlModifier() {
		if (this.optionValue?.length === 0) return null;
		return replaceOptionValueToRegexp(this.optionValue);
	}
	isHtmlFilteringRule() {
		return this.isReplace();
	}
	getRedirectResource() {
		const redirect = this.getRedirect();
		const priorityIndex = redirect.lastIndexOf(":");
		if (priorityIndex === -1) return redirect;
		return redirect.slice(0, priorityIndex);
	}
	getRedirectPriority() {
		const redirect = this.getRedirect();
		const priorityIndex = redirect.lastIndexOf(":");
		if (priorityIndex === -1) return 0;
		return Number(redirect.slice(priorityIndex + 1));
	}
	hasHostname() {
		return this.hostname !== void 0;
	}
	getHostname() {
		return this.hostname || "";
	}
	getFilter() {
		return this.filter || "";
	}
	getRegex() {
		if (this.regex === void 0) this.regex = this.filter !== void 0 && this.isRegex() ? compileRegex(this.filter, this.isLeftAnchor(), this.isRightAnchor(), this.isFullRegex(), this.isCaseSensitive === true) : MATCH_ALL;
		return this.regex;
	}
	getTokens() {
		TOKENS_BUFFER.reset();
		if (this.domains !== void 0 && this.domains.hostnames !== void 0 && this.domains.entities === void 0 && this.domains.notHostnames === void 0 && this.domains.notEntities === void 0 && this.domains.hostnames.length === 1) TOKENS_BUFFER.push(this.domains.hostnames[0]);
		if (this.isFullRegex() === false) {
			if (this.filter !== void 0) {
				const skipLastToken = !this.isRightAnchor();
				const skipFirstToken = !this.isLeftAnchor();
				tokenizeWithWildcardsInPlace(this.isRegex() ? this.filter.toLowerCase() : this.filter, skipFirstToken, skipLastToken, TOKENS_BUFFER);
			}
			if (this.hostname !== void 0) tokenizeInPlace(this.hostname, false, this.filter !== void 0 && this.filter.charCodeAt(0) === 42, TOKENS_BUFFER);
		} else if (this.filter !== void 0) tokenizeRegexInPlace(this.filter.toLowerCase(), TOKENS_BUFFER);
		if (TOKENS_BUFFER.empty() === true && this.domains !== void 0 && this.domains.hostnames !== void 0 && this.domains.entities === void 0 && this.domains.notHostnames === void 0 && this.domains.notEntities === void 0) {
			const result = [];
			for (const hostname of this.domains.hostnames) {
				const arr = new Uint32Array(1);
				arr[0] = hostname;
				result.push(arr);
			}
			return result;
		}
		if (TOKENS_BUFFER.empty() === true && this.fromAny() === false) {
			const types = getListOfRequestTypes(this);
			if (types.length !== 0) {
				const result = [];
				for (const type of types) {
					const arr = new Uint32Array(1);
					arr[0] = NORMALIZED_TYPE_TOKEN[type];
					result.push(arr);
				}
				return result;
			}
		}
		if (this.fromHttp() === true && this.fromHttps() === false) TOKENS_BUFFER.push(HTTP_HASH);
		else if (this.fromHttps() === true && this.fromHttp() === false) TOKENS_BUFFER.push(HTTPS_HASH);
		return [TOKENS_BUFFER.slice()];
	}
	/**
	* Check if this filter should apply to a request with this content type.
	*/
	isCptAllowed(cpt) {
		const mask = REQUEST_TYPE_TO_MASK[cpt];
		if (mask !== void 0) return getBit(this.mask, mask);
		return this.fromAny();
	}
	isException() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isException);
	}
	isHostnameAnchor() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isHostnameAnchor);
	}
	isRightAnchor() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isRightAnchor);
	}
	isLeftAnchor() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isLeftAnchor);
	}
	isImportant() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isImportant);
	}
	isFullRegex() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isFullRegex);
	}
	isRegex() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isRegex) || getBit(this.mask, NETWORK_FILTER_MASK.isFullRegex);
	}
	isPlain() {
		return !this.isRegex();
	}
	isCSP() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isCSP);
	}
	isElemHide() {
		return this.isSpecificHide() && this.isGenericHide();
	}
	isSpecificHide() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isSpecificHide);
	}
	isGenericHide() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isGenericHide);
	}
	isBadFilter() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isBadFilter);
	}
	isUnicode() {
		return getBit(this.mask, NETWORK_FILTER_MASK.isUnicode);
	}
	fromAny() {
		return this.getCptMask() === FROM_ANY;
	}
	thirdParty() {
		return getBit(this.mask, NETWORK_FILTER_MASK.thirdParty);
	}
	firstParty() {
		return getBit(this.mask, NETWORK_FILTER_MASK.firstParty);
	}
	fromImage() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromImage);
	}
	fromMedia() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromMedia);
	}
	fromObject() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromObject);
	}
	fromOther() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromOther);
	}
	fromPing() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromPing);
	}
	fromScript() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromScript);
	}
	fromStylesheet() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromStylesheet);
	}
	fromDocument() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromDocument);
	}
	fromSubdocument() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromSubdocument);
	}
	fromWebsocket() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromWebsocket);
	}
	fromHttp() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromHttp);
	}
	fromHttps() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromHttps);
	}
	fromXmlHttpRequest() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromXmlHttpRequest);
	}
	fromFont() {
		return getBit(this.mask, NETWORK_FILTER_MASK.fromFont);
	}
};
function setNetworkMask(mask, m, value) {
	if (value === true) return setBit(mask, m);
	return clearBit(mask, m);
}
/**
* Check if the sub-string contained between the indices start and end is a
* regex filter (it contains a '*' or '^' char).
*/
function checkIsRegex(filter, start, end) {
	const indexOfSeparator = filter.indexOf("^", start);
	if (indexOfSeparator !== -1 && indexOfSeparator < end) return true;
	const indexOfWildcard = filter.indexOf("*", start);
	return indexOfWildcard !== -1 && indexOfWildcard < end;
}
/**
* Handle hostname anchored filters, given 'hostname' from ||hostname and
* request's hostname, check if there is a match. This is tricky because
* filters authors rely and different assumptions. We can have prefix of suffix
* matches of anchor.
*/
function isAnchoredByHostname(filterHostname, hostname, isFollowedByWildcard) {
	if (filterHostname.length === 0) return true;
	if (filterHostname.length > hostname.length) return false;
	if (filterHostname.length === hostname.length) return filterHostname === hostname;
	const matchIndex = hostname.indexOf(filterHostname);
	if (matchIndex === -1) return false;
	if (matchIndex === 0) return isFollowedByWildcard === true || hostname.charCodeAt(filterHostname.length) === 46 || filterHostname.charCodeAt(filterHostname.length - 1) === 46;
	if (hostname.length === matchIndex + filterHostname.length) return hostname.charCodeAt(matchIndex - 1) === 46 || filterHostname.charCodeAt(0) === 46;
	return (isFollowedByWildcard === true || hostname.charCodeAt(filterHostname.length) === 46 || filterHostname.charCodeAt(filterHostname.length - 1) === 46) && (hostname.charCodeAt(matchIndex - 1) === 46 || filterHostname.charCodeAt(0) === 46);
}
/**
* Specialize a network filter depending on its type. It allows for more
* efficient matching function.
*/
function checkPattern(filter, request) {
	const pattern = filter.getFilter();
	const url = filter.isCaseSensitive ? request.url : request.normalizedUrl;
	if (filter.isHostnameAnchor() === true) {
		const filterHostname = filter.getHostname();
		if (isAnchoredByHostname(filterHostname, request.hostname, filter.filter !== void 0 && filter.filter.charCodeAt(0) === 42) === false) return false;
		if (filter.isRegex()) return filter.getRegex().test(url.slice(url.indexOf(filterHostname) + filterHostname.length));
		else if (filter.isRightAnchor() && filter.isLeftAnchor()) return pattern === url.slice(url.indexOf(filterHostname) + filterHostname.length);
		else if (filter.isRightAnchor()) {
			const requestHostname = request.hostname;
			if (filter.hasFilter() === false) return filterHostname.length === requestHostname.length || requestHostname.endsWith(filterHostname);
			else return url.endsWith(pattern);
		} else if (filter.isLeftAnchor()) return url.startsWith(pattern, url.indexOf(filterHostname) + filterHostname.length);
		if (filter.hasFilter() === false) return true;
		return url.indexOf(pattern, url.indexOf(filterHostname) + filterHostname.length) !== -1;
	} else if (filter.isRegex()) return filter.getRegex().test(url);
	else if (filter.isLeftAnchor() && filter.isRightAnchor()) return url === pattern;
	else if (filter.isLeftAnchor()) return url.startsWith(pattern);
	else if (filter.isRightAnchor()) return url.endsWith(pattern);
	if (filter.hasFilter() === false) return true;
	return url.indexOf(pattern) !== -1;
}
function checkOptions(filter, request) {
	if (filter.isCptAllowed(request.type) === false || request.isHttps === true && filter.fromHttps() === false || request.isHttp === true && filter.fromHttp() === false || filter.firstParty() === false && request.isFirstParty === true || filter.thirdParty() === false && request.isThirdParty === true) return false;
	if (filter.domains !== void 0 && filter.domains.match(request.sourceHostnameHashes, request.sourceEntityHashes) === false) return false;
	if (filter.denyallow !== void 0 && filter.denyallow.match(request.getHostnameHashes(), request.getEntityHashes()) === true) return false;
	return true;
}
//#endregion
export { NETWORK_FILTER_MASK, NetworkFilter as default };
