Skip to content

Commit

Permalink
feat: improve base64 validation based on RFC4648
Browse files Browse the repository at this point in the history
add padding to the option list
update regexes to support validation with/without padding
update default options to keep the changes backward compatible
add new test to cover different scenarios
  • Loading branch information
aseyfpour committed Nov 2, 2024
1 parent fc31e6e commit 21c49a8
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 127 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Validator | Description
**isAscii(str)** | check if the string contains ASCII chars only.
**isBase32(str [, options])** | check if the string is base32 encoded. `options` is optional and defaults to `{ crockford: false }`.<br/> When `crockford` is true it tests the given base32 encoded string using [Crockford's base32 alternative][Crockford Base32].
**isBase58(str)** | check if the string is base58 encoded.
**isBase64(str [, options])** | check if the string is base64 encoded. `options` is optional and defaults to `{ urlSafe: false }`<br/> when `urlSafe` is true it tests the given base64 encoded string is [url safe][Base64 URL Safe].
**isBase64(str [, options])** | check if the string is base64 encoded. `options` is optional and defaults to `{ urlSafe: false, padding: true }`<br/> when `urlSafe` is true default value for `padding` is false and it tests the given base64 encoded string is [url safe][Base64 URL Safe].
**isBefore(str [, date])** | check if the string is a date that is before the specified date.
**isBIC(str)** | check if the string is a BIC (Bank Identification Code) or SWIFT code.
**isBoolean(str [, options])** | check if the string is a boolean.<br/>`options` is an object which defaults to `{ loose: false }`. If `loose` is set to false, the validator will strictly match ['true', 'false', '0', '1']. If `loose` is set to true, the validator will also match 'yes', 'no', and will match a valid boolean string of any case. (e.g.: ['true', 'True', 'TRUE']).
Expand Down
29 changes: 12 additions & 17 deletions src/lib/isBase64.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import assertString from './util/assertString';
import merge from './util/merge';

const notBase64 = /[^A-Z0-9+\/=]/i;
const urlSafeBase64 = /^[A-Z0-9_\-]*$/i;

const defaultBase64Options = {
urlSafe: false,
};
const base64WithPadding = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
const base64WithoutPadding = /^[A-Za-z0-9+/]+$/;
const base64UrlWithPadding = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}==|[A-Za-z0-9_-]{3}=|[A-Za-z0-9_-]{4})$/;
const base64UrlWithoutPadding = /^[A-Za-z0-9_-]+$/;

export default function isBase64(str, options) {
assertString(str);
options = merge(options, defaultBase64Options);
const len = str.length;
options = merge(options, { urlSafe: false, padding: !options?.urlSafe });

if (options.urlSafe) {
return urlSafeBase64.test(str);
}
if (str === '') return true;

if (len % 4 !== 0 || notBase64.test(str)) {
return false;
let regex;
if (options.urlSafe) {
regex = options.padding ? base64UrlWithPadding : base64UrlWithoutPadding;
} else {
regex = options.padding ? base64WithPadding : base64WithoutPadding;
}

const firstPaddingChar = str.indexOf('=');
return firstPaddingChar === -1 ||
firstPaddingChar === len - 1 ||
(firstPaddingChar === len - 2 && str[len - 1] === '=');
return (!options.padding || str.length % 4 === 0) && regex.test(str);
}
128 changes: 128 additions & 0 deletions test/validators/isBase64.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import test from '../testFunctions';

describe('isBase64', () => {
it('should validate standard Base64 with padding', () => {
test({
validator: 'isBase64',
args: [{ urlSafe: false, padding: true }],
valid: [
'',
'TWFu',
'TWE=',
'TQ==',
'SGVsbG8=',
'U29mdHdhcmU=',
'YW55IGNhcm5hbCBwbGVhc3VyZS4=',
],
invalid: [
'TWF',
'TWE===',
'SGVsbG8@',
'SGVsbG8===',
'SGVsb G8=',
'====',
],
});
});

it('should validate standard Base64 without padding', () => {
test({
validator: 'isBase64',
args: [{ urlSafe: false, padding: false }],
valid: [
'',
'TWFu',
'TWE',
'TQ',
'SGVsbG8',
'U29mdHdhcmU',
'YW55IGNhcm5hbCBwbGVhc3VyZS4',
],
invalid: [
'TWE=',
'TQ===',
'SGVsbG8@',
'SGVsbG8===',
'SGVsb G8',
'====',
],
});
});

it('should validate Base64url with padding', () => {
test({
validator: 'isBase64',
args: [{ urlSafe: true, padding: true }],
valid: [
'',
'SGVsbG8=',
'U29mdHdhcmU=',
'YW55IGNhcm5hbCBwbGVhc3VyZS4=',
'SGVsbG8-',
'SGVsbG8_',
],
invalid: [
'SGVsbG8===',
'SGVsbG8@',
'SGVsb G8=',
'====',
],
});
});

it('should validate Base64url without padding', () => {
test({
validator: 'isBase64',
args: [{ urlSafe: true, padding: false }],
valid: [
'',
'SGVsbG8',
'U29mdHdhcmU',
'YW55IGNhcm5hbCBwbGVhc3VyZS4',
'SGVsbG8-',
'SGVsbG8_',
],
invalid: [
'SGVsbG8=',
'SGVsbG8===',
'SGVsbG8@',
'SGVsb G8',
'====',
],
});
});

it('should handle mixed cases correctly', () => {
test({
validator: 'isBase64',
args: [{ urlSafe: false, padding: true }],
valid: [
'',
'TWFu',
'TWE=',
'TQ==',
],
invalid: [
'TWE',
'TQ=',
'TQ===',
],
});

test({
validator: 'isBase64',
args: [{ urlSafe: true, padding: false }],
valid: [
'',
'SGVsbG8',
'SGVsbG8-',
'SGVsbG8_',
],
invalid: [
'SGVsbG8=',
'SGVsbG8@',
'SGVsb G8',
],
});
});
});
109 changes: 0 additions & 109 deletions test/validators/isISBN.test.js

This file was deleted.

0 comments on commit 21c49a8

Please sign in to comment.