Uint8ArrayPrototype.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. if (result.Error is not null)
  54. {
  55. throw result.Error;
  56. }
  57. SetUint8ArrayBytes(into, result.Bytes);
  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. if (result.Error is not null)
  92. {
  93. throw result.Error;
  94. }
  95. SetUint8ArrayBytes(into, result.Bytes);
  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. }
  117. else
  118. {
  119. outAscii = WebEncoders.Base64UrlEncode(toEncode);
  120. }
  121. return outAscii;
  122. }
  123. private JsValue ToHex(JsValue thisObject, JsValue[] arguments)
  124. {
  125. var o = ValidateUint8Array(thisObject);
  126. var toEncode = GetUint8ArrayBytes(o);
  127. using var outString = new ValueStringBuilder();
  128. foreach (var b in toEncode)
  129. {
  130. var b1 = (byte)(b >> 4);
  131. outString.Append((char)(b1 > 9 ? b1 - 10 + 'a' : b1 + '0'));
  132. var b2 = (byte)(b & 0x0F);
  133. outString.Append((char)(b2 > 9 ? b2 - 10 + 'a' : b2 + '0'));
  134. }
  135. return outString.ToString();
  136. }
  137. private ReadOnlySpan<byte> GetUint8ArrayBytes(JsTypedArray ta)
  138. {
  139. var buffer = ta._viewedArrayBuffer;
  140. var taRecord = IntrinsicTypedArrayPrototype.MakeTypedArrayWithBufferWitnessRecord(ta, ArrayBufferOrder.SeqCst);
  141. if (taRecord.IsTypedArrayOutOfBounds)
  142. {
  143. ExceptionHelper.ThrowTypeError(_realm, "TypedArray is out of bounds");
  144. }
  145. return buffer._arrayBufferData!.AsSpan(0, (int) taRecord.TypedArrayLength);
  146. }
  147. private JsTypedArray ValidateUint8Array(JsValue ta)
  148. {
  149. if (ta is not JsTypedArray { _arrayElementType: TypedArrayElementType.Uint8 } typedArray)
  150. {
  151. ExceptionHelper.ThrowTypeError(_engine.Realm, "Not a Uint8Array");
  152. return default;
  153. }
  154. return typedArray;
  155. }
  156. }