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}`);
});
};