UNPKG

@adguard/agtree

Version:
118 lines (115 loc) 5.23 kB
/* * AGTree v3.2.2 (build date: Tue, 08 Jul 2025 13:39:47 GMT) * (c) 2025 Adguard Software Ltd. * Released under the MIT license * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme */ import { AdblockSyntax } from '../../utils/adblockers.js'; import { StringUtils } from '../../utils/string.js'; import { ModifierListParser } from '../misc/modifier-list.js'; import { NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_EXCEPTION_MARKER_LEN, NETWORK_RULE_SEPARATOR, REGEX_MARKER, ESCAPE_CHARACTER } from '../../utils/constants.js'; import { RuleCategory, NetworkRuleType } from '../../nodes/index.js'; import { AdblockSyntaxError } from '../../errors/adblock-syntax-error.js'; import { defaultParserOptions } from '../options.js'; import { BaseParser } from '../base-parser.js'; import { ValueParser } from '../misc/value-parser.js'; /** * `NetworkRuleParser` is responsible for parsing network rules. * * Please note that this will parse all syntactically correct network rules. * Modifier compatibility is not checked at the parser level. * * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#basic-rules} * @see {@link https://help.eyeo.com/adblockplus/how-to-write-filters#basic} */ class NetworkRuleParser extends BaseParser { /** * Parses a network rule (also known as basic rule). * * @param raw Raw input to parse. * @param options Global parser options. * @param baseOffset Starting offset of the input. Node locations are calculated relative to this offset. * @returns Network rule AST * * @throws If the rule is syntactically incorrect. */ static parse(raw, options = defaultParserOptions, baseOffset = 0) { let offset = 0; // Skip leading whitespace offset = StringUtils.skipWS(raw, offset); // Handle exception rules let exception = false; // Rule starts with exception marker, eg @@||example.com, // where @@ is the exception marker if (raw.startsWith(NETWORK_RULE_EXCEPTION_MARKER, offset)) { offset += NETWORK_RULE_EXCEPTION_MARKER_LEN; exception = true; } // Save the start of the pattern const patternStart = offset; // Find corresponding (last) separator ($) character (if any) const separatorIndex = NetworkRuleParser.findNetworkRuleSeparatorIndex(raw); // Save the end of the pattern const patternEnd = separatorIndex === -1 ? StringUtils.skipWSBack(raw) + 1 : StringUtils.skipWSBack(raw, separatorIndex - 1) + 1; // Parse pattern const pattern = ValueParser.parse(raw.slice(patternStart, patternEnd), options, baseOffset + patternStart); // Parse modifiers (if any) let modifiers; // Get a last non-whitespace index const lastNonWsIndex = StringUtils.skipWSBack(raw); // Find start and end index of the modifiers const modifiersStart = separatorIndex + 1; const modifiersEnd = StringUtils.skipWSBack(raw) + 1; if (separatorIndex !== -1) { // Check for empty modifiers if (separatorIndex === lastNonWsIndex) { throw new AdblockSyntaxError('Empty modifiers are not allowed', baseOffset + separatorIndex, baseOffset + raw.length); } modifiers = ModifierListParser.parse(raw.slice(modifiersStart, modifiersEnd), options, baseOffset + modifiersStart); } // Throw error if there is no pattern and no modifiers if (pattern.value.length === 0 && (modifiers === undefined || modifiers.children.length === 0)) { throw new AdblockSyntaxError('Network rule must have a pattern or modifiers', baseOffset, baseOffset + raw.length); } const result = { type: NetworkRuleType.NetworkRule, category: RuleCategory.Network, syntax: AdblockSyntax.Common, exception, pattern, modifiers, }; if (options.includeRaws) { result.raws = { text: raw, }; } if (options.isLocIncluded) { result.start = baseOffset; result.end = baseOffset + raw.length; } return result; } /** * Finds the index of the separator character in a network rule. * * @param rule Network rule to check * @returns The index of the separator character, or -1 if there is no separator */ static findNetworkRuleSeparatorIndex(rule) { // As we are looking for the last separator, we start from the end of the string for (let i = rule.length - 1; i >= 0; i -= 1) { // If we find a potential separator, we should check // - if it's not escaped // - if it's not followed by a regex marker, for example: `example.org^$removeparam=/regex$/` // eslint-disable-next-line max-len if (rule[i] === NETWORK_RULE_SEPARATOR && rule[i + 1] !== REGEX_MARKER && rule[i - 1] !== ESCAPE_CHARACTER) { return i; } } return -1; } } export { NetworkRuleParser };