Skip to content

Commit 21c49a8

Browse files
committed
feat: improve base64 validation based on RFC4648
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
1 parent fc31e6e commit 21c49a8

File tree

4 files changed

+141
-127
lines changed

4 files changed

+141
-127
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Validator | Description
9393
**isAscii(str)** | check if the string contains ASCII chars only.
9494
**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].
9595
**isBase58(str)** | check if the string is base58 encoded.
96-
**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].
96+
**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].
9797
**isBefore(str [, date])** | check if the string is a date that is before the specified date.
9898
**isBIC(str)** | check if the string is a BIC (Bank Identification Code) or SWIFT code.
9999
**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']).

src/lib/isBase64.js

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
11
import assertString from './util/assertString';
22
import merge from './util/merge';
33

4-
const notBase64 = /[^A-Z0-9+\/=]/i;
5-
const urlSafeBase64 = /^[A-Z0-9_\-]*$/i;
6-
7-
const defaultBase64Options = {
8-
urlSafe: false,
9-
};
4+
const base64WithPadding = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
5+
const base64WithoutPadding = /^[A-Za-z0-9+/]+$/;
6+
const base64UrlWithPadding = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}==|[A-Za-z0-9_-]{3}=|[A-Za-z0-9_-]{4})$/;
7+
const base64UrlWithoutPadding = /^[A-Za-z0-9_-]+$/;
108

119
export default function isBase64(str, options) {
1210
assertString(str);
13-
options = merge(options, defaultBase64Options);
14-
const len = str.length;
11+
options = merge(options, { urlSafe: false, padding: !options?.urlSafe });
1512

16-
if (options.urlSafe) {
17-
return urlSafeBase64.test(str);
18-
}
13+
if (str === '') return true;
1914

20-
if (len % 4 !== 0 || notBase64.test(str)) {
21-
return false;
15+
let regex;
16+
if (options.urlSafe) {
17+
regex = options.padding ? base64UrlWithPadding : base64UrlWithoutPadding;
18+
} else {
19+
regex = options.padding ? base64WithPadding : base64WithoutPadding;
2220
}
2321

24-
const firstPaddingChar = str.indexOf('=');
25-
return firstPaddingChar === -1 ||
26-
firstPaddingChar === len - 1 ||
27-
(firstPaddingChar === len - 2 && str[len - 1] === '=');
22+
return (!options.padding || str.length % 4 === 0) && regex.test(str);
2823
}

test/validators/isBase64.test.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import test from '../testFunctions';
2+
3+
describe('isBase64', () => {
4+
it('should validate standard Base64 with padding', () => {
5+
test({
6+
validator: 'isBase64',
7+
args: [{ urlSafe: false, padding: true }],
8+
valid: [
9+
'',
10+
'TWFu',
11+
'TWE=',
12+
'TQ==',
13+
'SGVsbG8=',
14+
'U29mdHdhcmU=',
15+
'YW55IGNhcm5hbCBwbGVhc3VyZS4=',
16+
],
17+
invalid: [
18+
'TWF',
19+
'TWE===',
20+
'SGVsbG8@',
21+
'SGVsbG8===',
22+
'SGVsb G8=',
23+
'====',
24+
],
25+
});
26+
});
27+
28+
it('should validate standard Base64 without padding', () => {
29+
test({
30+
validator: 'isBase64',
31+
args: [{ urlSafe: false, padding: false }],
32+
valid: [
33+
'',
34+
'TWFu',
35+
'TWE',
36+
'TQ',
37+
'SGVsbG8',
38+
'U29mdHdhcmU',
39+
'YW55IGNhcm5hbCBwbGVhc3VyZS4',
40+
],
41+
invalid: [
42+
'TWE=',
43+
'TQ===',
44+
'SGVsbG8@',
45+
'SGVsbG8===',
46+
'SGVsb G8',
47+
'====',
48+
],
49+
});
50+
});
51+
52+
it('should validate Base64url with padding', () => {
53+
test({
54+
validator: 'isBase64',
55+
args: [{ urlSafe: true, padding: true }],
56+
valid: [
57+
'',
58+
'SGVsbG8=',
59+
'U29mdHdhcmU=',
60+
'YW55IGNhcm5hbCBwbGVhc3VyZS4=',
61+
'SGVsbG8-',
62+
'SGVsbG8_',
63+
],
64+
invalid: [
65+
'SGVsbG8===',
66+
'SGVsbG8@',
67+
'SGVsb G8=',
68+
'====',
69+
],
70+
});
71+
});
72+
73+
it('should validate Base64url without padding', () => {
74+
test({
75+
validator: 'isBase64',
76+
args: [{ urlSafe: true, padding: false }],
77+
valid: [
78+
'',
79+
'SGVsbG8',
80+
'U29mdHdhcmU',
81+
'YW55IGNhcm5hbCBwbGVhc3VyZS4',
82+
'SGVsbG8-',
83+
'SGVsbG8_',
84+
],
85+
invalid: [
86+
'SGVsbG8=',
87+
'SGVsbG8===',
88+
'SGVsbG8@',
89+
'SGVsb G8',
90+
'====',
91+
],
92+
});
93+
});
94+
95+
it('should handle mixed cases correctly', () => {
96+
test({
97+
validator: 'isBase64',
98+
args: [{ urlSafe: false, padding: true }],
99+
valid: [
100+
'',
101+
'TWFu',
102+
'TWE=',
103+
'TQ==',
104+
],
105+
invalid: [
106+
'TWE',
107+
'TQ=',
108+
'TQ===',
109+
],
110+
});
111+
112+
test({
113+
validator: 'isBase64',
114+
args: [{ urlSafe: true, padding: false }],
115+
valid: [
116+
'',
117+
'SGVsbG8',
118+
'SGVsbG8-',
119+
'SGVsbG8_',
120+
],
121+
invalid: [
122+
'SGVsbG8=',
123+
'SGVsbG8@',
124+
'SGVsb G8',
125+
],
126+
});
127+
});
128+
});

test/validators/isISBN.test.js

Lines changed: 0 additions & 109 deletions
This file was deleted.

0 commit comments

Comments
 (0)