From 61dd2e1f741064d9d68939e3bae6c5967d5aa029 Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Thu, 2 Oct 2025 13:36:38 +0500 Subject: [PATCH] s3: add limit on download file size --- Notesnook.API/Controllers/S3Controller.cs | 16 ++++++++++------ Notesnook.API/Helpers/StorageHelper.cs | 19 +++++++++++++++++++ Notesnook.API/Interfaces/IS3Service.cs | 4 ++-- Notesnook.API/Services/S3Service.cs | 14 ++++++++++++-- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/Notesnook.API/Controllers/S3Controller.cs b/Notesnook.API/Controllers/S3Controller.cs index 72f6eff..324d763 100644 --- a/Notesnook.API/Controllers/S3Controller.cs +++ b/Notesnook.API/Controllers/S3Controller.cs @@ -86,7 +86,7 @@ namespace Notesnook.API.Controllers [HttpGet("multipart")] - public async Task MultipartUpload([FromQuery] string name, [FromQuery] int parts, [FromQuery] string uploadId) + public async Task MultipartUpload([FromQuery] string name, [FromQuery] int parts, [FromQuery] string? uploadId) { var userId = this.User.FindFirstValue("sub"); try @@ -122,12 +122,16 @@ namespace Notesnook.API.Controllers } [HttpGet] - public IActionResult Download([FromQuery] string name) + public async Task Download([FromQuery] string name) { - var userId = this.User.FindFirstValue("sub"); - var url = S3Service.GetDownloadObjectUrl(userId, name); - if (url == null) return BadRequest("Could not create signed url."); - return Ok(url); + try + { + var userId = this.User.FindFirstValue("sub"); + var url = await S3Service.GetDownloadObjectUrl(userId, name); + if (url == null) return BadRequest("Could not create signed url."); + return Ok(url); + } + catch (Exception ex) { return BadRequest(ex.Message); } } [HttpHead] diff --git a/Notesnook.API/Helpers/StorageHelper.cs b/Notesnook.API/Helpers/StorageHelper.cs index 0f70497..216170e 100644 --- a/Notesnook.API/Helpers/StorageHelper.cs +++ b/Notesnook.API/Helpers/StorageHelper.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Notesnook.API.Models; using Streetwriters.Common.Enums; @@ -33,6 +34,11 @@ namespace Notesnook.API.Helpers return MAX_STORAGE_PER_MONTH[subscription.Plan]; } + public static long GetFileSizeLimitForPlan(Subscription subscription) + { + return MAX_FILE_SIZE[subscription.Plan]; + } + public static bool IsStorageLimitReached(Subscription subscription, Limit limit) { var storageLimit = GetStorageLimitForPlan(subscription); @@ -45,5 +51,18 @@ namespace Notesnook.API.Helpers var maxFileSize = MAX_FILE_SIZE[subscription.Plan]; return fileSize > maxFileSize; } + + private static readonly string[] sizes = ["B", "KB", "MB", "GB", "TB"]; + public static string FormatBytes(long size) + { + int order = 0; + while (size >= 1024 && order < sizes.Length - 1) + { + order++; + size = size / 1024; + } + + return String.Format("{0:0.##} {1}", size, sizes[order]); + } } } \ No newline at end of file diff --git a/Notesnook.API/Interfaces/IS3Service.cs b/Notesnook.API/Interfaces/IS3Service.cs index 6c83d38..91cfecd 100644 --- a/Notesnook.API/Interfaces/IS3Service.cs +++ b/Notesnook.API/Interfaces/IS3Service.cs @@ -32,8 +32,8 @@ namespace Notesnook.API.Interfaces Task DeleteDirectoryAsync(string userId); Task GetObjectSizeAsync(string userId, string name); string? GetUploadObjectUrl(string userId, string name); - string GetDownloadObjectUrl(string userId, string name); - Task StartMultipartUploadAsync(string userId, string name, int parts, string uploadId = null); + Task GetDownloadObjectUrl(string userId, string name); + Task StartMultipartUploadAsync(string userId, string name, int parts, string? uploadId = null); Task AbortMultipartUploadAsync(string userId, string name, string uploadId); Task CompleteMultipartUploadAsync(string userId, CompleteMultipartUploadRequest uploadRequest); } diff --git a/Notesnook.API/Services/S3Service.cs b/Notesnook.API/Services/S3Service.cs index 6055abc..f85729b 100644 --- a/Notesnook.API/Services/S3Service.cs +++ b/Notesnook.API/Services/S3Service.cs @@ -160,14 +160,24 @@ namespace Notesnook.API.Services return this.GetPresignedURL(userId, name, HttpVerb.PUT); } - public string GetDownloadObjectUrl(string userId, string name) + public async Task GetDownloadObjectUrl(string userId, string name) { + var subscriptionService = await WampServers.SubscriptionServer.GetServiceAsync(SubscriptionServerTopics.UserSubscriptionServiceTopic); + var subscription = await subscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId); + + var size = await GetObjectSizeAsync(userId, name); + if (StorageHelper.IsFileSizeExceeded(subscription, size)) + { + var fileSizeLimit = StorageHelper.GetFileSizeLimitForPlan(subscription); + throw new Exception($"You cannot download files larger than {StorageHelper.FormatBytes(fileSizeLimit)} on this plan."); + } + var url = this.GetPresignedURL(userId, name, HttpVerb.GET); if (url == null) return null; return url; } - public async Task StartMultipartUploadAsync(string userId, string name, int parts, string uploadId = null) + public async Task StartMultipartUploadAsync(string userId, string name, int parts, string? uploadId = null) { var objectName = GetFullObjectName(userId, name); if (userId == null || objectName == null) throw new Exception("Could not initiate multipart upload.");