Files
2026-07-23 18:21:43 -07:00

160 lines
5.8 KiB
C#

using DeluxeBackend.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SkiaSharp;
using System.Formats.Asn1;
using System.Security.Cryptography;
using System.Text;
namespace DeluxeBackend.Controllers
{
[Route("Images")]
[ApiController]
public class ImagesController : ControllerBase
{
private readonly HttpClient httpClient;
private readonly string _cachePath;
public ImagesController(HttpClient httpClient, IWebHostEnvironment env)
{
this.httpClient = httpClient;
_cachePath = Path.Combine(env.ContentRootPath, "image_cache");
}
[HttpGet("{*filePath}")]
public async Task<IActionResult> GetImage(
string filePath,
[FromQuery] int? width = null,
[FromQuery] int? height = null,
[FromQuery] string? cropSquare = null,
[FromQuery] string? sig = null)
{
if (string.IsNullOrEmpty(filePath)) return BadRequest();
if (sig != null && sig != "p1") return BadRequest();
bool crop = cropSquare?.ToLower() switch
{
"1" => true,
"true" => true,
_ => false
};
string cacheKey = GenerateCacheKey(filePath, width, height, crop);
string cachedFilePath = Path.Combine(_cachePath, cacheKey + ".png");
byte[] imageBytes;
if (System.IO.File.Exists(cachedFilePath))
{
imageBytes = await System.IO.File.ReadAllBytesAsync(cachedFilePath);
}
else
{
string cdnUrl = $"{CdnService.Url.TrimEnd('/')}/img/{filePath}";
try
{
var response = await httpClient.GetAsync(cdnUrl);
if (!response.IsSuccessStatusCode) return NotFound();
using var stream = await response.Content.ReadAsStreamAsync();
using var original = SKBitmap.Decode(stream);
if (original == null) return await ServeRawFallback(cdnUrl, sig, filePath);
SKBitmap currentBitmap = original;
if (crop)
{
int size = Math.Min(original.Width, original.Height);
int x = (original.Width - size) / 2;
int y = (original.Height - size) / 2;
var subset = new SKBitmap(size, size);
original.ExtractSubset(subset, new SKRectI(x, y, x + size, y + size));
currentBitmap = subset;
}
if (width.HasValue || height.HasValue)
{
int w = width ?? (int)(currentBitmap.Width * ((float)height! / currentBitmap.Height));
int h = height ?? (int)(currentBitmap.Height * ((float)width! / currentBitmap.Width));
var info = new SKImageInfo(w, h);
var resized = new SKBitmap(info);
currentBitmap.ScalePixels(resized, SKSamplingOptions.Default);
if (currentBitmap != original) currentBitmap.Dispose();
currentBitmap = resized;
}
using (var image = SKImage.FromBitmap(currentBitmap))
using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
{
imageBytes = data.ToArray();
}
if (currentBitmap != original) currentBitmap.Dispose();
if (!Directory.Exists(_cachePath)) Directory.CreateDirectory(_cachePath);
await System.IO.File.WriteAllBytesAsync(cachedFilePath, imageBytes);
}
catch
{
return await ServeRawFallback(cdnUrl, sig, filePath);
}
}
if (!string.IsNullOrEmpty(sig))
{
SignPayloadAndAppendHeader(imageBytes, sig);
}
return File(imageBytes, "image/png");
}
private async Task<IActionResult> ServeRawFallback(string url, string? sig, string? filename)
{
try
{
var response = await httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode) return NotFound();
byte[] rawBytes = await response.Content.ReadAsByteArrayAsync();
if (!string.IsNullOrEmpty(sig))
{
SignPayloadAndAppendHeader(Encoding.UTF8.GetBytes(filename), sig);
}
string contentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
return File(rawBytes, contentType);
}
catch
{
return StatusCode(500, "Error pipeline processing fallback asset safely.");
}
}
private void SignPayloadAndAppendHeader(byte[] payload, string sig)
{
using var rsa = RSA.Create();
rsa.ImportParameters(ServerConfig.RsaParams);
byte[] signatureBytes = rsa.SignData(payload, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
string base64Signature = Convert.ToBase64String(signatureBytes);
Response.Headers.Append("Content-Signature", $"key-id=KEY:RSA:{sig}.rec.net; data={base64Signature};");
}
private static string GenerateCacheKey(string path, int? w, int? h, bool crop)
{
string rawKey = $"{path}_{w}_{h}_{crop}";
byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(rawKey));
return Convert.ToHexString(hashBytes);
}
}
}