XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
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<int, DecoderState> decoders = [];
    private int nextDecoderId;
    private void InstallEncodingCallbacks()
    {
        var encoding = new BrowserHostObject(engine);
        Function(encoding, "btoa", args =>
        {
            string text = Argument(args, 0);
            if ((text.Length + 2L) / 3 * 4 > limits.MaximumHtmlCharacters) throw new BrowserLimitException("Base64 output exceeds its character budget.");
            var bytes = new byte[text.Length];
            for (int index = 0; index < text.Length; index++)
            {
                if (text[index] > 255) throw AdjacentError("InvalidCharacterError", "Base64 input must contain only byte characters.", 5);
                bytes[index] = (byte)text[index];
            }
            return Convert.ToBase64String(bytes);
        });
        Function(encoding, "atob", args =>
        {
            string input = Argument(args, 0);
            var text = new StringBuilder(input.Length + 3);
            foreach (char value in input) if (value is not ('\t' or '\n' or '\f' or '\r' or ' ')) text.Append(value);
            if (text.Length % 4 == 0)
            {
                if (text.Length > 0 && text[^1] == '=') text.Length--;
                if (text.Length > 0 && text[^1] == '=') text.Length--;
            }
            if (text.Length % 4 == 1) throw AdjacentError("InvalidCharacterError", "Invalid Base64 input.", 5);
            for (int index = 0; index < text.Length; index++)
                if (!(char.IsAsciiLetterOrDigit(text[index]) || text[index] is '+' or '/'))
                    throw AdjacentError("InvalidCharacterError", "Invalid Base64 input.", 5);
            while (text.Length % 4 != 0) text.Append('=');
            byte[] bytes = Convert.FromBase64String(text.ToString());
            var characters = new char[bytes.Length];
            for (int index = 0; index < bytes.Length; index++) characters[index] = (char)bytes[index];
            return new string(characters);
        });
        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;
        });
        if (host.FillRandom is not null) Function(encoding, "random", args =>
        {
            var view = Value(args, 0).AsObject(); int length = NumberIndex(view.Get("length"));
            if (length > 65536) throw new BrowserLimitException("The cryptographic random request exceeds 65536 bytes.");
            var bytes = new byte[length]; host.FillRandom(bytes);
            for (int index = 0; index < length; index++) view.Set(index.ToString(CultureInfo.InvariantCulture), (int)bytes[index], view);
            return JsValue.Undefined;
        });
        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);
    }
}