diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs index a63df84..51db986 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs @@ -150,24 +150,6 @@ namespace WelsonJS.Launcher.Properties { } } - /// - /// 과(와) 유사한 지역화된 문자열을 찾습니다. - /// - internal static string CriminalIpApiKey { - get { - return ResourceManager.GetString("CriminalIpApiKey", resourceCulture); - } - } - - /// - /// https://api.criminalip.io/v1/과(와) 유사한 지역화된 문자열을 찾습니다. - /// - internal static string CriminalIpApiPrefix { - get { - return ResourceManager.GetString("CriminalIpApiPrefix", resourceCulture); - } - } - /// /// yyyy-MM-dd HH:mm:ss과(와) 유사한 지역화된 문자열을 찾습니다. /// @@ -295,6 +277,42 @@ namespace WelsonJS.Launcher.Properties { } } + /// + /// 과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string IpQueryApiKey { + get { + return ResourceManager.GetString("IpQueryApiKey", resourceCulture); + } + } + + /// + /// 과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string IpQueryApiKey2 { + get { + return ResourceManager.GetString("IpQueryApiKey2", resourceCulture); + } + } + + /// + /// https://api.criminalip.io/v1/과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string IpQueryApiPrefix { + get { + return ResourceManager.GetString("IpQueryApiPrefix", resourceCulture); + } + } + + /// + /// https://api.abuseipdb.com/api/v2/과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string IpQueryApiPrefix2 { + get { + return ResourceManager.GetString("IpQueryApiPrefix2", resourceCulture); + } + } + /// /// false과(와) 유사한 지역화된 문자열을 찾습니다. /// diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx index 36cdf34..b733285 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx @@ -190,10 +190,10 @@ 90 - + - + https://api.criminalip.io/v1/ @@ -214,4 +214,10 @@ true + + + + + https://api.abuseipdb.com/api/v2/ + \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs index ea11b3b..45d08fb 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs @@ -6,7 +6,6 @@ using Microsoft.Win32; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Net; using System.Linq; @@ -45,8 +44,6 @@ namespace WelsonJS.Launcher.ResourceTools public async Task HandleAsync(HttpListenerContext context, string path) { - await Task.Delay(0); - string word = path.Substring(Prefix.Length); try @@ -121,10 +118,7 @@ namespace WelsonJS.Launcher.ResourceTools .Select(p => p.Trim()) .Where(p => !string.IsNullOrEmpty(p)); - foreach (string path in paths) - { - SearchAllExecutables(path, SearchOption.TopDirectoryOnly); - } + paths.ToList().ForEach(x => SearchAllExecutables(x, SearchOption.TopDirectoryOnly)); } private void DiscoverFromProgramDirectories() @@ -153,10 +147,7 @@ namespace WelsonJS.Launcher.ResourceTools Path.Combine(userProfile, "scoop", "apps") }; - foreach (string path in paths) - { - SearchAllExecutables(path); - } + paths.ToList().ForEach(x => SearchAllExecutables(x)); } private void SearchAllExecutables(string path, SearchOption searchOption = SearchOption.AllDirectories, int maxFiles = 1000) @@ -184,10 +175,7 @@ namespace WelsonJS.Launcher.ResourceTools private void AddDiscoveredExecutables(List executableFiles) { - foreach (var executableFile in executableFiles) - { - DiscoveredExecutables.Add(executableFile); - } + executableFiles.ForEach(x => DiscoveredExecutables.Add(x)); } private async Task SafeDiscoverAsync(Action discoveryMethod) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/IpQuery.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/IpQuery.cs index 42475c2..4976be3 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/IpQuery.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/IpQuery.cs @@ -1,12 +1,28 @@ -// CitiQuery.cs +// IpQuery.cs // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors // https://github.com/gnh1201/welsonjs -// +// +// PLEASE NOTE: +// Requires joining the IP Query API providers to provide IP information. +// WelsonJS has no affiliation with any IP Query API providers. +// +// Providers: +// 1) CriminalIP -> IpQueryApiPrefix, IpQueryApiKey +// 2) AbuseIPDB -> IpQueryApiPrefix2, IpQueryApiKey2 +// +// XML response structure: +// +// {"...json..."} +// {"...json..."} +// +// using System; -using System.Net.Http; using System.Net; +using System.Net.Http; using System.Threading.Tasks; +using System.Web; +using System.Xml.Linq; namespace WelsonJS.Launcher.ResourceTools { @@ -29,37 +45,121 @@ namespace WelsonJS.Launcher.ResourceTools { return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); } - public async Task HandleAsync(HttpListenerContext context, string path) { try { string target = path.Substring(Prefix.Length).Trim(); - string apiKey = Program.GetAppConfig("CriminalIpApiKey"); - if (string.IsNullOrEmpty(apiKey)) + if (string.IsNullOrWhiteSpace(target)) { - await Server.ServeResource(context, "Missing API key", "application/xml", 500); + await Server.ServeResource(context, "Missing IP target", "application/xml", 400); return; } - string encoded = Uri.EscapeDataString(target); - string apiPrefix = Program.GetAppConfig("CriminalIpApiPrefix"); - string url = $"{apiPrefix}asset/ip/report?ip={encoded}&full=true"; + string crimPrefix = Program.GetAppConfig("IpQueryApiPrefix"); + string crimKey = Program.GetAppConfig("IpQueryApiKey"); - var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.Add("x-api-key", apiKey); - request.Headers.Add("User-Agent", context.Request.UserAgent); + string abusePrefix = Program.GetAppConfig("IpQueryApiPrefix2"); + string abuseKey = Program.GetAppConfig("IpQueryApiKey2"); - HttpResponseMessage response = await _httpClient.SendAsync(request); - string content = await response.Content.ReadAsStringAsync(); + var root = new XElement("result", new XAttribute("target", target)); - context.Response.StatusCode = (int)response.StatusCode; - await Server.ServeResource(context, content, "application/json", (int)response.StatusCode); + var p1 = QueryProviderAsync(context, target, "criminalip", crimPrefix, crimKey); + var p2 = QueryProviderAsync(context, target, "abuseipdb", abusePrefix, abuseKey); + + await Task.WhenAll(p1, p2); + + root.Add(p1.Result); + root.Add(p2.Result); + + bool anySuccess = + (int.TryParse((string)p1.Result.Attribute("status"), out var s1) && s1 >= 200 && s1 < 300) || + (int.TryParse((string)p2.Result.Attribute("status"), out var s2) && s2 >= 200 && s2 < 300); + + int httpCode = anySuccess ? 200 : 502; + + context.Response.StatusCode = httpCode; + await Server.ServeResource(context, root.ToString(), "application/xml", httpCode); } catch (Exception ex) { - await Server.ServeResource(context, $"{ex.Message}", "application/xml", 500); + _logger.Error("Error processing IP query request: " + ex.Message); + await Server.ServeResource(context, "" + WebUtility.HtmlEncode(ex.Message) + "", "application/xml", 500); } } + + private async Task QueryProviderAsync(HttpListenerContext ctx, string ip, string provider, string prefix, string key) + { + var node = new XElement("response", new XAttribute("provider", provider)); + + if (string.IsNullOrWhiteSpace(prefix) || string.IsNullOrWhiteSpace(key)) + { + node.Add(new XAttribute("status", 500)); + node.Add(new XElement("error", "Missing configuration for " + provider)); + return node; + } + + try + { + HttpRequestMessage req = BuildProviderRequest(ctx, ip, provider, prefix, key); + try + { + using (HttpResponseMessage resp = await _httpClient.SendAsync(req)) + using (JsSerializer ser = new JsSerializer()) + { + string body = ser.Pretty(await resp.Content.ReadAsStringAsync(), 4); + node.Add(new XAttribute("status", (int)resp.StatusCode)); + node.Add(new XElement("text", body)); + return node; + } + } + finally + { + req.Dispose(); + } + } + catch (Exception ex) + { + node.Add(new XAttribute("status", 500)); + node.Add(new XElement("error", ex.Message)); + return node; + } + } + + private static HttpRequestMessage BuildProviderRequest(HttpListenerContext ctx, string ip, string provider, string prefix, string key) + { + HttpRequestMessage req; + + if (string.Equals(provider, "criminalip", StringComparison.OrdinalIgnoreCase)) + { + string url = prefix.TrimEnd('/') + "/asset/ip/report?ip=" + Uri.EscapeDataString(ip) + "&full=true"; + req = new HttpRequestMessage(HttpMethod.Get, url); + req.Headers.TryAddWithoutValidation("x-api-key", key); + } + else if (string.Equals(provider, "abuseipdb", StringComparison.OrdinalIgnoreCase)) + { + var ub = new UriBuilder(prefix.TrimEnd('/') + "/check"); + var q = HttpUtility.ParseQueryString(ub.Query); + q["ipAddress"] = ip; + q["maxAgeInDays"] = "90"; + q["verbose"] = ""; + ub.Query = q.ToString(); + + req = new HttpRequestMessage(HttpMethod.Get, ub.Uri); + req.Headers.TryAddWithoutValidation("Accept", "application/json"); + req.Headers.TryAddWithoutValidation("Key", key); + } + else + { + throw new ArgumentException("Unsupported provider: " + provider); + } + + if (!string.IsNullOrEmpty(ctx.Request.UserAgent)) + { + req.Headers.TryAddWithoutValidation("User-Agent", ctx.Request.UserAgent); + } + + return req; + } } -} +} \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs index 463fb04..db65c39 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs @@ -37,6 +37,7 @@ namespace WelsonJS.Launcher.ResourceTools if (string.IsNullOrWhiteSpace(query) || query.Length > 255) { + _logger.Error("Invalid WHOIS query parameter."); await Server.ServeResource(context, "Invalid query parameter", "application/xml", 400); return; } @@ -61,6 +62,7 @@ namespace WelsonJS.Launcher.ResourceTools } catch (Exception ex) { + _logger.Error("Error processing WHOIS request: " + ex.Message); await Server.ServeResource(context, $"Failed to process WHOIS request. {ex.Message}", "application/xml", 500); } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index 229b09b..e609a60 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -81,6 +81,7 @@ + diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config index 5263b9e..7902a42 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config @@ -19,8 +19,10 @@ - - + + + + diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html index 0d44d37..9a72022 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html @@ -589,59 +589,62 @@ appendTextToEditor("\n// IP address is required."); return; } - - const apiKey = settingsRef.current.CriminalIpApiKey; - if (!apiKey || apiKey.trim() === '') { - appendTextToEditor("\n// Criminal IP API key is not set."); - return; - } - - const apiPrefix = settingsRef.current.CriminalIpApiPrefix; const ip = encodeURIComponent(hostname.trim()); - axios.get(`/ip-query/${hostname}`).then(response => { - if (!response) { - appendTextToEditor("\n// No data returned from Criminal IP."); + axios.get(`/ip-query/${hostname}`).then(res => { + if (!res || !res.data) { + appendTextToEditor("\n// No data returned from server."); return; } - const lines = []; - lines.push(`/*\nCriminal IP Report: ${hostname}\n`); - - // network port data - lines.push(`## Network ports:`); - if (response.port.data.length == 0) { - lines.push(`* No open ports found.`); - } else { - response.port.data.forEach(x => { - lines.push(`### ${x.open_port_no}/${x.socket}`); - lines.push(`* Application: ${x.app_name} ${x.app_version}`); - lines.push(`* Discovered hostnames: ${x.dns_names}`); - lines.push(`* Confirmed Time: ${x.confirmed_time}`); - }); + // Parse XML and keep attributes (provider, status) + const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_" }); + const parsed = parser.parse(res.data); + + // Expected XML: + // + // + // { "...raw json..." } + // + // + // { "...raw json..." } + // + // + + // Normalize to an array of nodes + const responsesNode = parsed?.result?.response; + const responses = Array.isArray(responsesNode) + ? responsesNode + : (responsesNode ? [responsesNode] : []); + + if (responses.length === 0) { + appendTextToEditor("\n// No nodes found."); + return; } - // vulnerability data - lines.push(`## Vulnerabilities:`); - if (response.vulnerability.data.length == 0) { - lines.push(`* No vulnerabilities found.`); - } else { - response.vulnerability.data.forEach(x => { - lines.push(`### ${x.cve_id}`); - lines.push(`* ${x.cve_description}`); - lines.push(`* CVSSV2 Score: ${x.cvssv2_score}`); - lines.push(`* CVSSV3 Score: ${x.cvssv3_score}`); - }); + // Extract each content and print + let out = "\n// --- IP Query Results ---"; + for (const r of responses) { + const provider = r?.["@_provider"] || "unknown"; + const status = r?.["@_status"] || "n/a"; + + // Fast-XML-Parser usually makes a string, but be safe: + let txt = r?.text; + if (txt && typeof txt === "object") { + // If parser produced an object with a '#text' key or similar + txt = txt["#text"] || JSON.stringify(txt); + } + if (txt == null) txt = ""; + + out += `\n// provider=${provider}, status=${status}\n${txt}\n`; } + out = "\n/*" + out + "*/\n"; - lines.push(`*/\n`); - const report = lines.join('\n'); - - appendTextToEditor(report); - pushPromptMessage("system", report); + appendTextToEditor(out); + pushPromptMessage("system", out); }).catch(error => { console.error(error); - appendTextToEditor(`\n// Failed to query Criminal IP: ${error.message}`); + appendTextToEditor(`\n// Failed to query the IP: ${error.message}`); }); };