feat: naive sanity check for image URLs

This commit is contained in:
2025-07-22 13:09:13 +02:00
parent 9e1e9c86d3
commit e7cebc32a4

View File

@@ -158,32 +158,47 @@ public class QuotesController : ControllerBase
/// [AUTHED] Add a new quote /// [AUTHED] Add a new quote
/// </summary> /// </summary>
/// <returns>Newly created quote's id</returns> /// <returns>Newly created quote's id</returns>
/// <remarks>
/// <b>Note</b>:
/// User-provided image URLs are validated by checking
/// if they start with "https://", "http://" or "/".
/// This is rather a naive solution.
/// </remarks>
/// <param name="request">Form data containing required quote information</param> /// <param name="request">Form data containing required quote information</param>
/// <response code="201">Returned on valid request</response> /// <response code="201">Returned on valid request</response>
/// <response code="400">Returned when any of the categories does not exist</response> /// <response code="400">Returned when any of the categories does not exist</response>
/// <response code="403">Returned when user's id does not match the creator's id</response> /// <response code="403">Returned when user's id does not match the creator's id</response>
/// <response code="406">Returned when image url is invalid (does not start with "https://", "http://", or "/")</response>
[HttpPost("new")] [HttpPost("new")]
[Authorize] [Authorize]
[EnableCors] [EnableCors]
[ProducesResponseType(201)] [ProducesResponseType(201)]
[ProducesResponseType(typeof(ErrorDTO), 400)] [ProducesResponseType(typeof(ErrorDTO), 400)]
[ProducesResponseType(typeof(ErrorDTO), 403)] [ProducesResponseType(typeof(ErrorDTO), 403)]
[ProducesResponseType(typeof(ErrorDTO), 406)]
public async Task<IActionResult> CreateQuote([FromBody] CreateQuoteDTO request) public async Task<IActionResult> CreateQuote([FromBody] CreateQuoteDTO request)
{ {
// Get user ID from claims // Get user ID from claims
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userIdClaim == null || !int.TryParse(userIdClaim, out int userId)) if (userIdClaim == null || !int.TryParse(userIdClaim, out int userId))
// https://stackoverflow.com/a/47708867 // https://stackoverflow.com/a/47708867
return StatusCode(403, new ErrorDTO { Status = "error", Error_msg = "Invalid user ID" }); return StatusCode(403, new ErrorDTO { Status = "error", Error_msg = "Invalid user ID" });
// Find or create image // Try to find the image inside the DB
Image? image = null; Image? image = null;
if (!string.IsNullOrEmpty(request.ImageUrl)) if (!string.IsNullOrEmpty(request.ImageUrl))
{ {
image = await _db.Images.FirstOrDefaultAsync(i => i.Url == request.ImageUrl); image = await _db.Images.FirstOrDefaultAsync(i => i.Url == request.ImageUrl);
// Failed? Just insert it yourself
if (image == null) if (image == null)
{ {
// Simple (naive) sanity check for image URLs
if ( !request.ImageUrl.StartsWith("http://")
&& !request.ImageUrl.StartsWith("https://")
&& !request.ImageUrl.StartsWith("/"))
return StatusCode(406, new ErrorDTO { Status = "error", Error_msg = "Image URLs should point to http/https url or a local resource" });
image = new Image { Url = request.ImageUrl }; image = new Image { Url = request.ImageUrl };
_db.Images.Add(image); _db.Images.Add(image);
await _db.SaveChangesAsync(); await _db.SaveChangesAsync();
@@ -347,13 +362,19 @@ public class QuotesController : ControllerBase
/// While "categories = null" will not alter the quote's categories, /// While "categories = null" will not alter the quote's categories,
/// "categories = []" will (and in turn, empty each and every present category)!<br/> /// "categories = []" will (and in turn, empty each and every present category)!<br/>
/// Be careful when handling user-provided categories! /// Be careful when handling user-provided categories!
/// <br/><br/>
/// <b>Note</b>:
/// User-provided image URLs are validated by checking
/// if they start with "https://", "http://" or "/".
/// This is rather a naive solution.
/// </remarks> /// </remarks>
/// <returns>Newly modified quote as a DTO</returns> /// <returns>Newly modified quote as a DTO</returns>
/// <param name="id">Quote to be modified</param> /// <param name="id">Quote to be modified</param>
/// <param name="updatedQuote">Updated quote form data</param> /// <param name="updatedQuote">Updated quote form data. Id is ignored.</param>
/// <response code="204">Returned on valid request</response> /// <response code="204">Returned on valid request</response>
/// <response code="400">Returned when request text or author is empty (or whitespace)</response> /// <response code="400">Returned when request text or author is empty (or whitespace)</response>
/// <response code="404">Returned when no such quote exists</response> /// <response code="404">Returned when no such quote exists</response>
/// <response code="406">Returned when image url is invalid (does not start with "https://", "http://", or "/")</response>
[HttpPatch("{id}")] [HttpPatch("{id}")]
[Authorize] [Authorize]
[EnableCors] [EnableCors]
@@ -385,9 +406,16 @@ public class QuotesController : ControllerBase
if (!string.IsNullOrEmpty(updatedQuote.ImageUrl)) if (!string.IsNullOrEmpty(updatedQuote.ImageUrl))
{ {
image = await _db.Images.FirstOrDefaultAsync(i => i.Url == updatedQuote.ImageUrl); image = await _db.Images.FirstOrDefaultAsync(i => i.Url == updatedQuote.ImageUrl);
// Failed? Just insert it yourself // Failed? Just insert it yourself
if (image == null) if (image == null)
{ {
// Simple (naive) sanity check for image URLs
if ( !updatedQuote.ImageUrl.StartsWith("http://")
&& !updatedQuote.ImageUrl.StartsWith("https://")
&& !updatedQuote.ImageUrl.StartsWith("/"))
return StatusCode(406, new ErrorDTO { Status = "error", Error_msg = "Image URLs should point to http/https url or a local resource" });
image = new Image { Url = updatedQuote.ImageUrl }; image = new Image { Url = updatedQuote.ImageUrl };
_db.Images.Add(image); _db.Images.Add(image);
await _db.SaveChangesAsync(); await _db.SaveChangesAsync();