add new upload endpoint, so we can get rid of base64 for uploading images
All checks were successful
Server Build / publish (push) Successful in 30s
Voice Build / publish (push) Successful in 26s

This commit is contained in:
olcxja 2026-07-02 22:41:33 +02:00
commit e15ce6ba8b
4 changed files with 111 additions and 1 deletions

View file

@ -764,6 +764,54 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
} }
return; return;
} }
public static async Task Upload(HttpContext context, Func<Task> 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 public class CreateHolder

View file

@ -173,6 +173,27 @@ public class Utils
return decBody; return decBody;
} }
public static async Task<byte[]> 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) public static async Task UpdateLastLogin(string id)
{ {
if (!Fs.Exists($"{ACCOUNTS_DATA_DIR}/{id}/secret")) if (!Fs.Exists($"{ACCOUNTS_DATA_DIR}/{id}/secret"))
@ -304,6 +325,23 @@ public class Utils
return await Fs.ReadFile(path); return await Fs.ReadFile(path);
} }
public static async Task<string> 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<string> UpdateUserPublicStorageEntry(string id, string entry, string contentBase64) public static async Task<string> UpdateUserPublicStorageEntry(string id, string entry, string contentBase64)
{ {
if (string.IsNullOrEmpty(entry) || entry.Contains("..") || entry.Contains("/") || entry.Contains("\\")) if (string.IsNullOrEmpty(entry) || entry.Contains("..") || entry.Contains("/") || entry.Contains("\\"))

View file

@ -188,6 +188,9 @@ public class Program
case "/_larpix/encryptedrequest": case "/_larpix/encryptedrequest":
await Account.Requests.EncryptedRequest(context, next, query, reader); await Account.Requests.EncryptedRequest(context, next, query, reader);
return; return;
case "/_larpix/upload":
await Account.Requests.Upload(context, next, query);
return;
case "/_larpix/federation": case "/_larpix/federation":
await Federation.Receiver.FederationRequest(context, next, query, reader); await Federation.Receiver.FederationRequest(context, next, query, reader);
return; return;
@ -201,7 +204,10 @@ public class Program
} }
catch (Exception ex) 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); Console.WriteLine(ex);
} }

View file

@ -211,7 +211,25 @@ public class Utils
} }
return bodyBuilder.ToString(); return bodyBuilder.ToString();
}
public static async Task<byte[]> 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() public static async Task LoadEnv()