Uint8ArrayPrototype.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. using System.Text;
  2. using Jint.Collections;
  3. using Jint.Extensions;
  4. using Jint.Native.ArrayBuffer;
  5. using Jint.Runtime;
  6. using Jint.Runtime.Descriptors;
  7. using Jint.Runtime.Interop;
  8. namespace Jint.Native.TypedArray;
  9. internal sealed class Uint8ArrayPrototype : Prototype
  10. {
  11. private readonly TypedArrayConstructor _constructor;
  12. public Uint8ArrayPrototype(
  13. Engine engine,
  14. IntrinsicTypedArrayPrototype objectPrototype,
  15. TypedArrayConstructor constructor)
  16. : base(engine, engine.Realm)
  17. {
  18. _prototype = objectPrototype;
  19. _constructor = constructor;
  20. }
  21. protected override void Initialize()
  22. {
  23. const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
  24. var properties = new PropertyDictionary(6, checkExistingKeys: false)
  25. {
  26. ["BYTES_PER_ELEMENT"] = new(JsNumber.PositiveOne, PropertyFlag.AllForbidden),
  27. ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable),
  28. ["setFromBase64"] = new(new ClrFunction(Engine, "setFromBase64", SetFromBase64, 1, PropertyFlag.Configurable), PropertyFlags),
  29. ["setFromHex"] = new(new ClrFunction(Engine, "setFromHex", SetFromHex, 1, PropertyFlag.Configurable), PropertyFlags),
  30. ["toBase64"] = new(new ClrFunction(Engine, "toBase64", ToBase64, 0, PropertyFlag.Configurable), PropertyFlags),
  31. ["toHex"] = new(new ClrFunction(Engine, "toHex", ToHex, 0, PropertyFlag.Configurable), PropertyFlags),
  32. };
  33. SetProperties(properties);
  34. }
  35. private JsObject SetFromBase64(JsValue thisObject, JsValue[] arguments)
  36. {
  37. var into = ValidateUint8Array(thisObject);
  38. var s = arguments.At(0);
  39. if (!s.IsString())
  40. {
  41. ExceptionHelper.ThrowTypeError(_realm, "setFromBase64 must be called with a string");
  42. }
  43. var opts = Uint8ArrayConstructor.GetOptionsObject(_engine, arguments.At(1));
  44. var alphabet = Uint8ArrayConstructor.GetAndValidateAlphabetOption(_engine, opts);
  45. var lastChunkHandling = Uint8ArrayConstructor.GetAndValidateLastChunkHandling(_engine, opts);
  46. var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(into, ArrayBufferOrder.SeqCst);
  47. if (taRecord.IsTypedArrayOutOfBounds)
  48. {
  49. ExceptionHelper.ThrowTypeError(_realm, "TypedArray is out of bounds");
  50. }
  51. var byteLength = taRecord.TypedArrayLength;
  52. var result = Uint8ArrayConstructor.FromBase64(_engine, s.ToString(), alphabet.ToString(), lastChunkHandling.ToString(), byteLength);
  53. SetUint8ArrayBytes(into, result.Bytes);
  54. if (result.Error is not null)
  55. {
  56. throw result.Error;
  57. }
  58. var resultObject = OrdinaryObjectCreate(_engine, _engine.Intrinsics.Object);
  59. resultObject.CreateDataPropertyOrThrow("read", result.Read);
  60. resultObject.CreateDataPropertyOrThrow("written", result.Bytes.Length);
  61. return resultObject;
  62. }
  63. private static void SetUint8ArrayBytes(JsTypedArray into, byte[] bytes)
  64. {
  65. var offset = into._byteOffset;
  66. var len = bytes.Length;
  67. var index = 0;
  68. while (index < len)
  69. {
  70. var b = bytes[index];
  71. var byteIndexInBuffer = index + offset;
  72. into._viewedArrayBuffer.SetValueInBuffer(byteIndexInBuffer, TypedArrayElementType.Uint8, b, isTypedArray: true, ArrayBufferOrder.Unordered);
  73. index++;
  74. }
  75. }
  76. private JsObject SetFromHex(JsValue thisObject, JsValue[] arguments)
  77. {
  78. var into = ValidateUint8Array(thisObject);
  79. var s = arguments.At(0);
  80. if (!s.IsString())
  81. {
  82. ExceptionHelper.ThrowTypeError(_realm, "setFromHex must be called with a string");
  83. }
  84. var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(into, ArrayBufferOrder.SeqCst);
  85. if (taRecord.IsTypedArrayOutOfBounds)
  86. {
  87. ExceptionHelper.ThrowTypeError(_realm, "TypedArray is out of bounds");
  88. }
  89. var byteLength = taRecord.TypedArrayLength;
  90. var result = Uint8ArrayConstructor.FromHex(_engine, s.ToString(), byteLength);
  91. SetUint8ArrayBytes(into, result.Bytes);
  92. if (result.Error is not null)
  93. {
  94. throw result.Error;
  95. }
  96. var resultObject = OrdinaryObjectCreate(_engine, _engine.Intrinsics.Object);
  97. resultObject.CreateDataPropertyOrThrow("read", result.Read);
  98. resultObject.CreateDataPropertyOrThrow("written", result.Bytes.Length);
  99. return resultObject;
  100. }
  101. private JsValue ToBase64(JsValue thisObject, JsValue[] arguments)
  102. {
  103. var o = ValidateUint8Array(thisObject);
  104. var opts = Uint8ArrayConstructor.GetOptionsObject(_engine, arguments.At(0));
  105. var alphabet = Uint8ArrayConstructor.GetAndValidateAlphabetOption(_engine, opts);
  106. var omitPadding = TypeConverter.ToBoolean(opts.Get("omitPadding"));
  107. #if NETCOREAPP
  108. var toEncode = GetUint8ArrayBytes(o);
  109. #else
  110. var toEncode = GetUint8ArrayBytes(o).ToArray();
  111. #endif
  112. string outAscii;
  113. if (alphabet == "base64")
  114. {
  115. outAscii = Convert.ToBase64String(toEncode);
  116. if (omitPadding)
  117. {
  118. outAscii = outAscii.TrimEnd('=');
  119. }
  120. }
  121. else
  122. {
  123. outAscii = WebEncoders.Base64UrlEncode(toEncode, omitPadding);
  124. }
  125. return outAscii;
  126. }
  127. private JsValue ToHex(JsValue thisObject, JsValue[] arguments)
  128. {
  129. var o = ValidateUint8Array(thisObject);
  130. var toEncode = GetUint8ArrayBytes(o);
  131. using var outString = new ValueStringBuilder();
  132. foreach (var b in toEncode)
  133. {
  134. var b1 = (byte)(b >> 4);
  135. outString.Append((char)(b1 > 9 ? b1 - 10 + 'a' : b1 + '0'));
  136. var b2 = (byte)(b & 0x0F);
  137. outString.Append((char)(b2 > 9 ? b2 - 10 + 'a' : b2 + '0'));
  138. }
  139. return outString.ToString();
  140. }
  141. private ReadOnlySpan<byte> GetUint8ArrayBytes(JsTypedArray ta)
  142. {
  143. var buffer = ta._viewedArrayBuffer;
  144. var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(ta, ArrayBufferOrder.SeqCst);
  145. if (taRecord.IsTypedArrayOutOfBounds)
  146. {
  147. ExceptionHelper.ThrowTypeError(_realm, "TypedArray is out of bounds");
  148. }
  149. return buffer._arrayBufferData!.AsSpan(0, (int) taRecord.TypedArrayLength);
  150. }
  151. private JsTypedArray ValidateUint8Array(JsValue ta)
  152. {
  153. if (ta is not JsTypedArray { _arrayElementType: TypedArrayElementType.Uint8 } typedArray)
  154. {
  155. ExceptionHelper.ThrowTypeError(_engine.Realm, "Not a Uint8Array");
  156. return default;
  157. }
  158. return typedArray;
  159. }
  160. }