Skip to content

Commit d522dc3

Browse files
Jake ChampionJakeChampion
authored andcommitted
chore: update about base64 encode and decode functions to also support base64url (URL and filename-safe standard) alongside their current support for base64 (standard)
base64url support is required for importing and export CryptoKeys in JSON Web Key format
1 parent f4de4bc commit d522dc3

File tree

2 files changed

+96
-46
lines changed

2 files changed

+96
-46
lines changed

c-dependencies/js-compute-runtime/js-compute-builtins.cpp

Lines changed: 86 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -391,54 +391,93 @@ JS::Result<std::string> ConvertJSValueToByteString(JSContext *cx, JS::Handle<JS:
391391
return byteString;
392392
}
393393

394+
JS::Result<std::string> ConvertJSValueToByteString(JSContext *cx, std::string v) {
395+
JS::RootedValue s(cx);
396+
s.setString(JS_NewStringCopyN(cx, v.c_str(), v.length()));
397+
return ConvertJSValueToByteString(cx, s);
398+
}
399+
394400
// Maps an encoded character to a value in the Base64 alphabet, per
395401
// RFC 4648, Table 1. Invalid input characters map to UINT8_MAX.
396402
// https://datatracker.ietf.org/doc/html/rfc4648#section-4
397403

398-
static const uint8_t base64DecodeTable[] = {
399-
// clang-format off
400-
/* 0 */ 255, 255, 255, 255, 255, 255, 255, 255,
401-
/* 8 */ 255, 255, 255, 255, 255, 255, 255, 255,
402-
/* 16 */ 255, 255, 255, 255, 255, 255, 255, 255,
403-
/* 24 */ 255, 255, 255, 255, 255, 255, 255, 255,
404-
/* 32 */ 255, 255, 255, 255, 255, 255, 255, 255,
405-
/* 40 */ 255, 255, 255,
404+
constexpr uint8_t nonAlphabet = 255;
405+
406+
// clang-format off
407+
const uint8_t base64DecodeTable[128] = {
408+
/* 0 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
409+
/* 8 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
410+
/* 16 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
411+
/* 24 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
412+
/* 32 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
413+
/* 40 */ nonAlphabet, nonAlphabet, nonAlphabet,
406414
62 /* + */,
407-
255, 255, 255,
415+
nonAlphabet, nonAlphabet, nonAlphabet,
408416
63 /* / */,
409417

410418
/* 48 */ /* 0 - 9 */ 52, 53, 54, 55, 56, 57, 58, 59,
411-
/* 56 */ 60, 61, 255, 255, 255, 255, 255, 255,
419+
/* 56 */ 60, 61, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
412420

413-
/* 64 */ 255, /* A - Z */ 0, 1, 2, 3, 4, 5, 6,
421+
/* 64 */ nonAlphabet, /* A - Z */ 0, 1, 2, 3, 4, 5, 6,
414422
/* 72 */ 7, 8, 9, 10, 11, 12, 13, 14,
415423
/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22,
416-
/* 88 */ 23, 24, 25, 255, 255, 255, 255, 255,
417-
/* 96 */ 255, /* a - z */ 26, 27, 28, 29, 30, 31, 32,
424+
/* 88 */ 23, 24, 25, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
425+
/* 96 */ nonAlphabet, /* a - z */ 26, 27, 28, 29, 30, 31, 32,
418426
/* 104 */ 33, 34, 35, 36, 37, 38, 39, 40,
419427
/* 112 */ 41, 42, 43, 44, 45, 46, 47, 48,
420-
/* 120 */ 49, 50, 51, 255, 255, 255, 255, 255,
428+
/* 120 */ 49, 50, 51, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
421429
};
430+
431+
const uint8_t base64URLDecodeTable[128] = {
432+
/* 0 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
433+
/* 8 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
434+
/* 16 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
435+
/* 24 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
436+
/* 32 */ nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
437+
/* 40 */ nonAlphabet, nonAlphabet, nonAlphabet, 62, nonAlphabet, 62, nonAlphabet, 63,
438+
/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59,
439+
/* 56 */ 60, 61, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet,
440+
/* 64 */ nonAlphabet, 0, 1, 2, 3, 4, 5, 6,
441+
/* 72 */ 7, 8, 9, 10, 11, 12, 13, 14,
442+
/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22,
443+
/* 88 */ 23, 24, 25, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, 63,
444+
/* 96 */ nonAlphabet, 26, 27, 28, 29, 30, 31, 32,
445+
/* 104 */ 33, 34, 35, 36, 37, 38, 39, 40,
446+
/* 112 */ 41, 42, 43, 44, 45, 46, 47, 48,
447+
/* 120 */ 49, 50, 51, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet, nonAlphabet
448+
};
449+
450+
const char base64EncodeTable[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
451+
"abcdefghijklmnopqrstuvwxyz"
452+
"0123456789+/";
453+
454+
const char base64URLEncodeTable[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
455+
"abcdefghijklmnopqrstuvwxyz"
456+
"0123456789-_";
457+
422458
// clang-format on
423459

424-
bool base64CharacterToValue(char character, uint8_t *value) {
460+
bool base64CharacterToValue(char character, uint8_t *value, const uint8_t *decodeTable) {
425461
static const size_t mask = 127;
426462
auto index = static_cast<size_t>(character);
427463

428464
if (index & ~mask) {
429465
return false;
430466
}
431-
*value = base64DecodeTable[index & mask];
467+
*value = decodeTable[index & mask];
432468

433469
return *value != 255;
434470
}
435471

436-
inline JS::Result<mozilla::Ok> base64Decode4to3(std::string_view input, std::string &output) {
472+
inline JS::Result<mozilla::Ok> base64Decode4to3(std::string_view input, std::string &output,
473+
const uint8_t *decodeTable) {
437474
uint8_t w, x, y, z;
438475
// 8.1 Find the code point pointed to by position in the second column of Table 1: The Base 64
439476
// Alphabet of RFC 4648. Let n be the number given in the first cell of the same row. [RFC4648]
440-
if (!base64CharacterToValue(input[0], &w) || !base64CharacterToValue(input[1], &x) ||
441-
!base64CharacterToValue(input[2], &y) || !base64CharacterToValue(input[3], &z)) {
477+
if (!base64CharacterToValue(input[0], &w, decodeTable) ||
478+
!base64CharacterToValue(input[1], &x, decodeTable) ||
479+
!base64CharacterToValue(input[2], &y, decodeTable) ||
480+
!base64CharacterToValue(input[3], &z, decodeTable)) {
442481
return JS::Result<mozilla::Ok>(JS::Error());
443482
}
444483

@@ -451,12 +490,14 @@ inline JS::Result<mozilla::Ok> base64Decode4to3(std::string_view input, std::str
451490
return mozilla::Ok();
452491
}
453492

454-
inline JS::Result<mozilla::Ok> base64Decode3to2(std::string_view input, std::string &output) {
493+
inline JS::Result<mozilla::Ok> base64Decode3to2(std::string_view input, std::string &output,
494+
const uint8_t *decodeTable) {
455495
uint8_t w, x, y;
456496
// 8.1 Find the code point pointed to by position in the second column of Table 1: The Base 64
457497
// Alphabet of RFC 4648. Let n be the number given in the first cell of the same row. [RFC4648]
458-
if (!base64CharacterToValue(input[0], &w) || !base64CharacterToValue(input[1], &x) ||
459-
!base64CharacterToValue(input[2], &y)) {
498+
if (!base64CharacterToValue(input[0], &w, decodeTable) ||
499+
!base64CharacterToValue(input[1], &x, decodeTable) ||
500+
!base64CharacterToValue(input[2], &y, decodeTable)) {
460501
return JS::Result<mozilla::Ok>(JS::Error());
461502
}
462503
// 9. If buffer is not empty, it contains either 12 or 18 bits. If it contains 12 bits, then
@@ -469,11 +510,13 @@ inline JS::Result<mozilla::Ok> base64Decode3to2(std::string_view input, std::str
469510
return mozilla::Ok();
470511
}
471512

472-
inline JS::Result<mozilla::Ok> base64Decode2to1(std::string_view input, std::string &output) {
513+
inline JS::Result<mozilla::Ok> base64Decode2to1(std::string_view input, std::string &output,
514+
const uint8_t *decodeTable) {
473515
uint8_t w, x;
474516
// 8.1 Find the code point pointed to by position in the second column of Table 1: The Base 64
475517
// Alphabet of RFC 4648. Let n be the number given in the first cell of the same row. [RFC4648]
476-
if (!base64CharacterToValue(input[0], &w) || !base64CharacterToValue(input[1], &x)) {
518+
if (!base64CharacterToValue(input[0], &w, decodeTable) ||
519+
!base64CharacterToValue(input[1], &x, decodeTable)) {
477520
return JS::Result<mozilla::Ok>(JS::Error());
478521
}
479522
// 9. If buffer is not empty, it contains either 12 or 18 bits. If it contains 12 bits, then
@@ -499,7 +542,8 @@ bool isAsciiWhitespace(char c) {
499542
}
500543

501544
// https://infra.spec.whatwg.org/#forgiving-base64-decode
502-
JS::Result<std::string> forgivingBase64Decode(std::string_view data) {
545+
JS::Result<std::string> forgivingBase64Decode(std::string_view data,
546+
const uint8_t *decodeTable = base64DecodeTable) {
503547
// 1. Remove all ASCII whitespace from data.
504548
// ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE.
505549
auto hasWhitespace = std::find_if(data.begin(), data.end(), &isAsciiWhitespace);
@@ -558,17 +602,17 @@ JS::Result<std::string> forgivingBase64Decode(std::string_view data) {
558602
// dealt with some characters.
559603

560604
while (data_view.length() >= 4) {
561-
MOZ_TRY(base64Decode4to3(data_view, output));
605+
MOZ_TRY(base64Decode4to3(data_view, output, decodeTable));
562606
data_view.remove_prefix(4);
563607
}
564608

565609
switch (data_view.length()) {
566610
case 3: {
567-
MOZ_TRY(base64Decode3to2(data_view, output));
611+
MOZ_TRY(base64Decode3to2(data_view, output, decodeTable));
568612
break;
569613
}
570614
case 2: {
571-
MOZ_TRY(base64Decode2to1(data_view, output));
615+
MOZ_TRY(base64Decode2to1(data_view, output, decodeTable));
572616
break;
573617
}
574618
case 1:
@@ -612,12 +656,8 @@ bool atob(JSContext *cx, unsigned argc, Value *vp) {
612656
return true;
613657
}
614658

615-
const char base[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
616-
"abcdefghijklmnopqrstuvwxyz"
617-
"0123456789+/";
618-
619659
inline uint8_t CharTo8Bit(char character) { return uint8_t(character); }
620-
inline void base64Encode3to4(std::string_view data, std::string &output) {
660+
inline void base64Encode3to4(std::string_view data, std::string &output, const char *encodeTable) {
621661
uint32_t b32 = 0;
622662
int i, j = 18;
623663

@@ -627,24 +667,24 @@ inline void base64Encode3to4(std::string_view data, std::string &output) {
627667
}
628668

629669
for (i = 0; i < 4; ++i) {
630-
output += base[(uint32_t)((b32 >> j) & 0x3F)];
670+
output += encodeTable[(uint32_t)((b32 >> j) & 0x3F)];
631671
j -= 6;
632672
}
633673
}
634674

635-
inline void base64Encode2to4(std::string_view data, std::string &output) {
675+
inline void base64Encode2to4(std::string_view data, std::string &output, const char *encodeTable) {
636676
uint8_t src0 = CharTo8Bit(data[0]);
637677
uint8_t src1 = CharTo8Bit(data[1]);
638-
output += base[(uint32_t)((src0 >> 2) & 0x3F)];
639-
output += base[(uint32_t)(((src0 & 0x03) << 4) | ((src1 >> 4) & 0x0F))];
640-
output += base[(uint32_t)((src1 & 0x0F) << 2)];
678+
output += encodeTable[(uint32_t)((src0 >> 2) & 0x3F)];
679+
output += encodeTable[(uint32_t)(((src0 & 0x03) << 4) | ((src1 >> 4) & 0x0F))];
680+
output += encodeTable[(uint32_t)((src1 & 0x0F) << 2)];
641681
output += '=';
642682
}
643683

644-
inline void base64Encode1to4(std::string_view data, std::string &output) {
684+
inline void base64Encode1to4(std::string_view data, std::string &output, const char *encodeTable) {
645685
uint8_t src0 = CharTo8Bit(data[0]);
646-
output += base[(uint32_t)((src0 >> 2) & 0x3F)];
647-
output += base[(uint32_t)((src0 & 0x03) << 4)];
686+
output += encodeTable[(uint32_t)((src0 >> 2) & 0x3F)];
687+
output += encodeTable[(uint32_t)((src0 & 0x03) << 4)];
648688
output += '=';
649689
output += '=';
650690
}
@@ -654,23 +694,23 @@ inline void base64Encode1to4(std::string_view data, std::string &output) {
654694
// section 4 of RFC 4648 to data and return the result. [RFC4648] Note: This is named
655695
// forgiving-base64 encode for symmetry with forgiving-base64 decode, which is different from the
656696
// RFC as it defines error handling for certain inputs.
657-
std::string forgivingBase64Encode(std::string_view data) {
697+
std::string forgivingBase64Encode(std::string_view data, const char *encodeTable) {
658698
int length = data.length();
659699
std::string output = "";
660700
// The Base64 version of a string will be at least 133% the size of the string.
661701
output.reserve(length * 1.33);
662702
while (length >= 3) {
663-
base64Encode3to4(data, output);
703+
base64Encode3to4(data, output, encodeTable);
664704
data.remove_prefix(3);
665705
length -= 3;
666706
}
667707

668708
switch (length) {
669709
case 2:
670-
base64Encode2to4(data, output);
710+
base64Encode2to4(data, output, encodeTable);
671711
break;
672712
case 1:
673-
base64Encode1to4(data, output);
713+
base64Encode1to4(data, output, encodeTable);
674714
break;
675715
case 0:
676716
break;
@@ -702,7 +742,7 @@ bool btoa(JSContext *cx, unsigned argc, Value *vp) {
702742
}
703743
auto byteString = byteStringResult.unwrap();
704744

705-
auto result = forgivingBase64Encode(byteString);
745+
auto result = forgivingBase64Encode(byteString, GlobalProperties::base64EncodeTable);
706746

707747
JSString *str = JS_NewStringCopyN(cx, result.c_str(), result.length());
708748
if (!str) {

c-dependencies/js-compute-runtime/js-compute-builtins.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ bool define_fastly_sys(JSContext *cx, JS::HandleObject global);
8585

8686
bool RejectPromiseWithPendingError(JSContext *cx, JS::HandleObject promise);
8787

88+
namespace GlobalProperties {
89+
extern const uint8_t base64DecodeTable[128];
90+
extern const uint8_t base64URLDecodeTable[128];
91+
extern const char base64EncodeTable[65];
92+
extern const char base64URLEncodeTable[65];
93+
94+
std::string forgivingBase64Encode(std::string_view data, const char *encodeTable);
95+
JS::Result<std::string> forgivingBase64Decode(std::string_view data, const uint8_t *decodeTable);
96+
} // namespace GlobalProperties
97+
8898
bool has_pending_async_tasks();
8999
bool process_pending_async_tasks(JSContext *cx);
90100

0 commit comments

Comments
 (0)