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);
}
}