using System; using System.Collections.Generic; public static class NameserverRedirect { private static readonly Dictionary ServicePrefixes = new(StringComparer.OrdinalIgnoreCase) { ["api.rec.net"] = "", ["cdn.rec.net"] = "", ["images.rec.net"] = "/images", ["img.rec.net"] = "/images", ["data.rec.net"] = "/data", ["storage.rec.net"] = "/storage", ["notify.rec.net"] = "/notify", ["notifications.rec.net"] = "/notify", ["notify.radicade.internal"] = "/notify", ["notifications.radicade.internal"] = "/notify", ["auth.rec.net"] = "/auth", ["accounts.rec.net"] = "/accounts", ["rooms.rec.net"] = "/rooms", ["matchmaking.rec.net"] = "/matchmaking", ["chat.rec.net"] = "/chat", ["api.radicade.app"] = "", ["cdn.radicade.app"] = "/cdn", ["ai.radicade.app"] = "/ai", ["auth.radicade.app"] = "/auth", ["www.radicade.app"] = "/www", ["cards.radicade.app"] = "/cards", ["notifications.radicade.app"] = "/notify", ["images.radicade.app"] = "/images", ["commerce.radicade.app"] = "/commerce", ["data.radicade.app"] = "/data", ["datacollection.radicade.app"] = "/datacollection", ["discovery.radicade.app"] = "/discovery", ["matchmaking.radicade.app"] = "/matchmaking", ["storage.radicade.app"] = "/storage", ["chat.radicade.app"] = "/chat", ["geo.radicade.app"] = "/geo", ["leaderboard.radicade.app"] = "/leaderboard", ["accounts.radicade.app"] = "/accounts", ["link.radicade.app"] = "/link", ["locker.radicade.app"] = "/locker", ["moderation.radicade.app"] = "/moderation", ["roomcomments.radicade.app"] = "/roomcomments", ["roomieintegrations.radicade.app"] = "/roomieintegrations", ["clubs.radicade.app"] = "/clubs", ["rooms.radicade.app"] = "/rooms", ["econ.radicade.app"] = "/econ", ["bugreporting.radicade.app"] = "/bugreporting", ["cms.radicade.app"] = "/cms", ["gamelogs.radicade.app"] = "/gamelogs", ["platformnotifications.radicade.app"] = "/platformnotifications", ["stringscdn.radicade.app"] = "/stringscdn", ["strings.radicade.app"] = "/strings", ["lists.radicade.app"] = "/lists", ["playersettings.radicade.app"] = "/playersettings", ["studio.radicade.app"] = "/studio", ["studiocdn.radicade.app"] = "/studiocdn", ["thorn.radicade.app"] = "/thorn", ["videos.radicade.app"] = "/videos", }; public static string Rewrite(string requestUrl, string serverUrl) { if (!Uri.TryCreate(requestUrl, UriKind.Absolute, out var request) || (request.Scheme != "http" && request.Scheme != "https")) return requestUrl; bool nameserver = request.Host.Equals("ns.rec.net", StringComparison.OrdinalIgnoreCase) || request.Host.Equals("ns.radicade.internal", StringComparison.OrdinalIgnoreCase); bool patchedServer = request.Host.Equals("ns.radicade.app", StringComparison.OrdinalIgnoreCase); ServicePrefixes.TryGetValue(request.Host, out var servicePrefix); if (!nameserver && !patchedServer && servicePrefix == null) return requestUrl; if (!Uri.TryCreate(serverUrl, UriKind.Absolute, out var server) || (server.Scheme != "http" && server.Scheme != "https") || !string.IsNullOrEmpty(server.UserInfo) || !string.IsNullOrEmpty(server.Query) || !string.IsNullOrEmpty(server.Fragment)) throw new ArgumentException("Connection.ServerUrl must be an HTTP(S) base URL without credentials, query or fragment.", nameof(serverUrl)); var path = request.AbsolutePath; if (nameserver) { if (path == "/") path = "/ns"; } else if (servicePrefix is { Length: > 0 }) { if (servicePrefix == "/notify" && path.StartsWith("/notify/", StringComparison.OrdinalIgnoreCase)) path = path.Substring("/notify".Length); else if (path.StartsWith("/images/", StringComparison.OrdinalIgnoreCase)) path = path.Substring("/images".Length); else if (path.StartsWith("/img/", StringComparison.OrdinalIgnoreCase)) path = path.Substring("/img".Length); path = servicePrefix + path; } return server.AbsoluteUri.TrimEnd('/') + path + request.Query; } public static string RewriteWebSocket(string requestUrl, string serverUrl) { if (!Uri.TryCreate(requestUrl, UriKind.Absolute, out var request)) return requestUrl; var scheme = request.Scheme.ToLowerInvariant(); var secure = scheme == "wss" || scheme == "https"; if (scheme != "ws" && scheme != "wss" && scheme != "http" && scheme != "https") return requestUrl; var asHttp = new UriBuilder(request) { Scheme = secure ? "https" : "http" }.Uri.AbsoluteUri; var rewritten = Rewrite(asHttp, serverUrl); if (rewritten == asHttp) return requestUrl; if (!Uri.TryCreate(rewritten, UriKind.Absolute, out var result)) return requestUrl; return new UriBuilder(result) { Scheme = result.Scheme == "https" ? "wss" : "ws" }.Uri.AbsoluteUri; } public static bool IsHubUrl(string url) { if (string.IsNullOrWhiteSpace(url)) return false; var lower = url.ToLowerInvariant(); var query = lower.IndexOf('?'); var path = query < 0 ? lower : lower.Substring(0, query); return path.Contains("/hub/v1") || path.EndsWith("/hub") || path.Contains("/hub/") || path.Contains("notifyhub") || path.Contains("/notify/hub"); } public static bool HasHubToken(string url) { if (string.IsNullOrWhiteSpace(url)) return false; var query = url.IndexOf('?'); if (query < 0) return false; foreach (var pair in url.Substring(query + 1).Split('&')) { var equals = pair.IndexOf('='); if (equals <= 0 || equals + 1 >= pair.Length) continue; if (pair.Substring(0, equals).Equals("access_token", StringComparison.OrdinalIgnoreCase)) return true; } return false; } public static string EnsureHubToken(string url, string token) { if (!IsHubUrl(url)) return url; if (HasHubToken(url)) return url; if (string.IsNullOrWhiteSpace(token)) return url; var raw = token.Trim(); if (raw.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) raw = raw.Substring(7).Trim(); else if (raw.Equals("Bearer", StringComparison.OrdinalIgnoreCase)) raw = string.Empty; if (raw.Length == 0) return url; var query = url.IndexOf('?'); var basePart = query < 0 ? url : url.Substring(0, query); var rest = query < 0 ? string.Empty : url.Substring(query + 1).TrimStart('&'); var accessToken = "access_token=" + Uri.EscapeDataString(raw); return basePart + "?" + accessToken + (rest.Length == 0 ? string.Empty : "&" + rest); } }