aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-08-19 22:52:01 +0800
committer杨宇千 <crupest@outlook.com>2019-08-19 22:52:01 +0800
commit577d282f8e8f44f1a48b9dbf7dd90e8ef50c7a53 (patch)
tree5725e61da570be86173472d68bd6df521e593b65
parent134173eda92de04961dc69757b257c1c547d88a4 (diff)
downloadtimeline-577d282f8e8f44f1a48b9dbf7dd90e8ef50c7a53.tar.gz
timeline-577d282f8e8f44f1a48b9dbf7dd90e8ef50c7a53.tar.bz2
timeline-577d282f8e8f44f1a48b9dbf7dd90e8ef50c7a53.zip
Add check for content in avatar put.
-rw-r--r--Timeline.Tests/IntegratedTests/UserAvatarTests.cs53
-rw-r--r--Timeline/Controllers/UserAvatarController.cs26
-rw-r--r--Timeline/Filters/ContentHeaderAttributes.cs48
-rw-r--r--Timeline/Models/Http/Common.cs19
4 files changed, 140 insertions, 6 deletions
diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTests.cs b/Timeline.Tests/IntegratedTests/UserAvatarTests.cs
index 794f251b..0bed9598 100644
--- a/Timeline.Tests/IntegratedTests/UserAvatarTests.cs
+++ b/Timeline.Tests/IntegratedTests/UserAvatarTests.cs
@@ -7,8 +7,10 @@ using System;
using System.IO;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading.Tasks;
using Timeline.Controllers;
+using Timeline.Models.Http;
using Timeline.Services;
using Timeline.Tests.Helpers;
using Timeline.Tests.Helpers.Authentication;
@@ -76,8 +78,28 @@ namespace Timeline.Tests.IntegratedTests
}
{
- var res = await client.PutByteArrayAsync("users/user/avatar", new[] { (byte)0x00 }, "image/notaccept");
- res.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType);
+ var content = new ByteArrayContent(new[] { (byte)0x00 });
+ content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
+ var res = await client.PutAsync("users/user/avatar", content);
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
+ .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.Header_Missing_ContentLength);
+ }
+
+ {
+ var content = new ByteArrayContent(new[] { (byte)0x00 });
+ content.Headers.ContentLength = 1;
+ var res = await client.PutAsync("users/user/avatar", content);
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
+ .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.Header_Missing_ContentType);
+ }
+
+ {
+ var content = new ByteArrayContent(new[] { (byte)0x00 });
+ content.Headers.ContentLength = 0;
+ content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
+ var res = await client.PutAsync("users/user/avatar", content);
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
+ .And.Should().HaveBodyAsCommonResponseWithCode(CommonResponse.ErrorCodes.Header_Zero_ContentLength);
}
{
@@ -86,6 +108,33 @@ namespace Timeline.Tests.IntegratedTests
}
{
+ var content = new ByteArrayContent(new[] { (byte)0x00 });
+ content.Headers.ContentLength = 1000 * 1000 * 11;
+ content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
+ var res = await client.PutAsync("users/user/avatar", content);
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
+ .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_Content_TooBig);
+ }
+
+ {
+ var content = new ByteArrayContent(new[] { (byte)0x00 });
+ content.Headers.ContentLength = 2;
+ content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
+ var res = await client.PutAsync("users/user/avatar", content);
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
+ .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_Content_UnmatchedLength_Less);
+ }
+
+ {
+ var content = new ByteArrayContent(new[] { (byte)0x00, (byte)0x01 });
+ content.Headers.ContentLength = 1;
+ content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
+ var res = await client.PutAsync("users/user/avatar", content);
+ res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
+ .And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_Content_UnmatchedLength_Bigger);
+ }
+
+ {
var res = await client.PutByteArrayAsync("users/user/avatar", new[] { (byte)0x00 }, "image/png");
res.Should().HaveStatusCode(HttpStatusCode.BadRequest)
.And.Should().HaveBodyAsCommonResponseWithCode(UserAvatarController.ErrorCodes.Put_BadFormat_CantDecode);
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<IActionResult> Get(string username)
+ public async Task<IActionResult> 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<IActionResult> 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<IActionResult> Delete(string username)
+ public async Task<IActionResult> 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.
/// </summary>
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()
{