forked from mirrors/gecko-dev
Bug 1885337 - Part 2: Implement to/from base64 methods. r=dminor
Differential Revision: https://phabricator.services.mozilla.com/D204637
This commit is contained in:
parent
fa04aeae99
commit
0bd12eb08c
3 changed files with 792 additions and 0 deletions
|
|
@ -671,6 +671,13 @@ MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_ARRAY_LENGTH_BOUNDS, 1, JSEXN_RANGEERR, "att
|
|||
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_TOO_LARGE, 1, JSEXN_RANGEERR, "{0}Array too large")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_HEX_STRING_LENGTH, 0, JSEXN_SYNTAXERR, "hex-string must have an even number of characters")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_HEX_DIGIT, 1, JSEXN_SYNTAXERR, "'{0}' is not a valid hex-digit")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_BASE64_ALPHABET, 0, JSEXN_TYPEERR, "\"alphabet\" option must be one of \"base64\" or \"base64url\"")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_BASE64_LAST_CHUNK_HANDLING, 0, JSEXN_TYPEERR, "\"lastChunkHandling\" option must be one of \"loose\", \"strict\", or \"stop-before-partial\"")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_BASE64_CHAR, 1, JSEXN_SYNTAXERR, "'{0}' is not a valid base64 character")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK, 0, JSEXN_SYNTAXERR, "unexpected incomplete base64 chunk")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_BASE64_AFTER_PADDING, 1, JSEXN_SYNTAXERR, "unexpected '{0}' after base64 padding")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_MISSING_BASE64_PADDING, 0, JSEXN_SYNTAXERR, "missing padding '=' character")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_EXTRA_BASE64_BITS, 0, JSEXN_SYNTAXERR, "unexpected extra bits in last character")
|
||||
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, 1, JSEXN_TYPEERR, "cannot directly {0} builtin %TypedArray%")
|
||||
MSG_DEF(JSMSG_NON_TYPED_ARRAY_RETURNED, 0, JSEXN_TYPEERR, "constructor didn't return TypedArray object")
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
MACRO_(allowContentIter, "allowContentIter") \
|
||||
MACRO_(allowContentIterWith, "allowContentIterWith") \
|
||||
MACRO_(allowContentIterWithNext, "allowContentIterWithNext") \
|
||||
MACRO_(alphabet, "alphabet") \
|
||||
MACRO_(ambiguous, "ambiguous") \
|
||||
MACRO_(anonymous, "anonymous") \
|
||||
MACRO_(Any, "Any") \
|
||||
|
|
@ -340,6 +341,7 @@
|
|||
MACRO_(label, "label") \
|
||||
MACRO_(language, "language") \
|
||||
MACRO_(largestUnit, "largestUnit") \
|
||||
MACRO_(lastChunkHandling, "lastChunkHandling") \
|
||||
MACRO_(lastIndex, "lastIndex") \
|
||||
MACRO_(length, "length") \
|
||||
MACRO_(let, "let") \
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "vm/TypedArrayObject-inl.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/IntegerTypeTraits.h"
|
||||
#include "mozilla/Likely.h"
|
||||
|
|
@ -2135,6 +2136,544 @@ static bool FromHex(JSContext* cx, Handle<JSString*> string, size_t maxLength,
|
|||
return true;
|
||||
}
|
||||
|
||||
namespace Base64 {
|
||||
static constexpr uint8_t InvalidChar = UINT8_MAX;
|
||||
|
||||
static constexpr auto DecodeTable(const char (&alphabet)[65]) {
|
||||
std::array<uint8_t, 128> result = {};
|
||||
|
||||
// Initialize all elements to InvalidChar.
|
||||
for (auto& e : result) {
|
||||
e = InvalidChar;
|
||||
}
|
||||
|
||||
// Map the base64 characters to their values.
|
||||
for (uint8_t i = 0; i < 64; ++i) {
|
||||
result[alphabet[i]] = i;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace Base64
|
||||
|
||||
namespace Base64::Encode {
|
||||
static constexpr const char Base64[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
static_assert(std::char_traits<char>::length(Base64) == 64);
|
||||
|
||||
static constexpr const char Base64Url[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
static_assert(std::char_traits<char>::length(Base64Url) == 64);
|
||||
} // namespace Base64::Encode
|
||||
|
||||
namespace Base64::Decode {
|
||||
static constexpr auto Base64 = DecodeTable(Base64::Encode::Base64);
|
||||
static_assert(Base64.size() == 128,
|
||||
"128 elements to allow access through ASCII characters");
|
||||
|
||||
static constexpr auto Base64Url = DecodeTable(Base64::Encode::Base64Url);
|
||||
static_assert(Base64Url.size() == 128,
|
||||
"128 elements to allow access through ASCII characters");
|
||||
} // namespace Base64::Decode
|
||||
|
||||
enum class Alphabet {
|
||||
/**
|
||||
* Standard base64 alphabet.
|
||||
*/
|
||||
Base64,
|
||||
|
||||
/**
|
||||
* URL and filename safe base64 alphabet.
|
||||
*/
|
||||
Base64Url,
|
||||
};
|
||||
|
||||
enum class LastChunkHandling {
|
||||
/**
|
||||
* Allow partial chunks at the end of the input.
|
||||
*/
|
||||
Loose,
|
||||
|
||||
/**
|
||||
* Disallow partial chunks at the end of the input.
|
||||
*/
|
||||
Strict,
|
||||
|
||||
/**
|
||||
* Stop before partial chunks at the end of the input.
|
||||
*/
|
||||
StopBeforePartial,
|
||||
};
|
||||
|
||||
/**
|
||||
* FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] )
|
||||
*
|
||||
* https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64
|
||||
*/
|
||||
static bool FromBase64(JSContext* cx, Handle<JSString*> string,
|
||||
Alphabet alphabet, LastChunkHandling lastChunkHandling,
|
||||
size_t maxLength, ByteVector& bytes,
|
||||
size_t* readLength) {
|
||||
// Steps 1-2. (Not applicable in our implementation.)
|
||||
|
||||
// Step 3.
|
||||
size_t remaining = maxLength;
|
||||
if (remaining == 0) {
|
||||
MOZ_ASSERT(bytes.empty());
|
||||
*readLength = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
JSLinearString* linear = string->ensureLinear(cx);
|
||||
if (!linear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )
|
||||
//
|
||||
// Encode a complete base64 chunk.
|
||||
auto decodeChunk = [&](uint32_t chunk) {
|
||||
MOZ_ASSERT(chunk <= 0xffffff);
|
||||
MOZ_ASSERT(remaining >= 3);
|
||||
|
||||
if (!bytes.reserve(bytes.length() + 3)) {
|
||||
return false;
|
||||
}
|
||||
bytes.infallibleAppend(chunk >> 16);
|
||||
bytes.infallibleAppend(chunk >> 8);
|
||||
bytes.infallibleAppend(chunk);
|
||||
return true;
|
||||
};
|
||||
|
||||
// DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )
|
||||
//
|
||||
// Encode a three element partial base64 chunk.
|
||||
auto decodeChunk3 = [&](uint32_t chunk, bool throwOnExtraBits) {
|
||||
MOZ_ASSERT(chunk <= 0x3ffff);
|
||||
MOZ_ASSERT(remaining >= 2);
|
||||
|
||||
if (throwOnExtraBits && (chunk & 0x3) != 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_EXTRA_BASE64_BITS);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bytes.reserve(bytes.length() + 2)) {
|
||||
return false;
|
||||
}
|
||||
bytes.infallibleAppend(chunk >> 10);
|
||||
bytes.infallibleAppend(chunk >> 2);
|
||||
return true;
|
||||
};
|
||||
|
||||
// DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )
|
||||
//
|
||||
// Encode a two element partial base64 chunk.
|
||||
auto decodeChunk2 = [&](uint32_t chunk, bool throwOnExtraBits) {
|
||||
MOZ_ASSERT(chunk <= 0xfff);
|
||||
MOZ_ASSERT(remaining >= 1);
|
||||
|
||||
if (throwOnExtraBits && (chunk & 0xf) != 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_EXTRA_BASE64_BITS);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bytes.reserve(bytes.length() + 1)) {
|
||||
return false;
|
||||
}
|
||||
bytes.infallibleAppend(chunk >> 4);
|
||||
return true;
|
||||
};
|
||||
|
||||
// DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )
|
||||
//
|
||||
// Encode a partial base64 chunk.
|
||||
auto decodePartialChunk = [&](uint32_t chunk, uint32_t chunkLength,
|
||||
bool throwOnExtraBits = false) {
|
||||
MOZ_ASSERT(chunkLength == 2 || chunkLength == 3);
|
||||
return chunkLength == 2 ? decodeChunk2(chunk, throwOnExtraBits)
|
||||
: decodeChunk3(chunk, throwOnExtraBits);
|
||||
};
|
||||
|
||||
// Step 4.
|
||||
//
|
||||
// String index after the last fully read base64 chunk.
|
||||
size_t read = 0;
|
||||
|
||||
// Step 5.
|
||||
MOZ_ASSERT(bytes.empty());
|
||||
|
||||
// Step 6.
|
||||
//
|
||||
// Current base64 chunk, a uint24 number.
|
||||
uint32_t chunk = 0;
|
||||
|
||||
// Step 7.
|
||||
//
|
||||
// Current base64 chunk length, in the range [0..4].
|
||||
size_t chunkLength = 0;
|
||||
|
||||
// Step 8.
|
||||
//
|
||||
// Current string index.
|
||||
size_t index = 0;
|
||||
|
||||
// Step 9.
|
||||
size_t length = linear->length();
|
||||
|
||||
const auto& decode = alphabet == Alphabet::Base64 ? Base64::Decode::Base64
|
||||
: Base64::Decode::Base64Url;
|
||||
|
||||
// Step 10.
|
||||
for (; index < length; index++) {
|
||||
// Step 10.c. (Reordered)
|
||||
char16_t ch = linear->latin1OrTwoByteChar(index);
|
||||
|
||||
// Step 10.a.
|
||||
if (mozilla::IsAsciiWhitespace(ch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 10.b. (Moved out of loop.)
|
||||
|
||||
// Step 10.d. (Performed in for-loop step.)
|
||||
|
||||
// Step 10.e.
|
||||
if (ch == '=') {
|
||||
break;
|
||||
}
|
||||
|
||||
// Steps 10.f-g.
|
||||
uint8_t value = Base64::InvalidChar;
|
||||
if (mozilla::IsAscii(ch)) {
|
||||
value = decode[ch];
|
||||
}
|
||||
if (MOZ_UNLIKELY(value == Base64::InvalidChar)) {
|
||||
if (auto str = QuoteString(cx, ch)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_BAD_BASE64_CHAR, str.get());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 10.h. (Not applicable in our implementation.)
|
||||
|
||||
// Step 10.i.
|
||||
if ((remaining == 1 && chunkLength == 2) ||
|
||||
(remaining == 2 && chunkLength == 3)) {
|
||||
*readLength = read;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 10.j.
|
||||
chunk = (chunk << 6) | value;
|
||||
|
||||
// Step 10.k.
|
||||
chunkLength += 1;
|
||||
|
||||
// Step 10.l.
|
||||
if (chunkLength == 4) {
|
||||
// Step 10.l.i.
|
||||
if (!decodeChunk(chunk)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 10.l.ii.
|
||||
chunk = 0;
|
||||
|
||||
// Step 10.l.iii.
|
||||
chunkLength = 0;
|
||||
|
||||
// Step 10.l.iv.
|
||||
//
|
||||
// NB: Add +1 to include the |index| update from step 10.d.
|
||||
read = index + 1;
|
||||
|
||||
// Step 10.l.v.
|
||||
MOZ_ASSERT(remaining >= 3);
|
||||
remaining -= 3;
|
||||
if (remaining == 0) {
|
||||
*readLength = read;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10.b.
|
||||
if (index == length) {
|
||||
// Step 10.b.i.
|
||||
if (chunkLength > 0) {
|
||||
// Step 10.b.i.1.
|
||||
if (lastChunkHandling == LastChunkHandling::StopBeforePartial) {
|
||||
*readLength = read;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Steps 10.b.i.2-3.
|
||||
if (lastChunkHandling == LastChunkHandling::Loose) {
|
||||
// Step 10.b.i.2.a.
|
||||
if (chunkLength == 1) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK);
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(chunkLength == 2 || chunkLength == 3);
|
||||
|
||||
// Step 10.b.i.2.b.
|
||||
if (!decodePartialChunk(chunk, chunkLength)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Step 10.b.i.3.a.
|
||||
MOZ_ASSERT(lastChunkHandling == LastChunkHandling::Strict);
|
||||
|
||||
// Step 10.b.i.3.b.
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10.b.ii.
|
||||
*readLength = length;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 10.e.
|
||||
MOZ_ASSERT(index < length);
|
||||
MOZ_ASSERT(linear->latin1OrTwoByteChar(index) == '=');
|
||||
|
||||
// Step 10.e.i.
|
||||
if (chunkLength < 2) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK);
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(chunkLength == 2 || chunkLength == 3);
|
||||
|
||||
// Step 10.e.ii. (Inlined SkipAsciiWhitespace)
|
||||
while (++index < length) {
|
||||
char16_t ch = linear->latin1OrTwoByteChar(index);
|
||||
if (!mozilla::IsAsciiWhitespace(ch)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10.e.iii.
|
||||
if (chunkLength == 2) {
|
||||
// Step 10.e.iii.1.
|
||||
if (index == length) {
|
||||
// Step 10.e.iii.1.a.
|
||||
if (lastChunkHandling == LastChunkHandling::StopBeforePartial) {
|
||||
*readLength = read;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 10.e.iii.1.b.
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_MISSING_BASE64_PADDING);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 10.e.iii.2.
|
||||
char16_t ch = linear->latin1OrTwoByteChar(index);
|
||||
|
||||
// Step 10.e.iii.3.
|
||||
if (ch == '=') {
|
||||
// Step 10.e.iii.3.a. (Inlined SkipAsciiWhitespace)
|
||||
while (++index < length) {
|
||||
char16_t ch = linear->latin1OrTwoByteChar(index);
|
||||
if (!mozilla::IsAsciiWhitespace(ch)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10.e.iv.
|
||||
if (index < length) {
|
||||
char16_t ch = linear->latin1OrTwoByteChar(index);
|
||||
if (auto str = QuoteString(cx, ch)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_BAD_BASE64_AFTER_PADDING,
|
||||
str.get());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 10.e.v-vi.
|
||||
bool throwOnExtraBits = lastChunkHandling == LastChunkHandling::Strict;
|
||||
|
||||
// Step 10.e.vii.
|
||||
if (!decodePartialChunk(chunk, chunkLength, throwOnExtraBits)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 10.e.viii.
|
||||
*readLength = length;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.fromBase64 ( string [ , options ] )
|
||||
* Uint8Array.prototype.setFromBase64 ( string [ , options ] )
|
||||
* Uint8Array.prototype.toBase64 ( [ options ] )
|
||||
*
|
||||
* Helper to retrieve the "alphabet" option.
|
||||
*/
|
||||
static bool GetAlphabetOption(JSContext* cx, Handle<JSObject*> options,
|
||||
Alphabet* result) {
|
||||
Rooted<Value> value(cx);
|
||||
if (!GetProperty(cx, options, options, cx->names().alphabet, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.isUndefined()) {
|
||||
*result = Alphabet::Base64;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!value.isString()) {
|
||||
return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
|
||||
value, nullptr, "not a string");
|
||||
}
|
||||
|
||||
auto* linear = value.toString()->ensureLinear(cx);
|
||||
if (!linear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(linear, "base64")) {
|
||||
*result = Alphabet::Base64;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(linear, "base64url")) {
|
||||
*result = Alphabet::Base64Url;
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_BAD_BASE64_ALPHABET);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.fromBase64 ( string [ , options ] )
|
||||
* Uint8Array.prototype.setFromBase64 ( string [ , options ] )
|
||||
*
|
||||
* Helper to retrieve the "lastChunkHandling" option.
|
||||
*/
|
||||
static bool GetLastChunkHandlingOption(JSContext* cx, Handle<JSObject*> options,
|
||||
LastChunkHandling* result) {
|
||||
Rooted<Value> value(cx);
|
||||
if (!GetProperty(cx, options, options, cx->names().lastChunkHandling,
|
||||
&value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.isUndefined()) {
|
||||
*result = LastChunkHandling::Loose;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!value.isString()) {
|
||||
return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
|
||||
value, nullptr, "not a string");
|
||||
}
|
||||
|
||||
auto* linear = value.toString()->ensureLinear(cx);
|
||||
if (!linear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(linear, "loose")) {
|
||||
*result = LastChunkHandling::Loose;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(linear, "strict")) {
|
||||
*result = LastChunkHandling::Strict;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(linear, "stop-before-partial")) {
|
||||
*result = LastChunkHandling::StopBeforePartial;
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_BAD_BASE64_LAST_CHUNK_HANDLING);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.fromBase64 ( string [ , options ] )
|
||||
*
|
||||
* https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64
|
||||
*/
|
||||
static bool uint8array_fromBase64(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1.
|
||||
if (!args.get(0).isString()) {
|
||||
return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
|
||||
args.get(0), nullptr, "not a string");
|
||||
}
|
||||
Rooted<JSString*> string(cx, args[0].toString());
|
||||
|
||||
// Steps 2-9.
|
||||
auto alphabet = Alphabet::Base64;
|
||||
auto lastChunkHandling = LastChunkHandling::Loose;
|
||||
if (args.hasDefined(1)) {
|
||||
// Step 2. (Inlined GetOptionsObject)
|
||||
Rooted<JSObject*> options(
|
||||
cx, RequireObjectArg(cx, "options", "fromBase64", args[1]));
|
||||
if (!options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 3-6.
|
||||
if (!GetAlphabetOption(cx, options, &alphabet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 7-9.
|
||||
if (!GetLastChunkHandlingOption(cx, options, &lastChunkHandling)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10.
|
||||
constexpr size_t maxLength = std::numeric_limits<size_t>::max();
|
||||
ByteVector bytes(cx);
|
||||
size_t unusedReadLength;
|
||||
if (!FromBase64(cx, string, alphabet, lastChunkHandling, maxLength, bytes,
|
||||
&unusedReadLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
size_t resultLength = bytes.length();
|
||||
|
||||
// Step 12.
|
||||
auto* tarray =
|
||||
TypedArrayObjectTemplate<uint8_t>::fromLength(cx, resultLength);
|
||||
if (!tarray) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 13.
|
||||
auto target = SharedMem<uint8_t*>::unshared(tarray->dataPointerUnshared());
|
||||
auto source = SharedMem<uint8_t*>::unshared(bytes.begin());
|
||||
UnsharedOps::podCopy(target, source, resultLength);
|
||||
|
||||
// Step 14.
|
||||
args.rval().setObject(*tarray);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.fromHex ( string )
|
||||
*
|
||||
|
|
@ -2178,6 +2717,121 @@ static bool uint8array_fromHex(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.prototype.setFromBase64 ( string [ , options ] )
|
||||
*
|
||||
* https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64
|
||||
*/
|
||||
static bool uint8array_setFromBase64(JSContext* cx, const CallArgs& args) {
|
||||
Rooted<TypedArrayObject*> tarray(
|
||||
cx, &args.thisv().toObject().as<TypedArrayObject>());
|
||||
|
||||
// Step 3.
|
||||
if (!args.get(0).isString()) {
|
||||
return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
|
||||
args.get(0), nullptr, "not a string");
|
||||
}
|
||||
Rooted<JSString*> string(cx, args[0].toString());
|
||||
|
||||
// Steps 4-11.
|
||||
auto alphabet = Alphabet::Base64;
|
||||
auto lastChunkHandling = LastChunkHandling::Loose;
|
||||
if (args.hasDefined(1)) {
|
||||
// Step 2. (Inlined GetOptionsObject)
|
||||
Rooted<JSObject*> options(
|
||||
cx, RequireObjectArg(cx, "options", "setFromBase64", args[1]));
|
||||
if (!options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 3-6.
|
||||
if (!GetAlphabetOption(cx, options, &alphabet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 7-9.
|
||||
if (!GetLastChunkHandlingOption(cx, options, &lastChunkHandling)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Steps 12-14.
|
||||
auto length = tarray->length();
|
||||
if (!length) {
|
||||
ReportOutOfBounds(cx, tarray);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 15.
|
||||
size_t maxLength = *length;
|
||||
|
||||
// Steps 16-17.
|
||||
ByteVector bytes(cx);
|
||||
size_t readLength;
|
||||
if (!FromBase64(cx, string, alphabet, lastChunkHandling, maxLength, bytes,
|
||||
&readLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 18.
|
||||
size_t written = bytes.length();
|
||||
|
||||
// Step 19.
|
||||
//
|
||||
// The underlying buffer has neither been detached nor shrunk. (It may have
|
||||
// been grown when it's a growable shared buffer and a concurrent thread
|
||||
// resized the buffer.)
|
||||
MOZ_ASSERT(!tarray->hasDetachedBuffer());
|
||||
MOZ_ASSERT(tarray->length().valueOr(0) >= *length);
|
||||
|
||||
// Step 20.
|
||||
MOZ_ASSERT(written <= *length);
|
||||
|
||||
// Step 21. (Inlined SetUint8ArrayBytes)
|
||||
auto target = tarray->dataPointerEither().cast<uint8_t*>();
|
||||
auto source = SharedMem<uint8_t*>::unshared(bytes.begin());
|
||||
if (tarray->isSharedMemory()) {
|
||||
SharedOps::podCopy(target, source, written);
|
||||
} else {
|
||||
UnsharedOps::podCopy(target, source, written);
|
||||
}
|
||||
|
||||
// Step 22.
|
||||
Rooted<PlainObject*> result(cx, NewPlainObject(cx));
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 23.
|
||||
Rooted<Value> readValue(cx, NumberValue(readLength));
|
||||
if (!DefineDataProperty(cx, result, cx->names().read, readValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 24.
|
||||
Rooted<Value> writtenValue(cx, NumberValue(written));
|
||||
if (!DefineDataProperty(cx, result, cx->names().written, writtenValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 25.
|
||||
args.rval().setObject(*result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.prototype.setFromBase64 ( string [ , options ] )
|
||||
*
|
||||
* https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64
|
||||
*/
|
||||
static bool uint8array_setFromBase64(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Steps 1-2.
|
||||
return CallNonGenericMethod<IsUint8ArrayObject, uint8array_setFromBase64>(
|
||||
cx, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.prototype.setFromHex ( string )
|
||||
*
|
||||
|
|
@ -2270,6 +2924,132 @@ static bool uint8array_setFromHex(JSContext* cx, unsigned argc, Value* vp) {
|
|||
args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.prototype.toBase64 ( [ options ] )
|
||||
*
|
||||
* https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
|
||||
*/
|
||||
static bool uint8array_toBase64(JSContext* cx, const CallArgs& args) {
|
||||
Rooted<TypedArrayObject*> tarray(
|
||||
cx, &args.thisv().toObject().as<TypedArrayObject>());
|
||||
|
||||
// Steps 3-7.
|
||||
auto alphabet = Alphabet::Base64;
|
||||
if (args.hasDefined(0)) {
|
||||
// Step 3. (Inlined GetOptionsObject)
|
||||
Rooted<JSObject*> options(
|
||||
cx, RequireObjectArg(cx, "options", "toBase64", args[0]));
|
||||
if (!options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 4-7.
|
||||
if (!GetAlphabetOption(cx, options, &alphabet)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 8. (Partial)
|
||||
auto length = tarray->length();
|
||||
if (!length) {
|
||||
ReportOutOfBounds(cx, tarray);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute the output string length. Three input bytes are encoded as four
|
||||
// characters, so the output length is ⌈length × 4/3⌉.
|
||||
auto outLength = mozilla::CheckedInt<size_t>{*length};
|
||||
outLength += 2;
|
||||
outLength /= 3;
|
||||
outLength *= 4;
|
||||
if (!outLength.isValid() || outLength.value() > JSString::MAX_LENGTH) {
|
||||
ReportAllocationOverflow(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
JSStringBuilder sb(cx);
|
||||
if (!sb.reserve(outLength.value())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 9-10.
|
||||
const auto& base64Chars = alphabet == Alphabet::Base64
|
||||
? Base64::Encode::Base64
|
||||
: Base64::Encode::Base64Url;
|
||||
|
||||
auto encode = [&base64Chars](uint32_t value) {
|
||||
return base64Chars[value & 0x3f];
|
||||
};
|
||||
|
||||
// Our implementation directly converts the bytes to their string
|
||||
// representation instead of first collecting them into an intermediate list.
|
||||
auto data = tarray->dataPointerEither().cast<uint8_t*>();
|
||||
auto toRead = *length;
|
||||
for (; toRead >= 3; toRead -= 3) {
|
||||
// Combine three input bytes into a single uint24 value.
|
||||
auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
|
||||
auto byte1 = jit::AtomicOperations::loadSafeWhenRacy(data++);
|
||||
auto byte2 = jit::AtomicOperations::loadSafeWhenRacy(data++);
|
||||
auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2;
|
||||
|
||||
// Encode the uint24 value as base64.
|
||||
sb.infallibleAppend(encode(u24 >> 18));
|
||||
sb.infallibleAppend(encode(u24 >> 12));
|
||||
sb.infallibleAppend(encode(u24 >> 6));
|
||||
sb.infallibleAppend(encode(u24 >> 0));
|
||||
}
|
||||
|
||||
// Trailing two and one element bytes are padded with '='.
|
||||
if (toRead == 2) {
|
||||
// Combine two input bytes into a single uint24 value.
|
||||
auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
|
||||
auto byte1 = jit::AtomicOperations::loadSafeWhenRacy(data++);
|
||||
auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8);
|
||||
|
||||
// Encode the uint24 value as base64, including padding.
|
||||
sb.infallibleAppend(encode(u24 >> 18));
|
||||
sb.infallibleAppend(encode(u24 >> 12));
|
||||
sb.infallibleAppend(encode(u24 >> 6));
|
||||
sb.infallibleAppend('=');
|
||||
} else if (toRead == 1) {
|
||||
// Combine one input byte into a single uint24 value.
|
||||
auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
|
||||
auto u24 = uint32_t(byte0) << 16;
|
||||
|
||||
// Encode the uint24 value as base64, including padding.
|
||||
sb.infallibleAppend(encode(u24 >> 18));
|
||||
sb.infallibleAppend(encode(u24 >> 12));
|
||||
sb.infallibleAppend('=');
|
||||
sb.infallibleAppend('=');
|
||||
} else {
|
||||
MOZ_ASSERT(toRead == 0);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(sb.length() == outLength.value(), "all characters were written");
|
||||
|
||||
// Step 11.
|
||||
auto* str = sb.finishString();
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setString(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.prototype.toBase64 ( [ options ] )
|
||||
*
|
||||
* https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
|
||||
*/
|
||||
static bool uint8array_toBase64(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Steps 1-2.
|
||||
return CallNonGenericMethod<IsUint8ArrayObject, uint8array_toBase64>(cx,
|
||||
args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uint8Array.prototype.toHex ( )
|
||||
*
|
||||
|
|
@ -2665,12 +3445,15 @@ static const JSPropertySpec
|
|||
};
|
||||
|
||||
static const JSFunctionSpec uint8array_static_methods[] = {
|
||||
JS_FN("fromBase64", uint8array_fromBase64, 1, 0),
|
||||
JS_FN("fromHex", uint8array_fromHex, 1, 0),
|
||||
JS_FS_END,
|
||||
};
|
||||
|
||||
static const JSFunctionSpec uint8array_methods[] = {
|
||||
JS_FN("setFromBase64", uint8array_setFromBase64, 1, 0),
|
||||
JS_FN("setFromHex", uint8array_setFromHex, 1, 0),
|
||||
JS_FN("toBase64", uint8array_toBase64, 0, 0),
|
||||
JS_FN("toHex", uint8array_toHex, 0, 0),
|
||||
JS_FS_END,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue