From 81ab66acfae9be9002d737cd28d1718a9da573df Mon Sep 17 00:00:00 2001 From: NyaMisty <5344431+NyaMisty@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:26:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B9=E8=BF=9Bhttp=EF=BC=8C?= =?UTF-8?q?=E8=BF=94=E5=9B=9Eheader=E5=92=8Cstatus=5Fcode=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=AE=9E=E7=8E=B0url=E7=BA=A7=E5=88=AB=E7=BB=86?= =?UTF-8?q?=E7=B2=92=E5=BA=A6=E6=8E=A7=E5=88=B6=E5=92=8Cui=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=20(#2332)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Script/Dependence/Http.cs | 62 ++++++++++++---- .../Core/Script/Group/ScriptGroupProject.cs | 27 ++++++- .../Core/Script/Project/Manifest.cs | 1 + BetterGenshinImpact/Service/ScriptService.cs | 2 +- .../Editable/ScriptGroupProjectEditor.xaml | 39 +++++++++- .../ScriptGroupProjectEditorViewModel.cs | 71 ++++++++++++++++++- 6 files changed, 179 insertions(+), 23 deletions(-) diff --git a/BetterGenshinImpact/Core/Script/Dependence/Http.cs b/BetterGenshinImpact/Core/Script/Dependence/Http.cs index 5a5e43cf..825230f3 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Http.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Http.cs @@ -9,37 +9,61 @@ using System.Collections.Generic; using System.Net.Http; using System.Text; using BetterGenshinImpact.GameTask; +using Microsoft.Extensions.Logging; namespace BetterGenshinImpact.Core.Script.Dependence; public class Http { - private bool CheckHttpPermission() + private readonly ILogger _logger = App.GetLogger(); + + private void CheckHttpPermission(string url) { - try + var currentProject = TaskContext.Instance().CurrentScriptProject; + if (!currentProject?.AllowJsHTTP ?? false) { - var currentProject = TaskContext.Instance().CurrentScriptProject; - return currentProject?.AllowJsHTTP ?? false; + throw new UnauthorizedAccessException("当前JS脚本不允许使用HTTP请求,请在调度器通用设置中启用“JS HTTP权限”"); } - catch + var allowedUrls = currentProject?.Project?.Manifest.HttpAllowedUrls ?? []; + if (allowedUrls.Length == 0) { - return false; + throw new UnauthorizedAccessException("当前JS脚本没有配置允许请求的URL,请在脚本的manifest.json中配置http_allowed_urls"); } + if (allowedUrls.Any(allowedUrl => + { + // fuzzy match + var pattern = "^" + System.Text.RegularExpressions.Regex.Escape(allowedUrl).Replace("\\*", ".*") + "$"; + _logger.LogDebug($"[HTTP] 检查URL {url} 是否符合: {pattern}"); + var regex = new System.Text.RegularExpressions.Regex(pattern); + return regex.IsMatch(url); + })) + { + return; + } + throw new UnauthorizedAccessException($"当前JS脚本不允许请求此URL: {url},请在脚本的manifest.json中配置http_allowed_urls,当前允许的URL列表: [{string.Join(", ", allowedUrls)}]"); } + public class HttpReponse + { + public int status_code { get; set; } + public Dictionary headers { get; set; } = new(); + public string body { get; set; } = ""; + } + + /// /// 执行HTTP请求 /// /// HTTP方法 /// 请求URL - /// 请求体请求体 + /// 请求头,JSON格式 /// - public async Task Request(string method, string url, string? body, string? headersJson) + public async Task Request(string method, string url, string? body, string? headersJson) { - if (!CheckHttpPermission()) - { - throw new UnauthorizedAccessException("当前JS脚本不允许使用HTTP请求,请在调度器通用设置中启用“JS HTTP权限”"); - } + _logger.LogDebug($"[HTTP] 发送HTTP请求: {method} {url} Body: {(body != null ? body : "null")} Headers: {(headersJson != null ? headersJson : "null")}"); + CheckHttpPermission(url); + var dictHeaders = new Dictionary(); if (!string.IsNullOrWhiteSpace(headersJson)) { @@ -59,7 +83,7 @@ public class Http // header全部小写 dictHeaders = dictHeaders.ToDictionary(kvp => kvp.Key.ToLowerInvariant(), kvp => kvp.Value); - + // 提前取出来Content-Type,防止被覆盖 string contentType = "application/json"; if (dictHeaders.TryGetValue("content-type", out var ct)) @@ -67,7 +91,7 @@ public class Http contentType = ct; dictHeaders.Remove("content-type"); } - + // 使用HttpClient发送请求 using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Clear(); @@ -79,6 +103,14 @@ public class Http var content = body == null ? null : new StringContent(body, Encoding.UTF8, contentType); var response = await httpClient.SendAsync(new HttpRequestMessage(new HttpMethod(method), url) { Content = content }); - return await response.Content.ReadAsStringAsync(); + var responseCode = (int)response.StatusCode; + var responseHeaders = response.Headers.ToDictionary(h => h.Key, h => h.Value.First()); // 只取第一个值 + var responseBody = await response.Content.ReadAsStringAsync(); + return new HttpReponse + { + status_code = responseCode, + headers = responseHeaders, + body = responseBody, + }; } } diff --git a/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs b/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs index 28817a67..09e1b759 100644 --- a/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs +++ b/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs @@ -107,7 +107,19 @@ public partial class ScriptGroupProject : ObservableObject private bool? _allowJsNotification = true; [ObservableProperty] - private bool? _allowJsHTTP = false; + private string? _allowJsHTTPHash = ""; + + /// + /// 是否允许JS脚本发送HTTP请求,通过验证Hash来控制 + /// + [JsonIgnore] + public bool AllowJsHTTP + { + get + { + return GetHttpAllowedUrlsHash() == AllowJsHTTPHash; + } + } public ScriptGroupProject() @@ -167,6 +179,19 @@ public partial class ScriptGroupProject : ObservableObject Project = new ScriptProject(FolderName); } + public string GetHttpAllowedUrlsHash() + { + if (Project == null) + { + BuildScriptProjectRelation(); + } + if (Project == null) + { + return ""; + } + return string.Join("|", Project.Manifest.HttpAllowedUrls); + } + public async Task Run() { //执行记录 diff --git a/BetterGenshinImpact/Core/Script/Project/Manifest.cs b/BetterGenshinImpact/Core/Script/Project/Manifest.cs index 0aa31798..d732a711 100644 --- a/BetterGenshinImpact/Core/Script/Project/Manifest.cs +++ b/BetterGenshinImpact/Core/Script/Project/Manifest.cs @@ -26,6 +26,7 @@ public class Manifest public string[] Scripts { get; set; } = []; public string[] Library { get; set; } = []; public string[] SavedFiles { get; set; } = []; + public string[] HttpAllowedUrls { get; set; } = []; public static Manifest FromJson(string json) { diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index b513e6aa..7ba75295 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -486,7 +486,7 @@ public partial class ScriptService : IScriptService target.JsScriptSettingsObject = source.JsScriptSettingsObject; target.GroupInfo = source.GroupInfo; target.AllowJsNotification = source.AllowJsNotification; - target.AllowJsHTTP = source.AllowJsHTTP; + target.AllowJsHTTPHash = source.AllowJsHTTPHash; target.SkipFlag = source.SkipFlag; } diff --git a/BetterGenshinImpact/View/Windows/Editable/ScriptGroupProjectEditor.xaml b/BetterGenshinImpact/View/Windows/Editable/ScriptGroupProjectEditor.xaml index 31dcd0a3..abd8394e 100644 --- a/BetterGenshinImpact/View/Windows/Editable/ScriptGroupProjectEditor.xaml +++ b/BetterGenshinImpact/View/Windows/Editable/ScriptGroupProjectEditor.xaml @@ -6,10 +6,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:editable="clr-namespace:BetterGenshinImpact.ViewModel.Windows.Editable" xmlns:sys="clr-namespace:System;assembly=System.Runtime" - Width="200" - Height="300" + MinWidth="350" + MinHeight="300" d:DesignHeight="200" - d:DesignWidth="300" + d:DesignWidth="350" mc:Ignorable="d"> + + + + + + + + + + + + + + + + + _project.AllowJsHTTP; + get + { + return _project.AllowJsHTTP; + } set { - _project.AllowJsHTTP = value; + // 为了避免误用,AllowJsHTTP禁止set,通过更新Hash来控制 + // 脚本作者更新时,如果Hash变更会自动禁用http权限,避免安全风险 + if (value == null || value == false) + { + _project.AllowJsHTTPHash = null; + } + else + { + _project.AllowJsHTTPHash = _project.GetHttpAllowedUrlsHash(); + } OnPropertyChanged(); } } + + public record JsText(string Text, Brush Color); + public List JsHTTPInfoText + { + get + { + if (_project.Project == null) + { + _project.BuildScriptProjectRelation(); + } + if (_project.Project == null) + { + return new List + { + new JsText("当前脚本项目未加载", Brushes.Red) + }; + } + var urls = _project.Project.Manifest?.HttpAllowedUrls ?? []; + if (urls.Length == 0) + { + return new List + { + new JsText("当前脚本无需使用HTTP资源", Brushes.Green) + }; + } + return new List + { + new JsText($"当前脚本使用 {urls.Length} 个HTTP资源", Brushes.OrangeRed) + }; + } + } + + public record JsLine(string Text, Brush Color); + + public List JsHTTPInfo + { + get + { + if (_project.Project == null) + { + _project.BuildScriptProjectRelation(); + } + var urls = _project.Project?.Manifest?.HttpAllowedUrls ?? []; + var blocks = new List(); + foreach (var url in urls) + { + blocks.Add(new JsLine(url, Brushes.OrangeRed)); + } + return blocks; + } + } public string Status { @@ -68,7 +133,7 @@ public class ScriptGroupProjectEditorViewModel : ObservableObject { OnPropertyChanged(nameof(AllowJsNotification)); } - if (e.PropertyName == nameof(ScriptGroupProject.AllowJsHTTP)) + if (e.PropertyName == nameof(ScriptGroupProject.AllowJsHTTPHash)) { OnPropertyChanged(nameof(AllowJsHTTP)); }