فهرست منبع

ref: 迁移 HasAdminRole,ExceptionExtensions 部分重构

龙腾猫跃 5 ماه پیش
والد
کامیت
3cfe4d9b7b
4فایلهای تغییر یافته به همراه85 افزوده شده و 72 حذف شده
  1. 67 68
      Shared/Extensions/ExceptionExtensions.cs
  2. 3 3
      Shared/Utils/FileUtils.cs
  3. 1 1
      Tests/Utils/FileUtilsTest.cs
  4. 14 0
      Wpf/Utils/SystemUtils.cs

+ 67 - 68
Shared/Extensions/ExceptionExtensions.cs

@@ -1,87 +1,86 @@
 namespace MeloongCore.Extensions;
 namespace MeloongCore.Extensions;
 public static class ExceptionExtensions {
 public static class ExceptionExtensions {
+
     /// <summary>
     /// <summary>
-    /// 提取 <paramref name="ex"/> 与其 InnerException 的详细描述与堆栈信息。返回内容总是多于一行。
+    /// 返回该异常最底层的 <see cref="Exception.InnerException"/>。
     /// </summary>
     /// </summary>
-    /// <param name="showAllStacks">是否必须显示所有堆栈。通常用于判定堆栈信息。</param>
-    public static string GetDetail(this Exception? ex, bool showAllStacks = false) {
-        if (ex is null) return "无可用错误信息!";
-
-        // 获取最底层的异常
-        var outerEx = ex;
-        var innerEx = ex;
-        while (innerEx.InnerException is not null) innerEx = innerEx.InnerException;
-
-        // 获取各级错误的描述与堆栈信息
-        var descList = new List<string>();
-        bool isInner = false;
-        while (ex is not null) {
-            descList.Add((isInner ? "→ " : "") + ex.Message.ReplaceLineEndings("\r\n", true));
-            if (ex.StackTrace is not null) {
-                foreach (string stack in ex.StackTrace.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) {
-                    if (showAllStacks || stack.ContainsIgnoreCase("pcl"))
-                        descList.Add(stack.ReplaceLineEndings(""));
-                }
-            }
-            if (ex.GetType().FullName != "System.Exception")
-                descList.Add("   错误类型:" + ex.GetType().FullName);
-            ex = ex.InnerException;
-            isInner = true;
-        }
-
-        // 构造输出信息
-        string? usualReason = AnalyzeUsualReason(innerEx, outerEx, descList);
-        if (usualReason is not null) {
-            return usualReason + "\r\n\r\n————————————\r\n详细错误信息:\r\n" + descList.Join("\r\n");
-        } 
-        return descList.Join("\r\n");
+    public static Exception RootException(this Exception ex) {
+        while (ex.InnerException is { } inner) ex = inner;
+        return ex;
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// 提取 <paramref name="ex"/> 与其 InnerException 的描述,汇总到一行。
+    /// 提取 <paramref name="ex"/> 的详细描述与堆栈信息。返回内容总是多于一行。
+    /// </summary>
+    /// <param name="onlyRelatedStacks">是否显示所有堆栈,而不是仅与当前。</param>
+    public static string GetDetail(this Exception? ex, bool onlyRelatedStacks = true) {
+        if (ex is null) return "无可用错误信息!";
+        return GetSummary(ex, isDetail: true, onlyRelatedStacks);
+    }
+    /// <summary>
+    /// 提取 <paramref name="ex"/> 的简要描述,将信息汇总到一行。
     /// </summary>
     /// </summary>
     public static string GetBrief(this Exception? ex) {
     public static string GetBrief(this Exception? ex) {
         if (ex is null) return "无可用错误信息!";
         if (ex is null) return "无可用错误信息!";
+        return GetSummary(ex, isDetail: false);
+    }
 
 
-        // 获取最底层的异常
-        var outerEx = ex;
-        var innerEx = ex;
-        while (innerEx.InnerException is not null) innerEx = innerEx.InnerException;
-
-        // 获取各级错误的描述
-        var descList = new List<string>();
-        while (ex is not null) {
-            descList.Add(ex.Message.ReplaceLineEndings(" ", true));
-            ex = ex.InnerException;
+    /// <summary>
+    /// 提取 <paramref name="ex"/> 的描述。
+    /// </summary>
+    public static string GetSummary(Exception ex, bool isDetail, bool onlyRelatedStacks = true) {
+        var innerEx = ex.RootException();
+        var lines = new List<string>();
+        bool isInner = false;
+        for (Exception? cur = ex; cur is not null; cur = cur.InnerException) {
+            if (isDetail) {
+                lines.Add((isInner ? "→ " : "") + cur.Message.ReplaceLineEndings("\r\n", true));
+                if (cur.StackTrace is not null) {
+                    foreach (string stack in cur.StackTrace.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)) {
+                        if (onlyRelatedStacks && !stack.ContainsIgnoreCase("pcl")) continue;
+                        lines.Add(stack.ReplaceLineEndings(""));
+                    }
+                }
+                if (cur.GetType().FullName != "System.Exception") {
+                    lines.Add("   错误类型:" + cur.GetType().FullName);
+                }
+            } else {
+                lines.Add(cur.Message.ReplaceLineEndings(" ", true));
+            }
+            isInner = true;
         }
         }
-        descList = descList.Distinct().ToList();
-
-        // 构造输出信息
-        string? usualReason = AnalyzeUsualReason(innerEx, outerEx, descList);
-        if (usualReason is not null)
-            return usualReason + "详细错误:" + descList.First();
-        descList.Reverse();
-        return descList.Join(" ← ");
-    }
 
 
-    private static string? AnalyzeUsualReason(Exception innerEx, Exception outerEx, List<string> descList) {
+        string? commonReason = null;
         if (innerEx is TypeLoadException or BadImageFormatException or MissingMethodException or NotImplementedException or TypeInitializationException)
         if (innerEx is TypeLoadException or BadImageFormatException or MissingMethodException or NotImplementedException or TypeInitializationException)
-            return "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.8 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。";
-        if (innerEx is UnauthorizedAccessException)
-            return "PCL 的权限不足。请尝试右键 PCL 选择以管理员身份运行。";
-        if (innerEx is OutOfMemoryException)
-            return "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。";
-        if (innerEx is COMException)
-            return "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。";
-        if (innerEx is System.Net.Sockets.SocketException && descList.Any(l => l.Contains("WSAStartup")))
-            return "请尝试卸载中国移动云盘,然后再试。";
-        if (outerEx.IsNetworkRelated())
-            return "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。";
-        return null;
+            commonReason = "运行环境存在问题。请尝试重新安装 .NET Framework 4.8 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。";
+        else if (innerEx is UnauthorizedAccessException)
+            commonReason = "权限不足。请尝试右键程序,选择以管理员身份运行,或将文件移动到其他文件夹。";
+        else if (innerEx is OutOfMemoryException)
+            commonReason = "系统的运行内存不足。请在关闭一部分不需要的程序后再试。";
+        else if (innerEx is System.Net.Sockets.SocketException && lines.Any(l => l.Contains("WSAStartup")))
+            commonReason = "请尝试卸载中国移动云盘,然后再试。";
+        else if (ex.IsNetworkRelated())
+            commonReason = "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。";
+
+        if (isDetail) {
+            if (commonReason is null) {
+                return lines.Join("\r\n");
+            } else {
+                return commonReason + "\r\n\r\n————————————\r\n详细错误信息:\r\n" + lines.Join("\r\n");
+            }
+        } else {
+            lines = lines.Distinct().ToList();
+            if (commonReason is null) {
+                lines.Reverse();
+                return lines.Join(" ← ");
+            } else {
+                return commonReason + "详细错误:" + lines.First();
+            }
+        }
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// 判断某个 <paramref name="ex"/> 是否为网络问题所导致。
+    /// 判断某个 <paramref name="ex"/> 是否为网络连接不良所导致。
     /// </summary>
     /// </summary>
     public static bool IsNetworkRelated(this Exception ex) {
     public static bool IsNetworkRelated(this Exception ex) {
         // 提取 Message(不能用 GetDetail,因为堆栈的方法参数中就有 timeout 字样)
         // 提取 Message(不能用 GetDetail,因为堆栈的方法参数中就有 timeout 字样)
@@ -99,4 +98,4 @@ public static class ExceptionExtensions {
             "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", "基础连接已经关闭"
             "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", "基础连接已经关闭"
         }.Any(k => detail.ContainsIgnoreCase(k));
         }.Any(k => detail.ContainsIgnoreCase(k));
     }
     }
-}
+}

+ 3 - 3
Shared/Utils/FileUtils.cs

@@ -1,5 +1,4 @@
-using System.IO;
-using System.IO.Compression;
+using System.IO.Compression;
 
 
 namespace MeloongCore;
 namespace MeloongCore;
 public static class FileUtils {
 public static class FileUtils {
@@ -207,12 +206,13 @@ public static class FileUtils {
             // ZipSlip 修复
             // ZipSlip 修复
             string outputFilePath = PathUtils.WithLongPath(Path.GetFullPath(Path.Combine(outputDirectory, entry.FullName)));
             string outputFilePath = PathUtils.WithLongPath(Path.GetFullPath(Path.Combine(outputDirectory, entry.FullName)));
             if (!outputFilePath.StartsWithF(PathUtils.WithSeparator(PathUtils.WithLongPath(Path.GetFullPath(outputDirectory))), ignoreCase:true))
             if (!outputFilePath.StartsWithF(PathUtils.WithSeparator(PathUtils.WithLongPath(Path.GetFullPath(outputDirectory))), ignoreCase:true))
-                throw new UnauthorizedAccessException($"Zip 文件项 {entry.FullName} 的路径在压缩包之外,这可能导致安全问题");
+                throw new ZipSlipException($"Zip 文件项 {entry.FullName} 的路径在压缩包之外,这可能导致安全问题");
             // 实际的解压
             // 实际的解压
             using var entryStream = entry.Open();
             using var entryStream = entry.Open();
             FileUtils.Write(outputFilePath, entryStream);
             FileUtils.Write(outputFilePath, entryStream);
         }
         }
     }
     }
+    public class ZipSlipException(string message) : Exception(message) {}
 
 
     /// <summary>
     /// <summary>
     /// 将指定文件夹的内容打包为 zip 文件。
     /// 将指定文件夹的内容打包为 zip 文件。

+ 1 - 1
Tests/Utils/FileUtilsTest.cs

@@ -38,7 +38,7 @@ public class FileUtilsTest : TestWithFolder {
     [InlineData("DotDot ZipSlip.zip")]
     [InlineData("DotDot ZipSlip.zip")]
     [InlineData("AbsPath ZipSlip.zip")]
     [InlineData("AbsPath ZipSlip.zip")]
     public void Compression_ZipSlip(string testFile) {
     public void Compression_ZipSlip(string testFile) {
-        Assert.Throws<UnauthorizedAccessException>(() => FileUtils.ExtractToDirectory(GetTestFile(testFile), tempFolder));
+        Assert.Throws<FileUtils.ZipSlipException>(() => FileUtils.ExtractToDirectory(GetTestFile(testFile), tempFolder));
     }
     }
 
 
     #endregion
     #endregion

+ 14 - 0
Wpf/Utils/SystemUtils.cs

@@ -0,0 +1,14 @@
+using System.Security.Principal;
+
+namespace MeloongCore;
+public static class SystemUtils {
+
+    /// <summary>
+    /// 当前程序是否拥有管理员权限。
+    /// </summary>
+    public static bool HasAdminRole() {
+        using var id = WindowsIdentity.GetCurrent();
+        return new WindowsPrincipal(id).IsInRole(WindowsBuiltInRole.Administrator);
+    }
+
+}