mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
123 lines
3.7 KiB
C#
123 lines
3.7 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using RadicadeServer.DB;
|
|
|
|
namespace RadicadeServer.Utils
|
|
{
|
|
public static class AdminUtils
|
|
{
|
|
/// <summary>
|
|
/// The ceiling every admin upload is held to, on the request and on the multipart form.
|
|
///
|
|
/// Kestrel's own default is 30 MB, and no room import fits under it: an .assetbundle or
|
|
/// a .room save routinely runs to tens or hundreds of megabytes. A route that does not
|
|
/// raise the limit has its connection killed part-way through the body, so the panel sees
|
|
/// a broken connection instead of an answer - which is what made importing a large room
|
|
/// look like an authentication failure. Named once here so the next upload route cannot
|
|
/// quietly forget it.
|
|
/// </summary>
|
|
public const long MaxUploadBytes = 256L * 1024 * 1024;
|
|
|
|
/// <summary>
|
|
/// A room file being assembled from parts lives beside its final name under this suffix
|
|
/// until the last part lands. Nothing serves or lists one, so a half-imported bundle can
|
|
/// never be fetched by the client, and a name ending in this suffix is refused outright.
|
|
/// </summary>
|
|
public const string PartSuffix = ".uploading";
|
|
|
|
/// The multipart ceiling this server answers with, in the words the panel shows an admin.
|
|
public const string MaxUploadLabel = "256 MiB";
|
|
|
|
public static bool IsUploadPart(string? name) =>
|
|
!string.IsNullOrEmpty(name) && name.EndsWith(PartSuffix, StringComparison.OrdinalIgnoreCase);
|
|
|
|
private static bool SecretMatches(string? given, string? stored)
|
|
{
|
|
if (string.IsNullOrEmpty(given) || string.IsNullOrEmpty(stored))
|
|
return false;
|
|
|
|
return CryptographicOperations.FixedTimeEquals(
|
|
Encoding.UTF8.GetBytes(given),
|
|
Encoding.UTF8.GetBytes(stored));
|
|
}
|
|
|
|
public static bool IsAdminKey(this string? key)
|
|
{
|
|
return SecretMatches(key, DBConfig.AdminApiKey);
|
|
}
|
|
|
|
public static bool IsSupportKey(this string? key)
|
|
{
|
|
return SecretMatches(key, DBConfig.AdminApiKey)
|
|
|| SecretMatches(key, DBConfig.SupportApiKey);
|
|
}
|
|
|
|
public static bool TryResolveUploadName(string? requested, out string? name, out string? error)
|
|
{
|
|
name = null;
|
|
error = null;
|
|
|
|
if (string.IsNullOrWhiteSpace(requested)) return true;
|
|
|
|
var trimmed = requested.Trim();
|
|
|
|
if (trimmed.Length > 128)
|
|
{
|
|
error = "Name is too long.";
|
|
return false;
|
|
}
|
|
|
|
if (trimmed != Path.GetFileName(trimmed) || trimmed.Contains(".."))
|
|
{
|
|
error = "Name must be a bare file name.";
|
|
return false;
|
|
}
|
|
|
|
foreach (var c in trimmed)
|
|
{
|
|
if (!char.IsLetterOrDigit(c) && c != '-' && c != '_' && c != '.')
|
|
{
|
|
error = "Name may only contain letters, digits, '-', '_' and '.'.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
name = trimmed;
|
|
return true;
|
|
}
|
|
|
|
/// A subroom's Unity bundle is served under the name it is stored as
|
|
/// (RoomServerAPI's unity_assets route hands the client that file name, and
|
|
/// the client then fetches it from the CDN by name), so binding accepts any
|
|
/// uploaded '<name>.assetbundle'. That is deliberately wider than
|
|
/// StudioUnityAssetRegistry.IsValidAssetId, which only covers the
|
|
/// 40-character manifest ids the Studio pipeline mints.
|
|
public static bool TryResolveBundleName(string? requested, out string? name, out string? error)
|
|
{
|
|
if (!TryResolveUploadName(requested, out name, out error)) return false;
|
|
|
|
if (name == null)
|
|
{
|
|
error = "A bundle file name is required.";
|
|
return false;
|
|
}
|
|
|
|
if (!name.EndsWith(".assetbundle", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
error = $"'{name}' is not a .assetbundle file.";
|
|
name = null;
|
|
return false;
|
|
}
|
|
|
|
if (!System.IO.File.Exists(Path.Join(DBConfig.CDNStorage, name)))
|
|
{
|
|
error = $"No uploaded bundle named '{name}'.";
|
|
name = null;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|