diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 05:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 05:47:55 +0000 |
commit | 31d6ff6f931696850c348007241195ab3b2eddc7 (patch) | |
tree | 615cb1c57ce9f6611bad93326b9105098f379609 /src/js/base64-custom.js | |
parent | Initial commit. (diff) | |
download | ublock-origin-31d6ff6f931696850c348007241195ab3b2eddc7.tar.xz ublock-origin-31d6ff6f931696850c348007241195ab3b2eddc7.zip |
Adding upstream version 1.55.0+dfsg.upstream/1.55.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/js/base64-custom.js')
-rw-r--r-- | src/js/base64-custom.js | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/src/js/base64-custom.js b/src/js/base64-custom.js new file mode 100644 index 0000000..34141b8 --- /dev/null +++ b/src/js/base64-custom.js @@ -0,0 +1,246 @@ +/******************************************************************************* + + 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'; + +/******************************************************************************/ + +// 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 }; |