Skip to content

Commit 832cd01

Browse files
panvaruyadorno
authored andcommitted
buffer: add base64url encoding option
PR-URL: #36952 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent ba87be0 commit 832cd01

17 files changed

+312
-126
lines changed

doc/api/buffer.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ const buf7 = Buffer.from('tést', 'latin1');
5050
## Buffers and character encodings
5151
<!-- YAML
5252
changes:
53+
- version: REPLACEME
54+
pr-url: https://github.com/nodejs/node/pull/36952
55+
description: Introduced `base64url` encoding.
5356
- version: v6.4.0
5457
pr-url: https://github.com/nodejs/node/pull/7111
5558
description: Introduced `latin1` as an alias for `binary`.
@@ -106,6 +109,11 @@ string into a `Buffer` as decoding.
106109
specified in [RFC 4648, Section 5][]. Whitespace characters such as spaces,
107110
tabs, and new lines contained within the base64-encoded string are ignored.
108111

112+
* `'base64url'`: [base64url][] encoding as specified in
113+
[RFC 4648, Section 5][]. When creating a `Buffer` from a string, this
114+
encoding will also correctly accept regular base64-encoded strings. When
115+
encoding a `Buffer` to a string, this encoding will omit padding.
116+
109117
* `'hex'`: Encode each byte as two hexadecimal characters. Data truncation
110118
may occur when decoding strings that do exclusively contain valid hexadecimal
111119
characters. See below for an example.
@@ -482,9 +490,10 @@ Returns the byte length of a string when encoded using `encoding`.
482490
This is not the same as [`String.prototype.length`][], which does not account
483491
for the encoding that is used to convert the string into bytes.
484492

485-
For `'base64'` and `'hex'`, this function assumes valid input. For strings that
486-
contain non-base64/hex-encoded data (e.g. whitespace), the return value might be
487-
greater than the length of a `Buffer` created from the string.
493+
For `'base64'`, `'base64url'`, and `'hex'`, this function assumes valid input.
494+
For strings that contain non-base64/hex-encoded data (e.g. whitespace), the
495+
return value might be greater than the length of a `Buffer` created from the
496+
string.
488497

