From b9e39dd9c7dd1b7d82c6b145684bee5fb123f135 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Fri, 3 Oct 2025 17:35:34 +0900 Subject: [PATCH] Add CORS support to ResourceServer Implemented CORS handling in ResourceServer, including preflight (OPTIONS) request handling and configurable allowed origins via the new ResourceServerAllowOrigins app setting. Updated resources and configuration files to support the new setting. --- .../Properties/Resources.Designer.cs | 9 ++ .../Properties/Resources.resx | 3 + .../WelsonJS.Launcher/ResourceServer.cs | 97 +++++++++++++++++++ WelsonJS.Toolkit/WelsonJS.Launcher/app.config | 3 +- 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs index 51db986..9a9e2a2 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs @@ -331,6 +331,15 @@ namespace WelsonJS.Launcher.Properties { } } + /// + /// 과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string ResourceServerAllowOrigins { + get { + return ResourceManager.GetString("ResourceServerAllowOrigins", resourceCulture); + } + } + /// /// true과(와) 유사한 지역화된 문자열을 찾습니다. /// diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx index b733285..c682ff9 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx @@ -220,4 +220,7 @@ https://api.abuseipdb.com/api/v2/ + + + \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index e9830b0..32fa56f 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -353,6 +353,11 @@ namespace WelsonJS.Launcher public async Task ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200) { + if (HandleCorsPreflight(context)) + return; + + TryApplyCors(context); + string xmlHeader = ""; if (data == null) @@ -461,6 +466,98 @@ namespace WelsonJS.Launcher _logger?.Error($"Failed to fetch a blob config. Exception: {ex}"); } } + + + private static string[] GetAllowedOrigins() + { + // 1. Try explicit ResourceServerAllowOrigins config + var raw = Program.GetAppConfig("ResourceServerAllowOrigins"); + + if (string.IsNullOrEmpty(raw)) + { + // 2. Fallback: parse from ResourceServerPrefix + var prefix = Program.GetAppConfig("ResourceServerPrefix"); + if (!string.IsNullOrEmpty(prefix)) + { + try + { + var uri = new Uri(prefix); + var origin = uri.GetLeftPart(UriPartial.Authority); // protocol + host + port + return new[] { origin }; + } + catch + { + return Array.Empty(); + } + } + return Array.Empty(); + } + + // Split configured list + var parts = raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.Trim()) + .Where(s => !string.IsNullOrEmpty(s)) + .ToArray(); + return parts; + } + + private static bool TryApplyCors(HttpListenerContext context) + { + var origin = context.Request.Headers["Origin"]; + if (string.IsNullOrEmpty(origin)) + return false; + + var allowed = GetAllowedOrigins(); + if (allowed.Length == 0) + return false; + + var respHeaders = context.Response.Headers; + respHeaders["Vary"] = "Origin"; + + if (allowed.Any(a => a == "*")) + { + respHeaders["Access-Control-Allow-Origin"] = "*"; + return true; + } + + if (allowed.Contains(origin, StringComparer.OrdinalIgnoreCase)) + { + respHeaders["Access-Control-Allow-Origin"] = origin; + respHeaders["Access-Control-Allow-Credentials"] = "true"; + return true; + } + + return false; + } + + private static bool HandleCorsPreflight(HttpListenerContext context) + { + if (!string.Equals(context.Request.HttpMethod, "OPTIONS", StringComparison.OrdinalIgnoreCase)) + return false; + + if (string.IsNullOrEmpty(context.Request.Headers["Origin"])) + return false; + + // Apply CORS headers once here + TryApplyCors(context); + + var requestHeaders = context.Request.Headers["Access-Control-Request-Headers"]; + var requestMethod = context.Request.Headers["Access-Control-Request-Method"]; + + var h = context.Response.Headers; + h["Access-Control-Allow-Methods"] = string.IsNullOrEmpty(requestMethod) + ? "GET, POST, PUT, DELETE, OPTIONS" + : requestMethod; + h["Access-Control-Allow-Headers"] = string.IsNullOrEmpty(requestHeaders) + ? "Content-Type, Authorization, X-Requested-With" + : requestHeaders; + h["Access-Control-Max-Age"] = "600"; + + context.Response.StatusCode = 204; + context.Response.ContentLength64 = 0; + context.Response.OutputStream.Close(); + return true; + } } [XmlRoot("blobConfig")] diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config index 7902a42..b74b7fe 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config @@ -1,8 +1,9 @@ - + +