namespace WebDrop.Core; /// 头像(账号头像、空间头像)的 data URL 校验。浏览器里已经裁剪好并重新编码,服务端只做格式、大小和文件头检查 public static class AvatarData { /// 静态头像(256×256 WebP/PNG/JPEG)的长度上限(字符数) public const int MaxStaticChars = 350_000; /// 账号动图头像保留全部帧,约 4.5 MB public const int MaxAccountGifChars = 6_000_000; /// 空间头像会跟着空间信息发给所有成员和空间列表,动图上限比账号头像小,约 1.5 MB public const int MaxSpaceGifChars = 2_000_000; /// 缩略图(列表、聊天里默认分发的有损小图)的上限:静态约 75 KB,动图约 370 KB public const int MaxThumbStaticChars = 100_000; public const int MaxThumbGifChars = 800_000; private static readonly string[] Prefixes = ["data:image/png;base64,", "data:image/jpeg;base64,", "data:image/webp;base64,", "data:image/gif;base64,"]; public static void ValidateThumb(string thumb) => Validate(thumb, MaxThumbGifChars, MaxThumbStaticChars); /// 不合法时抛 BadRequest public static void Validate(string avatar, int maxGifChars, int maxStaticChars = MaxStaticChars) { var prefix = Prefixes.FirstOrDefault(p => avatar.StartsWith(p, StringComparison.Ordinal)); var gif = prefix?.Contains("gif", StringComparison.Ordinal) == true; if (prefix is null || avatar.Length > (gif ? maxGifChars : maxStaticChars) || avatar.Length <= prefix.Length) throw new WebDropException(ErrorCode.BadRequest); byte[] bytes; try { bytes = Convert.FromBase64String(avatar[prefix.Length..]); } catch (FormatException) { throw new WebDropException(ErrorCode.BadRequest); } if (gif && !(bytes.Length > 6 && bytes[0] == (byte)'G' && bytes[1] == (byte)'I' && bytes[2] == (byte)'F')) throw new WebDropException(ErrorCode.BadRequest); } }