From e5f5be69f854565d4f58d996cbf4347fa0eae0ff Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Mon, 19 Aug 2019 00:01:09 +0800 Subject: Add validator. --- Timeline.Tests/UserAvatarServiceTest.cs | 14 ++++++++--- Timeline/Services/UserAvatarService.cs | 44 +++++++++++++++++++++++++++++++-- Timeline/Timeline.csproj | 1 + nuget.config | 7 +++--- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/Timeline.Tests/UserAvatarServiceTest.cs b/Timeline.Tests/UserAvatarServiceTest.cs index a8e0562b..d767958a 100644 --- a/Timeline.Tests/UserAvatarServiceTest.cs +++ b/Timeline.Tests/UserAvatarServiceTest.cs @@ -24,7 +24,15 @@ namespace Timeline.Tests } } - public class UserAvatarServiceTest : IDisposable, IClassFixture + public class MockUserAvatarValidator : IUserAvatarValidator + { + public Task<(bool, string)> Validate(Avatar avatar) + { + return Task.FromResult((true, "Validate succeed.")); + } + } + + public class UserAvatarServiceTest : IDisposable, IClassFixture, IClassFixture { private static Avatar MockAvatar { get; } = new Avatar { @@ -45,14 +53,14 @@ namespace Timeline.Tests private readonly UserAvatarService _service; - public UserAvatarServiceTest(ITestOutputHelper outputHelper, MockDefaultUserAvatarProvider mockDefaultUserAvatarProvider) + public UserAvatarServiceTest(ITestOutputHelper outputHelper, MockDefaultUserAvatarProvider mockDefaultUserAvatarProvider, MockUserAvatarValidator mockUserAvatarValidator) { _mockDefaultUserAvatarProvider = mockDefaultUserAvatarProvider; _loggerFactory = MyTestLoggerFactory.Create(outputHelper); _database = new TestDatabase(); - _service = new UserAvatarService(_loggerFactory.CreateLogger(), _database.DatabaseContext, _mockDefaultUserAvatarProvider); + _service = new UserAvatarService(_loggerFactory.CreateLogger(), _database.DatabaseContext, _mockDefaultUserAvatarProvider, mockUserAvatarValidator); } public void Dispose() diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index 4f11978c..2a73cde5 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -2,6 +2,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats; using System; using System.IO; using System.Linq; @@ -45,6 +47,11 @@ namespace Timeline.Services Task GetDefaultAvatar(); } + public interface IUserAvatarValidator + { + Task<(bool valid, string message)> Validate(Avatar avatar); + } + public interface IUserAvatarService { /// @@ -87,6 +94,31 @@ namespace Timeline.Services } } + public class UserAvatarValidator : IUserAvatarValidator + { + public Task<(bool valid, string message)> Validate(Avatar avatar) + { + return Task.Run(() => + { + try + { + using (var image = Image.Load(avatar.Data, out IImageFormat format)) + { + if (!format.MimeTypes.Contains(avatar.Type)) + return (false, "Image's actual mime type is not the specified one."); + if (image.Width != image.Height) + return (false, "Image is not a square, aka width is not equal to height."); + } + return (true, "A good avatar."); + } + catch (UnknownImageFormatException e) + { + return (false, $"Failed to decode image. Exception: {e} ."); + } + }); + } + } + public class UserAvatarService : IUserAvatarService { @@ -95,12 +127,14 @@ namespace Timeline.Services private readonly DatabaseContext _database; private readonly IDefaultUserAvatarProvider _defaultUserAvatarProvider; + private readonly IUserAvatarValidator _avatarValidator; - public UserAvatarService(ILogger logger, DatabaseContext database, IDefaultUserAvatarProvider defaultUserAvatarProvider) + public UserAvatarService(ILogger logger, DatabaseContext database, IDefaultUserAvatarProvider defaultUserAvatarProvider, IUserAvatarValidator avatarValidator) { _logger = logger; _database = database; _defaultUserAvatarProvider = defaultUserAvatarProvider; + _avatarValidator = avatarValidator; } public async Task GetAvatar(string username) @@ -157,11 +191,15 @@ namespace Timeline.Services { _database.UserAvatars.Remove(avatarEntity); await _database.SaveChangesAsync(); + _logger.LogInformation("Removed an entry in user_avatars."); } } else { - // TODO: Use image library to check the format to prohibit bad data. + (bool valid, string message) = await _avatarValidator.Validate(avatar); + if (!valid) + throw new AvatarDataException(avatar, $"Failed to validate image. {message}"); + if (avatarEntity == null) { user.Avatar = new UserAvatar @@ -176,6 +214,7 @@ namespace Timeline.Services avatarEntity.Data = avatar.Data; } await _database.SaveChangesAsync(); + _logger.LogInformation("Added or modified an entry in user_avatars."); } } } @@ -186,6 +225,7 @@ namespace Timeline.Services { services.AddScoped(); services.AddSingleton(); + services.AddSingleton(); } } } diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj index 29ff3354..3855e0d1 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -12,5 +12,6 @@ + diff --git a/nuget.config b/nuget.config index 22519a86..e1fc7cfe 100644 --- a/nuget.config +++ b/nuget.config @@ -1,8 +1,9 @@ - + - - + + + -- cgit v1.2.3