489498
```js
490499
const str = '\u00bd + \u00bc = \u00be';
@@ -3418,6 +3427,7 @@ introducing security vulnerabilities into an application.
34183427
[`buffer.constants.MAX_STRING_LENGTH`]: #buffer_buffer_constants_max_string_length
34193428
[`buffer.kMaxLength`]: #buffer_buffer_kmaxlength
34203429
[`util.inspect()`]: util.md#util_util_inspect_object_options
3430+
[base64url]: https://tools.ietf.org/html/rfc4648#section-5
34213431
[binary strings]: https://developer.mozilla.org/en-US/docs/Web/API/DOMString/Binary
34223432
[endianness]: https://en.wikipedia.org/wiki/Endianness
34233433
[iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

lib/buffer.js

+19
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,20 @@ const encodingOps = {
646646
encodingsMap.base64,
647647
dir)
648648
},
649+
base64url: {
650+
encoding: 'base64url',
651+
encodingVal: encodingsMap.base64url,
652+
byteLength: (string) => base64ByteLength(string, string.length),
653+
write: (buf, string, offset, len) =>
654+
buf.base64urlWrite(string, offset, len),
655+
slice: (buf, start, end) => buf.base64urlSlice(start, end),
656+
indexOf: (buf, val, byteOffset, dir) =>
657+
indexOfBuffer(buf,
658+
fromStringFast(val, encodingOps.base64url),
659+
byteOffset,
660+
encodingsMap.base64url,
661+
dir)
662+
},
649663
hex: {
650664
encoding: 'hex',
651665
encodingVal: encodingsMap.hex,
@@ -702,6 +716,11 @@ function getEncodingOps(encoding) {
702716
if (encoding === 'hex' || StringPrototypeToLowerCase(encoding) === 'hex')
703717
return encodingOps.hex;
704718
break;
719+
case 9:
720+
if (encoding === 'base64url' ||
721+
StringPrototypeToLowerCase(encoding) === 'base64url')
722+
return encodingOps.base64url;
723+
break;
705724
}
706725
}
707726

lib/internal/buffer.js

+4
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ const { validateNumber } = require('internal/validators');
1818
const {
1919
asciiSlice,
2020
base64Slice,
21+
base64urlSlice,
2122
latin1Slice,
2223
hexSlice,
2324
ucs2Slice,
2425
utf8Slice,
2526
asciiWrite,
2627
base64Write,
28+
base64urlWrite,
2729
latin1Write,
2830
hexWrite,
2931
ucs2Write,
@@ -1027,12 +1029,14 @@ function addBufferPrototypeMethods(proto) {
10271029

10281030
proto.asciiSlice = asciiSlice;
10291031
proto.base64Slice = base64Slice;
1032+
proto.base64urlSlice = base64urlSlice;
10301033
proto.latin1Slice = latin1Slice;
10311034
proto.hexSlice = hexSlice;
10321035
proto.ucs2Slice = ucs2Slice;
10331036
proto.utf8Slice = utf8Slice;
10341037
proto.asciiWrite = asciiWrite;
10351038
proto.base64Write = base64Write;
1039+
proto.base64urlWrite = base64urlWrite;
10361040
proto.latin1Write = latin1Write;
10371041
proto.hexWrite = hexWrite;
10381042
proto.ucs2Write = ucs2Write;

lib/internal/util.js

+5
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ function slowCases(enc) {
180180
StringPrototypeToLowerCase(`${enc}`) === 'utf-16le')
181181
return 'utf16le';
182182
break;
183+
case 9:
184+
if (enc === 'base64url' || enc === 'BASE64URL' ||
185+
StringPrototypeToLowerCase(`${enc}`) === 'base64url')
186+
return 'base64url';
187+
break;
183188
default:
184189
if (enc === '') return 'utf8';
185190
}

src/api/encoding.cc

+4
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,17 @@ enum encoding ParseEncoding(const char* encoding,
6868
} else if (encoding[1] == 'a') {
6969
if (strncmp(encoding + 2, "se64", 5) == 0)
7070
return BASE64;
71+
if (strncmp(encoding + 2, "se64url", 8) == 0)
72+
return BASE64URL;
7173
}
7274
if (StringEqualNoCase(encoding, "binary"))
7375
return LATIN1; // BINARY is a deprecated alias of LATIN1.
7476
if (StringEqualNoCase(encoding, "buffer"))
7577
return BUFFER;
7678
if (StringEqualNoCase(encoding, "base64"))
7779
return BASE64;
80+
if (StringEqualNoCase(encoding, "base64url"))
81+
return BASE64URL;
7882
break;
7983

8084
case 'a':

src/node_buffer.cc

+4
Original file line numberDiff line numberDiff line change
@@ -1184,13 +1184,15 @@ void Initialize(Local<Object> target,
11841184

11851185
env->SetMethodNoSideEffect(target, "asciiSlice", StringSlice<ASCII>);
11861186
env->SetMethodNoSideEffect(target, "base64Slice", StringSlice<BASE64>);
1187+
env->SetMethodNoSideEffect(target, "base64urlSlice", StringSlice<BASE64URL>);
11871188
env->SetMethodNoSideEffect(target, "latin1Slice", StringSlice<LATIN1>);
11881189
env->SetMethodNoSideEffect(target, "hexSlice", StringSlice<HEX>);
11891190
env->SetMethodNoSideEffect(target, "ucs2Slice", StringSlice<UCS2>);
11901191
env->SetMethodNoSideEffect(target, "utf8Slice", StringSlice<UTF8>);
11911192

11921193
env->SetMethod(target, "asciiWrite", StringWrite<ASCII>);
11931194
env->SetMethod(target, "base64Write", StringWrite<BASE64>);
1195+
env->SetMethod(target, "base64urlWrite", StringWrite<BASE64URL>);
11941196
env->SetMethod(target, "latin1Write", StringWrite<LATIN1>);
11951197
env->SetMethod(target, "hexWrite", StringWrite<HEX>);
11961198
env->SetMethod(target, "ucs2Write", StringWrite<UCS2>);
@@ -1223,13 +1225,15 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
12231225

12241226
registry->Register(StringSlice<ASCII>);
12251227
registry->Register(StringSlice<BASE64>);
1228+
registry->Register(StringSlice<BASE64URL>);
12261229
registry->Register(StringSlice<LATIN1>);
12271230
registry->Register(StringSlice<HEX>);
12281231
registry->Register(StringSlice<UCS2>);
12291232
registry->Register(StringSlice<UTF8>);
12301233

12311234
registry->Register(StringWrite<ASCII>);
12321235
registry->Register(StringWrite<BASE64>);
1236+
registry->Register(StringWrite<BASE64URL>);
12331237
registry->Register(StringWrite<LATIN1>);
12341238
registry->Register(StringWrite<HEX>);
12351239
registry->Register(StringWrite<UCS2>);

src/string_decoder.cc

+6-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ MaybeLocal<String> StringDecoder::DecodeData(Isolate* isolate,
7070

7171
size_t nread = *nread_ptr;
7272

73-
if (Encoding() == UTF8 || Encoding() == UCS2 || Encoding() == BASE64) {
73+
if (Encoding() == UTF8 ||
74+
Encoding() == UCS2 ||
75+
Encoding() == BASE64 ||
76+
Encoding() == BASE64URL) {
7477
// See if we want bytes to finish a character from the previous
7578
// chunk; if so, copy the new bytes to the missing bytes buffer
7679
// and create a small string from it that is to be prepended to the
@@ -198,7 +201,7 @@ MaybeLocal<String> StringDecoder::DecodeData(Isolate* isolate,
198201
state_[kBufferedBytes] = 2;
199202
state_[kMissingBytes] = 2;
200203
}
201-
} else if (Encoding() == BASE64) {
204+
} else if (Encoding() == BASE64 || Encoding() == BASE64URL) {
202205
state_[kBufferedBytes] = nread % 3;
203206
if (state_[kBufferedBytes] > 0)
204207
state_[kMissingBytes] = 3 - BufferedBytes();
@@ -311,6 +314,7 @@ void InitializeStringDecoder(Local<Object> target,
311314
ADD_TO_ENCODINGS_ARRAY(ASCII, "ascii");
312315
ADD_TO_ENCODINGS_ARRAY(UTF8, "utf8");
313316
ADD_TO_ENCODINGS_ARRAY(BASE64, "base64");
317+
ADD_TO_ENCODINGS_ARRAY(BASE64URL, "base64url");
314318
ADD_TO_ENCODINGS_ARRAY(UCS2, "utf16le");
315319
ADD_TO_ENCODINGS_ARRAY(HEX, "hex");
316320
ADD_TO_ENCODINGS_ARRAY(BUFFER, "buffer");

test/addons/parse-encoding/binding.cc

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace {
66
#define ENCODING_MAP(V) \
77
V(ASCII) \
88
V(BASE64) \
9+
V(BASE64URL) \
910
V(BUFFER) \
1011
V(HEX) \
1112
V(LATIN1) \

test/addons/parse-encoding/test.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ assert.strictEqual(parseEncoding(''), 'UNKNOWN');
88

99
assert.strictEqual(parseEncoding('ascii'), 'ASCII');
1010
assert.strictEqual(parseEncoding('base64'), 'BASE64');
11+
assert.strictEqual(parseEncoding('base64url'), 'BASE64URL');
1112
assert.strictEqual(parseEncoding('binary'), 'LATIN1');
1213
assert.strictEqual(parseEncoding('buffer'), 'BUFFER');
1314
assert.strictEqual(parseEncoding('hex'), 'HEX');

0 commit comments

Comments
 (0)