// 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; using System.Net.Http; using System.Threading.Tasks; using System.Web; using System.Xml.Linq; namespace WelsonJS.Launcher.ResourceTools { public class IpQuery : IResourceTool { private readonly ResourceServer Server; private readonly HttpClient _httpClient; private readonly ICompatibleLogger _logger; private const string Prefix = "ip-query/"; public IpQuery(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger) { Server = server; _httpClient = httpClient; _logger = logger; } public bool CanHandle(string path) { return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); } public async Task HandleAsync(HttpListenerContext context, string path) { try { string target = path.Substring(Prefix.Length).Trim(); if (string.IsNullOrWhiteSpace(target)) { await Server.ServeResource(context, "Missing IP target", "application/xml", 400); return; } string crimPrefix = Program.GetAppConfig("IpQueryApiPrefix"); string crimKey = Program.GetAppConfig("IpQueryApiKey"); string abusePrefix = Program.GetAppConfig("IpQueryApiPrefix2"); string abuseKey = Program.GetAppConfig("IpQueryApiKey2"); var root = new XElement("result", new XAttribute("target", target)); 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) { _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; } } }