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 @@ - + +