diff --git a/README.md b/README.md index 4d6ae71..7274ce9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![NPM Downloads](https://img.shields.io/npm/dm/base64-arraybuffer.svg)](https://www.npmjs.org/package/base64-arraybuffer) [![NPM Version](https://img.shields.io/npm/v/base64-arraybuffer.svg)](https://www.npmjs.org/package/base64-arraybuffer) -Encode/decode base64 data into ArrayBuffers +Encode/decode base64 or base64url data into ArrayBuffers. ### Installing You can install the module via npm: @@ -12,11 +12,14 @@ You can install the module via npm: npm install base64-arraybuffer ## API -The library encodes and decodes base64 to and from ArrayBuffers +The library encodes and decodes base64/base64url to and from ArrayBuffers - __encode(buffer)__ - Encodes `ArrayBuffer` into base64 string - __decode(str)__ - Decodes base64 string to `ArrayBuffer` + - __encode(buffer, true)__ - Encodes `ArrayBuffer` into base64url string + - __decode(str, true)__ - Decodes base64url string to `ArrayBuffer` + ### Testing You can run the test suite with: diff --git a/package-lock.json b/package-lock.json index 1960bad..4fa9beb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "base64-arraybuffer", "version": "1.0.1", "license": "MIT", "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 2de9733..0227e11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,34 +1,43 @@ -const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +const + chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', + charsUrl = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', -// Use a lookup table to find the index. -const lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); -for (let i = 0; i < chars.length; i++) { - lookup[chars.charCodeAt(i)] = i; -} + genLookup = (target: string) => { + let lookupTemp = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); + for (let i = 0; i < chars.length; i++) { + lookupTemp[target.charCodeAt(i)] = i; + } + return lookupTemp; + }, + + // Use a lookup table to find the index. + lookup = genLookup(chars), + lookupUrl = genLookup(charsUrl); -export const encode = (arraybuffer: ArrayBuffer): string => { +export const encode = (arraybuffer: ArrayBuffer, urlMode?: boolean): string => { let bytes = new Uint8Array(arraybuffer), i, len = bytes.length, - base64 = ''; + base64 = '', + target = urlMode ? charsUrl : chars; for (i = 0; i < len; i += 3) { - base64 += chars[bytes[i] >> 2]; - base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; - base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; - base64 += chars[bytes[i + 2] & 63]; + base64 += target[bytes[i] >> 2]; + base64 += target[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + base64 += target[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + base64 += target[bytes[i + 2] & 63]; } if (len % 3 === 2) { - base64 = base64.substring(0, base64.length - 1) + '='; + base64 = base64.substring(0, base64.length - 1) + (urlMode ? '' : '='); } else if (len % 3 === 1) { - base64 = base64.substring(0, base64.length - 2) + '=='; + base64 = base64.substring(0, base64.length - 2) + (urlMode ? '' : '=='); } return base64; }; -export const decode = (base64: string): ArrayBuffer => { +export const decode = (base64: string, urlMode?: boolean): ArrayBuffer => { let bufferLength = base64.length * 0.75, len = base64.length, i, @@ -45,14 +54,16 @@ export const decode = (base64: string): ArrayBuffer => { } } - const arraybuffer = new ArrayBuffer(bufferLength), - bytes = new Uint8Array(arraybuffer); + const + arraybuffer = new ArrayBuffer(bufferLength), + bytes = new Uint8Array(arraybuffer), + target = urlMode ? lookupUrl : lookup; for (i = 0; i < len; i += 4) { - encoded1 = lookup[base64.charCodeAt(i)]; - encoded2 = lookup[base64.charCodeAt(i + 1)]; - encoded3 = lookup[base64.charCodeAt(i + 2)]; - encoded4 = lookup[base64.charCodeAt(i + 3)]; + encoded1 = target[base64.charCodeAt(i)]; + encoded2 = target[base64.charCodeAt(i + 1)]; + encoded3 = target[base64.charCodeAt(i + 2)]; + encoded4 = target[base64.charCodeAt(i + 3)]; bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); diff --git a/test/base64-arraybuffer.ts b/test/base64-arraybuffer.ts index 6d6f4be..0667f22 100644 --- a/test/base64-arraybuffer.ts +++ b/test/base64-arraybuffer.ts @@ -68,3 +68,34 @@ describe('decode', () => { ) )); }); + + +describe('encode url', () => { + it('encode "Hello world"', () => equal(encode(stringArrayBuffer('Hello world'), true), 'SGVsbG8gd29ybGQ')); + it('encode "Man"', () => equal(encode(stringArrayBuffer('Man'), true), 'TWFu')); + it('encode "Ma"', () => equal(encode(stringArrayBuffer('Ma'), true), 'TWE')); + it('encode "Hello worlds!"', () => equal(encode(stringArrayBuffer('Hello worlds!'), true), 'SGVsbG8gd29ybGRzIQ')); + it('encode all binary characters', () => + equal( + encode(rangeArrayBuffer(), true), + 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w' + )); +}); + +describe('decode url', () => { + it('decode "Man"', () => ok(testArrayBuffers(decode('TWFu', true), stringArrayBuffer('Man')))); + it('decode "Hello world"', () => + ok(testArrayBuffers(decode('SGVsbG8gd29ybGQ', true), stringArrayBuffer('Hello world')))); + it('decode "Hello worlds!"', () => + ok(testArrayBuffers(decode('SGVsbG8gd29ybGRzIQ', true), stringArrayBuffer('Hello worlds!')))); + it('decode all binary characters', () => + ok( + testArrayBuffers( + decode( + 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w', + true + ), + rangeArrayBuffer() + ) + )); +}); \ No newline at end of file