mirror of
https://github.com/gnh1201/welsonjs.git
synced 2026-02-15 15:18:26 +00:00
Refactor IP query to support multiple providers
Replaces the single CriminalIP API integration with a new IP query system supporting both CriminalIP and AbuseIPDB providers. Updates configuration keys, resource files, and the editor UI to handle multiple API endpoints and keys. Refactors backend logic to aggregate and return results from both providers in a unified XML format, and updates the frontend to parse and display these results. Adds improved error handling and logging for IP query and WHOIS operations.
This commit is contained in:
parent
a3e00d1762
commit
5fb255e1e3
|
|
@ -150,24 +150,6 @@ namespace WelsonJS.Launcher.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
internal static string CriminalIpApiKey {
|
||||
get {
|
||||
return ResourceManager.GetString("CriminalIpApiKey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://api.criminalip.io/v1/과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
internal static string CriminalIpApiPrefix {
|
||||
get {
|
||||
return ResourceManager.GetString("CriminalIpApiPrefix", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// yyyy-MM-dd HH:mm:ss과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
|
|
@ -295,6 +277,42 @@ namespace WelsonJS.Launcher.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
internal static string IpQueryApiKey {
|
||||
get {
|
||||
return ResourceManager.GetString("IpQueryApiKey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
internal static string IpQueryApiKey2 {
|
||||
get {
|
||||
return ResourceManager.GetString("IpQueryApiKey2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://api.criminalip.io/v1/과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
internal static string IpQueryApiPrefix {
|
||||
get {
|
||||
return ResourceManager.GetString("IpQueryApiPrefix", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://api.abuseipdb.com/api/v2/과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
internal static string IpQueryApiPrefix2 {
|
||||
get {
|
||||
return ResourceManager.GetString("IpQueryApiPrefix2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// false과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -190,10 +190,10 @@
|
|||
<data name="HttpClientTimeout" xml:space="preserve">
|
||||
<value>90</value>
|
||||
</data>
|
||||
<data name="CriminalIpApiKey" xml:space="preserve">
|
||||
<data name="IpQueryApiKey" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="CriminalIpApiPrefix" xml:space="preserve">
|
||||
<data name="IpQueryApiPrefix" xml:space="preserve">
|
||||
<value>https://api.criminalip.io/v1/</value>
|
||||
</data>
|
||||
<data name="DateTimeFormat" xml:space="preserve">
|
||||
|
|
@ -214,4 +214,10 @@
|
|||
<data name="ChromiumAppMode" xml:space="preserve">
|
||||
<value>true</value>
|
||||
</data>
|
||||
<data name="IpQueryApiKey2" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="IpQueryApiPrefix2" xml:space="preserve">
|
||||
<value>https://api.abuseipdb.com/api/v2/</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -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<string> executableFiles)
|
||||
{
|
||||
foreach (var executableFile in executableFiles)
|
||||
{
|
||||
DiscoveredExecutables.Add(executableFile);
|
||||
}
|
||||
executableFiles.ForEach(x => DiscoveredExecutables.Add(x));
|
||||
}
|
||||
|
||||
private async Task SafeDiscoverAsync(Action discoveryMethod)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
// <result target="1.1.1.1">
|
||||
// <response provider="criminalip" status="200"><text>{"...json..."}</text></response>
|
||||
// <response provider="abuseipdb" status="200"><text>{"...json..."}</text></response>
|
||||
// </result>
|
||||
//
|
||||
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, "<error>Missing API key</error>", "application/xml", 500);
|
||||
await Server.ServeResource(context, "<error>Missing IP target</error>", "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, $"<error>{ex.Message}</error>", "application/xml", 500);
|
||||
_logger.Error("Error processing IP query request: " + ex.Message);
|
||||
await Server.ServeResource(context, "<error>" + WebUtility.HtmlEncode(ex.Message) + "</error>", "application/xml", 500);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<XElement> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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, "<error>Invalid query parameter</error>", "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, $"<error>Failed to process WHOIS request. {ex.Message}</error>", "application/xml", 500);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@
|
|||
<add key="BlobStoragePrefix" value="https://catswords.blob.core.windows.net/welsonjs/"/>
|
||||
<add key="BlobConfigUrl" value="https://catswords.blob.core.windows.net/welsonjs/blob.config.xml"/>
|
||||
<add key="HttpClientTimeout" value="90"/>
|
||||
<add key="CitiApiKey" value=""/>
|
||||
<add key="CitiApiPrefix" value="https://api.criminalip.io/v1/"/>
|
||||
<add key="IpQueryApiKey" value=""/>
|
||||
<add key="IpQueryApiPrefix" value="https://api.criminalip.io/v1/"/>
|
||||
<add key="IpQueryApiKey2" value=""/>
|
||||
<add key="IpQueryApiPrefix2" value="https://api.abuseipdb.com/api/v2/"/>
|
||||
<add key="DateTimeFormat" value="yyyy-MM-dd HH:mm:ss"/>
|
||||
<add key="NativeRequireSigned" value="false"/>
|
||||
</appSettings>
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
// <result target="...">
|
||||
// <response provider="criminalip" status="200">
|
||||
// <text>{ "...raw json..." }</text>
|
||||
// </response>
|
||||
// <response provider="abuseipdb" status="200">
|
||||
// <text>{ "...raw json..." }</text>
|
||||
// </response>
|
||||
// </result>
|
||||
|
||||
// Normalize to an array of <response> nodes
|
||||
const responsesNode = parsed?.result?.response;
|
||||
const responses = Array.isArray(responsesNode)
|
||||
? responsesNode
|
||||
: (responsesNode ? [responsesNode] : []);
|
||||
|
||||
if (responses.length === 0) {
|
||||
appendTextToEditor("\n// No <response> 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 <response><text> 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 <text> 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}`);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user