diff --git a/Refresh.Common/Extensions/NameValueCollectionExtensions.cs b/Refresh.Common/Extensions/NameValueCollectionExtensions.cs new file mode 100644 index 00000000..6002ed32 --- /dev/null +++ b/Refresh.Common/Extensions/NameValueCollectionExtensions.cs @@ -0,0 +1,36 @@ +using System.Collections.Specialized; +using System.Text; +using System.Web; + +namespace Refresh.Common.Extensions; + +public static class NameValueCollectionExtensions +{ + public static string ToQueryString(this NameValueCollection queryParams) + { + StringBuilder builder = new(); + + if (queryParams.Count == 0) + return string.Empty; + + builder.Append('?'); + for (int i = 0; i < queryParams.Count; i++) + { + string? key = queryParams.GetKey(i); + string? val = queryParams.Get(i); + + if (key == null) + continue; + + builder.Append(HttpUtility.UrlEncode(key)); + builder.Append('='); + if(val != null) + builder.Append(HttpUtility.UrlEncode(val)); + + if(i < queryParams.Count - 1) + builder.Append('&'); + } + + return builder.ToString(); + } +} diff --git a/Refresh.GameServer/Middlewares/DigestMiddleware.cs b/Refresh.GameServer/Middlewares/DigestMiddleware.cs index 0c552da1..8d8f1f65 100644 --- a/Refresh.GameServer/Middlewares/DigestMiddleware.cs +++ b/Refresh.GameServer/Middlewares/DigestMiddleware.cs @@ -117,9 +117,15 @@ public void HandleRequest(ListenerContext context, Lazy databa } // If the client asks for a particular digest index, use that digest - if (int.TryParse(context.RequestHeaders["Refresh-Ps3-Digest-Index"], out int ps3DigestIndex) && - int.TryParse(context.RequestHeaders["Refresh-Ps4-Digest-Index"], out int ps4DigestIndex)) + if (int.TryParse(context.Query["force_ps3_digest"], out int ps3DigestIndex) && + int.TryParse(context.Query["force_ps4_digest"], out int ps4DigestIndex)) { + if (ps3DigestIndex >= this._config.Sha1DigestKeys.Length) + ps3DigestIndex = 0; + + if (ps4DigestIndex >= this._config.HmacDigestKeys.Length) + ps4DigestIndex = 0; + string digest = isPs4 ? this._config.HmacDigestKeys[ps4DigestIndex] : this._config.Sha1DigestKeys[ps3DigestIndex]; @@ -128,7 +134,7 @@ public void HandleRequest(ListenerContext context, Lazy databa if(token != null) gameDatabase.SetTokenDigestInfo(token, digest, isPs4); - + return; } diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index e68a91b4..2819e793 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -94,11 +94,11 @@ protected override void SetupMiddlewares() { this.Server.AddMiddleware(); this.Server.AddMiddleware(new DeflateMiddleware(this._config!)); + this.Server.AddMiddleware(); // Digest middleware must be run before LegacyAdapterMiddleware, because digest is based on the raw route, not the fixed route this.Server.AddMiddleware(new DigestMiddleware(this._config!)); this.Server.AddMiddleware(); this.Server.AddMiddleware(); - this.Server.AddMiddleware(); this.Server.AddMiddleware(new PresenceAuthenticationMiddleware(this._integrationConfig!)); } diff --git a/Refresh.HttpsProxy/Middlewares/ProxyMiddleware.cs b/Refresh.HttpsProxy/Middlewares/ProxyMiddleware.cs deleted file mode 100644 index 1fd6b158..00000000 --- a/Refresh.HttpsProxy/Middlewares/ProxyMiddleware.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Text; -using System.Web; -using Bunkum.Core.Database; -using Bunkum.Core.Endpoints.Middlewares; -using Bunkum.Listener.Request; -using Refresh.HttpsProxy.Config; - -namespace Refresh.HttpsProxy.Middlewares; - -public class ProxyMiddleware(ProxyConfig config) : IMiddleware -{ - private readonly ThreadLocal _httpClients = new(() => new HttpClient()); - - public void HandleRequest(ListenerContext context, Lazy database, Action next) - { - HttpClient client = this._httpClients.Value!; - - UriBuilder uri = new(config.TargetServerUrl) - { - Path = context.Uri.AbsolutePath, - }; - - StringBuilder queryString = new(); - - // We apparently need to prepend this ourselves - if (context.Query.Count > 0) - queryString.Append('?'); - - // Fill in all the query strings - for (int i = 0; i < context.Query.Count; i++) - { - string key = context.Query.GetKey(i)!; - string name = HttpUtility.HtmlEncode(key); - string value = HttpUtility.HtmlEncode(context.Query[key]!); - - queryString.AppendFormat("{0}={1}", name, value); - - if (i < context.Query.Count - 1) - queryString.Append('&'); - } - - uri.Query = queryString.ToString(); - - HttpRequestMessage requestMessage = new() - { - RequestUri = uri.Uri, - Content = new StreamContent(context.InputStream), - Method = new HttpMethod(context.Method.Value), - Version = new Version(1, 1), - VersionPolicy = HttpVersionPolicy.RequestVersionExact, - }; - - // Move all the request headers over - for (int i = 0; i < context.RequestHeaders.Count; i++) - { - string key = context.RequestHeaders.GetKey(i)!; - string[] value = context.RequestHeaders.GetValues(i)!; - - // Fixup the host value to the correct one, so that the request actually goes through - if (key.Equals("Host", StringComparison.InvariantCultureIgnoreCase)) - value = [uri.Host]; - - requestMessage.Headers.TryAddWithoutValidation(key, value); - } - - requestMessage.Headers.Add("Refresh-Ps3-Digest-Index", config.Ps3DigestIndex.ToString()); - requestMessage.Headers.Add("Refresh-Ps4-Digest-Index", config.Ps4DigestIndex.ToString()); - - // Send our HTTP request - HttpResponseMessage response = client.Send(requestMessage); - - //Set the response information - context.ResponseCode = response.StatusCode; - - context.ResponseHeaders.Clear(); - foreach (KeyValuePair> responseHeader in response.Headers) - { - context.ResponseHeaders[responseHeader.Key] = string.Join(',', responseHeader.Value); - } - context.ResponseStream.SetLength(0); - response.Content.ReadAsStream().CopyTo(context.ResponseStream); - } -} \ No newline at end of file diff --git a/Refresh.HttpsProxy/Middlewares/RedirectMiddleware.cs b/Refresh.HttpsProxy/Middlewares/RedirectMiddleware.cs new file mode 100644 index 00000000..081570f8 --- /dev/null +++ b/Refresh.HttpsProxy/Middlewares/RedirectMiddleware.cs @@ -0,0 +1,27 @@ +using System.Net; +using Bunkum.Core.Database; +using Bunkum.Core.Endpoints.Middlewares; +using Bunkum.Listener.Request; +using Refresh.Common.Extensions; +using Refresh.HttpsProxy.Config; + +namespace Refresh.HttpsProxy.Middlewares; + +public class RedirectMiddleware(ProxyConfig config) : IMiddleware +{ + public void HandleRequest(ListenerContext context, Lazy database, Action next) + { + UriBuilder uri = new(config.TargetServerUrl) + { + Path = context.Uri.AbsolutePath, + }; + + context.Query["force_ps3_digest"] = config.Ps3DigestIndex.ToString(); + context.Query["force_ps4_digest"] = config.Ps4DigestIndex.ToString(); + + uri.Query = context.Query.ToQueryString(); + + context.ResponseCode = HttpStatusCode.TemporaryRedirect; + context.ResponseHeaders["Location"] = uri.ToString(); + } +} \ No newline at end of file diff --git a/Refresh.HttpsProxy/Program.cs b/Refresh.HttpsProxy/Program.cs index 0faaa3c8..4058a9c0 100644 --- a/Refresh.HttpsProxy/Program.cs +++ b/Refresh.HttpsProxy/Program.cs @@ -26,7 +26,7 @@ Action initialize = s => { ProxyConfig config = Config.LoadFromJsonFile("proxy.json", s.Logger); - s.AddMiddleware(new ProxyMiddleware(config)); + s.AddMiddleware(new RedirectMiddleware(config)); }; httpsServer.Initialize = initialize; diff --git a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs index 1c4101a9..ecb67f15 100644 --- a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs +++ b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs @@ -123,16 +123,13 @@ public void DigestSelectionHeaderWorks(bool isHmac) string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, Encoding.ASCII.GetBytes(expectedResultStr), "", null, false, isHmac); - context.Http.DefaultRequestHeaders.Add("Refresh-Ps3-Digest-Index", "1"); - context.Http.DefaultRequestHeaders.Add("Refresh-Ps4-Digest-Index", "1"); - // TODO: once we model PS4 clients in our tokens, make the request come from an authenticated PS4 client. if(isHmac) context.Http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "MM CHTTPClient LBP3 01.26"); // send a blank digest to make it have to guess context.Http.DefaultRequestHeaders.Add("X-Digest-A", ""); - HttpResponseMessage response = context.Http.GetAsync(endpoint).Result; + HttpResponseMessage response = context.Http.GetAsync(endpoint + "?force_ps3_digest=1&force_ps4_digest=1").Result; Assert.Multiple(() => {