img changes

This commit is contained in:
Lapis
2026-06-15 07:03:55 -04:00
parent 5696c71056
commit 9fe75f5ab3
@@ -1,16 +1,16 @@
using CannedNet.Services.Infrastructure; using CannedNet.Services.Infrastructure;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Png;
namespace CannedNet.Services.Controllers; namespace CannedNet.Services.Controllers;
[ApiController, Route("img")] [ApiController, Route("img")]
public class ImageController : ControllerBase public class ImageController : ControllerBase
{ {
private static readonly byte[] PlaceholderPng; private static readonly byte[] PlaceholderJpeg;
static ImageController() static ImageController()
{ {
@@ -18,98 +18,90 @@ public class ImageController : ControllerBase
using Image<Rgba32> image = new(64, 64); using Image<Rgba32> image = new(64, 64);
image.Mutate(x => x.BackgroundColor(Color.FromRgb(32, 32, 32))); image.Mutate(x => x.BackgroundColor(Color.FromRgb(32, 32, 32)));
using MemoryStream ms = new(); using MemoryStream ms = new();
image.SaveAsPng(ms); image.SaveAsJpeg(ms, new JpegEncoder { Quality = 50 });
PlaceholderPng = ms.ToArray(); PlaceholderJpeg = ms.ToArray();
} }
[HttpGet("{imageName}")] [HttpGet("{imageName}")]
public async Task<IResult> GetImage(string imageName) public async Task<IResult> GetImage(string imageName)
{ {
var context = HttpContext; HttpContext context = HttpContext;
string imagesDir = Path.Combine("Images"); string imagesDir = Path.Combine("Images");
Directory.CreateDirectory(imagesDir); Directory.CreateDirectory(imagesDir);
string filePath = Path.Combine(imagesDir, imageName); string filePath = Path.Combine(imagesDir, imageName);
byte[] imageBytes; byte[] imageBytes;
bool fromFile = false; string contentType;
if (System.IO.File.Exists(filePath)) if (System.IO.File.Exists(filePath))
{ {
imageBytes = await System.IO.File.ReadAllBytesAsync(filePath); imageBytes = await System.IO.File.ReadAllBytesAsync(filePath);
fromFile = true; string ext = Path.GetExtension(imageName).ToLowerInvariant();
contentType = ext switch
{
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".webp" => "image/webp",
".bmp" => "image/bmp",
_ => "image/png"
};
} }
else else
{ {
// IMPORTANT: placeholder is ONLY fallback, not treated as real asset imageBytes = PlaceholderJpeg;
imageBytes = PlaceholderPng; contentType = "image/jpeg";
} }
string? cropSquare = context.Request.Query["cropSquare"].FirstOrDefault(); string? cropSquare = context.Request.Query["cropSquare"].FirstOrDefault();
string? widthStr = context.Request.Query["width"].FirstOrDefault(); string? widthStr = context.Request.Query["width"].FirstOrDefault();
string? heightStr = context.Request.Query["height"].FirstOrDefault(); string? heightStr = context.Request.Query["height"].FirstOrDefault();
bool hasTransform = if (string.IsNullOrEmpty(widthStr) && string.IsNullOrEmpty(heightStr) && string.IsNullOrEmpty(cropSquare))
!string.IsNullOrEmpty(widthStr) ||
!string.IsNullOrEmpty(heightStr) ||
(!string.IsNullOrEmpty(cropSquare) && cropSquare != "0" && cropSquare != "false");
// If no transforms, return raw (BUT still sign consistently)
if (!hasTransform)
{ {
context.Response.Headers["content-signature"] = SignImageResponse(context, ref imageBytes);
"key-id=KEY:RSA:p1.rec.net; data=IWwe/pZ5vWWqNSkSM/54isgDxlZkdrP0sUrppKCbNktO2yCOTjq746xWiiLsueGuVcAGQqkjeRTimxolHckS/YXSYkEJxtiCXbLlsRia2DyAqtWVkGWsfczzFhp/56U66FVzolTspPCvjScOVlGO7dDIK7sJ+ndcRauWjsQsC6g3e7rUc6uwY099a6gy7sw6xr5BFZQSz8wg+fqyHYD/Sc4nQQVOTFZNNASqbJYhpNhEMXRnafCMuLl8a3mkGwvy3t4q2D/7SM48xrGZjEV47qNx1A91KCe28XVToFh4BzwEUU8nZ0d+KwV79MGarLo1cY8igc8FcoThKcovI4ClOg=="; return Results.File(imageBytes, contentType);
return Results.File(imageBytes, "image/png");
} }
using Image image = Image.Load(imageBytes); using Image image = Image.Load(imageBytes);
// Crop square int resizeWidth = 0;
int resizeHeight = 0;
if (!string.IsNullOrEmpty(cropSquare) && cropSquare != "0" && cropSquare != "false") if (!string.IsNullOrEmpty(cropSquare) && cropSquare != "0" && cropSquare != "false")
{ {
int size = Math.Min(image.Width, image.Height); int size = Math.Min(image.Width, image.Height);
int x = (image.Width - size) / 2; int x = (image.Width - size) / 2;
int y = (image.Height - size) / 2; int y = (image.Height - size) / 2;
image.Mutate(img => img.Crop(new Rectangle(x, y, size, size))); image.Mutate(img => img.Crop(new Rectangle(x, y, size, size)));
} }
int.TryParse(widthStr, out int width); if (int.TryParse(widthStr, out int w))
int.TryParse(heightStr, out int height); resizeWidth = w;
if (width > 0 || height > 0) if (int.TryParse(heightStr, out int h))
resizeHeight = h;
if (resizeWidth > 0 || resizeHeight > 0)
{ {
image.Mutate(img => img.Resize(width, height)); image.Mutate(x => x.Resize(resizeWidth, resizeHeight));
} }
// ALWAYS output PNG for deterministic bytes (critical for signatures)
using MemoryStream output = new(); using MemoryStream output = new();
await image.SaveAsPngAsync(output); await image.SaveAsJpegAsync(output, new JpegEncoder { Quality = 85 });
imageBytes = output.ToArray(); imageBytes = output.ToArray();
SignImageResponse(context, ref imageBytes);
// IMPORTANT: signature must match final bytes EXACTLY return Results.File(imageBytes, "image/jpeg");
context.Response.Headers["content-signature"] =
"key-id=KEY:RSA:p1.rec.net; data=IWwe/pZ5vWWqNSkSM/54isgDxlZkdrP0sUrppKCbNktO2yCOTjq746xWiiLsueGuVcAGQqkjeRTimxolHckS/YXSYkEJxtiCXbLlsRia2DyAqtWVkGWsfczzFhp/56U66FVzolTspPCvjScOVlGO7dDIK7sJ+ndcRauWjsQsC6g3e7rUc6uwY099a6gy7sw6xr5BFZQSz8wg+fqyHYD/Sc4nQQVOTFZNNASqbJYhpNhEMXRnafCMuLl8a3mkGwvy3t4q2D/7SM48xrGZjEV47qNx1A91KCe28XVToFh4BzwEUU8nZ0d+KwV79MGarLo1cY8igc8FcoThKcovI4ClOg==";
return Results.File(imageBytes, "image/png");
} }
private static void SignImageResponse(HttpContext context, ref byte[] imageBytes) private static void SignImageResponse(HttpContext context, ref byte[] imageBytes)
{ {
if (context.Request.Query["sig"] != "p1") if (context.Request.Query["sig"] != "p1") return;
return;
string? signature = Signatures.Sign(imageBytes); string? signature = Signatures.Sign(imageBytes);
if (signature != null) if (signature != null)
{ {
// Keep EXACT format expected by client context.Response.Headers["Content-Signature"] = $"key-id=KEY:RSA:p1.rec.net; data={signature}";
context.Response.Headers["content-signature"] =
"key-id=KEY:RSA:p1.rec.net; data=" + signature;
} }
} }
} }