UNPKG

@turnkey/encoding

Version:

Encoding utility functions

179 lines (175 loc) 6.59 kB
'use strict'; var hex = require('./hex.js'); /** * Code modified from https://github.com/github/webauthn-json/blob/e932b3585fa70b0bd5b5a4012ba7dbad7b0a0d0f/src/webauthn-json/base64url.ts#L23 */ /** * Converts a plain string into a base64url-encoded string. * * @param {string} input - The input string to encode. * @returns {string} - The base64url-encoded string. */ function stringToBase64urlString(input) { // string to base64 // we do not rely on the browser's btoa since it's not present in React Native environments const base64String = btoa(input); return base64StringToBase64UrlEncodedString(base64String); } /** * Converts a hex string into a base64url-encoded string. * * @param {string} input - The input hex string. * @param {number} [length] - Optional length for the resulting buffer. Pads with leading 0s if needed. * @returns {string} - The base64url-encoded representation of the hex string. * @throws {Error} - If the hex string is invalid or too long for the specified length. */ function hexStringToBase64url(input, length) { // Add an extra 0 to the start of the string to get a valid hex string (even length) // (e.g. 0x0123 instead of 0x123) const hexString = input.padStart(Math.ceil(input.length / 2) * 2, "0"); const buffer = hex.uint8ArrayFromHexString(hexString, length); return stringToBase64urlString(buffer.reduce((result, x) => result + String.fromCharCode(x), "")); } /** * Converts a base64 string into a base64url-encoded string. * * @param {string} input - The input base64 string. * @returns {string} - The base64url-encoded string. */ function base64StringToBase64UrlEncodedString(input) { return input.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } /** * Converts a base64url-encoded string into a standard base64-encoded string. * * - Replaces URL-safe characters (`-` and `_`) back to standard base64 characters (`+` and `/`). * - Pads the result with `=` to ensure the length is a multiple of 4. * * @param {string} input - The base64url-encoded string to convert. * @returns {string} - The equivalent base64-encoded string. */ function base64UrlToBase64(input) { let b64 = input.replace(/-/g, "+").replace(/_/g, "/"); const padLen = (4 - (b64.length % 4)) % 4; return b64 + "=".repeat(padLen); } /** * Decodes a base64url-encoded string into a plain UTF-8 string. * * - Converts the input from base64url to base64. * - Decodes the base64 string into a plain string using a pure JS `atob` implementation. * * @param {string} input - The base64url-encoded string to decode. * @returns {string} - The decoded plain string. * @throws {Error} If the input is not correctly base64url/base64 encoded. */ function decodeBase64urlToString(input) { const b64 = base64UrlToBase64(input); return atob(b64); } // Pure JS implementation of btoa. This is adapted from the following: // https://github.com/jsdom/abab/blob/80874ae1fe1cde2e587bb6e51b6d7c9b42ca1d34/lib/btoa.js function btoa(s) { if (arguments.length === 0) { throw new TypeError("1 argument required, but only 0 present."); } let i; // String conversion as required by Web IDL. s = `${s}`; // "The btoa() method must throw an "InvalidCharacterError" DOMException if // data contains any character whose code point is greater than U+00FF." for (i = 0; i < s.length; i++) { if (s.charCodeAt(i) > 255) { throw new Error(`InvalidCharacterError: found code point greater than 255:${s.charCodeAt(i)} at position ${i}`); } } let out = ""; for (i = 0; i < s.length; i += 3) { const groupsOfSix = [ undefined, undefined, undefined, undefined, ]; groupsOfSix[0] = s.charCodeAt(i) >> 2; groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4; if (s.length > i + 1) { groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4; groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2; } if (s.length > i + 2) { groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6; groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f; } for (let j = 0; j < groupsOfSix.length; j++) { if (typeof groupsOfSix[j] === "undefined") { out += "="; } else { out += btoaLookup(groupsOfSix[j]); } } } return out; } function btoaLookup(index) { /** * Lookup table for btoa(), which converts a six-bit number into the * corresponding ASCII character. */ const keystr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; if (index >= 0 && index < 64) { return keystr[index]; } // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests. return undefined; } // Pure JS implementation of btoa. function atob(input) { if (arguments.length === 0) { throw new TypeError("1 argument required, but only 0 present."); } // convert to string and remove invalid characters upfront const str = String(input).replace(/[^A-Za-z0-9+/=]/g, ""); // the atob() method must throw an "InvalidCharacterError" if // the length of the string is not a multiple of 4 if (str.length % 4 === 1) { throw new Error("InvalidCharacterError: The string to be decoded is not correctly encoded."); } const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; let output = ""; let buffer = 0; let bits = 0; let i = 0; // process each character while (i < str.length) { const ch = str.charAt(i); const index = keyStr.indexOf(ch); if (index < 0 || index > 64) { i++; continue; } if (ch === "=") { // we skip padding characters bits = 0; } else { buffer = (buffer << 6) | index; bits += 6; } // output complete bytes while (bits >= 8) { bits -= 8; output += String.fromCharCode((buffer >> bits) & 0xff); } i++; } return output; } exports.atob = atob; exports.base64StringToBase64UrlEncodedString = base64StringToBase64UrlEncodedString; exports.base64UrlToBase64 = base64UrlToBase64; exports.decodeBase64urlToString = decodeBase64urlToString; exports.hexStringToBase64url = hexStringToBase64url; exports.stringToBase64urlString = stringToBase64urlString; //# sourceMappingURL=base64.js.map