Add remaining project files
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user