using System.Globalization; using System.Text; using Jint; using Jint.Native; using Jint.Runtime.Interop; namespace XFESystem.Browser; public sealed partial class DomBridge { private sealed class DecoderState(Decoder decoder, bool ignoreBom, string name) { public Decoder Decoder = decoder; public bool IgnoreBom = ignoreBom; public string Name = name; public bool First = true; } private readonly Dictionary decoders = []; private int nextDecoderId; private void InstallEncodingCallbacks() { var encoding = new BrowserHostObject(engine); Function(encoding, "encode", args => { var text = Value(args, 0).IsUndefined() ? string.Empty : Argument(args, 0); var bytes = Encoding.UTF8.GetBytes(text); if (bytes.LongLength > limits.MaximumScriptMemoryBytes / 2) throw new BrowserLimitException("UTF-8 output exceeds its byte budget."); var array = CallInternal(bootstrap.Get("byteArray"), [bytes.Length]).AsObject(); for (var index = 0; index < bytes.Length; ++index) array.Set(index.ToString(CultureInfo.InvariantCulture), (int)bytes[index], array); return array; }); Function(encoding, "decoder", args => { if (decoders.Count >= limits.MaximumPendingTasks) throw new BrowserLimitException("The UTF-8 decoder limit was reached."); var name = Argument(args, 2); bool fatal = Bool(Value(args, 0)); Encoding codec = name switch { "utf-8" => new UTF8Encoding(false, fatal), "utf-16le" => new UnicodeEncoding(false, false, fatal), "utf-16be" => new UnicodeEncoding(true, false, fatal), _ => throw new ArgumentException("Unsupported text decoding label.") }; var id = ++nextDecoderId; decoders.Add(id, new(codec.GetDecoder(), Bool(Value(args, 1)), name)); return id; }); Function(encoding, "decode", args => { if (!decoders.TryGetValue(NumberIndex(Value(args, 0)), out var state)) throw new ArgumentException("Unknown text decoder."); var view = CallInternal(bootstrap.Get("bytesOf"), [Value(args, 1)]).AsObject(); var length = NumberIndex(view.Get("length")); if (length > limits.MaximumHtmlCharacters * 4L || length > limits.MaximumScriptMemoryBytes / 2) throw new BrowserLimitException("UTF-8 input exceeds its byte budget."); var bytes = new byte[length]; for (var index = 0; index < length; ++index) bytes[index] = (byte)Math.Clamp(Number(view.Get(index.ToString(CultureInfo.InvariantCulture))), 0, 255); bool flush = !Bool(Value(args, 2)); var characters = new char[length + 2]; int count; try { count = state.Decoder.GetChars(bytes, 0, bytes.Length, characters, 0, flush); } catch (DecoderFallbackException) { state.Decoder.Reset(); state.First = true; throw new ArgumentException("The encoded data is not valid " + state.Name + "."); } var result = new string(characters, 0, count); if (state.First && result.Length > 0) { state.First = false; if (!state.IgnoreBom && result[0] == '\uFEFF') result = result[1..]; } if (flush) { state.Decoder.Reset(); state.First = true; } if (result.Length > limits.MaximumHtmlCharacters) throw new BrowserLimitException("Decoded text exceeds its character budget."); return result; }); engine.SetValue("__xfeEncoding", encoding); } }