Skip to content

Commit a1acf0f

Browse files
committed
fixup! buffer: add base64url encoding option
Backport parts of dae283d
1 parent 5b33458 commit a1acf0f

File tree

6 files changed

+90
-14
lines changed

6 files changed

+90
-14
lines changed

src/base64-inl.h

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,13 @@ size_t base64_decode(char* const dst, const size_t dstlen,
123123
inline size_t base64_encode(const char* src,
124124
size_t slen,
125125
char* dst,
126-
size_t dlen) {
126+
size_t dlen,
127+
Base64Mode mode) {
127128
// We know how much we'll write, just make sure that there's space.
128-
CHECK(dlen >= base64_encoded_size(slen) &&
129+
CHECK(dlen >= base64_encoded_size(slen, mode) &&
129130
"not enough space provided for base64 encode");
130131

131-
dlen = base64_encoded_size(slen);
132+
dlen = base64_encoded_size(slen, mode);
132133

133134
unsigned a;
134135
unsigned b;
@@ -137,9 +138,7 @@ inline size_t base64_encode(const char* src,
137138
unsigned k;
138139
unsigned n;
139140

140-
static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
141-
"abcdefghijklmnopqrstuvwxyz"
142-
"0123456789+/";
141+
const char* table = base64_select_table(mode);
143142

144143
i = 0;
145144
k = 0;
@@ -164,16 +163,19 @@ inline size_t base64_encode(const char* src,
164163
a = src[i + 0] & 0xff;
165164
dst[k + 0] = table[a >> 2];
166165
dst[k + 1] = table[(a & 3) << 4];
167-
dst[k + 2] = '=';
168-
dst[k + 3] = '=';
166+
if (mode == Base64Mode::NORMAL) {
167+
dst[k + 2] = '=';
168+
dst[k + 3] = '=';
169+
}
169170
break;
170171
case 2:
171172
a = src[i + 0] & 0xff;
172173
b = src[i + 1] & 0xff;
173174
dst[k + 0] = table[a >> 2];
174175
dst[k + 1] = table[((a & 3) << 4) | (b >> 4)];
175176
dst[k + 2] = table[(b & 0x0f) << 2];
176-
dst[k + 3] = '=';
177+
if (mode == Base64Mode::NORMAL)
178+
dst[k + 3] = '=';
177179
break;
178180
}
179181

src/base64.h

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,40 @@
55

66
#include "util.h"
77

8+
#include <cmath>
89
#include <cstddef>
910
#include <cstdint>
1011

1112
namespace node {
1213
//// Base 64 ////
13-
static inline constexpr size_t base64_encoded_size(size_t size) {
14-
return ((size + 2) / 3 * 4);
14+
15+
enum class Base64Mode {
16+
NORMAL,
17+
URL
18+
};
19+
20+
static constexpr char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
21+
"abcdefghijklmnopqrstuvwxyz"
22+
"0123456789+/";
23+
24+
static constexpr char base64_table_url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
25+
"abcdefghijklmnopqrstuvwxyz"
26+
"0123456789-_";
27+
28+
static inline const char* base64_select_table(Base64Mode mode) {
29+
switch (mode) {
30+
case Base64Mode::NORMAL: return base64_table;
31+
case Base64Mode::URL: return base64_table_url;
32+
default: UNREACHABLE();
33+
}
34+
}
35+
36+
static inline constexpr size_t base64_encoded_size(
37+
size_t size,
38+
Base64Mode mode = Base64Mode::NORMAL) {
39+
return mode == Base64Mode::NORMAL
40+
? ((size + 2) / 3 * 4)
41+
: std::ceil(static_cast<double>(size * 4) / 3);
1542
}
1643

1744
// Doesn't check for padding at the end. Can be 1-2 bytes over.
@@ -32,7 +59,8 @@ size_t base64_decode(char* const dst, const size_t dstlen,
3259
inline size_t base64_encode(const char* src,
3360
size_t slen,
3461
char* dst,
35-
size_t dlen);
62+
size_t dlen,
63+
Base64Mode mode = Base64Mode::NORMAL);
3664
} // namespace node
3765

3866

src/node.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,18 @@ inline void NODE_SET_PROTOTYPE_METHOD(v8::Local<v8::FunctionTemplate> recv,
664664
#define NODE_SET_PROTOTYPE_METHOD node::NODE_SET_PROTOTYPE_METHOD
665665

666666
// BINARY is a deprecated alias of LATIN1.
667-
enum encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER, LATIN1 = BINARY};
667+
// BASE64URL is not currently exposed to the JavaScript side.
668+
enum encoding {
669+
ASCII,
670+
UTF8,
671+
BASE64,
672+
UCS2,
673+
BINARY,
674+
HEX,
675+
BUFFER,
676+
BASE64URL,
677+
LATIN1 = BINARY
678+
};
668679

669680
NODE_EXTERN enum encoding ParseEncoding(
670681
v8::Isolate* isolate,

src/string_bytes.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ size_t StringBytes::Write(Isolate* isolate,
358358
break;
359359
}
360360

361+
case BASE64URL:
362+
// Fall through
361363
case BASE64:
362364
if (str->IsExternalOneByte()) {
363365
auto ext = str->GetExternalOneByteStringResource();
@@ -425,6 +427,8 @@ Maybe<size_t> StringBytes::StorageSize(Isolate* isolate,
425427
data_size = str->Length() * sizeof(uint16_t);
426428
break;
427429

430+
case BASE64URL:
431+
// Fall through
428432
case BASE64:
429433
data_size = base64_decoded_size_fast(str->Length());
430434
break;
@@ -466,6 +470,8 @@ Maybe<size_t> StringBytes::Size(Isolate* isolate,
466470
case UCS2:
467471
return Just(str->Length() * sizeof(uint16_t));
468472

473+
case BASE64URL:
474+
// Fall through
469475
case BASE64: {
470476
String::Value value(isolate, str);
471477
return Just(base64_decoded_size(*value, value.length()));
@@ -691,6 +697,20 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
691697
return ExternOneByteString::New(isolate, dst, dlen, error);
692698
}
693699

700+
case BASE64URL: {
701+
size_t dlen = base64_encoded_size(buflen, Base64Mode::URL);
702+
char* dst = node::UncheckedMalloc(dlen);
703+
if (dst == nullptr) {
704+
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
705+
return MaybeLocal<Value>();
706+
}
707+
708+
size_t written = base64_encode(buf, buflen, dst, dlen, Base64Mode::URL);
709+
CHECK_EQ(written, dlen);
710+
711+
return ExternOneByteString::New(isolate, dst, dlen, error);
712+
}
713+
694714
case HEX: {
695715
size_t dlen = buflen * 2;
696716
char* dst = node::UncheckedMalloc(dlen);

test/cctest/test_base64.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ TEST(Base64Test, Encode) {
4444
"IGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLg==");
4545
}
4646

47+
TEST(Base64Test, EncodeURL) {
48+
auto test = [](const char* string, const char* base64_string) {
49+
const size_t len = strlen(base64_string);
50+
char* const buffer = new char[len + 1];
51+
buffer[len] = 0;
52+
base64_encode(string, strlen(string), buffer, len, node::Base64Mode::URL);
53+
EXPECT_STREQ(base64_string, buffer);
54+
delete[] buffer;
55+
};
56+
57+
test("\x68\xd9\x16\x25\x5c\x1e\x40\x92\x2d\xfb", "aNkWJVweQJIt-w");
58+
test("\xac\xc7\x93\xaa\x83\x6f\xc3\xe3\x3f\x75", "rMeTqoNvw-M_dQ");
59+
}
60+
4761
TEST(Base64Test, Decode) {
4862
auto test = [](const char* base64_string, const char* string) {
4963
const size_t len = strlen(string);
@@ -75,6 +89,7 @@ TEST(Base64Test, Decode) {
7589
test("YWJj ZGVm", "abcdef");
7690
test("Y W J j Z G V m", "abcdef");
7791
test("Y W\n JjZ \nG Vm", "abcdef");
92+
test("rMeTqoNvw-M_dQ", "\xac\xc7\x93\xaa\x83\x6f\xc3\xe3\x3f\x75");
7893

7994
const char* text =
8095
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "

test/parallel/test-buffer-alloc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ const base64flavors = ['base64', 'base64url'];
345345
assert.strictEqual(Buffer.from(quote).toString('base64'), expected);
346346
assert.strictEqual(
347347
Buffer.from(quote).toString('base64url'),
348-
expected.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '')
348+
expected.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
349349
);
350350

351351
base64flavors.forEach((encoding) => {

0 commit comments

Comments
 (0)