Uint8ArrayPrototype.cs 6.7 KB

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