using System.IO; using System.Reflection; using Lskj.AgentPet.Host.Core; namespace Lskj.AgentPet.Host; internal sealed class EmbeddedPetResource { public EmbeddedPetResource(byte[] bytes, string contentType, bool document) { Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); IsDocument = document; } public byte[] Bytes { get; } public string ContentType { get; } public bool IsDocument { get; } } internal static class EmbeddedPetResources { public const string PageUrl = "https://lserp-pet.local/index.html"; private const int MaximumResourceBytes = 2 * 1024 * 1024; private const int MaximumTotalBytes = 4 * 1024 * 1024; private static readonly ResourceSpec[] Specs = { new(PageUrl, "Lskj.AgentPet.Web.index.html", "text/html; charset=utf-8", true), new("https://lserp-pet.local/pet.css", "Lskj.AgentPet.Web.pet.css", "text/css; charset=utf-8", false), new("https://lserp-pet.local/pet-runtime.js", "Lskj.AgentPet.Web.pet-runtime.js", "text/javascript; charset=utf-8", false), new("https://lserp-pet.local/bridge-client.js", "Lskj.AgentPet.Web.bridge-client.js", "text/javascript; charset=utf-8", false), new("https://lserp-pet.local/pet-shell.js", "Lskj.AgentPet.Web.pet-shell.js", "text/javascript; charset=utf-8", false) }; public static IReadOnlyDictionary Load() { Assembly assembly = typeof(EmbeddedPetResources).Assembly; Dictionary result = new(StringComparer.Ordinal); int totalBytes = 0; foreach (ResourceSpec spec in Specs) { using Stream? stream = assembly.GetManifestResourceStream(spec.Name); if (stream is null || !stream.CanRead || stream.Length <= 0 || stream.Length > MaximumResourceBytes) { throw new HostError( "pet_web_resource_invalid", "签名桌宠程序集缺少有效的嵌入式页面资源。"); } totalBytes = checked(totalBytes + (int)stream.Length); if (totalBytes > MaximumTotalBytes) { throw new HostError( "pet_web_resource_invalid", "签名桌宠页面资源超过安全上限。"); } byte[] bytes = new byte[(int)stream.Length]; int offset = 0; while (offset < bytes.Length) { int read = stream.Read(bytes, offset, bytes.Length - offset); if (read <= 0) throw new HostError( "pet_web_resource_invalid", "无法完整读取签名桌宠页面资源。"); offset += read; } result.Add( spec.Url, new EmbeddedPetResource(bytes, spec.ContentType, spec.Document)); } return result; } private sealed class ResourceSpec { public ResourceSpec( string url, string name, string contentType, bool document) { Url = url; Name = name; ContentType = contentType; Document = document; } public string Url { get; } public string Name { get; } public string ContentType { get; } public bool Document { get; } } }