diff --git a/Jint/Extensions/WebEncoders.cs b/Jint/Extensions/WebEncoders.cs index 80bb40b429..45d0b2188f 100644 --- a/Jint/Extensions/WebEncoders.cs +++ b/Jint/Extensions/WebEncoders.cs @@ -106,15 +106,16 @@ private static int GetArraySizeRequiredToDecode(int count) /// Encodes using base64url encoding. /// /// The binary input to encode. + /// /// The base64url-encoded form of . - public static string Base64UrlEncode(byte[] input) + public static string Base64UrlEncode(byte[] input, bool omitPadding) { if (input == null) { throw new ArgumentNullException(nameof(input)); } - return Base64UrlEncode(input, offset: 0, count: input.Length); + return Base64UrlEncode(input, offset: 0, count: input.Length, omitPadding); } /// @@ -123,8 +124,9 @@ public static string Base64UrlEncode(byte[] input) /// The binary input to encode. /// The offset into at which to begin encoding. /// The number of bytes from to encode. + /// /// The base64url-encoded form of . - public static string Base64UrlEncode(byte[] input, int offset, int count) + public static string Base64UrlEncode(byte[] input, int offset, int count, bool omitPadding) { if (input == null) { @@ -132,7 +134,7 @@ public static string Base64UrlEncode(byte[] input, int offset, int count) } #if NETCOREAPP - return Base64UrlEncode(input.AsSpan(offset, count)); + return Base64UrlEncode(input.AsSpan(offset, count), omitPadding); #else // Special-case empty input if (count == 0) @@ -141,7 +143,7 @@ public static string Base64UrlEncode(byte[] input, int offset, int count) } var buffer = new char[GetArraySizeRequiredToEncode(count)]; - var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count); + var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count, omitPadding); return new string(buffer, startIndex: 0, length: numBase64Chars); #endif @@ -162,10 +164,11 @@ public static string Base64UrlEncode(byte[] input, int offset, int count) /// . /// /// The number of bytes from to encode. + /// /// /// The number of characters written to , less any padding characters. /// - public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count) + public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count, bool omitPadding) { if (input == null) { @@ -188,7 +191,7 @@ public static int Base64UrlEncode(byte[] input, int offset, char[] output, int o } #if NETCOREAPP - return Base64UrlEncode(input.AsSpan(offset, count), output.AsSpan(outputOffset)); + return Base64UrlEncode(input.AsSpan(offset, count), output.AsSpan(outputOffset), omitPadding); #else // Special-case empty input. if (count == 0) @@ -213,7 +216,7 @@ public static int Base64UrlEncode(byte[] input, int offset, char[] output, int o { output[i] = '_'; } - else if (ch == '=') + else if (omitPadding && ch == '=') { // We've reached a padding character; truncate the remainder. return i - outputOffset; @@ -226,7 +229,7 @@ public static int Base64UrlEncode(byte[] input, int offset, char[] output, int o /// /// Get the minimum output char[] size required for encoding - /// s with the method. + /// s with the method. /// /// The number of characters to encode. /// @@ -243,8 +246,9 @@ public static int GetArraySizeRequiredToEncode(int count) /// Encodes using base64url encoding. /// /// The binary input to encode. + /// /// The base64url-encoded form of . - public static string Base64UrlEncode(ReadOnlySpan input) + public static string Base64UrlEncode(ReadOnlySpan input, bool omitPadding) { if (input.IsEmpty) { @@ -258,7 +262,7 @@ public static string Base64UrlEncode(ReadOnlySpan input) ? stackalloc char[bufferSize] : bufferToReturnToPool = ArrayPool.Shared.Rent(bufferSize); - var numBase64Chars = Base64UrlEncode(input, buffer); + var numBase64Chars = Base64UrlEncode(input, buffer, omitPadding); var base64Url = new string(buffer.Slice(0, numBase64Chars)); if (bufferToReturnToPool != null) @@ -269,7 +273,7 @@ public static string Base64UrlEncode(ReadOnlySpan input) return base64Url; } - private static int Base64UrlEncode(ReadOnlySpan input, Span output) + private static int Base64UrlEncode(ReadOnlySpan input, Span output, bool omitPadding) { Debug.Assert(output.Length >= GetArraySizeRequiredToEncode(input.Length)); @@ -294,7 +298,7 @@ private static int Base64UrlEncode(ReadOnlySpan input, Span output) { output[i] = '_'; } - else if (ch == '=') + else if (omitPadding && ch == '=') { // We've reached a padding character; truncate the remainder. return i; diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index 7b7d711594..d04494c84a 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -1267,7 +1267,7 @@ static string StringFromJsValue(JsValue value) return s; } - using var sb = new ValueStringBuilder(stackalloc char[256]); + using var sb = new ValueStringBuilder(); sb.Append(s); for (uint k = 1; k < len; k++) { diff --git a/Jint/Native/JsTypedArray.cs b/Jint/Native/JsTypedArray.cs index 592133d375..82e38907eb 100644 --- a/Jint/Native/JsTypedArray.cs +++ b/Jint/Native/JsTypedArray.cs @@ -52,7 +52,11 @@ public JsValue this[uint index] public uint Length => GetLength(); - internal override uint GetLength() => IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.Unordered).TypedArrayLength; + internal override uint GetLength() + { + var record = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(this, ArrayBufferOrder.Unordered); + return record.IsTypedArrayOutOfBounds ? 0 : record.TypedArrayLength; + } internal override bool IsArrayLike => true; diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs b/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs index 6f4b0e68ea..08ad54437d 100644 --- a/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs +++ b/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs @@ -129,14 +129,14 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin { if (chunkLength == 1) { - return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 chunk length."), read); + return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 chunk length."), read); } DecodeBase64Chunk(engine, bytes, chunk, chunkLength, throwOnExtraBits: false); } else // strict { - return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 chunk length in strict mode."), read); + return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 chunk length in strict mode."), read); } } @@ -150,7 +150,7 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin { if (chunkLength < 2) { - return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid '=' placement in base64 string."), read); + return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid '=' placement in base64 string."), read); } index = SkipAsciiWhitespace(input, index); @@ -163,13 +163,13 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin return new FromEncodingResult(bytes.ToArray(), Error: null, read); } - return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 string termination."), read); + return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 string termination."), read); } currentChar = input[index]; if (currentChar != '=') { - return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Expected '=' in base64 string."), read); + return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Expected '=' in base64 string."), read); } index = SkipAsciiWhitespace(input, index + 1); @@ -177,7 +177,7 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin if (index < length) { - return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Extra characters after base64 string."), read); + return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Extra characters after base64 string."), read); } DecodeBase64Chunk(engine, bytes, chunk, chunkLength, throwOnExtraBits); @@ -188,7 +188,7 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin { if (currentChar is '+' or '/') { - return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid character in base64url string."), read); + return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid character in base64url string."), read); } if (currentChar == '-') @@ -204,7 +204,7 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin if (!Base64Alphabet.Contains(currentChar)) { - return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 character."), read); + return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 character."), read); } ulong remaining = maxLength - (ulong) bytes.Count; @@ -318,7 +318,7 @@ internal static FromEncodingResult FromHex(Engine engine, string s, uint maxLeng if (length % 2 != 0) { - return new FromEncodingResult(bytes, ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex string"), read); + return new FromEncodingResult([], ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex string"), read); } var byteIndex = 0; @@ -327,7 +327,7 @@ internal static FromEncodingResult FromHex(Engine engine, string s, uint maxLeng var hexits = s.AsSpan(read, 2); if (!HexAlphabet.Contains(hexits[0]) || !HexAlphabet.Contains(hexits[1])) { - return new FromEncodingResult(bytes, ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex value"), read); + return new FromEncodingResult(bytes.AsSpan(0, byteIndex).ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex value"), read); } #if SUPPORTS_SPAN_PARSE diff --git a/Jint/Native/TypedArray/Uint8ArrayPrototype.cs b/Jint/Native/TypedArray/Uint8ArrayPrototype.cs index 295f48d7da..2dea5a4d91 100644 --- a/Jint/Native/TypedArray/Uint8ArrayPrototype.cs +++ b/Jint/Native/TypedArray/Uint8ArrayPrototype.cs @@ -60,13 +60,13 @@ private JsObject SetFromBase64(JsValue thisObject, JsValue[] arguments) var byteLength = taRecord.TypedArrayLength; var result = Uint8ArrayConstructor.FromBase64(_engine, s.ToString(), alphabet.ToString(), lastChunkHandling.ToString(), byteLength); + SetUint8ArrayBytes(into, result.Bytes); + if (result.Error is not null) { throw result.Error; } - SetUint8ArrayBytes(into, result.Bytes); - var resultObject = OrdinaryObjectCreate(_engine, _engine.Intrinsics.Object); resultObject.CreateDataPropertyOrThrow("read", result.Read); resultObject.CreateDataPropertyOrThrow("written", result.Bytes.Length); @@ -105,13 +105,13 @@ private JsObject SetFromHex(JsValue thisObject, JsValue[] arguments) var byteLength = taRecord.TypedArrayLength; var result = Uint8ArrayConstructor.FromHex(_engine, s.ToString(), byteLength); + SetUint8ArrayBytes(into, result.Bytes); + if (result.Error is not null) { throw result.Error; } - SetUint8ArrayBytes(into, result.Bytes); - var resultObject = OrdinaryObjectCreate(_engine, _engine.Intrinsics.Object); resultObject.CreateDataPropertyOrThrow("read", result.Read); resultObject.CreateDataPropertyOrThrow("written", result.Bytes.Length); @@ -136,10 +136,14 @@ private JsValue ToBase64(JsValue thisObject, JsValue[] arguments) if (alphabet == "base64") { outAscii = Convert.ToBase64String(toEncode); + if (omitPadding) + { + outAscii = outAscii.TrimEnd('='); + } } else { - outAscii = WebEncoders.Base64UrlEncode(toEncode); + outAscii = WebEncoders.Base64UrlEncode(toEncode, omitPadding); } return outAscii;