-
Notifications
You must be signed in to change notification settings - Fork 543
Add native JavaScript class bindings and optimized utilities #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,147 +1,28 @@ | ||
| /** | ||
| * Ported from https://github.com/beatgammit/base64-js/blob/master/index.js | ||
| */ | ||
|
|
||
| const lookup: string[] = []; | ||
| const revLookup: { [key: string]: number } = {}; | ||
|
|
||
| const code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; | ||
| for (let i = 0, len = code.length; i < len; ++i) { | ||
| lookup[i] = code[i]; | ||
| revLookup[code.charCodeAt(i)] = i; | ||
| } | ||
|
|
||
| // Support decoding URL-safe base64 strings, as Node.js does. | ||
| // See: https://en.wikipedia.org/wiki/Base64#URL_applications | ||
| revLookup['-'.charCodeAt(0)] = 62; | ||
| revLookup['_'.charCodeAt(0)] = 63; | ||
|
|
||
| function getLens(b64: string): [number, number] { | ||
| const len = b64.length; | ||
|
|
||
| if (len % 4 > 0) { | ||
| throw new Error('Invalid string. Length must be a multiple of 4'); | ||
| } | ||
|
|
||
| // Trim off extra bytes after placeholder bytes are found | ||
| // See: https://github.com/beatgammit/base64-js/issues/42 | ||
| let validLen = b64.indexOf('='); | ||
| if (validLen === -1) validLen = len; | ||
|
|
||
| const placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4); | ||
|
|
||
| return [validLen, placeHoldersLen]; | ||
| } | ||
|
|
||
| function _byteLength(b64: string, validLen: number, placeHoldersLen: number): number { | ||
| return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen; | ||
| } | ||
|
|
||
| function tripletToBase64(num: number): string { | ||
| return lookup[(num >> 18) & 0x3f] + lookup[(num >> 12) & 0x3f] + lookup[(num >> 6) & 0x3f] + lookup[num & 0x3f]; | ||
| } | ||
|
|
||
| function encodeChunk(uint8: Uint8Array, start: number, end: number): string { | ||
| let tmp: number; | ||
| const output: string[] = []; | ||
| for (let i = start; i < end; i += 3) { | ||
| tmp = ((uint8[i] << 16) & 0xff0000) + ((uint8[i + 1] << 8) & 0xff00) + (uint8[i + 2] & 0xff); | ||
| output.push(tripletToBase64(tmp)); | ||
| } | ||
| return output.join(''); | ||
| } | ||
| import { decodeFromBase64, encodeToBase64 } from './Base64Native'; | ||
|
|
||
| export namespace Base64 { | ||
| interface Base64Options { | ||
| urlSafe?: boolean; | ||
| } | ||
|
|
||
| export function fromByteArray(uint8: Uint8Array, { urlSafe }: Base64Options = {}): string { | ||
| let tmp: number; | ||
| const len = uint8.length; | ||
| const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes | ||
| const parts: string[] = []; | ||
| const maxChunkLength = 16383; // must be multiple of 3 | ||
|
|
||
| // go through the array every three bytes, we'll deal with trailing stuff later | ||
| for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { | ||
| parts.push(encodeChunk(uint8, i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength)); | ||
| } | ||
|
|
||
| // pad the end with zeros, but make sure to not forget the extra bytes | ||
| if (extraBytes === 1) { | ||
| tmp = uint8[len - 1]; | ||
| parts.push(lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f] + '=='); | ||
| } else if (extraBytes === 2) { | ||
| tmp = (uint8[len - 2] << 8) + uint8[len - 1]; | ||
| parts.push(lookup[tmp >> 10] + lookup[(tmp >> 4) & 0x3f] + lookup[(tmp << 2) & 0x3f] + '='); | ||
| } | ||
|
|
||
| const base64String = parts.join(''); | ||
|
|
||
| if (urlSafe) { | ||
| // Convert to URL-safe base64 | ||
| return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); | ||
| } else { | ||
| return base64String; | ||
| } | ||
| return encodeToBase64(uint8, urlSafe === true); | ||
| } | ||
|
|
||
| export function toByteArray(b64: string): Uint8Array { | ||
| let tmp = 0; | ||
|
|
||
| // Add padding to make length multiple of 4, if necessary | ||
| const originalLen = b64.length; | ||
| if (originalLen % 4 > 0) { | ||
| const paddingCount = 4 - (originalLen % 4); | ||
| b64 += '='.repeat(paddingCount); | ||
| } | ||
|
|
||
| const lens = getLens(b64); | ||
| const validLen = lens[0]; | ||
| const placeHoldersLen = lens[1]; | ||
|
|
||
| const arr = new Uint8Array(_byteLength(b64, validLen, placeHoldersLen)); | ||
|
|
||
| let curByte = 0; | ||
|
|
||
| // if there are placeholders, only get up to the last complete 4 chars | ||
| const len = placeHoldersLen > 0 ? validLen - 4 : validLen; | ||
|
|
||
| let i = 0; | ||
| for (i = 0; i < len; i += 4) { | ||
| tmp = | ||
| (revLookup[b64.charCodeAt(i)] << 18) | | ||
| (revLookup[b64.charCodeAt(i + 1)] << 12) | | ||
| (revLookup[b64.charCodeAt(i + 2)] << 6) | | ||
| revLookup[b64.charCodeAt(i + 3)]; | ||
| arr[curByte++] = (tmp >> 16) & 0xff; | ||
| arr[curByte++] = (tmp >> 8) & 0xff; | ||
| arr[curByte++] = tmp & 0xff; | ||
| } | ||
|
|
||
| if (placeHoldersLen === 2) { | ||
| tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4); | ||
| arr[curByte++] = tmp & 0xff; | ||
| } | ||
|
|
||
| if (placeHoldersLen === 1) { | ||
| tmp = | ||
| (revLookup[b64.charCodeAt(i)] << 10) | | ||
| (revLookup[b64.charCodeAt(i + 1)] << 4) | | ||
| (revLookup[b64.charCodeAt(i + 2)] >> 2); | ||
| arr[curByte++] = (tmp >> 8) & 0xff; | ||
| arr[curByte++] = tmp & 0xff; | ||
| } | ||
|
|
||
| return arr; | ||
| return decodeFromBase64(b64); | ||
| } | ||
|
|
||
| // base64 is 4/3 + up to two characters of the original data | ||
| export function byteLength(b64: string): number { | ||
| const lens = getLens(b64); | ||
| const validLen = lens[0]; | ||
| const placeHoldersLen = lens[1]; | ||
| let validLen = b64.indexOf('='); | ||
| if (validLen === -1) validLen = b64.length; | ||
|
|
||
| if (validLen % 4 === 1) { | ||
| throw new Error('Invalid string. Length must be a valid Base64 length'); | ||
| } | ||
|
|
||
| const placeHoldersLen = validLen === b64.length ? (4 - (validLen % 4)) % 4 : 4 - (validLen % 4); | ||
| return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen; | ||
|
Comment on lines
+21
to
26
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Both threw before this change. A decoded-size helper returning a negative/incorrect value is risky for any caller that uses it to size an allocation. Suggest rejecting
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't remember the exact specifics but I know I did that on purpose,
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prior to this change: Base64 decode was lenient, it added missing padding before decoding,
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed the unpadded leniency itself is good — The thing I was flagging is narrower: for a few malformed inputs
The Not a blocker and fine to defer — but might be worth clamping to 0, or rejecting |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export function encodeToBase64(uint8: Uint8Array, urlSafe: boolean): string; | ||
|
|
||
| export function decodeFromBase64(base64: string): Uint8Array; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This swaps the pure-JS Base64 implementation for the C++-backed
Base64Nativemodule, but there's no web polyglot fallback for it — unlike the Unicode migration in this same PR.coreutils/web/only containsUnicodeNative.ts, andweb_register_native_module_id_overridesincoreutils/BUILD.bazelonly mapsUnicodeNative.js; there's noBase64Nativeentry. On web,./Base64Nativewon't resolve at runtime, soencodeToBase64/decodeFromBase64will beundefinedandBase64.fromByteArray/toByteArray/byteLengthwill throw.Could you add a
coreutils/web/Base64Native.tsmirroringUnicodeNative.ts, plus a matching entry in the override map?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I was hoping that would all be part of my web PR which has this change, but I understand in the mean time this is breaking things on web.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realistically, it'll be at least a week before the web stuff lands at the earliest so we need a stop gap until then.