UNPKG

@adguard/agtree

Version:
111 lines (108 loc) 5.25 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 { CLOSE_PARENTHESIS, OPEN_PARENTHESIS, UBO_SCRIPTLET_MASK, UBO_SCRIPTLET_MASK_LEGACY, SPACE, ESCAPE_CHARACTER, COMMA } from '../../../utils/constants.js'; import { StringUtils } from '../../../utils/string.js'; import { AdblockSyntaxError } from '../../../errors/adblock-syntax-error.js'; import { defaultParserOptions } from '../../options.js'; import { BaseParser } from '../../base-parser.js'; import { UboParameterListParser } from '../../misc/ubo-parameter-list-parser.js'; /** * @file uBlock scriptlet injection body parser */ /** * `UboScriptletInjectionBodyParser` is responsible for parsing the body of a uBlock-style scriptlet rule. * * Please note that the parser will parse any scriptlet rule if it is syntactically correct. * For example, it will parse this: * ```adblock * example.com##+js(scriptlet0, arg0) * ``` * * but it didn't check if the scriptlet `scriptlet0` actually supported by any adblocker. * * @see {@link https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#scriptlet-injection} */ class UboScriptletInjectionBodyParser extends BaseParser { /** * Error messages used by the parser. */ static ERROR_MESSAGES = { NO_SCRIPTLET_MASK: `Invalid uBO scriptlet call, no scriptlet call mask '${UBO_SCRIPTLET_MASK}' found`, NO_OPENING_PARENTHESIS: `Invalid uBO scriptlet call, no opening parentheses '${OPEN_PARENTHESIS}' found`, NO_CLOSING_PARENTHESIS: `Invalid uBO scriptlet call, no closing parentheses '${CLOSE_PARENTHESIS}' found`, NO_SCRIPTLET_NAME: 'Invalid uBO scriptlet call, no scriptlet name specified', WHITESPACE_AFTER_MASK: 'Invalid uBO scriptlet call, whitespace is not allowed after the scriptlet call mask', }; /** * Parses the body of a uBlock-style scriptlet 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 Node of the parsed scriptlet call body * @throws If the body is syntactically incorrect * @example * ``` * ##+js(scriptlet0, arg0) * ``` */ static parse(raw, options = defaultParserOptions, baseOffset = 0) { let offset = 0; // Skip leading spaces offset = StringUtils.skipWS(raw, offset); let scriptletMaskLength = 0; if (raw.startsWith(UBO_SCRIPTLET_MASK, offset)) { scriptletMaskLength = UBO_SCRIPTLET_MASK.length; } else if (raw.startsWith(UBO_SCRIPTLET_MASK_LEGACY, offset)) { scriptletMaskLength = UBO_SCRIPTLET_MASK_LEGACY.length; } // Scriptlet call should start with "+js" if (!scriptletMaskLength) { throw new AdblockSyntaxError(this.ERROR_MESSAGES.NO_SCRIPTLET_MASK, baseOffset + offset, baseOffset + raw.length); } offset += scriptletMaskLength; // Whitespace is not allowed after the mask if (raw[offset] === SPACE) { throw new AdblockSyntaxError(this.ERROR_MESSAGES.WHITESPACE_AFTER_MASK, baseOffset + offset, baseOffset + raw.length); } // Parameter list should be wrapped in parentheses if (raw[offset] !== OPEN_PARENTHESIS) { throw new AdblockSyntaxError(this.ERROR_MESSAGES.NO_OPENING_PARENTHESIS, baseOffset + offset, baseOffset + raw.length); } // Save the offset of the opening parentheses const openingParenthesesIndex = offset; // Skip whitespace from the end const closingParenthesesIndex = StringUtils.skipWSBack(raw, raw.length - 1); // Closing parentheses should be present if (raw[closingParenthesesIndex] !== CLOSE_PARENTHESIS || raw[closingParenthesesIndex - 1] === ESCAPE_CHARACTER) { throw new AdblockSyntaxError(this.ERROR_MESSAGES.NO_CLOSING_PARENTHESIS, baseOffset + offset, baseOffset + raw.length); } const result = { type: 'ScriptletInjectionRuleBody', children: [], }; if (options.isLocIncluded) { result.start = baseOffset; result.end = baseOffset + raw.length; } // Special case: empty scriptlet call, like +js(), +js( ), etc. if (StringUtils.skipWS(raw, openingParenthesesIndex + 1) === closingParenthesesIndex) { return result; } // Parse parameter list const params = UboParameterListParser.parse(raw.slice(openingParenthesesIndex + 1, closingParenthesesIndex), options, baseOffset + openingParenthesesIndex + 1, COMMA); // Do not allow parameters without scriptlet: +js(, arg0, arg1) if (params.children.length > 0 && params.children[0] === null) { throw new AdblockSyntaxError(this.ERROR_MESSAGES.NO_SCRIPTLET_NAME, baseOffset + offset, baseOffset + raw.length); } result.children.push(params); return result; } } export { UboScriptletInjectionBodyParser };