1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-09-29 14:17:11 +02:00
uBlock/src/js/base64-custom.js
Raymond Hill 22022f636f
Modularize codebase with export/import
Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1664

The changes are enough to fulfill the related issue.

A new platform has been added in order to allow for building
a NodeJS package. From the root of the project:

    ./tools/make-nodejs

This will create new uBlock0.nodejs directory in the
./dist/build directory, which is a valid NodeJS package.

From the root of the package, you can try:

    node test

This will instantiate a static network filtering engine,
populated by easylist and easyprivacy, which can be used
to match network requests by filling the appropriate
filtering context object.

The test.js file contains code which is typical example
of usage of the package.

Limitations: the NodeJS package can't execute the WASM
versions of the code since the WASM module requires the
use of fetch(), which is not available in NodeJS.

This is a first pass at modularizing the codebase, and
while at it a number of opportunistic small rewrites
have also been made.

This commit requires the minimum supported version for
Chromium and Firefox be raised to 61 and 60 respectively.
2021-07-27 17:26:04 -04:00

247 lines
8.8 KiB
JavaScript

/*******************************************************************************
uBlock Origin - a browser extension to block requests.
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';
/******************************************************************************/
// Custom base64 codecs. These codecs are meant to encode/decode typed arrays
// to/from strings.
// https://github.com/uBlockOrigin/uBlock-issues/issues/461
// Provide a fallback encoding for Chromium 59 and less by issuing a plain
// JSON string. The fallback can be removed once min supported version is
// above 59.
// TODO: rename µBlock.base64 to µBlock.SparseBase64, now that
// µBlock.DenseBase64 has been introduced.
// TODO: Should no longer need to test presence of TextEncoder/TextDecoder.
const valToDigit = new Uint8Array(64);
const digitToVal = new Uint8Array(128);
{
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@%';
for ( let i = 0, n = chars.length; i < n; i++ ) {
const c = chars.charCodeAt(i);
valToDigit[i] = c;
digitToVal[c] = i;
}
}
// The sparse base64 codec is best for buffers which contains a lot of
// small u32 integer values. Those small u32 integer values are better
// represented with stringified integers, because small values can be
// represented with fewer bits than the usual base64 codec. For example,
// 0 become '0 ', i.e. 16 bits instead of 48 bits with official base64
// codec.
const sparseBase64 = {
magic: 'Base64_1',
encode: function(arrbuf, arrlen) {
const inputLength = (arrlen + 3) >>> 2;
const inbuf = new Uint32Array(arrbuf, 0, inputLength);
const outputLength = this.magic.length + 7 + inputLength * 7;
const outbuf = new Uint8Array(outputLength);
// magic bytes
let j = 0;
for ( let i = 0; i < this.magic.length; i++ ) {
outbuf[j++] = this.magic.charCodeAt(i);
}
// array size
let v = inputLength;
do {
outbuf[j++] = valToDigit[v & 0b111111];
v >>>= 6;
} while ( v !== 0 );
outbuf[j++] = 0x20 /* ' ' */;
// array content
for ( let i = 0; i < inputLength; i++ ) {
v = inbuf[i];
do {
outbuf[j++] = valToDigit[v & 0b111111];
v >>>= 6;
} while ( v !== 0 );
outbuf[j++] = 0x20 /* ' ' */;
}
if ( typeof TextDecoder === 'undefined' ) {
return JSON.stringify(
Array.from(new Uint32Array(outbuf.buffer, 0, j >>> 2))
);
}
const textDecoder = new TextDecoder();
return textDecoder.decode(new Uint8Array(outbuf.buffer, 0, j));
},
decode: function(instr, arrbuf) {
if ( instr.charCodeAt(0) === 0x5B /* '[' */ ) {
const inbuf = JSON.parse(instr);
if ( arrbuf instanceof ArrayBuffer === false ) {
return new Uint32Array(inbuf);
}
const outbuf = new Uint32Array(arrbuf);
outbuf.set(inbuf);
return outbuf;
}
if ( instr.startsWith(this.magic) === false ) {
throw new Error('Invalid µBlock.base64 encoding');
}
const inputLength = instr.length;
const outputLength = this.decodeSize(instr) >> 2;
const outbuf = arrbuf instanceof ArrayBuffer === false
? new Uint32Array(outputLength)
: new Uint32Array(arrbuf);
let i = instr.indexOf(' ', this.magic.length) + 1;
if ( i === -1 ) {
throw new Error('Invalid µBlock.base64 encoding');
}
// array content
let j = 0;
for (;;) {
if ( j === outputLength || i >= inputLength ) { break; }
let v = 0, l = 0;
for (;;) {
const c = instr.charCodeAt(i++);
if ( c === 0x20 /* ' ' */ ) { break; }
v += digitToVal[c] << l;
l += 6;
}
outbuf[j++] = v;
}
if ( i < inputLength || j < outputLength ) {
throw new Error('Invalid µBlock.base64 encoding');
}
return outbuf;
},
decodeSize: function(instr) {
if ( instr.startsWith(this.magic) === false ) { return 0; }
let v = 0, l = 0, i = this.magic.length;
for (;;) {
const c = instr.charCodeAt(i++);
if ( c === 0x20 /* ' ' */ ) { break; }
v += digitToVal[c] << l;
l += 6;
}
return v << 2;
},
};
// The dense base64 codec is best for typed buffers which values are
// more random. For example, buffer contents as a result of compression
// contain less repetitive values and thus the content is more
// random-looking.
// TODO: Investigate that in Firefox, creating a new Uint8Array from the
// ArrayBuffer fails, the content of the resulting Uint8Array is
// non-sensical. WASM-related?
const denseBase64 = {
magic: 'DenseBase64_1',
encode: function(input) {
const m = input.length % 3;
const n = input.length - m;
let outputLength = n / 3 * 4;
if ( m !== 0 ) {
outputLength += m + 1;
}
const output = new Uint8Array(outputLength);
let j = 0;
for ( let i = 0; i < n; i += 3) {
const i1 = input[i+0];
const i2 = input[i+1];
const i3 = input[i+2];
output[j+0] = valToDigit[ i1 >>> 2];
output[j+1] = valToDigit[i1 << 4 & 0b110000 | i2 >>> 4];
output[j+2] = valToDigit[i2 << 2 & 0b111100 | i3 >>> 6];
output[j+3] = valToDigit[i3 & 0b111111 ];
j += 4;
}
if ( m !== 0 ) {
const i1 = input[n];
output[j+0] = valToDigit[i1 >>> 2];
if ( m === 1 ) { // 1 value
output[j+1] = valToDigit[i1 << 4 & 0b110000];
} else { // 2 values
const i2 = input[n+1];
output[j+1] = valToDigit[i1 << 4 & 0b110000 | i2 >>> 4];
output[j+2] = valToDigit[i2 << 2 & 0b111100 ];
}
}
const textDecoder = new TextDecoder();
const b64str = textDecoder.decode(output);
return this.magic + b64str;
},
decode: function(instr, arrbuf) {
if ( instr.startsWith(this.magic) === false ) {
throw new Error('Invalid µBlock.denseBase64 encoding');
}
const outputLength = this.decodeSize(instr);
const outbuf = arrbuf instanceof ArrayBuffer === false
? new Uint8Array(outputLength)
: new Uint8Array(arrbuf);
const inputLength = instr.length - this.magic.length;
let i = this.magic.length;
let j = 0;
const m = inputLength & 3;
const n = i + inputLength - m;
while ( i < n ) {
const i1 = digitToVal[instr.charCodeAt(i+0)];
const i2 = digitToVal[instr.charCodeAt(i+1)];
const i3 = digitToVal[instr.charCodeAt(i+2)];
const i4 = digitToVal[instr.charCodeAt(i+3)];
i += 4;
outbuf[j+0] = i1 << 2 | i2 >>> 4;
outbuf[j+1] = i2 << 4 & 0b11110000 | i3 >>> 2;
outbuf[j+2] = i3 << 6 & 0b11000000 | i4;
j += 3;
}
if ( m !== 0 ) {
const i1 = digitToVal[instr.charCodeAt(i+0)];
const i2 = digitToVal[instr.charCodeAt(i+1)];
outbuf[j+0] = i1 << 2 | i2 >>> 4;
if ( m === 3 ) {
const i3 = digitToVal[instr.charCodeAt(i+2)];
outbuf[j+1] = i2 << 4 & 0b11110000 | i3 >>> 2;
}
}
return outbuf;
},
decodeSize: function(instr) {
if ( instr.startsWith(this.magic) === false ) { return 0; }
const inputLength = instr.length - this.magic.length;
const m = inputLength & 3;
const n = inputLength - m;
let outputLength = (n >>> 2) * 3;
if ( m !== 0 ) {
outputLength += m - 1;
}
return outputLength;
},
};
/******************************************************************************/
export { denseBase64, sparseBase64 };