voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
361 lines (320 loc) • 10.7 kB
text/typescript
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const stringToByteArray = function (str: string): number[] {
// TODO(user): Use native implementations if/when available
const out: number[] = [];
let p = 0;
for (let i = 0; i < str.length; i++) {
let c = str.charCodeAt(i);
if (c < 128) {
out[p++] = c;
} else if (c < 2048) {
out[p++] = (c >> 6) | 192;
out[p++] = (c & 63) | 128;
} else if (
(c & 0xfc00) === 0xd800 &&
i + 1 < str.length &&
(str.charCodeAt(i + 1) & 0xfc00) === 0xdc00
) {
// Surrogate Pair
c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff);
out[p++] = (c >> 18) | 240;
out[p++] = ((c >> 12) & 63) | 128;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
} else {
out[p++] = (c >> 12) | 224;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
}
}
return out;
};
/**
* Turns an array of numbers into the string given by the concatenation of the
* characters to which the numbers correspond.
* @param bytes Array of numbers representing characters.
* @return Stringification of the array.
*/
const byteArrayToString = function (bytes: number[]): string {
// TODO(user): Use native implementations if/when available
const out: string[] = [];
let pos = 0,
c = 0;
while (pos < bytes.length) {
const c1 = bytes[pos++];
if (c1 < 128) {
out[c++] = String.fromCharCode(c1);
} else if (c1 > 191 && c1 < 224) {
const c2 = bytes[pos++];
out[c++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
} else if (c1 > 239 && c1 < 365) {
// Surrogate Pair
const c2 = bytes[pos++];
const c3 = bytes[pos++];
const c4 = bytes[pos++];
const u =
(((c1 & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63)) -
0x10000;
out[c++] = String.fromCharCode(0xd800 + (u >> 10));
out[c++] = String.fromCharCode(0xdc00 + (u & 1023));
} else {
const c2 = bytes[pos++];
const c3 = bytes[pos++];
out[c++] = String.fromCharCode(
((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)
);
}
}
return out.join('');
};
interface Base64 {
byteToCharMap_: { [key: number]: string } | null;
charToByteMap_: { [key: string]: number } | null;
byteToCharMapWebSafe_: { [key: number]: string } | null;
charToByteMapWebSafe_: { [key: string]: number } | null;
ENCODED_VALS_BASE: string;
readonly ENCODED_VALS: string;
readonly ENCODED_VALS_WEBSAFE: string;
HAS_NATIVE_SUPPORT: boolean;
encodeByteArray(input: number[] | Uint8Array, webSafe?: boolean): string;
encodeString(input: string, webSafe?: boolean): string;
decodeString(input: string, webSafe: boolean): string;
decodeStringToByteArray(input: string, webSafe: boolean): number[];
init_(): void;
}
// We define it as an object literal instead of a class because a class compiled down to es5 can't
// be treeshaked. https://github.com/rollup/rollup/issues/1691
// Static lookup maps, lazily populated by init_()
export const base64: Base64 = {
/**
* Maps bytes to characters.
*/
byteToCharMap_: null,
/**
* Maps characters to bytes.
*/
charToByteMap_: null,
/**
* Maps bytes to websafe characters.
* @private
*/
byteToCharMapWebSafe_: null,
/**
* Maps websafe characters to bytes.
* @private
*/
charToByteMapWebSafe_: null,
/**
* Our default alphabet, shared between
* ENCODED_VALS and ENCODED_VALS_WEBSAFE
*/
ENCODED_VALS_BASE:
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789',
/**
* Our default alphabet. Value 64 (=) is special; it means "nothing."
*/
get ENCODED_VALS() {
return this.ENCODED_VALS_BASE + '+/=';
},
/**
* Our websafe alphabet.
*/
get ENCODED_VALS_WEBSAFE() {
return this.ENCODED_VALS_BASE + '-_.';
},
/**
* Whether this browser supports the atob and btoa functions. This extension
* started at Mozilla but is now implemented by many browsers. We use the
* ASSUME_* variables to avoid pulling in the full useragent detection library
* but still allowing the standard per-browser compilations.
*
*/
HAS_NATIVE_SUPPORT: typeof atob === 'function',
/**
* Base64-encode an array of bytes.
*
* @param input An array of bytes (numbers with
* value in [0, 255]) to encode.
* @param webSafe Boolean indicating we should use the
* alternative alphabet.
* @return The base64 encoded string.
*/
encodeByteArray(input: number[] | Uint8Array, webSafe?: boolean): string {
if (!Array.isArray(input)) {
throw Error('encodeByteArray takes an array as a parameter');
}
this.init_();
const byteToCharMap = webSafe
? this.byteToCharMapWebSafe_!
: this.byteToCharMap_!;
const output = [];
for (let i = 0; i < input.length; i += 3) {
const byte1 = input[i];
const haveByte2 = i + 1 < input.length;
const byte2 = haveByte2 ? input[i + 1] : 0;
const haveByte3 = i + 2 < input.length;
const byte3 = haveByte3 ? input[i + 2] : 0;
const outByte1 = byte1 >> 2;
const outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
let outByte3 = ((byte2 & 0x0f) << 2) | (byte3 >> 6);
let outByte4 = byte3 & 0x3f;
if (!haveByte3) {
outByte4 = 64;
if (!haveByte2) {
outByte3 = 64;
}
}
output.push(
byteToCharMap[outByte1],
byteToCharMap[outByte2],
byteToCharMap[outByte3],
byteToCharMap[outByte4]
);
}
return output.join('');
},
/**
* Base64-encode a string.
*
* @param input A string to encode.
* @param webSafe If true, we should use the
* alternative alphabet.
* @return The base64 encoded string.
*/
encodeString(input: string, webSafe?: boolean): string {
// Shortcut for Mozilla browsers that implement
// a native base64 encoder in the form of "btoa/atob"
if (this.HAS_NATIVE_SUPPORT && !webSafe) {
return btoa(input);
}
return this.encodeByteArray(stringToByteArray(input), webSafe);
},
/**
* Base64-decode a string.
*
* @param input to decode.
* @param webSafe True if we should use the
* alternative alphabet.
* @return string representing the decoded value.
*/
decodeString(input: string, webSafe: boolean): string {
// Shortcut for Mozilla browsers that implement
// a native base64 encoder in the form of "btoa/atob"
if (this.HAS_NATIVE_SUPPORT && !webSafe) {
return atob(input);
}
return byteArrayToString(this.decodeStringToByteArray(input, webSafe));
},
/**
* Base64-decode a string.
*
* In base-64 decoding, groups of four characters are converted into three
* bytes. If the encoder did not apply padding, the input length may not
* be a multiple of 4.
*
* In this case, the last group will have fewer than 4 characters, and
* padding will be inferred. If the group has one or two characters, it decodes
* to one byte. If the group has three characters, it decodes to two bytes.
*
* @param input Input to decode.
* @param webSafe True if we should use the web-safe alphabet.
* @return bytes representing the decoded value.
*/
decodeStringToByteArray(input: string, webSafe: boolean): number[] {
this.init_();
const charToByteMap = webSafe
? this.charToByteMapWebSafe_!
: this.charToByteMap_!;
const output: number[] = [];
for (let i = 0; i < input.length; ) {
const byte1 = charToByteMap[input.charAt(i++)];
const haveByte2 = i < input.length;
const byte2 = haveByte2 ? charToByteMap[input.charAt(i)] : 0;
++i;
const haveByte3 = i < input.length;
const byte3 = haveByte3 ? charToByteMap[input.charAt(i)] : 64;
++i;
const haveByte4 = i < input.length;
const byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 64;
++i;
if (byte1 == null || byte2 == null || byte3 == null || byte4 == null) {
throw Error();
}
const outByte1 = (byte1 << 2) | (byte2 >> 4);
output.push(outByte1);
if (byte3 !== 64) {
const outByte2 = ((byte2 << 4) & 0xf0) | (byte3 >> 2);
output.push(outByte2);
if (byte4 !== 64) {
const outByte3 = ((byte3 << 6) & 0xc0) | byte4;
output.push(outByte3);
}
}
}
return output;
},
/**
* Lazy static initialization function. Called before
* accessing any of the static map variables.
* @private
*/
init_() {
if (!this.byteToCharMap_) {
this.byteToCharMap_ = {};
this.charToByteMap_ = {};
this.byteToCharMapWebSafe_ = {};
this.charToByteMapWebSafe_ = {};
// We want quick mappings back and forth, so we precompute two maps.
for (let i = 0; i < this.ENCODED_VALS.length; i++) {
this.byteToCharMap_[i] = this.ENCODED_VALS.charAt(i);
this.charToByteMap_[this.byteToCharMap_[i]] = i;
this.byteToCharMapWebSafe_[i] = this.ENCODED_VALS_WEBSAFE.charAt(i);
this.charToByteMapWebSafe_[this.byteToCharMapWebSafe_[i]] = i;
// Be forgiving when decoding and correctly decode both encodings.
if (i >= this.ENCODED_VALS_BASE.length) {
this.charToByteMap_[this.ENCODED_VALS_WEBSAFE.charAt(i)] = i;
this.charToByteMapWebSafe_[this.ENCODED_VALS.charAt(i)] = i;
}
}
}
}
};
/**
* URL-safe base64 encoding
*/
export const base64Encode = function (str: string): string {
const utf8Bytes = stringToByteArray(str);
return base64.encodeByteArray(utf8Bytes, true);
};
/**
* URL-safe base64 decoding
*
* NOTE: DO NOT use the global atob() function - it does NOT support the
* base64Url variant encoding.
*
* @param str To be decoded
* @return Decoded result, if possible
*/
export const base64Decode = function (str: string): string | null {
try {
return base64.decodeString(str, true);
} catch (e) {
console.error('base64Decode failed: ', e);
}
return null;
};