diff --git a/LarpixServer/Account/Requests.cs b/LarpixServer/Account/Requests.cs index 8b7e714..6ff642c 100644 --- a/LarpixServer/Account/Requests.cs +++ b/LarpixServer/Account/Requests.cs @@ -764,6 +764,54 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec } return; } + public static async Task Upload(HttpContext context, Func next, IQueryCollection query) + { + if (!query.TryGetValue("id", out var idQuery)) + return; + + if (!context.Request.Headers.TryGetValue("secret", out var secret)) + return; + + if (!context.Request.Headers.TryGetValue("target", out var targetHeader)) + return; + + string id = Utils.GetValidIdOrZero(idQuery.ToString()); + string password = await Utils.GetPassword(id); + + string target = await Utils.NonceDecryptBody(id, password, targetHeader, false); + string secretStr = await Utils.NonceDecryptBody(id, password, secret, false); + + string auth = await Utils.Auth(id, secretStr, password); + + if (auth != Utils.LOGIN_SUCCESS) + { + await context.Response.WriteAsync(Encryption.Encryption.EncryptString(auth, secretStr)); + return; + } + + byte[] bodyBytes = await LarpixServer.Utils.Utils.LoadBodyBytes(context.Request.Body, LarpixServer.Utils.Utils.PUBLIC_STORAGE_LIMIT); + byte[] fileBytes = await Utils.NonceDecryptBytes(id, password, bodyBytes, true); + + if (fileBytes == null) + { + await context.Response.WriteAsync(Encryption.Encryption.EncryptString("error:invalid.nonce", password)); + return; + } + + SemaphoreSlim userLock = Utils.GetUserLock(id); + await userLock.WaitAsync(); + try + { + await context.Response.WriteAsync( + Encryption.Encryption.EncryptString( + await Utils.UpdateUserPublicStorageEntryRaw(id, target, fileBytes) + , password)); + } + finally + { + userLock.Release(); + } + } } public class CreateHolder diff --git a/LarpixServer/Account/Utils.cs b/LarpixServer/Account/Utils.cs index 803584a..76292ef 100644 --- a/LarpixServer/Account/Utils.cs +++ b/LarpixServer/Account/Utils.cs @@ -173,6 +173,27 @@ public class Utils return decBody; } + public static async Task NonceDecryptBytes(string id, string password, byte[] body, bool delEntry = true) + { + (string, DateTimeOffset) nonce; + if (delEntry) + { + if (!Requests.nonceHolder.TryRemove(id, out nonce)) + { + return null; + } + } + else + { + if (!Requests.nonceHolder.TryGetValue(id, out nonce)) + { + return null; + } + } + + return Encryption.Encryption.DecryptByte(body, nonce.Item1 + password); + } + public static async Task UpdateLastLogin(string id) { if (!Fs.Exists($"{ACCOUNTS_DATA_DIR}/{id}/secret")) @@ -304,6 +325,23 @@ public class Utils return await Fs.ReadFile(path); } + public static async Task UpdateUserPublicStorageEntryRaw(string id, string entry, byte[] contentBytes) + { + if (string.IsNullOrEmpty(entry) || entry.Contains("..") || entry.Contains("/") || entry.Contains("\\")) + { + return "error:invalid.entry.name"; + } + + if (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"; + } + public static async Task UpdateUserPublicStorageEntry(string id, string entry, string contentBase64) { if (string.IsNullOrEmpty(entry) || entry.Contains("..") || entry.Contains("/") || entry.Contains("\\")) diff --git a/LarpixServer/Program.cs b/LarpixServer/Program.cs index fed657b..50befbd 100644 --- a/LarpixServer/Program.cs +++ b/LarpixServer/Program.cs @@ -188,6 +188,9 @@ public class Program case "/_larpix/encryptedrequest": await Account.Requests.EncryptedRequest(context, next, query, reader); return; + case "/_larpix/upload": + await Account.Requests.Upload(context, next, query); + return; case "/_larpix/federation": await Federation.Receiver.FederationRequest(context, next, query, reader); return; @@ -201,7 +204,10 @@ public class Program } catch (Exception ex) { - await context.Response.WriteAsync($"error:unknown.error"); + if (ex.Message.StartsWith("error:")) + await context.Response.WriteAsync(ex.Message); + else + await context.Response.WriteAsync($"error:unknown.error"); Console.WriteLine(ex); } diff --git a/LarpixServer/Utils/Utils.cs b/LarpixServer/Utils/Utils.cs index dfe4a01..c6a3cdc 100644 --- a/LarpixServer/Utils/Utils.cs +++ b/LarpixServer/Utils/Utils.cs @@ -211,7 +211,25 @@ public class Utils } return bodyBuilder.ToString(); + } + public static async Task LoadBodyBytes(Stream bodyStream, int maxBodySize) + { + using MemoryStream ms = new MemoryStream(); + byte[] buffer = new byte[8192]; + int read; + int totalRead = 0; + + while ((read = await bodyStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + totalRead += read; + if (totalRead > maxBodySize) + throw new Exception("error:body.too.large"); + + ms.Write(buffer, 0, read); + } + + return ms.ToArray(); } public static async Task LoadEnv()