diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/IResourceTool.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/IResourceTool.cs new file mode 100644 index 0000000..cbd71b7 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/IResourceTool.cs @@ -0,0 +1,25 @@ +using System.Net; +using System.Threading.Tasks; + +namespace WelsonJS.Launcher +{ + /// + /// Defines a contract for resource tools that can handle specific HTTP requests. + /// + public interface IResourceTool + { + /// + /// Determines whether this tool can handle the specified path. + /// + /// The request path to check. + /// True if this tool can handle the request; otherwise, false. + bool CanHandle(string path); + /// + /// Asynchronously processes the HTTP request for the specified path. + /// + /// The HTTP listener context containing request and response objects. + /// The request path to handle. + /// A task representing the asynchronous operation. + Task HandleAsync(HttpListenerContext context, string path); + } +} \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index 420c8b8..11e21e5 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -5,7 +5,6 @@ using System.IO.Compression; using System.Security.Principal; using System.Threading.Tasks; using System.Windows.Forms; -using WelsonJS.Launcher.Tools; namespace WelsonJS.Launcher { diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index 899200f..aebad66 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Windows.Forms; using System.Configuration; -using WelsonJS.Launcher.Tools; namespace WelsonJS.Launcher { diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs index c4f5ce7..c4557b5 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs @@ -60,6 +60,24 @@ namespace WelsonJS.Launcher.Properties { } } + /// + /// 과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string AzureAiServiceApiKey { + get { + return ResourceManager.GetString("AzureAiServiceApiKey", resourceCulture); + } + } + + /// + /// https://ai-catswords656881030318.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string AzureAiServiceUrl { + get { + return ResourceManager.GetString("AzureAiServiceUrl", resourceCulture); + } + } + /// /// https://copilot.microsoft.com/과(와) 유사한 지역화된 문자열을 찾습니다. /// @@ -69,6 +87,24 @@ namespace WelsonJS.Launcher.Properties { } } + /// + /// http://localhost:9222/과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string DevToolsPrefix { + get { + return ResourceManager.GetString("DevToolsPrefix", resourceCulture); + } + } + + /// + /// 1.1.1.1과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string DnsServerAddress { + get { + return ResourceManager.GetString("DnsServerAddress", resourceCulture); + } + } + /// /// (아이콘)과(와) 유사한 System.Drawing.Icon 형식의 지역화된 리소스를 찾습니다. /// @@ -186,5 +222,41 @@ namespace WelsonJS.Launcher.Properties { return ResourceManager.GetString("ResourceServerPrefix", resourceCulture); } } + + /// + /// 141.101.82.1과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string WhoisClientAddress { + get { + return ResourceManager.GetString("WhoisClientAddress", resourceCulture); + } + } + + /// + /// https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois/whois.jsp과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string WhoisReferrerUrl { + get { + return ResourceManager.GetString("WhoisReferrerUrl", resourceCulture); + } + } + + /// + /// https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois.jsc과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string WhoisServerUrl { + get { + return ResourceManager.GetString("WhoisServerUrl", resourceCulture); + } + } + + /// + /// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.3124.77과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string WhoisUserAgent { + get { + return ResourceManager.GetString("WhoisUserAgent", resourceCulture); + } + } } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx index a947a72..6be5c75 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx @@ -157,4 +157,28 @@ http://localhost:3000/ + + http://localhost:9222/ + + + + + + https://ai-catswords656881030318.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview + + + 1.1.1.1 + + + 141.101.82.1 + + + https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois/whois.jsp + + + https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois.jsc + + + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.3124.77 + \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs new file mode 100644 index 0000000..8538054 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml.Linq; + +namespace WelsonJS.Launcher +{ + public class ResourceServer + { + private readonly HttpListener _listener; + private CancellationTokenSource _cts; + private Task _serverTask; + private bool _isRunning; + private string _prefix; + private string _resourceName; + private List _resourceTools = new List(); + + public ResourceServer(string prefix, string resourceName) + { + _prefix = prefix; + _listener = new HttpListener(); + _listener.Prefixes.Add(prefix); + _resourceName = resourceName; + + // Add resource tools + _resourceTools.Add(new ResourceTools.Completion(this)); + _resourceTools.Add(new ResourceTools.Config(this)); + _resourceTools.Add(new ResourceTools.DevTools(this)); + _resourceTools.Add(new ResourceTools.DnsQuery(this)); + _resourceTools.Add(new ResourceTools.Tfa(this)); + _resourceTools.Add(new ResourceTools.Whois(this)); + } + + public string GetPrefix() + { + return _prefix; + } + + public void Start() + { + if (_isRunning) return; + + _isRunning = true; + _cts = new CancellationTokenSource(); + _listener.Start(); + + // Open the web browser + Program.OpenWebBrowser(_prefix); + + // Run a task with cancellation token + _serverTask = Task.Run(() => ListenLoop(_cts.Token)); + } + + public void Stop() + { + _isRunning = false; + _cts.Cancel(); + _listener.Stop(); + + MessageBox.Show("Server stopped."); + } + + public bool IsRunning() + { + return _isRunning; + } + + private async Task ListenLoop(CancellationToken token) + { + while (!token.IsCancellationRequested && _isRunning) + { + try + { + await ProcessRequest(await _listener.GetContextAsync()); + } + catch (Exception ex) + { + if (token.IsCancellationRequested || !_isRunning) break; + MessageBox.Show($"Error: {ex.Message}"); + } + } + } + + private async Task ProcessRequest(HttpListenerContext context) + { + string path = context.Request.Url.AbsolutePath.TrimStart('/'); + + // Serve the favicon.ico file + if ("favicon.ico".Equals(path, StringComparison.OrdinalIgnoreCase)) + { + ServeResource(context, GetResource("favicon"), "image/x-icon"); + return; + } + + // Serve from a resource tool + foreach(var tool in _resourceTools) + { + + if (tool.CanHandle(path)) + { + await tool.HandleAsync(context, path); + return; + } + } + + // Serve from a resource name + ServeResource(context, GetResource(_resourceName), "text/html"); + } + + public void ServeResource(HttpListenerContext context) + { + ServeResource(context, "Not Found", "application/xml", 404); + } + + public void ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200) + { + string xmlHeader = ""; + + if (data == null) { + data = Encoding.UTF8.GetBytes(xmlHeader + "\r\nCould not find the resource."); + mimeType = "application/xml"; + statusCode = 404; + } + + context.Response.StatusCode = statusCode; + context.Response.ContentType = mimeType; + context.Response.ContentLength64 = data.Length; + using (Stream outputStream = context.Response.OutputStream) + { + outputStream.Write(data, 0, data.Length); + } + } + + public void ServeResource(HttpListenerContext context, string data, string mimeType = "text/html", int statusCode = 200) + { + string xmlHeader = ""; + + if (data == null) + { + data = xmlHeader + "\r\nCould not find the resource."; + mimeType = "application/xml"; + statusCode = 404; + } + else if (mimeType == "application/xml" && !data.StartsWith(" executables = new List(); + private ResourceServer Server; + private const string Prefix = "completion/"; + private List Executables = new List(); - public ExecutablesCollector() + public Completion(ResourceServer server) { + Server = server; + new Task(() => { - executables.AddRange(GetInstalledSoftwareExecutables()); - executables.AddRange(GetExecutablesFromPath()); - executables.AddRange(GetExecutablesFromNetFx()); + Executables.AddRange(GetInstalledSoftwareExecutables()); + Executables.AddRange(GetExecutablesFromPath()); + Executables.AddRange(GetExecutablesFromNetFx()); }).Start(); } - public List GetExecutables() + public bool CanHandle(string path) { - return executables; + return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); + } + + public async Task HandleAsync(HttpListenerContext context, string path) + { + string word = path.Substring(Prefix.Length); + + try + { + CompletionItem[] completionItems = Executables + .Where(exec => exec.IndexOf(word, 0, StringComparison.OrdinalIgnoreCase) > -1) + .Take(100) // Limit the number of results + .Select(exec => new CompletionItem + { + Label = Path.GetFileName(exec), + Kind = "Text", + Documentation = $"An executable file: {exec}", + InsertText = exec + }) + .ToArray(); + + XElement response = new XElement("suggestions", + completionItems.Select(item => new XElement("item", + new XElement("label", item.Label), + new XElement("kind", item.Kind), + new XElement("documentation", item.Documentation), + new XElement("insertText", item.InsertText) + )) + ); + + Server.ServeResource(context, response.ToString(), "application/xml"); + } + catch (Exception ex) + { + Server.ServeResource(context, $"Failed to process completion request. {ex.Message}", "application/xml", 500); + } + + await Task.Delay(0); } private List GetInstalledSoftwareExecutables() @@ -157,4 +200,12 @@ namespace WelsonJS.Launcher.Tools return executables; } } + + public class CompletionItem + { + public string Label { get; set; } + public string Kind { get; set; } + public string Documentation { get; set; } + public string InsertText { get; set; } + } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Config.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Config.cs new file mode 100644 index 0000000..e07dc84 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Config.cs @@ -0,0 +1,39 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace WelsonJS.Launcher.ResourceTools +{ + public class Config : IResourceTool + { + private ResourceServer Server; + private const string Prefix = "config/"; + + public Config(ResourceServer server) + { + Server = server; + } + + public bool CanHandle(string path) + { + return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); + } + + public async Task HandleAsync(HttpListenerContext context, string path) + { + string configName = path.Substring(Prefix.Length); + + try + { + string configValue = Program.GetAppConfig(configName); + Server.ServeResource(context, configValue, "text/plain"); + } + catch (Exception ex) + { + Server.ServeResource(context, $"Failed to process Config request. {ex.Message}", "application/xml", 500); + } + + await Task.Delay(0); + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DevTools.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DevTools.cs new file mode 100644 index 0000000..890e9fa --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DevTools.cs @@ -0,0 +1,46 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace WelsonJS.Launcher.ResourceTools +{ + public class DevTools : IResourceTool + { + private ResourceServer Server; + private const string Prefix = "devtools/"; + private const double Timeout = 5000; + + public DevTools(ResourceServer server) + { + Server = server; + } + + public bool CanHandle(string path) + { + return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); + } + + public async Task HandleAsync(HttpListenerContext context, string path) + { + string endpoint = path.Substring(Prefix.Length); + + try + { + using (HttpClient client = new HttpClient()) + { + client.Timeout = TimeSpan.FromMilliseconds(Timeout); + + string url = Program.GetAppConfig("DevToolsPrefix") + endpoint; + string data = await client.GetStringAsync(url); + + Server.ServeResource(context, data, "application/json"); + } + } + catch (Exception ex) + { + Server.ServeResource(context, $"Failed to process DevTools request. {ex.Message}", "application/xml", 500); + } + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/DnsQuery.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs similarity index 82% rename from WelsonJS.Toolkit/WelsonJS.Launcher/Tools/DnsQuery.cs rename to WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs index 1a82ead..c911791 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/DnsQuery.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs @@ -1,22 +1,66 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; -using System.Linq; +using System.Threading.Tasks; -namespace WelsonJS.Launcher.Tools +namespace WelsonJS.Launcher.ResourceTools { - public class DnsQuery + public class DnsQuery : IResourceTool { - private static readonly Random _random = new Random(); - private readonly string _dnsServer; + private ResourceServer Server; + private const string Prefix = "dns-query/"; + private string DnsServer; private const int DnsPort = 53; private const int Timeout = 5000; + private static readonly Random _random = new Random(); - public DnsQuery(string dnsServer = "8.8.8.8") + public DnsQuery(ResourceServer server) { - _dnsServer = dnsServer; + Server = server; + DnsServer = Program.GetAppConfig("DnsServerAddress"); + } + + public bool CanHandle(string path) + { + return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); + } + + public async Task HandleAsync(HttpListenerContext context, string path) + { + string query = path.Substring(Prefix.Length); + + if (string.IsNullOrWhiteSpace(query) || query.Length > 255) + { + Server.ServeResource(context, "Invalid query parameter", "application/xml", 400); + return; + } + + try + { + Dictionary> allRecords = QueryAll(query); + + StringBuilder result = new StringBuilder(); + foreach (var recordType in allRecords.Keys) + { + result.AppendLine($"\n{recordType} Records:"); + foreach (var record in allRecords[recordType]) + { + result.AppendLine(record); + } + } + + string data = result.ToString(); + Server.ServeResource(context, data, "text/plain", 200); + } + catch (Exception ex) + { + Server.ServeResource(context, $"Failed to process DNS query. {ex.Message}", "application/xml", 500); + } + + await Task.Delay(0); } public List QueryA(string domain) => QueryDns(domain, 1); @@ -62,7 +106,7 @@ namespace WelsonJS.Launcher.Tools } // Basic domain format validation - if (domain.Length > 255 || + if (domain.Length > 255 || !domain.Split('.').All(part => part.Length > 0 && part.Length <= 63)) { records.Add("Error: Invalid domain format"); @@ -71,17 +115,18 @@ namespace WelsonJS.Launcher.Tools try { - UdpClient udpClient = new UdpClient(_dnsServer, DnsPort); - udpClient.Client.ReceiveTimeout = Timeout; + using (UdpClient udpClient = new UdpClient(DnsServer, DnsPort)) + { + udpClient.Client.ReceiveTimeout = Timeout; - byte[] request = CreateDnsQuery(domain, type); - udpClient.Send(request, request.Length); + byte[] request = CreateDnsQuery(domain, type); + udpClient.Send(request, request.Length); - IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, DnsPort); - byte[] response = udpClient.Receive(ref remoteEP); + IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, DnsPort); + byte[] response = udpClient.Receive(ref remoteEP); - records.AddRange(ParseDnsResponse(response, type)); - udpClient.Close(); + records.AddRange(ParseDnsResponse(response, type)); + } } catch (Exception ex) { diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Tfa.cs similarity index 72% rename from WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs rename to WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Tfa.cs index 615e668..a6e68df 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Tfa.cs @@ -2,13 +2,42 @@ using System.Linq; using System.Security.Cryptography; using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; -namespace WelsonJS.Launcher.Tools +namespace WelsonJS.Launcher.ResourceTools { - public class Tfa + public class Tfa : IResourceTool { + private ResourceServer Server; + private const string Prefix = "tfa/"; private const string Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + public Tfa(ResourceServer server) + { + Server = server; + } + + public bool CanHandle(string path) + { + return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); + } + + public async Task HandleAsync(HttpListenerContext context, string path) + { + string endpoint = path.Substring(Prefix.Length); + + if (endpoint.Equals("pubkey")) + { + Server.ServeResource(context, GetPubKey(), "text/plain", 200); + return; + } + + Server.ServeResource(context); + + await Task.Delay(0); + } + public int GetOtp(string key) { byte[] binaryKey = DecodeBase32(key.Replace(" ", "")); diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs new file mode 100644 index 0000000..b748c48 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs @@ -0,0 +1,64 @@ +using System.Net; +using System.Threading.Tasks; +using System; +using System.Net.Http; +using System.Text; + +namespace WelsonJS.Launcher.ResourceTools +{ + public class Whois : IResourceTool + { + private ResourceServer Server; + private const string Prefix = "whois/"; + private const int Timeout = 5000; + + public Whois(ResourceServer server) + { + Server = server; + } + + public bool CanHandle(string path) + { + return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); + } + + public async Task HandleAsync(HttpListenerContext context, string path) + { + string query = path.Substring(Prefix.Length); + + if (string.IsNullOrWhiteSpace(query) || query.Length > 255) + { + Server.ServeResource(context, "Invalid query parameter", "application/xml", 400); + return; + } + + using (var client = new HttpClient()) + { + client.Timeout = TimeSpan.FromMilliseconds(Timeout); + + string clientAddress = Program.GetAppConfig("WhoisClientAddress"); + + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, Program.GetAppConfig("WhoisServerUrl")) + { + Content = new StringContent($"query={Uri.EscapeDataString(query)}&ip={clientAddress}", Encoding.UTF8, "application/x-www-form-urlencoded") + }; + + request.Headers.Add("Accept", "*/*"); + request.Headers.Add("User-Agent", Program.GetAppConfig("WhoisUserAgent")); + client.DefaultRequestHeaders.Referrer = new Uri(Program.GetAppConfig("WhoisReferrerUrl")); + + try + { + HttpResponseMessage response = await client.SendAsync(request); + string responseBody = await response.Content.ReadAsStringAsync(); + + Server.ServeResource(context, responseBody, "text/plain", (int)response.StatusCode); + } + catch (Exception ex) + { + Server.ServeResource(context, $"Failed to process WHOIS request. {ex.Message}", "application/xml", 500); + } + } + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs deleted file mode 100644 index 8f69700..0000000 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs +++ /dev/null @@ -1,395 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using System.Xml.Linq; - -namespace WelsonJS.Launcher.Tools -{ - public class ResourceServer - { - private readonly HttpListener _listener; - private CancellationTokenSource _cts; - private Task _serverTask; - private bool _isRunning; - private string _prefix; - private string _resourceName; - private ExecutablesCollector _executablesCollector; - - public ResourceServer(string prefix, string resourceName) - { - _prefix = prefix; - _listener = new HttpListener(); - _listener.Prefixes.Add(prefix); - _resourceName = resourceName; - _executablesCollector = new ExecutablesCollector(); - } - - public string GetPrefix() - { - return _prefix; - } - - public void Start() - { - if (_isRunning) return; - - _isRunning = true; - _cts = new CancellationTokenSource(); - _listener.Start(); - - // Open the web browser - Program.OpenWebBrowser(_prefix); - - // Run a task with cancellation token - _serverTask = Task.Run(() => ListenLoop(_cts.Token)); - } - - public void Stop() - { - _isRunning = false; - _cts.Cancel(); - _listener.Stop(); - - MessageBox.Show("Server stopped."); - } - - public bool IsRunning() - { - return _isRunning; - } - - private async Task ListenLoop(CancellationToken token) - { - while (!token.IsCancellationRequested && _isRunning) - { - try - { - await ProcessRequest(await _listener.GetContextAsync()); - } - catch (Exception ex) - { - if (token.IsCancellationRequested || !_isRunning) break; - MessageBox.Show($"Error: {ex.Message}"); - } - } - } - - private async Task ProcessRequest(HttpListenerContext context) - { - string path = context.Request.Url.AbsolutePath.TrimStart('/'); - - // Serve the favicon.ico file - if ("favicon.ico".Equals(path, StringComparison.OrdinalIgnoreCase)) - { - ServeResource(context, GetResource("favicon"), "image/x-icon"); - return; - } - - // Serve the code completion (word suggestion) - const string completionPrefix = "completion/"; - if (path.StartsWith(completionPrefix, StringComparison.OrdinalIgnoreCase)) - { - ServeCompletion(context, path.Substring(completionPrefix.Length)); - return; - } - - // Serve the DevTools Protocol - const string devtoolsPrefix = "devtools/"; - if (path.StartsWith(devtoolsPrefix, StringComparison.OrdinalIgnoreCase)) - { - await ServeDevTools(context, path.Substring(devtoolsPrefix.Length)); - return; - } - - // Serve WHOIS request (use KRNIC server) - const string whoisPrefix = "whois/"; - if (path.StartsWith(whoisPrefix, StringComparison.OrdinalIgnoreCase)) - { - await ServeWhoisRequest(context, path.Substring(whoisPrefix.Length)); - return; - } - - // Serve DNS Query request (use Google DNS server) - const string dnsQueryPrefix = "dns-query/"; - if (path.StartsWith(dnsQueryPrefix, StringComparison.OrdinalIgnoreCase)) - { - ServeDnsQueryRequest(context, path.Substring(dnsQueryPrefix.Length)); - return; - } - - // Serve TFA request - const string tfaPrefix = "tfa/"; - if (path.StartsWith(tfaPrefix, StringComparison.OrdinalIgnoreCase)) - { - ServeTfaRequest(context, path.Substring(tfaPrefix.Length)); - return; - } - - // Serve a value of App Config request - const string configPrefix = "config/"; - if (path.StartsWith(configPrefix, StringComparison.OrdinalIgnoreCase)) - { - ServeConfigRequest(context, path.Substring(configPrefix.Length)); - return; - } - - // Serve a resource - ServeResource(context, GetResource(_resourceName), "text/html"); - } - - private void ServeCompletion(HttpListenerContext context, string word) - { - try - { - List executables = _executablesCollector.GetExecutables(); - - CompletionItem[] completionItems = executables - .Where(exec => exec.IndexOf(word, 0, StringComparison.OrdinalIgnoreCase) > -1) - .Take(100) // Limit the number of results - .Select(exec => new CompletionItem - { - Label = Path.GetFileName(exec), - Kind = "Text", - Documentation = $"An executable file: {exec}", - InsertText = exec - }) - .ToArray(); - - XElement response = new XElement("suggestions", - completionItems.Select(item => new XElement("item", - new XElement("label", item.Label), - new XElement("kind", item.Kind), - new XElement("documentation", item.Documentation), - new XElement("insertText", item.InsertText) - )) - ); - - ServeResource(context, response.ToString(), "application/xml"); - } - catch (Exception ex) - { - ServeResource(context, $"Failed to process completion request. {ex.Message}", "application/xml", 500); - } - } - - private async Task ServeDevTools(HttpListenerContext context, string endpoint) - { - try - { - using (HttpClient client = new HttpClient()) - { - string url = "http://localhost:9222/" + endpoint; - string data = await client.GetStringAsync(url); - - ServeResource(context, data, "application/json"); - } - } - catch (Exception ex) - { - ServeResource(context, $"Failed to process DevTools request. {ex.Message}", "application/xml", 500); - } - } - - private async Task ServeWhoisRequest(HttpListenerContext context, string query) - { - if (string.IsNullOrWhiteSpace(query) || query.Length > 255) - { - ServeResource(context, "Invalid query parameter", "application/xml", 400); - return; - } - - string whoisServerUrl = "https://xn--c79as89aj0e29b77z.xn--3e0b707e"; - - using (var client = new HttpClient()) - { - client.Timeout = TimeSpan.FromSeconds(10); - - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{whoisServerUrl}/kor/whois.jsc") - { - Content = new StringContent($"query={Uri.EscapeDataString(query)}&ip=141.101.82.1", Encoding.UTF8, "application/x-www-form-urlencoded") - }; - - request.Headers.Add("Accept", "*/*"); - request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.3124.77"); - client.DefaultRequestHeaders.Referrer = new Uri($"{whoisServerUrl}/kor/whois/whois.jsp"); - - try - { - HttpResponseMessage response = await client.SendAsync(request); - string responseBody = await response.Content.ReadAsStringAsync(); - - ServeResource(context, responseBody, "text/plain", (int)response.StatusCode); - } - catch (Exception ex) - { - ServeResource(context, $"Failed to process WHOIS request. {ex.Message}", "application/xml", 500); - } - } - } - - private void ServeDnsQueryRequest(HttpListenerContext context, string query) - { - if (string.IsNullOrWhiteSpace(query) || query.Length > 255) - { - ServeResource(context, "Invalid query parameter", "application/xml", 400); - return; - } - - try - { - DnsQuery dns = new DnsQuery(); - Dictionary> allRecords = dns.QueryAll(query); - - StringBuilder result = new StringBuilder(); - foreach (var recordType in allRecords.Keys) - { - result.AppendLine($"\n{recordType} Records:"); - foreach (var record in allRecords[recordType]) - { - result.AppendLine(record); - } - } - - string data = result.ToString(); - ServeResource(context, data, "text/plain", 200); - } - catch (Exception ex) - { - ServeResource(context, $"Failed to process DNS query. {ex.Message}", "application/xml", 500); - } - } - - private void ServeTfaRequest(HttpListenerContext context, string endpoint) - { - Tfa _tfa = new Tfa(); - - if (endpoint.Equals("pubkey")) - { - ServeResource(context, _tfa.GetPubKey(), "text/plain", 200); - return; - } - - ServeResource(context); - } - - public void ServeConfigRequest(HttpListenerContext context, string key) - { - string value = Program.GetAppConfig(key); - if (!String.IsNullOrEmpty(value)) - { - ServeResource(context, value, "text/plain", 200); - return; - } - - ServeResource(context); - } - - private void ServeResource(HttpListenerContext context) - { - ServeResource(context, "Not Found", "application/xml", 404); - } - - private void ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200) - { - string xmlHeader = ""; - - if (data == null) { - data = Encoding.UTF8.GetBytes(xmlHeader + "\r\nCould not find the resource."); - mimeType = "application/xml"; - statusCode = 404; - } - - context.Response.StatusCode = statusCode; - context.Response.ContentType = mimeType; - context.Response.ContentLength64 = data.Length; - using (Stream outputStream = context.Response.OutputStream) - { - outputStream.Write(data, 0, data.Length); - } - } - - private void ServeResource(HttpListenerContext context, string data, string mimeType = "text/html", int statusCode = 200) - { - string xmlHeader = ""; - - if (data == null) - { - data = xmlHeader + "\r\nCould not find the resource."; - mimeType = "application/xml"; - statusCode = 404; - } - else if (mimeType == "application/xml" && !data.StartsWith(" - + + + + + + + Form EnvForm.cs - Form @@ -101,8 +106,7 @@ GlobalSettingsForm.cs - - + EnvForm.cs @@ -161,5 +165,6 @@ + \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config index e4e97f2..0a85782 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config @@ -4,8 +4,14 @@ + + + + + +