├── .gitignore ├── index.d.ts ├── .github ├── dependabot.yml └── workflows │ ├── publish.yml │ └── main.yml ├── test ├── corrupt.js ├── big-data.js ├── url-safe.js └── convert.js ├── bench ├── basic.js └── bench.js ├── LICENSE ├── README.md ├── package.json ├── base64js.min.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export function byteLength(b64: string): number; 2 | export function toByteArray(b64: string): Uint8Array; 3 | export function fromByteArray(uint8: Uint8Array): string; 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | open-pull-requests-limit: 999 6 | rebase-strategy: disabled 7 | schedule: 8 | interval: daily 9 | -------------------------------------------------------------------------------- /test/corrupt.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const b64 = require('../') 3 | 4 | test('padding bytes found inside base64 string', function (t) { 5 | // See https://github.com/beatgammit/base64-js/issues/42 6 | const str = 'SQ==QU0=' 7 | t.deepEqual(b64.toByteArray(str), new Uint8Array([73])) 8 | t.equal(b64.byteLength(str), 1) 9 | t.end() 10 | }) 11 | -------------------------------------------------------------------------------- /bench/basic.js: -------------------------------------------------------------------------------- 1 | const random = require('crypto').randomBytes 2 | 3 | const b64 = require('../') 4 | let data = random(1e6).toString('base64') 5 | const start = Date.now() 6 | const raw = b64.toByteArray(data) 7 | const middle1 = Date.now() 8 | data = b64.fromByteArray(raw) 9 | const middle2 = Date.now() 10 | const len = b64.byteLength(data) 11 | const end = Date.now() 12 | 13 | console.log( 14 | 'decode ms, decode ops/ms, encode ms, encode ops/ms, length ms, length ops/ms' 15 | ) 16 | console.log( 17 | middle1 - start, 18 | data.length / (middle1 - start), 19 | middle2 - middle1, 20 | data.length / (middle2 - middle1), 21 | end - middle2, 22 | len / (end - middle2) 23 | ) 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | publish: 7 | name: Publish to NPM 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | id-token: write 12 | steps: 13 | - name: Check out repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 22 20 | registry-url: https://registry.npmjs.org 21 | 22 | - name: Publish to NPM 23 | run: npm publish --provenance --access public 24 | env: 25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | -------------------------------------------------------------------------------- /test/big-data.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const b64 = require('../') 3 | 4 | test('convert big data to base64', function (t) { 5 | const big = new Uint8Array(64 * 1024 * 1024) 6 | for (let i = 0, length = big.length; i < length; ++i) { 7 | big[i] = i % 256 8 | } 9 | const b64str = b64.fromByteArray(big) 10 | const arr = b64.toByteArray(b64str) 11 | t.ok(equal(arr, big)) 12 | t.equal(b64.byteLength(b64str), arr.length) 13 | t.end() 14 | }) 15 | 16 | function equal (a, b) { 17 | let i 18 | const length = a.length 19 | if (length !== b.length) return false 20 | for (i = 0; i < length; ++i) { 21 | if (a[i] !== b[i]) return false 22 | } 23 | return true 24 | } 25 | -------------------------------------------------------------------------------- /test/url-safe.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const b64 = require('../') 3 | 4 | test('decode url-safe style base64 strings', function (t) { 5 | const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff] 6 | 7 | let str = '//++/++/++//' 8 | let actual = b64.toByteArray(str) 9 | for (let i = 0; i < actual.length; i++) { 10 | t.equal(actual[i], expected[i]) 11 | } 12 | 13 | t.equal(b64.byteLength(str), actual.length) 14 | 15 | str = '__--_--_--__' 16 | actual = b64.toByteArray(str) 17 | for (let i = 0; i < actual.length; i++) { 18 | t.equal(actual[i], expected[i]) 19 | } 20 | 21 | t.equal(b64.byteLength(str), actual.length) 22 | 23 | t.end() 24 | }) 25 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | const base64 = require('../') 2 | const benchmark = require('benchmark') 3 | 4 | const suite = new benchmark.Suite() 5 | const random = require('crypto').randomBytes 6 | const data = random(1e6).toString('base64') 7 | const raw = base64.toByteArray(data) 8 | 9 | suite 10 | .add('base64.toByteArray() (decode)', function () { 11 | const raw2 = base64.toByteArray(data) // eslint-disable-line no-unused-vars 12 | }) 13 | .add('base64.fromByteArray() (encode)', function () { 14 | const data2 = base64.fromByteArray(raw) // eslint-disable-line no-unused-vars 15 | }) 16 | .add('base64.byteLength() (encode)', function () { 17 | const len = base64.byteLength(data) // eslint-disable-line no-unused-vars 18 | }) 19 | .on('error', function (event) { 20 | console.error(event.target.error.stack) 21 | }) 22 | .on('cycle', function (event) { 23 | console.log(String(event.target)) 24 | }) 25 | .run({ async: true }) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jameson Little 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # base64-js 2 | 3 | ### Ongoing work has temporarily moved from [beatgammit/base64-js](https://github.com/beatgammit/base64-js) to [feross/base64-js](https://github.com/feross/base64-js). 4 | 5 | `base64-js` does basic base64 encoding/decoding in pure JS. 6 | 7 | [![CI](https://github.com/feross/base64-js/actions/workflows/main.yml/badge.svg)](https://github.com/feross/base64-js/actions/workflows/main.yml) 8 | 9 | Many browsers already have base64 encoding/decoding functionality, but it is for text data, not all-purpose binary data. 10 | 11 | Sometimes encoding/decoding binary data in the browser is useful, and that is what this module does. 12 | 13 | ## install 14 | 15 | With [npm](https://npmjs.org) do: 16 | 17 | `npm install base64-js` and `var base64js = require('base64-js')` 18 | 19 | For use in web browsers do: 20 | 21 | `` 22 | 23 | [Get supported base64-js with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-base64-js?utm_source=npm-base64-js&utm_medium=referral&utm_campaign=readme) 24 | 25 | ## methods 26 | 27 | `base64js` has three exposed functions, `byteLength`, `toByteArray` and `fromByteArray`, which both take a single argument. 28 | 29 | - `byteLength` - Takes a base64 string and returns length of byte array 30 | - `toByteArray` - Takes a base64 string and returns a byte array 31 | - `fromByteArray` - Takes a byte array and returns a base64 string 32 | 33 | ## license 34 | 35 | MIT 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base64-js", 3 | "type": "commonjs", 4 | "version": "1.5.1", 5 | "description": "Base64 encoding/decoding in pure JS", 6 | "keywords": [ 7 | "base64" 8 | ], 9 | "homepage": "https://github.com/feross/base64-js", 10 | "bugs": { 11 | "url": "https://github.com/feross/base64-js/issues" 12 | }, 13 | "license": "MIT", 14 | "author": { 15 | "name": "T. Jameson Little", 16 | "email": "t.jameson.little@gmail.com", 17 | "url": "https://jamesonlittle.com" 18 | }, 19 | "funding": [ 20 | { 21 | "type": "github", 22 | "url": "https://github.com/sponsors/feross" 23 | }, 24 | { 25 | "type": "patreon", 26 | "url": "https://www.patreon.com/feross" 27 | }, 28 | { 29 | "type": "consulting", 30 | "url": "https://feross.org/support" 31 | } 32 | ], 33 | "files": [ 34 | "base64js.min.js", 35 | "index.d.ts", 36 | "index.js" 37 | ], 38 | "main": "index.js", 39 | "typings": "index.d.ts", 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/feross/base64-js.git" 43 | }, 44 | "scripts": { 45 | "build": "browserify -s base64js -r ./ | minify > base64js.min.js", 46 | "lint": "standard", 47 | "test": "npm run lint && npm run unit", 48 | "unit": "tape test/*.js" 49 | }, 50 | "devDependencies": { 51 | "babel-minify": "^0.5.1", 52 | "benchmark": "^2.1.4", 53 | "browserify": "^17.0.0", 54 | "standard": "*", 55 | "tape": "^5.4.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 22 19 | 20 | - name: Install dependencies 21 | run: npm install 22 | 23 | - name: Run build 24 | run: npm run build 25 | 26 | lint: 27 | name: Lint 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Check out repository 31 | uses: actions/checkout@v4 32 | 33 | - name: Set up Node.js 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: 22 37 | 38 | - name: Install dependencies 39 | run: npm install 40 | 41 | - name: Run linter 42 | run: npm run lint 43 | 44 | test: 45 | name: Test on Node.js ${{ matrix.node }} 46 | runs-on: ubuntu-latest 47 | strategy: 48 | matrix: 49 | node: [6, 8, 10, 12, 14, 16, 18, 20, 22] 50 | steps: 51 | - name: Check out repository 52 | uses: actions/checkout@v4 53 | 54 | - name: Set up Node.js 55 | uses: actions/setup-node@v4 56 | with: 57 | node-version: ${{ matrix.node }} 58 | 59 | - name: Install dependencies 60 | run: npm install 61 | 62 | - name: Run tests 63 | run: npm run unit 64 | -------------------------------------------------------------------------------- /base64js.min.js: -------------------------------------------------------------------------------- 1 | (function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"==typeof window?"undefined"==typeof global?"undefined"==typeof self?this:self:global:window,b.base64js=a()}})(function(){return function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;c>16,j[k++]=255&b>>8,j[k++]=255&b;return 2===h&&(b=l[a.charCodeAt(c)]<<2|l[a.charCodeAt(c+1)]>>4,j[k++]=255&b),1===h&&(b=l[a.charCodeAt(c)]<<10|l[a.charCodeAt(c+1)]<<4|l[a.charCodeAt(c+2)]>>2,j[k++]=255&b>>8,j[k++]=255&b),j}function g(a){return k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}function h(a,b,c){for(var d,e=[],f=b;fj?j:g+f));return 1===d?(b=a[c-1],e.push(k[b>>2]+k[63&b<<4]+"==")):2===d&&(b=(a[c-2]<<8)+a[c-1],e.push(k[b>>10]+k[63&b>>4]+k[63&b<<2]+"=")),e.join("")}c.byteLength=function(a){var b=d(a),c=b[0],e=b[1];return 3*(c+e)/4-e},c.toByteArray=f,c.fromByteArray=j;for(var k=[],l=[],m="undefined"==typeof Uint8Array?Array:Uint8Array,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0,p=n.length;o 0) { 26 | throw new Error('Invalid string. Length must be a multiple of 4') 27 | } 28 | 29 | // Trim off extra bytes after placeholder bytes are found 30 | // See: https://github.com/beatgammit/base64-js/issues/42 31 | var validLen = b64.indexOf('=') 32 | if (validLen === -1) validLen = len 33 | 34 | var placeHoldersLen = validLen === len 35 | ? 0 36 | : 4 - (validLen % 4) 37 | 38 | return [validLen, placeHoldersLen] 39 | } 40 | 41 | // base64 is 4/3 + up to two characters of the original data 42 | function byteLength (b64) { 43 | var lens = getLens(b64) 44 | var validLen = lens[0] 45 | var placeHoldersLen = lens[1] 46 | return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen 47 | } 48 | 49 | function _byteLength (b64, validLen, placeHoldersLen) { 50 | return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen 51 | } 52 | 53 | function toByteArray (b64) { 54 | var tmp 55 | var lens = getLens(b64) 56 | var validLen = lens[0] 57 | var placeHoldersLen = lens[1] 58 | 59 | var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)) 60 | 61 | var curByte = 0 62 | 63 | // if there are placeholders, only get up to the last complete 4 chars 64 | var len = placeHoldersLen > 0 65 | ? validLen - 4 66 | : validLen 67 | 68 | var i 69 | for (i = 0; i < len; i += 4) { 70 | tmp = 71 | (revLookup[b64.charCodeAt(i)] << 18) | 72 | (revLookup[b64.charCodeAt(i + 1)] << 12) | 73 | (revLookup[b64.charCodeAt(i + 2)] << 6) | 74 | revLookup[b64.charCodeAt(i + 3)] 75 | arr[curByte++] = (tmp >> 16) & 0xFF 76 | arr[curByte++] = (tmp >> 8) & 0xFF 77 | arr[curByte++] = tmp & 0xFF 78 | } 79 | 80 | if (placeHoldersLen === 2) { 81 | tmp = 82 | (revLookup[b64.charCodeAt(i)] << 2) | 83 | (revLookup[b64.charCodeAt(i + 1)] >> 4) 84 | arr[curByte++] = tmp & 0xFF 85 | } 86 | 87 | if (placeHoldersLen === 1) { 88 | tmp = 89 | (revLookup[b64.charCodeAt(i)] << 10) | 90 | (revLookup[b64.charCodeAt(i + 1)] << 4) | 91 | (revLookup[b64.charCodeAt(i + 2)] >> 2) 92 | arr[curByte++] = (tmp >> 8) & 0xFF 93 | arr[curByte++] = tmp & 0xFF 94 | } 95 | 96 | return arr 97 | } 98 | 99 | function tripletToBase64 (num) { 100 | return lookup[num >> 18 & 0x3F] + 101 | lookup[num >> 12 & 0x3F] + 102 | lookup[num >> 6 & 0x3F] + 103 | lookup[num & 0x3F] 104 | } 105 | 106 | function encodeChunk (uint8, start, end) { 107 | var tmp 108 | var output = [] 109 | for (var i = start; i < end; i += 3) { 110 | tmp = 111 | ((uint8[i] << 16) & 0xFF0000) + 112 | ((uint8[i + 1] << 8) & 0xFF00) + 113 | (uint8[i + 2] & 0xFF) 114 | output.push(tripletToBase64(tmp)) 115 | } 116 | return output.join('') 117 | } 118 | 119 | function fromByteArray (uint8) { 120 | var tmp 121 | var len = uint8.length 122 | var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes 123 | var parts = [] 124 | var maxChunkLength = 16383 // must be multiple of 3 125 | 126 | // go through the array every three bytes, we'll deal with trailing stuff later 127 | for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { 128 | parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) 129 | } 130 | 131 | // pad the end with zeros, but make sure to not forget the extra bytes 132 | if (extraBytes === 1) { 133 | tmp = uint8[len - 1] 134 | parts.push( 135 | lookup[tmp >> 2] + 136 | lookup[(tmp << 4) & 0x3F] + 137 | '==' 138 | ) 139 | } else if (extraBytes === 2) { 140 | tmp = (uint8[len - 2] << 8) + uint8[len - 1] 141 | parts.push( 142 | lookup[tmp >> 10] + 143 | lookup[(tmp >> 4) & 0x3F] + 144 | lookup[(tmp << 2) & 0x3F] + 145 | '=' 146 | ) 147 | } 148 | 149 | return parts.join('') 150 | } 151 | --------------------------------------------------------------------------------