mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
358 lines
11 KiB
TypeScript
358 lines
11 KiB
TypeScript
const [charToIndex, indexToChar, paddingChar] = /* #__PURE__ */ (() => {
|
|
// Array is faster than object literal or `Map`
|
|
const charToIndex: number[] = [];
|
|
const indexToChar: number[] = [];
|
|
const paddingChar = "=".charCodeAt(0);
|
|
|
|
function addRange(start: string, end: string) {
|
|
const charCodeStart = start.charCodeAt(0);
|
|
const charCodeEnd = end.charCodeAt(0);
|
|
|
|
for (
|
|
let charCode = charCodeStart;
|
|
charCode <= charCodeEnd;
|
|
charCode += 1
|
|
) {
|
|
charToIndex[charCode] = indexToChar.length;
|
|
indexToChar.push(charCode);
|
|
}
|
|
}
|
|
|
|
addRange("A", "Z");
|
|
addRange("a", "z");
|
|
addRange("0", "9");
|
|
addRange("+", "+");
|
|
addRange("/", "/");
|
|
|
|
return [charToIndex, indexToChar, paddingChar];
|
|
})();
|
|
|
|
/**
|
|
* Calculate the required length of the output buffer for the given input length.
|
|
*
|
|
* @param inputLength Length of the input in bytes
|
|
* @returns Length of the output in bytes
|
|
*/
|
|
export function calculateBase64EncodedLength(
|
|
inputLength: number,
|
|
): [outputLength: number, paddingLength: number] {
|
|
const remainder = inputLength % 3;
|
|
const paddingLength = remainder !== 0 ? 3 - remainder : 0;
|
|
return [((inputLength + paddingLength) / 3) * 4, paddingLength];
|
|
}
|
|
|
|
/**
|
|
* Encode the given input buffer into base64.
|
|
*
|
|
* @param input The input buffer
|
|
* @returns The encoded output buffer
|
|
*/
|
|
export function encodeBase64(input: Uint8Array): Uint8Array;
|
|
/**
|
|
* Encode the given input into base64 and write it to the output buffer.
|
|
*
|
|
* The output buffer must be at least as long as the value returned by `calculateBase64EncodedLength`.
|
|
* It can points to the same buffer as the input, as long as `output.offset <= input.offset - input.length / 3`,
|
|
* or `output.offset >= input.offset - 1`
|
|
*
|
|
* @param input The input buffer
|
|
* @param output The output buffer
|
|
* @returns The number of bytes written to the output buffer
|
|
*/
|
|
export function encodeBase64(input: Uint8Array, output: Uint8Array): number;
|
|
export function encodeBase64(
|
|
input: Uint8Array,
|
|
output?: Uint8Array,
|
|
): Uint8Array | number {
|
|
const [outputLength, paddingLength] = calculateBase64EncodedLength(
|
|
input.length,
|
|
);
|
|
|
|
if (!output) {
|
|
output = new Uint8Array(outputLength);
|
|
encodeForward(input, output, paddingLength);
|
|
return output;
|
|
} else {
|
|
if (output.length < outputLength) {
|
|
throw new TypeError("output buffer is too small");
|
|
}
|
|
|
|
output = output.subarray(0, outputLength);
|
|
|
|
// When input and output are on same ArrayBuffer,
|
|
// we check if it's possible to encode in-place.
|
|
if (input.buffer !== output.buffer) {
|
|
encodeForward(input, output, paddingLength);
|
|
} else if (
|
|
output.byteOffset + output.length - (paddingLength + 1) <=
|
|
input.byteOffset + input.length
|
|
) {
|
|
// Output ends before input ends.
|
|
// Can encode forwards, because writing output won't catch up with reading input.
|
|
|
|
// The output end is subtracted by `(paddingLength + 1)` because
|
|
// depending on padding length, it's possible to write 1-3 extra bytes after input ends.
|
|
//
|
|
// The following diagrams show how the last read from input and the last write to output
|
|
// are not conflicting.
|
|
//
|
|
// spell: disable
|
|
//
|
|
// `paddingLength === 2` can write 3 extra bytes:
|
|
//
|
|
// input: | aaaaaabb | | | |
|
|
// output: | aaaaaa | bb0000 | = | = |
|
|
//
|
|
// `paddingLength === 1` can write 2 extra bytes:
|
|
//
|
|
// input: | aaaaaabb | bbbbcccc | | |
|
|
// output: | aaaaaa | bbbbbb | cccc00 | = |
|
|
//
|
|
// `paddingLength === 0` can write 1 extra byte:
|
|
//
|
|
// input: | aaaaaabb | bbbbcccc | ccdddddd | |
|
|
// output: | aaaaaa | bbbbbb | cccccc | dddddd |
|
|
//
|
|
// spell: enable
|
|
|
|
encodeForward(input, output, paddingLength);
|
|
} else if (output.byteOffset >= input.byteOffset - 1) {
|
|
// Output starts after input starts
|
|
// So in backwards, writing output won't catch up with reading input.
|
|
|
|
// The input start is subtracted by `1`, Because as the first 3 bytes becomes 4 bytes,
|
|
// it's possible to write 1 extra byte before input starts.
|
|
// spell: disable-next-line
|
|
// input: | | aaaaaabb | bbbbcccc | ccdddddd |
|
|
// output: | aaaaaa | bbbbbb | cccccc | dddddd |
|
|
|
|
// Must encode backwards.
|
|
encodeBackward(input, output, paddingLength);
|
|
} else {
|
|
// Input is in the middle of output,
|
|
// It's not possible to read either the first or the last three bytes
|
|
// before they are overwritten by the output.
|
|
throw new TypeError("input and output cannot overlap");
|
|
}
|
|
|
|
return outputLength;
|
|
}
|
|
}
|
|
|
|
function encodeForward(
|
|
input: Uint8Array,
|
|
output: Uint8Array,
|
|
paddingLength: number,
|
|
) {
|
|
let inputIndex = 0;
|
|
let outputIndex = 0;
|
|
|
|
while (inputIndex < input.length - 2) {
|
|
/* cspell: disable-next-line */
|
|
// aaaaaabb
|
|
const x = input[inputIndex]!;
|
|
inputIndex += 1;
|
|
|
|
/* cspell: disable-next-line */
|
|
// bbbbcccc
|
|
const y = input[inputIndex]!;
|
|
inputIndex += 1;
|
|
|
|
/* cspell: disable-next-line */
|
|
// ccdddddd
|
|
const z = input[inputIndex]!;
|
|
inputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[x >> 2]!;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[((x & 0b11) << 4) | (y >> 4)]!;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[((y & 0b1111) << 2) | (z >> 6)]!;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[z & 0b111111]!;
|
|
outputIndex += 1;
|
|
}
|
|
|
|
if (paddingLength === 2) {
|
|
/* cspell: disable-next-line */
|
|
// aaaaaabb
|
|
const x = input[inputIndex]!;
|
|
inputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[x >> 2]!;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[(x & 0b11) << 4]!;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = paddingChar;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = paddingChar;
|
|
} else if (paddingLength === 1) {
|
|
/* cspell: disable-next-line */
|
|
// aaaaaabb
|
|
const x = input[inputIndex]!;
|
|
inputIndex += 1;
|
|
|
|
/* cspell: disable-next-line */
|
|
// bbbbcccc
|
|
const y = input[inputIndex]!;
|
|
inputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[x >> 2]!;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[((x & 0b11) << 4) | (y >> 4)]!;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = indexToChar[(y & 0b1111) << 2]!;
|
|
outputIndex += 1;
|
|
|
|
output[outputIndex] = paddingChar;
|
|
}
|
|
}
|
|
|
|
function encodeBackward(
|
|
input: Uint8Array,
|
|
output: Uint8Array,
|
|
paddingLength: number,
|
|
) {
|
|
let inputIndex = input.length - 1;
|
|
let outputIndex = output.length - 1;
|
|
|
|
if (paddingLength === 2) {
|
|
/* cspell: disable-next-line */
|
|
// aaaaaabb
|
|
const x = input[inputIndex]!;
|
|
inputIndex -= 1;
|
|
|
|
output[outputIndex] = paddingChar;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = paddingChar;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[(x & 0b11) << 4]!;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[x >> 2]!;
|
|
outputIndex -= 1;
|
|
} else if (paddingLength === 1) {
|
|
/* cspell: disable-next-line */
|
|
// bbbbcccc
|
|
const y = input[inputIndex]!;
|
|
inputIndex -= 1;
|
|
|
|
/* cspell: disable-next-line */
|
|
// aaaaaabb
|
|
const x = input[inputIndex]!;
|
|
inputIndex -= 1;
|
|
|
|
output[outputIndex] = paddingChar;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[(y & 0b1111) << 2]!;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[((x & 0b11) << 4) | (y >> 4)]!;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[x >> 2]!;
|
|
outputIndex -= 1;
|
|
}
|
|
|
|
while (inputIndex >= 0) {
|
|
/* cspell: disable-next-line */
|
|
// ccdddddd
|
|
const z = input[inputIndex]!;
|
|
inputIndex -= 1;
|
|
|
|
/* cspell: disable-next-line */
|
|
// bbbbcccc
|
|
const y = input[inputIndex]!;
|
|
inputIndex -= 1;
|
|
|
|
/* cspell: disable-next-line */
|
|
// aaaaaabb
|
|
const x = input[inputIndex]!;
|
|
inputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[z & 0b111111]!;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[((y & 0b1111) << 2) | (z >> 6)]!;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[((x & 0b11) << 4) | (y >> 4)]!;
|
|
outputIndex -= 1;
|
|
|
|
output[outputIndex] = indexToChar[x >> 2]!;
|
|
outputIndex -= 1;
|
|
}
|
|
}
|
|
|
|
export function decodeBase64(input: string): Uint8Array {
|
|
let padding: number;
|
|
if (input[input.length - 2] === "=") {
|
|
padding = 2;
|
|
} else if (input[input.length - 1] === "=") {
|
|
padding = 1;
|
|
} else {
|
|
padding = 0;
|
|
}
|
|
|
|
const result = new Uint8Array((input.length / 4) * 3 - padding);
|
|
let sIndex = 0;
|
|
let dIndex = 0;
|
|
|
|
while (sIndex < input.length - (padding !== 0 ? 4 : 0)) {
|
|
const a = charToIndex[input.charCodeAt(sIndex)]!;
|
|
sIndex += 1;
|
|
|
|
const b = charToIndex[input.charCodeAt(sIndex)]!;
|
|
sIndex += 1;
|
|
|
|
const c = charToIndex[input.charCodeAt(sIndex)]!;
|
|
sIndex += 1;
|
|
|
|
const d = charToIndex[input.charCodeAt(sIndex)]!;
|
|
sIndex += 1;
|
|
|
|
result[dIndex] = (a << 2) | ((b & 0b11_0000) >> 4);
|
|
dIndex += 1;
|
|
|
|
result[dIndex] = ((b & 0b1111) << 4) | ((c & 0b11_1100) >> 2);
|
|
dIndex += 1;
|
|
|
|
result[dIndex] = ((c & 0b11) << 6) | d;
|
|
dIndex += 1;
|
|
}
|
|
|
|
if (padding === 1) {
|
|
const a = charToIndex[input.charCodeAt(sIndex)]!;
|
|
sIndex += 1;
|
|
|
|
const b = charToIndex[input.charCodeAt(sIndex)]!;
|
|
sIndex += 1;
|
|
|
|
const c = charToIndex[input.charCodeAt(sIndex)]!;
|
|
|
|
result[dIndex] = (a << 2) | ((b & 0b11_0000) >> 4);
|
|
dIndex += 1;
|
|
|
|
result[dIndex] = ((b & 0b1111) << 4) | ((c & 0b11_1100) >> 2);
|
|
} else if (padding === 2) {
|
|
const a = charToIndex[input.charCodeAt(sIndex)]!;
|
|
sIndex += 1;
|
|
|
|
const b = charToIndex[input.charCodeAt(sIndex)]!;
|
|
|
|
result[dIndex] = (a << 2) | ((b & 0b11_0000) >> 4);
|
|
}
|
|
|
|
return result;
|
|
}
|