uBlock/src/js/static-filtering-io.js

145 lines
4.4 KiB
JavaScript

/*******************************************************************************
uBlock Origin - a comprehensive, efficient content blocker
Copyright (C) 2014-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
'use strict';
/******************************************************************************/
// https://www.reddit.com/r/uBlockOrigin/comments/oq6kt5/ubo_loads_generic_filter_instead_of_specific/
// Ensure blocks of content are sorted in ascending id order, such that the
// specific cosmetic filters will be found (and thus reported) before the
// generic ones.
const serialize = JSON.stringify;
const unserialize = JSON.parse;
const blockStartPrefix = '#block-start-'; // ensure no special regex characters
const blockEndPrefix = '#block-end-'; // ensure no special regex characters
class CompiledListWriter {
constructor() {
this.blockId = undefined;
this.block = undefined;
this.blocks = new Map();
this.properties = new Map();
}
push(args) {
this.block.push(serialize(args));
}
pushMany(many) {
for ( const args of many ) {
this.block.push(serialize(args));
}
}
last() {
if ( Array.isArray(this.block) && this.block.length !== 0 ) {
return this.block[this.block.length - 1];
}
}
select(blockId) {
if ( blockId === this.blockId ) { return; }
this.blockId = blockId;
this.block = this.blocks.get(blockId);
if ( this.block === undefined ) {
this.blocks.set(blockId, (this.block = []));
}
return this;
}
toString() {
const result = [];
const sortedBlocks =
Array.from(this.blocks).sort((a, b) => a[0] - b[0]);
for ( const [ id, lines ] of sortedBlocks ) {
if ( lines.length === 0 ) { continue; }
result.push(
blockStartPrefix + id,
lines.join('\n'),
blockEndPrefix + id
);
}
return result.join('\n');
}
static serialize(arg) {
return serialize(arg);
}
}
class CompiledListReader {
constructor(raw, blockId) {
this.block = '';
this.len = 0;
this.offset = 0;
this.line = '';
this.blocks = new Map();
this.properties = new Map();
const reBlockStart = new RegExp(`^${blockStartPrefix}([\\w:]+)\\n`, 'gm');
let match = reBlockStart.exec(raw);
while ( match !== null ) {
const sectionId = match[1];
const beg = match.index + match[0].length;
const end = raw.indexOf(blockEndPrefix + sectionId, beg);
this.blocks.set(sectionId, raw.slice(beg, end));
reBlockStart.lastIndex = end;
match = reBlockStart.exec(raw);
}
if ( blockId !== undefined ) {
this.select(blockId);
}
}
next() {
if ( this.offset === this.len ) {
this.line = '';
return false;
}
let pos = this.block.indexOf('\n', this.offset);
if ( pos !== -1 ) {
this.line = this.block.slice(this.offset, pos);
this.offset = pos + 1;
} else {
this.line = this.block.slice(this.offset);
this.offset = this.len;
}
return true;
}
select(blockId) {
this.block = this.blocks.get(blockId) || '';
this.len = this.block.length;
this.offset = 0;
return this;
}
fingerprint() {
return this.line;
}
args() {
return unserialize(this.line);
}
static unserialize(arg) {
return unserialize(arg);
}
}
/******************************************************************************/
export {
CompiledListReader,
CompiledListWriter,
};