mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Formats;
|
||||
|
||||
namespace RadicadeServer.Images
|
||||
{
|
||||
[Route("images")]
|
||||
public class ImageAPI : Controller
|
||||
{
|
||||
private const string SignatureData =
|
||||
"IWwe/pZ5vWWqNSkSM/54isgDxlZkdrP0sUrppKCbNktO2yCOTjq746xWiiLsueGuVcAGQqkjeRTimxolHckS/YXSYkEJxtiCXbLlsRia2DyAqtWVkGWsfczzFhp/56U66FVzolTspPCvjScOVlGO7dDIK7sJ+ndcRauWjsQsC6g3e7rUc6uwY099a6gy7sw6xr5BFZQSz8wg+fqyHYD/Sc4nQQVOTFZNNASqbJYhpNhEMXRnafCMuLl8a3mkGwvy3t4q2D/7SM48xrGZjEV47qNx1A91KCe28XVToFh4BzwEUU8nZ0d+KwV79MGarLo1cY8igc8FcoThKcovI4ClOg==";
|
||||
|
||||
public const string PatchedKeyId = "KEY:RSA:p1.radicade.internal";
|
||||
|
||||
public const string StockKeyId = "KEY:RSA:p1.rec.net";
|
||||
|
||||
public static readonly string SignatureString = BuildSignature(PatchedKeyId);
|
||||
|
||||
public static readonly string StockSignatureString = BuildSignature(StockKeyId);
|
||||
|
||||
public const string KeyIdQueryParameter = "rcsig";
|
||||
|
||||
public static readonly string BareRouteKeyId =
|
||||
IsWellFormedKeyId(Environment.GetEnvironmentVariable("RADICADE_IMAGE_BARE_KEYID") ?? string.Empty)
|
||||
? Environment.GetEnvironmentVariable("RADICADE_IMAGE_BARE_KEYID") ?? StockKeyId
|
||||
: StockKeyId;
|
||||
|
||||
private static string BuildSignature(string keyId) => $"key-id={keyId}; data={SignatureData}";
|
||||
|
||||
[HttpGet("img/{imageName}")]
|
||||
public async Task<IActionResult> GetImageFile(string imageName, [FromQuery] int? width, [FromQuery] int? cropSquare, [FromQuery] string sig,
|
||||
[FromQuery(Name = KeyIdQueryParameter)] string clientKeyId)
|
||||
{
|
||||
return await ProcessImage(imageName, width, cropSquare, false, SignatureFor(clientKeyId, "img", PatchedKeyId));
|
||||
}
|
||||
|
||||
[HttpGet("{imageName}")]
|
||||
public async Task<IActionResult> GetImageFileAlt(string imageName, [FromQuery] int? width, [FromQuery] int? cropSquare, [FromQuery] string sig,
|
||||
[FromQuery(Name = KeyIdQueryParameter)] string clientKeyId)
|
||||
{
|
||||
return await ProcessImage(imageName, width, cropSquare, false, SignatureFor(clientKeyId, "bare", BareRouteKeyId));
|
||||
}
|
||||
|
||||
[HttpGet("default")]
|
||||
public async Task<IActionResult> GetDefaultImage([FromQuery] int? width, [FromQuery] int? cropSquare, [FromQuery] string sig,
|
||||
[FromQuery(Name = KeyIdQueryParameter)] string clientKeyId)
|
||||
{
|
||||
return await ProcessImage(DBConfig.DefaultRoomImage, width, cropSquare, false, SignatureFor(clientKeyId, "default", BareRouteKeyId));
|
||||
}
|
||||
|
||||
public static string SignatureFor(string clientKeyId, string route, string routeDefaultKeyId)
|
||||
{
|
||||
var keyId = IsWellFormedKeyId(clientKeyId) ? clientKeyId : routeDefaultKeyId;
|
||||
return BuildSignature(keyId);
|
||||
}
|
||||
|
||||
public static bool IsWellFormedKeyId(string keyId)
|
||||
{
|
||||
const string prefix = "KEY:RSA:p1.";
|
||||
if (string.IsNullOrEmpty(keyId) || keyId.Length > 96 || !keyId.StartsWith(prefix, StringComparison.Ordinal))
|
||||
return false;
|
||||
var tail = keyId.Substring(prefix.Length);
|
||||
if (tail.Length == 0 || tail.Length > 64)
|
||||
return false;
|
||||
foreach (var c in tail)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(c) && c != '.' && c != '-' && c != '_')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string[] AssetRoots() => new[] { DBConfig.CDNImages, DBConfig.CDNCustom, DBConfig.CDNStorage };
|
||||
|
||||
private static readonly string[] ExtensionVariants = { ".png", ".jpg", ".jpeg" };
|
||||
|
||||
public static string? ResolveAsset(string imageName) => ResolveAsset(imageName, AssetRoots());
|
||||
|
||||
public static string? ResolveAsset(string imageName, IEnumerable<string> roots)
|
||||
{
|
||||
var name = Path.GetFileName(imageName);
|
||||
if (string.IsNullOrWhiteSpace(name) || name == "." || name == "..")
|
||||
return null;
|
||||
|
||||
foreach (var candidate in CandidateNames(name))
|
||||
{
|
||||
foreach (var root in roots)
|
||||
{
|
||||
var path = Path.Join(root, candidate);
|
||||
if (WithinRoot(root, path) && System.IO.File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> CandidateNames(string name)
|
||||
{
|
||||
yield return name;
|
||||
|
||||
var existing = Path.GetExtension(name);
|
||||
|
||||
foreach (var variant in ExtensionVariants)
|
||||
{
|
||||
if (existing.Length == 0)
|
||||
{
|
||||
yield return name + variant;
|
||||
}
|
||||
else if (!string.Equals(existing, variant, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
yield return string.Concat(name.AsSpan(0, name.Length - existing.Length), variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool WithinRoot(string root, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fullRoot = Path.GetFullPath(root) + Path.DirectorySeparatorChar;
|
||||
return Path.GetFullPath(path).StartsWith(fullRoot, StringComparison.Ordinal);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IActionResult> ProcessImage(string imageName, int? width, int? cropSquare, bool isFallback = false, string? signature = null)
|
||||
{
|
||||
Response.Headers["Content-Signature"] = signature ?? SignatureString;
|
||||
|
||||
var fullPath = ResolveAsset(imageName);
|
||||
|
||||
if (fullPath == null)
|
||||
{
|
||||
if (!isFallback)
|
||||
{
|
||||
var fallback = ResolveAsset(DBConfig.DefaultRoomImage);
|
||||
if (fallback != null)
|
||||
{
|
||||
return await ServeImageFile(fallback, width, cropSquare);
|
||||
}
|
||||
}
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return await ServeImageFile(fullPath, width, cropSquare);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> ServeImageFile(string fullPath, int? width, int? cropSquare)
|
||||
{
|
||||
if (!width.HasValue)
|
||||
{
|
||||
return PhysicalFile(fullPath, "image/jpeg");
|
||||
}
|
||||
|
||||
Image image;
|
||||
try
|
||||
{
|
||||
image = await Image.LoadAsync(fullPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return PhysicalFile(fullPath, "image/jpeg");
|
||||
}
|
||||
|
||||
using (image)
|
||||
{
|
||||
if (cropSquare == 1)
|
||||
{
|
||||
int size = Math.Min(image.Width, image.Height);
|
||||
|
||||
var cropRect = new Rectangle((image.Width - size) / 2, (image.Height - size) / 2, size, size);
|
||||
|
||||
image.Mutate(x =>
|
||||
{
|
||||
x.Crop(cropRect);
|
||||
x.Resize(width.Value, width.Value);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
image.Mutate(x => x.Resize(width.Value, 0));
|
||||
}
|
||||
|
||||
IImageFormat format = await Image.DetectFormatAsync(fullPath);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
|
||||
await image.SaveAsync(ms, format);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
return File(ms, format.DefaultMimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user