From 577d282f8e8f44f1a48b9dbf7dd90e8ef50c7a53 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Mon, 19 Aug 2019 22:52:01 +0800 Subject: Add check for content in avatar put. --- Timeline/Controllers/UserAvatarController.cs | 26 ++++++++++++--- Timeline/Filters/ContentHeaderAttributes.cs | 48 ++++++++++++++++++++++++++++ Timeline/Models/Http/Common.cs | 19 +++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 Timeline/Filters/ContentHeaderAttributes.cs (limited to 'Timeline') diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs index 89d2650c..ffadcb86 100644 --- a/Timeline/Controllers/UserAvatarController.cs +++ b/Timeline/Controllers/UserAvatarController.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; using Timeline.Authenticate; +using Timeline.Filters; using Timeline.Models.Http; using Timeline.Services; @@ -22,6 +23,9 @@ namespace Timeline.Controllers public const int Put_BadFormat_CantDecode = -2011; public const int Put_BadFormat_UnmatchedFormat = -2012; public const int Put_BadFormat_BadSize = -2013; + public const int Put_Content_TooBig = -2021; + public const int Put_Content_UnmatchedLength_Less = -2022; + public const int Put_Content_UnmatchedLength_Bigger = -2023; public const int Delete_UserNotExist = -3001; public const int Delete_Forbid = -3002; @@ -55,7 +59,7 @@ namespace Timeline.Controllers [HttpGet("users/{username}/avatar")] [Authorize] - public async Task Get(string username) + public async Task Get([FromRoute] string username) { const string IfModifiedSinceHeaderKey = "If-Modified-Since"; try @@ -83,9 +87,15 @@ namespace Timeline.Controllers [HttpPut("users/{username}/avatar")] [Authorize] + [RequireContentType, RequireContentLength] [Consumes("image/png", "image/jpeg", "image/gif", "image/webp")] public async Task Put(string username) { + var contentLength = Request.ContentLength.Value; + if (contentLength > 1000 * 1000 * 10) + return BadRequest(new CommonResponse(ErrorCodes.Put_Content_TooBig, + "Content can't be bigger than 10MB.")); + if (!User.IsAdmin() && User.Identity.Name != username) { _logger.LogInformation($"Attempt to put a avatar of other user as a non-admin failed. Operator Username: {User.Identity.Name} ; Username To Put Avatar: {username} ."); @@ -95,8 +105,16 @@ namespace Timeline.Controllers try { - var data = new byte[Convert.ToInt32(Request.ContentLength)]; - await Request.Body.ReadAsync(data, 0, data.Length); + var data = new byte[contentLength]; + var bytesRead = await Request.Body.ReadAsync(data); + + if (bytesRead != contentLength) + return BadRequest(new CommonResponse(ErrorCodes.Put_Content_UnmatchedLength_Less, + $"Content length in header is {contentLength} but actual length is {bytesRead}.")); + + if (Request.Body.ReadByte() != -1) + return BadRequest(new CommonResponse(ErrorCodes.Put_Content_UnmatchedLength_Bigger, + $"Content length in header is {contentLength} but actual length is bigger than that.")); await _service.SetAvatar(username, new Avatar { @@ -121,7 +139,7 @@ namespace Timeline.Controllers [HttpDelete("users/{username}/avatar")] [Authorize] - public async Task Delete(string username) + public async Task Delete([FromRoute] string username) { if (!User.IsAdmin() && User.Identity.Name != username) { diff --git a/Timeline/Filters/ContentHeaderAttributes.cs b/Timeline/Filters/ContentHeaderAttributes.cs new file mode 100644 index 00000000..14685a01 --- /dev/null +++ b/Timeline/Filters/ContentHeaderAttributes.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Timeline.Models.Http; + +namespace Timeline.Filters +{ + public class RequireContentTypeAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext context) + { + if (context.HttpContext.Request.ContentType == null) + { + context.Result = new BadRequestObjectResult(CommonResponse.MissingContentType()); + } + } + } + + public class RequireContentLengthAttribute : ActionFilterAttribute + { + public RequireContentLengthAttribute() + : this(true) + { + + } + + public RequireContentLengthAttribute(bool requireNonZero) + { + RequireNonZero = requireNonZero; + } + + public bool RequireNonZero { get; set; } + + public override void OnActionExecuting(ActionExecutingContext context) + { + if (context.HttpContext.Request.ContentLength == null) + { + context.Result = new BadRequestObjectResult(CommonResponse.MissingContentLength()); + return; + } + + if (RequireNonZero && context.HttpContext.Request.ContentLength.Value == 0) + { + context.Result = new BadRequestObjectResult(CommonResponse.ZeroContentLength()); + return; + } + } + } +} diff --git a/Timeline/Models/Http/Common.cs b/Timeline/Models/Http/Common.cs index b4932754..50f6836e 100644 --- a/Timeline/Models/Http/Common.cs +++ b/Timeline/Models/Http/Common.cs @@ -9,6 +9,10 @@ namespace Timeline.Models.Http /// For example a required field is null. /// public const int InvalidModel = -100; + + public const int Header_Missing_ContentType = -111; + public const int Header_Missing_ContentLength = -112; + public const int Header_Zero_ContentLength = -113; } public static CommonResponse InvalidModel(string message) @@ -16,6 +20,21 @@ namespace Timeline.Models.Http return new CommonResponse(ErrorCodes.InvalidModel, message); } + public static CommonResponse MissingContentType() + { + return new CommonResponse(ErrorCodes.Header_Missing_ContentType, "Header Content-Type is required."); + } + + public static CommonResponse MissingContentLength() + { + return new CommonResponse(ErrorCodes.Header_Missing_ContentLength, "Header Content-Length is missing or of bad format."); + } + + public static CommonResponse ZeroContentLength() + { + return new CommonResponse(ErrorCodes.Header_Zero_ContentLength, "Header Content-Length must not be 0."); + } + public CommonResponse() { -- cgit v1.2.3