using RadicadeServer.Utils; namespace RadicadeServer.CDN { /// /// What may be read out of the CDN storage directory and handed to a client, and which file /// a requested name actually is. /// /// Two rules live here because every route that serves a stored blob needs both, and a route /// that re-implements them gets one of them wrong: /// /// - A request is a single bare file name in that one directory. A name carrying a separator /// or .. would let a URL read outside it, and a name ending in the admin upload's /// .uploading suffix is a half-imported bundle: it must answer as if it does not /// exist rather than serve a truncated file, which the client would then fail to load with /// no way to tell why. /// - A bundle is asked for under two spellings. The subroom's UnityAssetId is stored /// both bare and with the .assetbundle suffix an import appends, so a name resolves /// to the exact file first and to <name>.assetbundle second. /// internal static class CdnStorageNames { internal static bool IsServable(string? name) => !string.IsNullOrEmpty(name) && name != "." && !AdminUtils.IsUploadPart(name) && !name.Contains('/') && !name.Contains('\\') && !name.Contains("..") && name.IndexOf('\0') < 0; /// /// The stored file a requested name is, or null when nothing by either spelling exists. /// A name this server must never serve answers null too, so a route that only checks the /// result is still safe. /// internal static string? Resolve(string? name) { if (!IsServable(name)) return null; if (System.IO.File.Exists(Path.Join(DBConfig.CDNStorage, name))) return name; var withExtension = name + ".assetbundle"; return System.IO.File.Exists(Path.Join(DBConfig.CDNStorage, withExtension)) ? withExtension : null; } } }