├── .eslintignore ├── .npmrc ├── .gitignore ├── types └── index.d.ts ├── tsconfig.json ├── CHANGELOG.md ├── runall.mjs ├── UNLICENSE ├── src └── index.js ├── package.json ├── lib └── index.js ├── README.md └── test └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | *.tgz 4 | 5 | pnpm-lock.yaml 6 | package-lock.json 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export function encode(base64: string): string; 2 | export function decode(safe: string): string; 3 | export function trim(string: string): string; 4 | export function isBase64(string: string): boolean; 5 | export function isUrlSafeBase64(string: string): boolean; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "checkJs": true, 6 | "declaration": true, 7 | "declarationDir": "types", 8 | "moduleResolution": "node", 9 | "noImplicitAny": false, 10 | "strict": true, 11 | "target": "ES2021", 12 | "emitDeclarationOnly": true, 13 | "noEmitOnError": true 14 | }, 15 | "include": [ 16 | "**/*.js" 17 | ], 18 | "exclude": [ 19 | "lib", 20 | "bin", 21 | "coverage", 22 | "dist", 23 | "docs", 24 | "examples", 25 | "node_modules", 26 | "test", 27 | "tmp" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.3.1 (2023-08-19) 2 | 3 | - chore: bump dev dependencies (#40c195a) 4 | 5 | # 1.3.0 (2022-12-23) 6 | 7 | - chore: bump devdependencies (#74049c4) 8 | - fix(#4): use = instead of . for padding (#78b119e) 9 | - chore: bump (#3671ec2) 10 | - chore: bump dependencies (#198bb4b) 11 | 12 | # 1.2.0 (2022-03-27) 13 | 14 | - chore: overhaul (#49a10b5) 15 | - chore: sort package json (#601486e) 16 | 17 | # 1.1.1 (2019-10-26) 18 | 19 | - chore: bump deps (#6124ae2) 20 | - bump dev dependencies (#dfb7e1c) 21 | 22 | # 1.1.0 (2017-06-25) 23 | 24 | - adding checks (#042c902) 25 | 26 | # 1.0.0 (2017-04-20) 27 | 28 | - update links (#b0002fd) 29 | - init (#3110f34) 30 | 31 | -------------------------------------------------------------------------------- /runall.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * `npm run` with multiple arguments 5 | */ 6 | 7 | // @ts-nocheck 8 | import { spawn } from 'child_process' 9 | 10 | function npmRun (arg) { 11 | return new Promise((resolve, reject) => { 12 | const sub = spawn('npm', ['run', arg], { stdio: 'inherit' }) 13 | sub.on('error', err => { reject(err) }) 14 | sub.on('exit', code => { 15 | code > 0 16 | ? reject(new Error('' + code)) 17 | : resolve() 18 | }) 19 | }) 20 | } 21 | 22 | async function main (argv) { 23 | for (const arg of argv) { 24 | await npmRun(arg) 25 | } 26 | } 27 | 28 | main(process.argv.slice(2)) 29 | .catch(() => { 30 | process.exit(1) 31 | }) 32 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module url-safe-base64 3 | * @license UNLICENSE 4 | * @example 5 | * import { 6 | * encode, decode, trim, 7 | * isBase64, isUrlSafeBase64 8 | * } from 'url-safe-base64' 9 | * const safe = encode('A/B+C==') 10 | * // > 'A-B_C..' 11 | * trim(safe) 12 | * // > 'A-B_C' 13 | * const base64 = decode(safe) 14 | * // > 'A/B+C==' 15 | * isBase64(base64) 16 | * // > true 17 | * isBase64(safe) 18 | * // > false 19 | * isUrlSafeBase64(base64) 20 | * // > false 21 | * isUrlSafeBase64(safe) 22 | * // > true 23 | */ 24 | 25 | const ENC = { 26 | '+': '-', 27 | '/': '_' 28 | } 29 | const DEC = { 30 | '-': '+', 31 | _: '/', 32 | '.': '=' 33 | } 34 | 35 | /** 36 | * encode base64 string url safe 37 | * @param {String} base64 - base64 encoded string 38 | * @return {String} url-safe-base64 encoded 39 | */ 40 | export const encode = (base64) => { 41 | return base64.replace(/[+/]/g, (m) => ENC[m]) 42 | } 43 | 44 | /** 45 | * decode url-safe-base64 string to base64 46 | * @param {String} safe - url-safe-base64 string 47 | * @return {String} base64 encoded 48 | */ 49 | export const decode = (safe) => { 50 | return safe.replace(/[-_.]/g, (m) => DEC[m]) 51 | } 52 | 53 | /** 54 | * trim padding - `window.atob` might handle trimmed strings, e.g. in Chrome@57, Firefox@52 55 | * @param {String} string - base64 or url-safe-base64 string 56 | * @return {String} string with padding chars removed 57 | */ 58 | export const trim = (string) => { 59 | return string.replace(/[.=]{1,2}$/, '') 60 | } 61 | 62 | /** 63 | * checks if `string` is base64 encoded 64 | * @param {String} string 65 | * @return {Boolean} true if base64 encoded 66 | */ 67 | export const isBase64 = (string) => /^[A-Za-z0-9+/]*[=]{0,2}$/.test(string) 68 | 69 | /** 70 | * checks if `string` is url-safe-base64 encoded 71 | * @param {String} string 72 | * @return {Boolean} true if url-safe-base64 encoded 73 | */ 74 | export const isUrlSafeBase64 = (string) => /^[A-Za-z0-9_-]*[.=]{0,2}$/.test(string) 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "url-safe-base64", 3 | "version": "1.3.1-0", 4 | "description": "url safe base64 en- and decoding", 5 | "keywords": [ 6 | "base64", 7 | "safe", 8 | "url" 9 | ], 10 | "homepage": "https://github.com/commenthol/url-safe-base64#readme", 11 | "bugs": { 12 | "url": "https://github.com/commenthol/url-safe-base64/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/commenthol/url-safe-base64.git" 17 | }, 18 | "license": "Unlicense", 19 | "author": "commenthol@gmail.com", 20 | "main": "lib", 21 | "module": "src", 22 | "types": "types", 23 | "directories": { 24 | "lib": "lib", 25 | "test": "test" 26 | }, 27 | "files": [ 28 | "src", 29 | "lib", 30 | "types" 31 | ], 32 | "scripts": { 33 | "changelog": "npx conv-changelog -o CHANGELOG.md", 34 | "ci": "./runall.mjs clean lint transpile test types", 35 | "clean": "rimraf lib *.tgz", 36 | "dox": "dox -r < lib/index.js | doxme --readme > README.md", 37 | "lint": "eslint --ext .js .", 38 | "test": "mocha", 39 | "transpile": "babel -d lib src", 40 | "types": "rimraf types; tsc" 41 | }, 42 | "babel": { 43 | "presets": [ 44 | "@babel/preset-env" 45 | ] 46 | }, 47 | "eslintConfig": { 48 | "extends": "standard", 49 | "rules": {} 50 | }, 51 | "mocha": { 52 | "checkLeaks": true, 53 | "colors": true, 54 | "require": [ 55 | "@babel/register" 56 | ] 57 | }, 58 | "dependencies": {}, 59 | "devDependencies": { 60 | "@babel/cli": "^7.22.10", 61 | "@babel/core": "^7.22.10", 62 | "@babel/preset-env": "^7.22.10", 63 | "@babel/register": "^7.22.5", 64 | "eslint": "^8.47.0", 65 | "eslint-config-standard": "^17.1.0", 66 | "eslint-plugin-import": "^2.28.1", 67 | "eslint-plugin-n": "^16.0.1", 68 | "eslint-plugin-promise": "^6.1.1", 69 | "mocha": "^10.2.0", 70 | "rimraf": "^5.0.1", 71 | "typescript": "^5.1.6" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.trim = exports.isUrlSafeBase64 = exports.isBase64 = exports.encode = exports.decode = void 0; 7 | /** 8 | * @module url-safe-base64 9 | * @license UNLICENSE 10 | * @example 11 | * import { 12 | * encode, decode, trim, 13 | * isBase64, isUrlSafeBase64 14 | * } from 'url-safe-base64' 15 | * const safe = encode('A/B+C==') 16 | * // > 'A-B_C..' 17 | * trim(safe) 18 | * // > 'A-B_C' 19 | * const base64 = decode(safe) 20 | * // > 'A/B+C==' 21 | * isBase64(base64) 22 | * // > true 23 | * isBase64(safe) 24 | * // > false 25 | * isUrlSafeBase64(base64) 26 | * // > false 27 | * isUrlSafeBase64(safe) 28 | * // > true 29 | */ 30 | 31 | var ENC = { 32 | '+': '-', 33 | '/': '_' 34 | }; 35 | var DEC = { 36 | '-': '+', 37 | _: '/', 38 | '.': '=' 39 | }; 40 | 41 | /** 42 | * encode base64 string url safe 43 | * @param {String} base64 - base64 encoded string 44 | * @return {String} url-safe-base64 encoded 45 | */ 46 | var encode = function encode(base64) { 47 | return base64.replace(/[+/]/g, function (m) { 48 | return ENC[m]; 49 | }); 50 | }; 51 | 52 | /** 53 | * decode url-safe-base64 string to base64 54 | * @param {String} safe - url-safe-base64 string 55 | * @return {String} base64 encoded 56 | */ 57 | exports.encode = encode; 58 | var decode = function decode(safe) { 59 | return safe.replace(/[-_.]/g, function (m) { 60 | return DEC[m]; 61 | }); 62 | }; 63 | 64 | /** 65 | * trim padding - `window.atob` might handle trimmed strings, e.g. in Chrome@57, Firefox@52 66 | * @param {String} string - base64 or url-safe-base64 string 67 | * @return {String} string with padding chars removed 68 | */ 69 | exports.decode = decode; 70 | var trim = function trim(string) { 71 | return string.replace(/[.=]{1,2}$/, ''); 72 | }; 73 | 74 | /** 75 | * checks if `string` is base64 encoded 76 | * @param {String} string 77 | * @return {Boolean} true if base64 encoded 78 | */ 79 | exports.trim = trim; 80 | var isBase64 = function isBase64(string) { 81 | return /^[A-Za-z0-9+/]*[=]{0,2}$/.test(string); 82 | }; 83 | 84 | /** 85 | * checks if `string` is url-safe-base64 encoded 86 | * @param {String} string 87 | * @return {Boolean} true if url-safe-base64 encoded 88 | */ 89 | exports.isBase64 = isBase64; 90 | var isUrlSafeBase64 = function isUrlSafeBase64(string) { 91 | return /^[A-Za-z0-9_-]*[.=]{0,2}$/.test(string); 92 | }; 93 | exports.isUrlSafeBase64 = isUrlSafeBase64; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # url-safe-base64 2 | 3 | > url safe base64 en- and decoding 4 | 5 | [![NPM version](https://badge.fury.io/js/url-safe-base64.svg)](https://www.npmjs.com/package/url-safe-base64/) 6 | 7 | ## TOC 8 | 9 | * [Example](#example) 10 | * [API](#api) 11 | * [`encode(base64)`](#encodebase64) 12 | * [`decode(safe)`](#decodesafe) 13 | * [`trim(string)`](#trimstr) 14 | * [`isBase64(string)`](#isbase64string) 15 | * [`isUrlSafeBase64(string)`](#isurlsafebase64string) 16 | * [Installation](#installation) 17 | * [Tests](#tests) 18 | * [LICENSE](#license) 19 | 20 | ## Example 21 | 22 | ```js 23 | import { 24 | encode, decode, trim, 25 | isBase64, isUrlSafeBase64 26 | } from 'url-safe-base64' 27 | const safe = encode('A/B+C==') 28 | // > 'A-B_C==' 29 | trim(safe) 30 | // > 'A-B_C' 31 | const base64 = decode(safe) 32 | // > 'A/B+C==' 33 | isBase64(base64) 34 | // > true 35 | isBase64(safe) 36 | // > false 37 | isUrlSafeBase64(base64) 38 | // > false 39 | isUrlSafeBase64(safe) 40 | // > true 41 | ``` 42 | 43 | ## API 44 | 45 | ### `encode(base64)` 46 | 47 | encode base64 string url safe 48 | 49 | **Parameters** 50 | 51 | | parameter | type | description | 52 | | --------- | ------ | ----------------------- | 53 | | `base64` | String | base64 encoded string | 54 | 55 | **Returns** `String`, url-safe-base64 encoded 56 | 57 | 58 | ### `decode(safe)` 59 | 60 | decode url-safe-base64 string to base64 61 | 62 | **Parameters** 63 | 64 | | parameter | type | description | 65 | | --------- | ------ | ------------------------ | 66 | | `safe` | String | - url-safe-base64 string | 67 | 68 | **Returns** `String`, base64 encoded 69 | 70 | 71 | ### `trim(string)` 72 | 73 | trim padding - `window.atob` might handle trimmed strings, e.g. in Chrome@57, Firefox@52 74 | 75 | **Parameters** 76 | 77 | | parameter | type | description | 78 | | --------- | ------ | ---------------------------------- | 79 | | `string` | String | - base64 or url-safe-base64 string | 80 | 81 | **Returns** `String`, string with padding chars removed 82 | 83 | 84 | ### `isBase64(string)` 85 | 86 | checks if `string` is base64 encoded 87 | 88 | **Returns** `Boolean`, true if base64 encoded 89 | 90 | 91 | ### `isUrlSafeBase64(string)` 92 | 93 | checks if `string` is url-safe-base64 encoded 94 | 95 | **Returns** `Boolean`, true if url-safe-base64 encoded 96 | 97 | ## Installation 98 | 99 | ```sh 100 | $ npm install --save url-safe-base64 101 | ``` 102 | 103 | ## Tests 104 | 105 | ```sh 106 | $ npm test 107 | ``` 108 | 109 | ## LICENSE 110 | 111 | UNLICENSE 112 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import { encode, decode, trim, isBase64, isUrlSafeBase64 } from '..' 5 | 6 | const base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 7 | const urlSafeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=' 8 | // non standard padding uses `.` 9 | const urlSafeCharsAlt = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.' 10 | 11 | describe('url-safe', function () { 12 | const b64 = [base64chars, base64chars, base64chars].join() 13 | const safe = [urlSafeChars, urlSafeChars, urlSafeChars].join() 14 | 15 | it('should encode base64 chars', function () { 16 | const res = encode(b64) 17 | assert.strictEqual(res, safe) 18 | }) 19 | 20 | it('should decode url-safe-base64 chars into base64', function () { 21 | const res = decode(safe) 22 | assert.strictEqual(res, b64) 23 | }) 24 | 25 | describe('should check if base64 encoded', function () { 26 | it('base64chars', function () { 27 | const res = isBase64(base64chars) 28 | assert.strictEqual(res, true) 29 | }) 30 | it('urlSafeChars', function () { 31 | const res = isBase64(urlSafeChars) 32 | assert.strictEqual(res, false) 33 | }) 34 | it('b64', function () { 35 | const res = isBase64(b64) 36 | assert.strictEqual(res, false) 37 | }) 38 | it('safe', function () { 39 | const res = isBase64(safe) 40 | assert.strictEqual(res, false) 41 | }) 42 | }) 43 | 44 | describe('should check if url-safe-base64 encoded', function () { 45 | it('base64chars', function () { 46 | const res = isUrlSafeBase64(base64chars) 47 | assert.strictEqual(res, false) 48 | }) 49 | it('urlSafeChars', function () { 50 | const res = isUrlSafeBase64(urlSafeChars) 51 | assert.strictEqual(res, true) 52 | }) 53 | it('urlSafeCharsAlt', function () { 54 | const res = isUrlSafeBase64(urlSafeCharsAlt) 55 | assert.strictEqual(res, true) 56 | }) 57 | it('b64', function () { 58 | const res = isUrlSafeBase64(b64) 59 | assert.strictEqual(res, false) 60 | }) 61 | it('safe', function () { 62 | const res = isUrlSafeBase64(safe) 63 | assert.strictEqual(res, false) 64 | }) 65 | }) 66 | 67 | describe('should trim padding from url-safe-base64', function () { 68 | ;[ 69 | ['A', 'A'], 70 | ['A=', 'A'], 71 | ['A==', 'A'], 72 | ['A===', 'A='], 73 | ['A.', 'A'], 74 | ['A..', 'A'], 75 | ['A...', 'A.'] 76 | ].forEach((test) => { 77 | it(test[0], function () { 78 | const res = trim(test[0]) 79 | assert.strictEqual(res, test[1]) 80 | }) 81 | }) 82 | }) 83 | }) 84 | --------------------------------------------------------------------------------