using System.Text; using Jint.Extensions; using Jint.Native.ArrayBuffer; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Native.TypedArray; internal sealed class Uint8ArrayPrototype : Prototype { private readonly TypedArrayConstructor _constructor; public Uint8ArrayPrototype( Engine engine, IntrinsicTypedArrayPrototype objectPrototype, TypedArrayConstructor constructor) : base(engine, engine.Realm) { _prototype = objectPrototype; _constructor = constructor; } protected override void Initialize() { const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; var properties = new PropertyDictionary(6, checkExistingKeys: false) { ["BYTES_PER_ELEMENT"] = new(JsNumber.PositiveOne, PropertyFlag.AllForbidden), ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable), ["setFromBase64"] = new(new ClrFunction(Engine, "setFromBase64", SetFromBase64, 1, PropertyFlag.Configurable), PropertyFlags), ["setFromHex"] = new(new ClrFunction(Engine, "setFromHex", SetFromHex, 1, PropertyFlag.Configurable), PropertyFlags), ["toBase64"] = new(new ClrFunction(Engine, "toBase64", ToBase64, 0, PropertyFlag.Configurable), PropertyFlags), ["toHex"] = new(new ClrFunction(Engine, "toHex", ToHex, 0, PropertyFlag.Configurable), PropertyFlags), }; SetProperties(properties); } private JsObject SetFromBase64(JsValue thisObject, JsCallArguments arguments) { var into = ValidateUint8Array(thisObject); var s = arguments.At(0); if (!s.IsString()) { Throw.TypeError(_realm, "setFromBase64 must be called with a string"); } var opts = Uint8ArrayConstructor.GetOptionsObject(_engine, arguments.At(1)); var alphabet = Uint8ArrayConstructor.GetAndValidateAlphabetOption(_engine, opts); var lastChunkHandling = Uint8ArrayConstructor.GetAndValidateLastChunkHandling(_engine, opts); var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(into, ArrayBufferOrder.SeqCst); if (taRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm, "TypedArray is out of bounds"); } 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; } var resultObject = OrdinaryObjectCreate(_engine, _engine.Intrinsics.Object); resultObject.CreateDataPropertyOrThrow("read", result.Read); resultObject.CreateDataPropertyOrThrow("written", result.Bytes.Length); return resultObject; } private static void SetUint8ArrayBytes(JsTypedArray into, byte[] bytes) { var offset = into._byteOffset; var len = bytes.Length; var index = 0; while (index < len) { var b = bytes[index]; var byteIndexInBuffer = index + offset; into._viewedArrayBuffer.SetValueInBuffer(byteIndexInBuffer, TypedArrayElementType.Uint8, b, isTypedArray: true, ArrayBufferOrder.Unordered); index++; } } private JsObject SetFromHex(JsValue thisObject, JsCallArguments arguments) { var into = ValidateUint8Array(thisObject); var s = arguments.At(0); if (!s.IsString()) { Throw.TypeError(_realm, "setFromHex must be called with a string"); } var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(into, ArrayBufferOrder.SeqCst); if (taRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm, "TypedArray is out of bounds"); } 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; } var resultObject = OrdinaryObjectCreate(_engine, _engine.Intrinsics.Object); resultObject.CreateDataPropertyOrThrow("read", result.Read); resultObject.CreateDataPropertyOrThrow("written", result.Bytes.Length); return resultObject; } private JsValue ToBase64(JsValue thisObject, JsCallArguments arguments) { var o = ValidateUint8Array(thisObject); var opts = Uint8ArrayConstructor.GetOptionsObject(_engine, arguments.At(0)); var alphabet = Uint8ArrayConstructor.GetAndValidateAlphabetOption(_engine, opts); var omitPadding = TypeConverter.ToBoolean(opts.Get("omitPadding")); #if NETCOREAPP var toEncode = GetUint8ArrayBytes(o); #else var toEncode = GetUint8ArrayBytes(o).ToArray(); #endif string outAscii; if (alphabet == "base64") { outAscii = Convert.ToBase64String(toEncode); if (omitPadding) { outAscii = outAscii.TrimEnd('='); } } else { outAscii = WebEncoders.Base64UrlEncode(toEncode, omitPadding); } return outAscii; } private JsValue ToHex(JsValue thisObject, JsCallArguments arguments) { var o = ValidateUint8Array(thisObject); var toEncode = GetUint8ArrayBytes(o); using var outString = new ValueStringBuilder(); foreach (var b in toEncode) { var b1 = (byte) (b >> 4); outString.Append((char) (b1 > 9 ? b1 - 10 + 'a' : b1 + '0')); var b2 = (byte) (b & 0x0F); outString.Append((char) (b2 > 9 ? b2 - 10 + 'a' : b2 + '0')); } return outString.ToString(); } private ReadOnlySpan GetUint8ArrayBytes(JsTypedArray ta) { var buffer = ta._viewedArrayBuffer; var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(ta, ArrayBufferOrder.SeqCst); if (taRecord.IsTypedArrayOutOfBounds) { Throw.TypeError(_realm, "TypedArray is out of bounds"); } return buffer._arrayBufferData!.AsSpan(0, (int) taRecord.TypedArrayLength); } private JsTypedArray ValidateUint8Array(JsValue ta) { if (ta is not JsTypedArray { _arrayElementType: TypedArrayElementType.Uint8 } typedArray) { Throw.TypeError(_engine.Realm, "Not a Uint8Array"); return default; } return typedArray; } }