Skip to content

Commit 0a9ef60

Browse files
panvatargos
authored andcommitted
buffer: add base64url encoding option
PR-URL: nodejs#36952 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 73e6781 commit 0a9ef60

17 files changed

+310
-126
lines changed

doc/api/buffer.md

Lines changed: 13 additions & 3 deletions
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.
@@ -469,9 +477,10 @@ Returns the byte length of a string when encoded using `encoding`.
469477
This is not the same as [`String.prototype.length`][], which does not account
470478
for the encoding that is used to convert the string into bytes.
471479

472-
For `'base64'` and `'hex'`, this function assumes valid input. For strings that
473-
contain non-base64/hex-encoded data (e.g. whitespace), the return value might be
474-
greater than the length of a `Buffer` created from the string.
480+
For `'base64'`, `'base64url'`, and `'hex'`, this function assumes valid input.
481+
For strings that contain non-base64/hex-encoded data (e.g. whitespace), the
482+
return value might be greater than the length of a `Buffer` created from the
483+
string.
475484

476485
```js
477486
const str = '\u00bd + \u00bc = \u00be';
@@ -3427,6 +3436,7 @@ introducing security vulnerabilities into an application.
34273436
[`buffer.kMaxLength`]: #buffer_buffer_kmaxlength
34283437
[`util.inspect()`]: util.md#util_util_inspect_object_options
34293438
[`v8::TypedArray::kMaxLength`]: https://v8.github.io/api/head/classv8_1_1TypedArray.html#a54a48f4373da0850663c4393d843b9b0
3439+
[base64url]: https://tools.ietf.org/html/rfc4648#section-5
34303440
[binary strings]: https://developer.mozilla.org/en-US/docs/Web/API/DOMString/Binary
34313441
[endianness]: https://en.wikipedia.org/wiki/Endianness
34323442
[iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

lib/buffer.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,20 @@ const encodingOps = {
659659
encodingsMap.base64,
660660
dir)
661661
},
662+
base64url: {
663+
encoding: 'base64url',
664+
encodingVal: encodingsMap.base64url,
665+
byteLength: (string) => base64ByteLength(string, string.length),
666+
write: (buf, string, offset, len) =>
667+
buf.base64urlWrite(string, offset, len),
668+
slice: (buf, start, end) => buf.base64urlSlice(start, end),
669+
indexOf: (buf, val, byteOffset, dir) =>
670+
indexOfBuffer(buf,
671+
fromStringFast(val, encodingOps.base64url),
672+
byteOffset,
673+
encodingsMap.base64url,
674+
dir)
675+
},
662676
hex: {
663677
encoding: 'hex',
664678
encodingVal: encodingsMap.hex,
@@ -715,6 +729,11 @@ function getEncodingOps(encoding) {
715729
if (encoding === 'hex' || StringPrototypeToLowerCase(encoding) === 'hex')
716730
return encodingOps.hex;
717731
break;
732+
case 9:
733+
if (encoding === 'base64url' ||
734+
StringPrototypeToLowerCase(encoding) === 'base64url')
735+
return encodingOps.base64url;
736+
break;
718737
}
719738
}
720739

lib/internal/buffer.js

Lines changed: 4 additions & 0 deletions
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,
@@ -1026,12 +1028,14 @@ function addBufferPrototypeMethods(proto) {
10261028

10271029
proto.asciiSlice = asciiSlice;
10281030
proto.base64Slice = base64Slice;
1031+
proto.base64urlSlice = base64urlSlice;
10291032
proto.latin1Slice = latin1Slice;
10301033
proto.hexSlice = hexSlice;
10311034
proto.ucs2Slice = ucs2Slice;
10321035
proto.utf8Slice = utf8Slice;
10331036
proto.asciiWrite = asciiWrite;
10341037
proto.base64Write = base64Write;
1038+
proto.base64urlWrite = base64urlWrite;
10351039
proto.latin1Write = latin1Write;
10361040
proto.hexWrite = hexWrite;
10371041
proto.ucs2Write = ucs2Write;

lib/internal/util.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ function slowCases(enc) {
177177
`${enc}`.toLowerCase() === 'utf-16le')
178178
return 'utf16le';
179179
break;
180+
case 9:
181+
if (enc === 'base64url' || enc === 'BASE64URL' ||
182+
`${enc}`.toLowerCase() === 'base64url')
183+
return 'base64url';
184+
break;
180185
default:
181186
if (enc === '') return 'utf8';
182187
}

src/api/encoding.cc

Lines changed: 4 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,13 +1163,15 @@ void Initialize(Local<Object> target,
11631163

11641164
env->SetMethodNoSideEffect(target, "asciiSlice", StringSlice<ASCII>);
11651165
env->SetMethodNoSideEffect(target, "base64Slice", StringSlice<BASE64>);
1166+
env->SetMethodNoSideEffect(target, "base64urlSlice", StringSlice<BASE64URL>);
11661167
env->SetMethodNoSideEffect(target, "latin1Slice", StringSlice<LATIN1>);
11671168
env->SetMethodNoSideEffect(target, "hexSlice", StringSlice<HEX>);
11681169
env->SetMethodNoSideEffect(target, "ucs2Slice", StringSlice<UCS2>);
11691170
env->SetMethodNoSideEffect(target, "utf8Slice", StringSlice<UTF8>);
11701171

11711172
env->SetMethod(target, "asciiWrite", StringWrite<ASCII>);
11721173
env->SetMethod(target, "base64Write", StringWrite<BASE64>);
1174+
env->SetMethod(target, "base64urlWrite", StringWrite<BASE64URL>);
11731175
env->SetMethod(target, "latin1Write", StringWrite<LATIN1>);
11741176
env->SetMethod(target, "hexWrite", StringWrite<HEX>);
11751177
env->SetMethod(target, "ucs2Write", StringWrite<UCS2>);

src/string_decoder.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ MaybeLocal<String> StringDecoder::DecodeData(Isolate* isolate,
6969

7070
size_t nread = *nread_ptr;
7171

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

test/addons/parse-encoding/binding.cc

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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)