mirror of
https://github.com/CannedNet/CannedNet.git
synced 2026-10-25 15:09:23 -07:00
img changes
This commit is contained in:
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user