diff --git a/LarpixServer/Account/BackgroundDelete.cs b/LarpixServer/Account/BackgroundDelete.cs index 6807006..abbfdde 100644 --- a/LarpixServer/Account/BackgroundDelete.cs +++ b/LarpixServer/Account/BackgroundDelete.cs @@ -21,9 +21,10 @@ public class BackgroundDelete : BackgroundService { try { - foreach (var account in Directory.EnumerateDirectories(LarpixServer.Utils.Utils.ACCOUNTS_DATA_DIR, "*", - SearchOption.TopDirectoryOnly)) + if (!Fs.Exists(LarpixServer.Utils.Utils.ACCOUNTS_DATA_DIR)) return; + foreach (var accountName in Fs.ReadDirectory(LarpixServer.Utils.Utils.ACCOUNTS_DATA_DIR)) { + string account = Path.Combine(LarpixServer.Utils.Utils.ACCOUNTS_DATA_DIR, accountName); if (stoppingToken.IsCancellationRequested) return; try { diff --git a/LarpixServer/Account/Requests.cs b/LarpixServer/Account/Requests.cs index 118cb60..6c4ac7b 100644 --- a/LarpixServer/Account/Requests.cs +++ b/LarpixServer/Account/Requests.cs @@ -885,7 +885,7 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec return; } - byte[] fileBytes = await LarpixServer.Utils.Utils.LoadBodyBytes(context.Request.Body, 50 * 1024 * 1024); // 50MB limit for attachments + byte[] fileBytes = await LoadBodyBytes(context.Request.Body, ATTACHMENT_SIZE_LIMIT); // attachment size limit if (fileBytes == null || fileBytes.Length == 0) { await context.Response.WriteAsync(Encryption.Encryption.EncryptString("error:empty.body", password)); diff --git a/LarpixServer/Account/Utils.cs b/LarpixServer/Account/Utils.cs index d1af889..d630e50 100644 --- a/LarpixServer/Account/Utils.cs +++ b/LarpixServer/Account/Utils.cs @@ -332,12 +332,16 @@ public class Utils return "error:invalid.entry.name"; } - if (contentBytes.Length > PUBLIC_STORAGE_LIMIT) + string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public"; + string path = $"{dirPath}/{entry}"; + long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); + long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; + + if (currentTotalSize - fileExistingSize + contentBytes.Length > PUBLIC_STORAGE_LIMIT) { return "error:storage.limit.exceeded"; } - string path = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public/{entry}"; await Fs.WriteFile(path, contentBytes); return "success:storage.entry.updated"; } @@ -358,12 +362,16 @@ public class Utils contentBytes = Encoding.UTF8.GetBytes(contentBase64); } - if (contentBytes.Length > PUBLIC_STORAGE_LIMIT) + string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public"; + string path = $"{dirPath}/{entry}"; + long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); + long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; + + if (currentTotalSize - fileExistingSize + contentBytes.Length > PUBLIC_STORAGE_LIMIT) { return "error:storage.limit.exceeded"; } - string path = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public/{entry}"; await Fs.WriteFile(path, contentBytes); return "success:storage.entry.updated"; } @@ -398,12 +406,16 @@ public class Utils contentBytes = Encoding.UTF8.GetBytes(contentBase64); } - if (contentBytes.Length > PRIVATE_STORAGE_LIMIT) + string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/private"; + string path = $"{dirPath}/{entry}"; + long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); + long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; + + if (currentTotalSize - fileExistingSize + contentBytes.Length > PRIVATE_STORAGE_LIMIT) { return "error:storage.limit.exceeded"; } - string path = $"{ACCOUNTS_DATA_DIR}/{id}/storage/private/{entry}"; await Fs.WriteFile(path, contentBytes); return "success:storage.entry.updated"; } diff --git a/LarpixServer/Program.cs b/LarpixServer/Program.cs index ef84300..cb6ba0f 100644 --- a/LarpixServer/Program.cs +++ b/LarpixServer/Program.cs @@ -21,15 +21,17 @@ public class Program { serverOptions.AddServerHeader = false; serverOptions.ListenAnyIP(PORT); + serverOptions.Limits.MaxRequestBodySize = null; }); var app = builder.Build(); string stagingDir = $"{LarpixServer.Utils.Utils.ROOMS_DIR}/dms_staging"; - if (Directory.Exists(stagingDir)) + if (Fs.Exists(stagingDir)) { - foreach (var file in Directory.GetFiles(stagingDir)) + foreach (var fileName in Fs.ReadDirectory(stagingDir)) { + var file = Path.Combine(stagingDir, fileName); var fileInfo = new FileInfo(file); var age = DateTime.UtcNow - fileInfo.LastWriteTimeUtc; var remaining = TimeSpan.FromMinutes(10) - age; diff --git a/LarpixServer/Room/Requests.cs b/LarpixServer/Room/Requests.cs index d2b34c0..7c77e49 100644 --- a/LarpixServer/Room/Requests.cs +++ b/LarpixServer/Room/Requests.cs @@ -381,6 +381,12 @@ public class Requests string responded = serializedBody.string4; if (!await IsMemberOfDm(id, dmId)) return "error:forbidden"; + + if (content != null && content.Length > LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE) + { + string sizeStr = LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE >= 1024 ? $"{LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE / 1024.0:0.##} KB" : $"{LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE} B"; + return $"error:message.too.long:{sizeStr}"; + } string msgDir = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages"; long last = 0; @@ -409,7 +415,7 @@ public class Requests if (attachmentsNode is System.Text.Json.Nodes.JsonArray arr) { string attDir = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/attachments/{nextId}"; - if (!Directory.Exists(attDir)) Directory.CreateDirectory(attDir); + if (!Fs.Exists(attDir)) Fs.CreateDirectory(attDir); for (int i = 0; i < arr.Count; i++) { @@ -422,7 +428,7 @@ public class Requests { string attId = attObj["id"].ToString(); string destPath = $"{attDir}/{attId}"; - System.IO.File.Move(stagingPath, destPath); + Fs.MoveFile(stagingPath, destPath); } attObj.Remove("staging_id"); // Clean it up before saving } @@ -529,6 +535,12 @@ public class Requests if (!IsSafeMsgId(msgId)) return "error:invalid.msg.id"; if (!await IsMemberOfDm(id, dmId)) return "error:forbidden"; + + if (reactions != null && reactions.Length > LarpixServer.Utils.Utils.MAX_REACTION_SIZE) + { + string sizeStr = LarpixServer.Utils.Utils.MAX_REACTION_SIZE >= 1024 ? $"{LarpixServer.Utils.Utils.MAX_REACTION_SIZE / 1024.0:0.##} KB" : $"{LarpixServer.Utils.Utils.MAX_REACTION_SIZE} B"; + return $"error:reaction.too.long:{sizeStr}"; + } string msgPath = $"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/{msgId}"; if (!Fs.Exists(msgPath)) return "error:message.not.found"; diff --git a/LarpixServer/Utils/Utils.cs b/LarpixServer/Utils/Utils.cs index b8c03d3..2708c7f 100644 --- a/LarpixServer/Utils/Utils.cs +++ b/LarpixServer/Utils/Utils.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Text; using System.Text.Json; +using LarpixServer.Filesystem; using LarpixServer.Utils.Jsons; namespace LarpixServer.Utils; @@ -36,6 +37,10 @@ public class Utils public static int PRIVATE_STORAGE_LIMIT = 5242880; public static int PUBLIC_STORAGE_LIMIT = 10485760; + public static int ATTACHMENT_SIZE_LIMIT = 52428800; //50mb + public static int MAX_MESSAGE_SIZE = 4000; + public static int MAX_REACTION_SIZE = 100; + public static Dictionary mimeTypes = new() { ["unknown"] = "application/octet-stream", @@ -206,7 +211,7 @@ public class Utils totalRead += read; if (totalRead > MAX_BODY_SIZE) - throw new Exception("error:body.too.large"); + throw new Exception($"error:body.too.large:{MAX_BODY_SIZE / 1024 / 1024}"); bodyBuilder.Append(buffer, 0, read); } @@ -225,7 +230,7 @@ public class Utils { totalRead += read; if (totalRead > maxBodySize) - throw new Exception("error:body.too.large"); + throw new Exception($"error:body.too.large:{maxBodySize / 1024 / 1024}"); ms.Write(buffer, 0, read); } @@ -233,6 +238,19 @@ public class Utils return ms.ToArray(); } + public static long GetDirectorySize(string directoryPath) + { + if (!Directory.Exists(directoryPath)) return 0; + + long size = 0; + DirectoryInfo d = new DirectoryInfo(directoryPath); + foreach (FileInfo fi in d.GetFiles("*", SearchOption.AllDirectories)) + { + size += fi.Length; + } + return size; + } + public static async Task LoadEnv() { if (File.Exists(".env")) @@ -298,6 +316,10 @@ public class Utils PRIVATE_STORAGE_LIMIT = int.TryParse(Environment.GetEnvironmentVariable("PRIVATE_STORAGE_LIMIT"), out res) ? res : PRIVATE_STORAGE_LIMIT; PUBLIC_STORAGE_LIMIT = int.TryParse(Environment.GetEnvironmentVariable("PUBLIC_STORAGE_LIMIT"), out res) ? res : PUBLIC_STORAGE_LIMIT; + MAX_BODY_SIZE = int.TryParse(Environment.GetEnvironmentVariable("MAX_BODY_SIZE"), out res) ? res : MAX_BODY_SIZE; + ATTACHMENT_SIZE_LIMIT = int.TryParse(Environment.GetEnvironmentVariable("ATTACHMENT_SIZE_LIMIT"), out res) ? res : ATTACHMENT_SIZE_LIMIT; + MAX_MESSAGE_SIZE = int.TryParse(Environment.GetEnvironmentVariable("MAX_MESSAGE_SIZE"), out res) ? res : MAX_MESSAGE_SIZE; + MAX_REACTION_SIZE = int.TryParse(Environment.GetEnvironmentVariable("MAX_REACTION_SIZE"), out res) ? res : MAX_REACTION_SIZE; ROOMS_DIR = DATA_DIR + "/rooms"; ACCOUNTS_DIR = DATA_DIR + "/accounts"; @@ -305,67 +327,69 @@ public class Utils ACCOUNTS_DATA_DIR = ACCOUNTS_DIR + "/data"; ACCOUNTS_FREEID_DIR = ACCOUNTS_DIR + "/freeid"; - if (!Directory.Exists(DATA_DIR)) + if (!Fs.Exists(DATA_DIR)) { - Directory.CreateDirectory(DATA_DIR); + Fs.CreateDirectory(DATA_DIR); } - if (!Directory.Exists(ROOMS_DIR)) + if (!Fs.Exists(ROOMS_DIR)) { - Directory.CreateDirectory(ROOMS_DIR); + Fs.CreateDirectory(ROOMS_DIR); } - if (!Directory.Exists(ROOMS_DIR + "/dms")) - Directory.CreateDirectory(ROOMS_DIR + "/dms"); - if (!Directory.Exists(ROOMS_DIR + "/dms_staging")) - Directory.CreateDirectory(ROOMS_DIR + "/dms_staging"); - if (!Directory.Exists(ROOMS_DIR + "/groups")) + if (!Fs.Exists(ROOMS_DIR + "/dms")) + Fs.CreateDirectory(ROOMS_DIR + "/dms"); + if (!Fs.Exists(ROOMS_DIR + "/dms_staging")) + Fs.CreateDirectory(ROOMS_DIR + "/dms_staging"); + if (!Fs.Exists(ROOMS_DIR + "/groups")) { - Directory.CreateDirectory(ROOMS_DIR + "/groups"); + Fs.CreateDirectory(ROOMS_DIR + "/groups"); } - if (!Directory.Exists(ROOMS_DIR + "/spaces")) + if (!Fs.Exists(ROOMS_DIR + "/spaces")) { - Directory.CreateDirectory(ROOMS_DIR + "/spaces"); + Fs.CreateDirectory(ROOMS_DIR + "/spaces"); } - if (!Directory.Exists(ROOMS_DIR + "/freegroups")) + if (!Fs.Exists(ROOMS_DIR + "/freegroups")) { - Directory.CreateDirectory(ROOMS_DIR + "/freegroups"); + Fs.CreateDirectory(ROOMS_DIR + "/freegroups"); } - if (!Directory.Exists(ROOMS_DIR + "/freespaces")) + if (!Fs.Exists(ROOMS_DIR + "/freespaces")) { - Directory.CreateDirectory(ROOMS_DIR + "/freespaces"); - } - if (!File.Exists(ROOMS_DIR + "/lastgroup")) - { - await File.WriteAllTextAsync(ROOMS_DIR + "/lastgroup", "0"); - } - if (!File.Exists(ROOMS_DIR + "/lastspace")) - { - await File.WriteAllTextAsync(ROOMS_DIR + "/lastspace", "0"); + Fs.CreateDirectory(ROOMS_DIR + "/freespaces"); } - if (!Directory.Exists(ACCOUNTS_DIR)) + if (!Fs.Exists(ROOMS_DIR + "/lastgroup")) { - Directory.CreateDirectory(ACCOUNTS_DIR); + await Fs.WriteFile(ROOMS_DIR + "/lastgroup", Encoding.UTF8.GetBytes("0")); } - if (!Directory.Exists(ACCOUNTS_NAME_DIR)) + if (!Fs.Exists(ROOMS_DIR + "/lastspace")) { - Directory.CreateDirectory(ACCOUNTS_NAME_DIR); + await Fs.WriteFile(ROOMS_DIR + "/lastspace", Encoding.UTF8.GetBytes("0")); } - if (!Directory.Exists(ACCOUNTS_DATA_DIR)) + + if (!Fs.Exists(ACCOUNTS_DIR)) { - Directory.CreateDirectory(ACCOUNTS_DATA_DIR); + Fs.CreateDirectory(ACCOUNTS_DIR); } - if (!Directory.Exists(ACCOUNTS_FREEID_DIR)) + if (!Fs.Exists(ACCOUNTS_NAME_DIR)) { - Directory.CreateDirectory(ACCOUNTS_FREEID_DIR); + Fs.CreateDirectory(ACCOUNTS_NAME_DIR); } - if (!File.Exists(ACCOUNTS_DIR + "/last")) + if (!Fs.Exists(ACCOUNTS_DATA_DIR)) { - await File.WriteAllTextAsync(ACCOUNTS_DIR + "/last", "0"); + Fs.CreateDirectory(ACCOUNTS_DATA_DIR); } - if (!File.Exists($"{ACCOUNTS_DIR}/registration")) + if (!Fs.Exists(ACCOUNTS_FREEID_DIR)) { - await File.WriteAllTextAsync($"{ACCOUNTS_DIR}/registration", "first;"); + Fs.CreateDirectory(ACCOUNTS_FREEID_DIR); + } + + if (!Fs.Exists(ACCOUNTS_DIR + "/last")) + { + await Fs.WriteFile(ACCOUNTS_DIR + "/last", Encoding.UTF8.GetBytes("0")); + } + if (!Fs.Exists($"{ACCOUNTS_DIR}/registration")) + { + await Fs.WriteFile($"{ACCOUNTS_DIR}/registration", Encoding.UTF8.GetBytes("first;")); } } } \ No newline at end of file