mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Add photo upload wiring and image validation
This commit is contained in:
@@ -1,10 +1,33 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Features.ImageUploads.Commands.UploadPhoto;
|
||||
|
||||
/// <summary>Validates <see cref="UploadPhotoCommand" />.</summary>
|
||||
public class UploadPhotoValidator : AbstractValidator<UploadPhotoCommand>
|
||||
{
|
||||
private static readonly byte[] PngSignature =
|
||||
[
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
|
||||
];
|
||||
|
||||
private static readonly byte[] JpegSignature =
|
||||
[
|
||||
0xFF, 0xD8, 0xFF
|
||||
];
|
||||
|
||||
private static readonly byte[] RiffSignature =
|
||||
[
|
||||
0x52, 0x49, 0x46, 0x46 // "RIFF"
|
||||
];
|
||||
|
||||
private static readonly byte[] WebPMarker =
|
||||
[
|
||||
0x57, 0x45, 0x42, 0x50 // "WEBP"
|
||||
];
|
||||
|
||||
private const long MaxFileSizeInBytes = 0x100000 * 20; // 20 MB
|
||||
|
||||
public UploadPhotoValidator()
|
||||
{
|
||||
RuleFor(x => x.UploadedById).NotEmpty().WithMessage("UploadedById is required.");
|
||||
@@ -13,16 +36,36 @@ public class UploadPhotoValidator : AbstractValidator<UploadPhotoCommand>
|
||||
|
||||
RuleFor(x => x.Key).NotEmpty().WithMessage("Key is required.");
|
||||
|
||||
RuleFor(x => x.File.Length)
|
||||
.GreaterThan(0)
|
||||
.When(x => x.File is not null)
|
||||
.WithMessage("File cannot be empty.");
|
||||
|
||||
RuleFor(x => x.File.ContentType)
|
||||
.Must(contentType =>
|
||||
contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
.When(x => x.File is not null)
|
||||
.Must(contentType => contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
|
||||
.WithMessage("File must be an image.");
|
||||
|
||||
// RuleFor(x => x.File.Length)
|
||||
// .InclusiveBetween(1, MaxFileSizeInBytes)
|
||||
// .WithMessage($"The file must be between 1 byte and {FileSizeConverter.MiBToMB(MaxFileSizeInBytes )} MB.");
|
||||
|
||||
RuleFor(x => x.File)
|
||||
.MustAsync(HaveValidImageSignatureAsync)
|
||||
.WithMessage("The file is not a valid PNG, JPEG, or WebP image.");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> HaveValidImageSignatureAsync(IFormFile file, CancellationToken cancellationToken)
|
||||
{
|
||||
var buffer = new byte[12];
|
||||
await using var stream = file.OpenReadStream();
|
||||
var bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken);
|
||||
|
||||
return IsPng(buffer, bytesRead) || IsJpeg(buffer, bytesRead) || IsWebP(buffer, bytesRead);
|
||||
}
|
||||
|
||||
private static bool IsPng(byte[] buffer, int bytesRead) =>
|
||||
bytesRead >= PngSignature.Length && buffer.AsSpan(0, PngSignature.Length).SequenceEqual(PngSignature);
|
||||
|
||||
private static bool IsJpeg(byte[] buffer, int bytesRead) =>
|
||||
bytesRead >= JpegSignature.Length && buffer.AsSpan(0, JpegSignature.Length).SequenceEqual(JpegSignature);
|
||||
|
||||
private static bool IsWebP(byte[] buffer, int bytesRead) =>
|
||||
bytesRead >= 12
|
||||
&& buffer.AsSpan(0, 4).SequenceEqual(RiffSignature)
|
||||
&& buffer.AsSpan(8, 4).SequenceEqual(WebPMarker);
|
||||
}
|
||||
Reference in New Issue
Block a user