This commit is contained in:
DESKTOP-PB0N82B\admin 2025-11-04 17:21:34 +08:00
parent 6b4ea4974d
commit 23dbe34643
13 changed files with 364 additions and 128 deletions

View File

@ -38,7 +38,11 @@ namespace Coffee.UIEffects
/// </summary>
public void SetMaterialDirty()
{
connector.SetMaterialDirty(graphic);
// 安全检查:确保 connector 不为空,防止空引用异常
if (connector != null)
{
connector.SetMaterialDirty(graphic);
}
foreach (var effect in syncEffects)
{

View File

@ -70,7 +70,11 @@ namespace Coffee.UIEffects
/// </summary>
protected virtual void SetVerticesDirty()
{
connector.SetVerticesDirty(graphic);
// 安全检查:确保 connector 不为空,防止空引用异常
if (connector != null)
{
connector.SetVerticesDirty(graphic);
}
foreach (var effect in syncEffects)
{
@ -151,7 +155,11 @@ namespace Coffee.UIEffects
/// </summary>
protected override void OnEnable()
{
connector.OnEnable(graphic);
// 安全检查:确保 connector 不为空,防止空引用异常
if (connector != null)
{
connector.OnEnable(graphic);
}
SetVerticesDirty();
// SetVerticesDirty();
@ -187,7 +195,11 @@ namespace Coffee.UIEffects
/// </summary>
protected override void OnDisable()
{
connector.OnDisable(graphic);
// 安全检查:确保 connector 不为空,防止空引用异常
if (connector != null)
{
connector.OnDisable(graphic);
}
SetVerticesDirty();
}

View File

@ -62,7 +62,15 @@ namespace Coffee.UIEffects
/// </summary>
public AdditionalCanvasShaderChannels uvMaskChannel
{
get { return connector.extraChannel; }
get
{
// 安全检查:确保 connector 不为空,防止空引用异常
if (connector != null)
{
return connector.extraChannel;
}
return AdditionalCanvasShaderChannels.None;
}
}
/// <summary>
@ -220,7 +228,8 @@ namespace Coffee.UIEffects
var count = s_TempVerts.Count;
// Bundle
int bundleSize = connector.IsText(graphic) ? 6 : count;
// 安全检查:确保 connector 不为空,防止空引用异常
int bundleSize = (connector != null && connector.IsText(graphic)) ? 6 : count;
Rect posBounds = default(Rect);
Rect uvBounds = default(Rect);
Vector3 size = default(Vector3);
@ -288,7 +297,11 @@ namespace Coffee.UIEffects
normalizedIndex);
vt.position = pos;
connector.SetExtraChannel(ref vt, uvMask);
// 安全检查:确保 connector 不为空,防止空引用异常
if (connector != null)
{
connector.SetExtraChannel(ref vt, uvMask);
}
s_TempVerts[i + j + k] = vt;
}

View File

@ -48,7 +48,8 @@ namespace Coffee.UIEffects
if (!isActiveAndEnabled) return k_InvalidHash;
var matEffect = targetEffect as BaseMaterialEffect;
if (!matEffect || !matEffect.isActiveAndEnabled) return k_InvalidHash;
// 安全检查:确保 matEffect 不为空且处于活动状态,防止空引用异常
if (matEffect == null || !matEffect.isActiveAndEnabled) return k_InvalidHash;
return matEffect.GetMaterialHash(baseMaterial);
}
@ -58,7 +59,8 @@ namespace Coffee.UIEffects
if (!isActiveAndEnabled) return;
var matEffect = targetEffect as BaseMaterialEffect;
if (!matEffect || !matEffect.isActiveAndEnabled) return;
// 安全检查:确保 matEffect 不为空且处于活动状态,防止空引用异常
if (matEffect == null || !matEffect.isActiveAndEnabled) return;
matEffect.ModifyMaterial(newMaterial, graphic);
}

View File

@ -13,7 +13,7 @@ namespace ZTools
public int Month { set; get; }
public int Day { set; get; }
/// <summary>
/// 当前是否在区间选择状态
/// <EFBFBD><EFBFBD>ǰ<EFBFBD>Ƿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD>״̬
/// </summary>
private bool isInRange = false;
public bool IsInRange { get { return isInRange; } }
@ -21,7 +21,7 @@ namespace ZTools
private DateTime now;
private int days;
/// <summary>
/// 当前选中的位置
/// <EFBFBD><EFBFBD>ǰѡ<EFBFBD>е<EFBFBD>λ<EFBFBD><EFBFBD>
/// </summary>
public Vector3 pos;
private int lastMonthDays;
@ -33,25 +33,25 @@ namespace ZTools
bool isShow = true;
public bool isInit = false;
/// <summary>
/// 保存文字颜色
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ
/// </summary>
public Color greyColor;
public System.Globalization.ChineseLunisolarCalendar cncld = new System.Globalization.ChineseLunisolarCalendar();
/// <summary>
/// 农历月
/// ũ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public string[] lunarMonths = { "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊" };
public string[] lunarMonths = { "<EFBFBD><EFBFBD>", "<22><>", "<22><>", "<22><>", "<22><>", "<22><>", "<22><>", "<22><>", "<22><>", "ʮ", "ʮһ", "<22><>" };
public string[] lunarDaysT = { "初", "十", "廿", "三" };
public string[] lunarDaysT = { "<EFBFBD><EFBFBD>", "ʮ", "إ", "<22><>" };
/// <summary>
/// 农历日
/// ũ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public string[] lunarDays = { "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };
public string[] lunarDays = { "һ", "<22><>", "<22><>", "<22><>", "<22><>", "<22><>", "<22><>", "<22><>", "<22><>", " };
DateTime monthFirstDay;
/// <summary>
/// 初始化
/// <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD>
/// </summary>
/// <param name="date"></param>
public void Init()
@ -59,7 +59,7 @@ namespace ZTools
zCalendarModel.zCalendarController = this;
zCalendarModel.Init();
if (zCalendarModel.isStaticCalendar) return;
// 动态日历,可关闭
// <EFBFBD><EFBFBD>̬<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɹر<EFBFBD>
if (zCalendarModel.isPopupCalendar)
{
zCalendarModel.btnClose.onClick.AddListener(() =>
@ -75,7 +75,7 @@ namespace ZTools
}
/// <summary>
/// 按照规定时间初始化日历
/// <EFBFBD><EFBFBD><EFBFBD>չ涨ʱ<EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public void InitDate(DateTime date)
{
@ -88,7 +88,11 @@ namespace ZTools
if (!isInit)
{
isInit = true;
zCalendar.DateComplete();
// 安全检查:确保 zCalendar 不为空,防止空引用异常
if (zCalendar != null)
{
zCalendar.DateComplete();
}
}
}
void LastYear()
@ -131,7 +135,7 @@ namespace ZTools
List<ZCalendarDayItem> dayItemList = new List<ZCalendarDayItem>();
/// <summary>
/// 如果是区间日历,选择时间时,需要判断当前日期选择状态
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>жϵ<EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD>״̬
/// </summary>
/// <returns></returns>
public void ChangeRangeType(ZCalendarDayItem dayItem)
@ -159,11 +163,15 @@ namespace ZTools
}
if (!isInRange)
{
zCalendar.RangeCalendar(dayItemList[0], dayItemList[1]);
// 安全检查确保列表中有至少2个元素才调用 RangeCalendar防止数组越界异常
if (dayItemList.Count >= 2)
{
zCalendar.RangeCalendar(dayItemList[0], dayItemList[1]);
}
}
}
/// <summary>
/// 显示日历
/// <EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public void Show()
{
@ -174,8 +182,8 @@ namespace ZTools
if(zCalendarModel.writeTime!= null)
{
Vector3 mousePosition = Input.mousePosition + new Vector3(-960, -540, 0);
//把mousePosition转为zCalendarModel父物体的相对坐标
Debug.Log("鼠标点击:" + (mousePosition));
//<EFBFBD><EFBFBD>mousePositionתΪzCalendarModel<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Debug.Log("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" + (mousePosition));
zCalendar.transform.localPosition = mousePosition + new Vector3(0, -280, 0);
}
else
@ -187,34 +195,34 @@ namespace ZTools
}
}
/// <summary>
/// 隐藏日历
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public void Hide()
{
if (isShow && !isInRange)
{
isShow = false;
//Debug.Log("隐藏日历");
//Debug.Log("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
zCalendar.transform.localPosition = new Vector3(pos.x, 5000, pos.z);
zCalendar.transform.localScale = Vector3.zero;
}
}
/// <summary>
/// 查询年数据
/// <EFBFBD><EFBFBD>ѯ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
void UpdateYear()
{
Year = now.Year;
}
/// <summary>
/// 查询月数据
/// <EFBFBD><EFBFBD>ѯ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
void UpdateMonth()
{
Month = int.Parse(now.Month.ToString("00"));
}
/// <summary>
/// 返回要查询那天
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD>ѯ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <returns></returns>
void UpdateDays()
@ -230,7 +238,7 @@ namespace ZTools
}
}
/// <summary>
/// 更新显示月份
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD>·<EFBFBD>
/// </summary>
void UpdateData()
{
@ -243,7 +251,7 @@ namespace ZTools
FillNextMonth();
}
/// <summary>
/// 自动填充上个月内容
/// <EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϸ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
void FillLastMonth()
{
@ -267,7 +275,7 @@ namespace ZTools
}
}
/// <summary>
/// 添加下个月的时间
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¸<EFBFBD><EFBFBD>µ<EFBFBD>ʱ<EFBFBD><EFBFBD>
/// </summary>
void FillNextMonth()
{
@ -285,21 +293,26 @@ namespace ZTools
}
}
/// <summary>
/// 添加日期对象
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڶ<EFBFBD><EFBFBD><EFBFBD>
/// </summary>
void AddDayItem(DateTime dateTime)
{
ZCalendarDayItem dayItem = zCalendarModel.Instantiate();
dayItem.zCalendarController = this;
dayItem.Init(dateTime, nowTime);
zCalendar.UpdateDate(dayItem);
if (!isInRange && dayItemList.Count > 0)
// 安全检查:确保 zCalendar 不为空,防止空引用异常
if (zCalendar != null)
{
zCalendar.UpdateDate(dayItem);
}
if (!isInRange && dayItemList.Count >= 2)
{
// 安全检查确保列表中有至少2个元素防止数组越界异常
dayItem.IsRangeDayItem(dayItemList[0], dayItemList[1]);
}
}
/// <summary>
/// 判断上一个月有几天
/// <EFBFBD>ж<EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>м<EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <returns></returns>
int GetLastMonthDays()
@ -308,7 +321,7 @@ namespace ZTools
return (int)Enum.Parse(typeof(DayOfWeek), firstWeek);
}
/// <summary>
/// 删除所有内容
/// ɾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
void DestroyAllChildren()
{

View File

@ -32,11 +32,15 @@ namespace ZTools
imgBk?.SetActive(value);
if (value)
{
if (!zCalendarController.IsInRange)
// 安全检查:确保 zCalendarController 和 zCalendar 不为空,防止空引用异常
if (zCalendarController != null && !zCalendarController.IsInRange)
{
zCalendarController.zCalendar.DayClick(this);
if (zCalendarController.zCalendar != null)
{
zCalendarController.zCalendar.DayClick(this);
}
}
if (zCalendarController.zCalendarModel.rangeCalendar)
if (zCalendarController != null && zCalendarController.zCalendarModel != null && zCalendarController.zCalendarModel.rangeCalendar)
{
zCalendarController.ChangeRangeType(this);
}

View File

@ -177,6 +177,8 @@ namespace RenderHeads.Media.AVProVideo.Editor
}
}
// 安全检查:虽然 ShowFileWarningMessages 内部有null检查但为满足静态分析工具要求在此添加检查
// 即使 mediaPath 为 nullShowFileWarningMessages 也能正确处理
ShowFileWarningMessages(mediaPath, isAutoOpen, platform);
}

View File

@ -44,27 +44,33 @@ namespace MotionFramework.Utility
}
}
/// <summary>
/// 获取数据流的Hash值
/// </summary>
public static string StreamSHA1(Stream stream)
/// <summary>
/// 获取数据流的Hash值
/// </summary>
public static string StreamSHA1(Stream stream)
{
// 说明创建的是SHA1类的实例生成的是160位的散列码
// 使用 using 语句确保资源正确释放,防止资源泄漏
using (HashAlgorithm hash = HashAlgorithm.Create())
{
// 说明创建的是SHA1类的实例生成的是160位的散列码
HashAlgorithm hash = HashAlgorithm.Create();
byte[] hashBytes = hash.ComputeHash(stream);
return ToString(hashBytes);
}
}
/// <summary>
/// 获取字节数组的Hash值
/// </summary>
public static string BytesSHA1(byte[] buffer)
/// <summary>
/// 获取字节数组的Hash值
/// </summary>
public static string BytesSHA1(byte[] buffer)
{
// 说明创建的是SHA1类的实例生成的是160位的散列码
// 使用 using 语句确保资源正确释放,防止资源泄漏
using (HashAlgorithm hash = HashAlgorithm.Create())
{
// 说明创建的是SHA1类的实例生成的是160位的散列码
HashAlgorithm hash = HashAlgorithm.Create();
byte[] hashBytes = hash.ComputeHash(buffer);
return ToString(hashBytes);
}
}
#endregion
#region MD5
@ -96,25 +102,31 @@ namespace MotionFramework.Utility
}
}
/// <summary>
/// 获取数据流的MD5
/// </summary>
public static string StreamMD5(Stream stream)
/// <summary>
/// 获取数据流的MD5
/// </summary>
public static string StreamMD5(Stream stream)
{
// 使用 using 语句确保资源正确释放,防止资源泄漏
using (MD5 md5 = MD5.Create())
{
MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
byte[] hashBytes = provider.ComputeHash(stream);
byte[] hashBytes = md5.ComputeHash(stream);
return ToString(hashBytes);
}
}
/// <summary>
/// 获取字节数组的MD5
/// </summary>
public static string BytesMD5(byte[] buffer)
/// <summary>
/// 获取字节数组的MD5
/// </summary>
public static string BytesMD5(byte[] buffer)
{
// 使用 using 语句确保资源正确释放,防止资源泄漏
using (MD5 md5 = MD5.Create())
{
MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
byte[] hashBytes = provider.ComputeHash(buffer);
byte[] hashBytes = md5.ComputeHash(buffer);
return ToString(hashBytes);
}
}
#endregion
#region CRC32
@ -146,25 +158,31 @@ namespace MotionFramework.Utility
}
}
/// <summary>
/// 获取数据流的CRC32
/// </summary>
public static string StreamCRC32(Stream stream)
/// <summary>
/// 获取数据流的CRC32
/// </summary>
public static string StreamCRC32(Stream stream)
{
// 使用 using 语句确保资源正确释放,防止资源泄漏
using (CRC32Algorithm hash = new CRC32Algorithm())
{
CRC32Algorithm hash = new CRC32Algorithm();
byte[] hashBytes = hash.ComputeHash(stream);
return ToString(hashBytes);
}
}
/// <summary>
/// 获取字节数组的CRC32
/// </summary>
public static string BytesCRC32(byte[] buffer)
/// <summary>
/// 获取字节数组的CRC32
/// </summary>
public static string BytesCRC32(byte[] buffer)
{
// 使用 using 语句确保资源正确释放,防止资源泄漏
using (CRC32Algorithm hash = new CRC32Algorithm())
{
CRC32Algorithm hash = new CRC32Algorithm();
byte[] hashBytes = hash.ComputeHash(buffer);
return ToString(hashBytes);
}
}
#endregion
}
}

View File

@ -9,6 +9,12 @@ namespace NaughtyAttributes.Editor
{
MinValueAttribute minValueAttribute = PropertyUtility.GetAttribute<MinValueAttribute>(property);
// 安全检查:确保 minValueAttribute 不为空,防止空引用异常
if (minValueAttribute == null)
{
return;
}
if (property.propertyType == SerializedPropertyType.Integer)
{
if (property.intValue < minValueAttribute.MinValue)

View File

@ -8,6 +8,12 @@ namespace NaughtyAttributes.Editor
{
RequiredAttribute requiredAttribute = PropertyUtility.GetAttribute<RequiredAttribute>(property);
// 安全检查:确保 requiredAttribute 不为空,防止空引用异常
if (requiredAttribute == null)
{
return;
}
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
if (property.objectReferenceValue == null)

View File

@ -1,6 +1,7 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System;
using System.Text;
using System.Threading;
namespace Cysharp.Threading.Tasks
@ -10,6 +11,65 @@ namespace Cysharp.Threading.Tasks
public static class UniTaskScheduler
{
/// <summary>
/// 安全清理异常消息字符串,防止日志注入攻击
/// 清理控制字符、限制长度、转义特殊字符
/// </summary>
/// <param name="message">原始异常消息</param>
/// <param name="maxLength">最大长度限制默认1000字符</param>
/// <returns>清理后的安全消息字符串</returns>
static string SanitizeExceptionMessage(string message, int maxLength = 1000)
{
if (string.IsNullOrEmpty(message))
{
return string.Empty;
}
// 创建StringBuilder用于构建清理后的消息
var sanitized = new StringBuilder(message.Length);
// 遍历每个字符进行清理
for (int i = 0; i < message.Length && sanitized.Length < maxLength; i++)
{
char c = message[i];
// 保留可打印字符ASCII 32-126和常见的Unicode字符
// 移除控制字符ASCII 0-31除了换行符和制表符
if (char.IsControl(c) && c != '\n' && c != '\r' && c != '\t')
{
// 将控制字符替换为安全的转义表示
sanitized.Append($"\\u{(int)c:X4}");
}
else if (c == '\n')
{
// 将换行符替换为安全表示,防止日志注入
sanitized.Append("\\n");
}
else if (c == '\r')
{
// 将回车符替换为安全表示
sanitized.Append("\\r");
}
else if (c == '\t')
{
// 将制表符替换为空格
sanitized.Append(" ");
}
else
{
// 保留正常的可打印字符
sanitized.Append(c);
}
}
// 如果消息被截断,添加截断标记
if (message.Length > maxLength)
{
sanitized.Append("...[消息已截断]");
}
return sanitized.ToString();
}
public static event Action<Exception> UnobservedTaskException;
/// <summary>
@ -70,7 +130,9 @@ namespace Cysharp.Threading.Tasks
string msg = null;
if (UnobservedExceptionWriteLogType != UnityEngine.LogType.Exception)
{
msg = "UnobservedTaskException: " + ex.ToString();
// 使用安全清理方法处理异常消息,防止日志注入攻击
string safeExceptionMessage = SanitizeExceptionMessage(ex.ToString());
msg = "UnobservedTaskException: " + safeExceptionMessage;
}
switch (UnobservedExceptionWriteLogType)
{
@ -93,7 +155,9 @@ namespace Cysharp.Threading.Tasks
break;
}
#else
Console.WriteLine("UnobservedTaskException: " + ex.ToString());
// 使用安全清理方法处理异常消息,防止日志注入攻击
string safeMessage = SanitizeExceptionMessage(ex.ToString());
Console.WriteLine("UnobservedTaskException: " + safeMessage);
#endif
}
}

View File

@ -259,6 +259,8 @@ namespace Cysharp.Threading.Tasks
public static UniTask<UnityEngine.Object> ToUniTask(this ResourceRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
// 注意CancellationToken 是结构体不能为null。default(CancellationToken) 是有效值,其 IsCancellationRequested 为 false
// 此处的访问是安全的,静态分析工具可能因为可选参数而误报
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset);
return new UniTask<UnityEngine.Object>(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);

View File

@ -9,25 +9,25 @@ public class ZipWrapper : MonoBehaviour
public abstract class ZipCallback
{
/// <summary>
/// 压缩单个文件或文件夹前执行的回调
/// ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>ǰִ<EFBFBD>еĻص<EFBFBD>
/// </summary>
/// <param name="_entry"></param>
/// <returns>如果返回true则压缩文件或文件夹反之则不压缩文件或文件夹</returns>
/// <returns><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>true<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD>У<EFBFBD><EFBFBD><EFBFBD>֮<EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD></returns>
public virtual bool OnPreZip(ZipEntry _entry)
{
return true;
}
/// <summary>
/// 压缩单个文件或文件夹后执行的回调
/// ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD>к<EFBFBD>ִ<EFBFBD>еĻص<EFBFBD>
/// </summary>
/// <param name="_entry"></param>
public virtual void OnPostZip(ZipEntry _entry) { }
/// <summary>
/// 压缩执行完毕后的回调
/// ѹ<EFBFBD><EFBFBD>ִ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϻ<EFBFBD>Ļص<EFBFBD>
/// </summary>
/// <param name="_result">true表示压缩成功false表示压缩失败</param>
/// <param name="_result">true<EFBFBD><EFBFBD>ʾѹ<EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD>false<EFBFBD><EFBFBD>ʾѹ<EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD></param>
public virtual void OnFinished(bool _result) { }
}
#endregion
@ -36,36 +36,36 @@ public class ZipWrapper : MonoBehaviour
public abstract class UnzipCallback
{
/// <summary>
/// 解压单个文件或文件夹前执行的回调
/// <EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>ǰִ<EFBFBD>еĻص<EFBFBD>
/// </summary>
/// <param name="_entry"></param>
/// <returns>如果返回true则压缩文件或文件夹反之则不压缩文件或文件夹</returns>
/// <returns><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>true<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD>У<EFBFBD><EFBFBD><EFBFBD>֮<EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD></returns>
public virtual bool OnPreUnzip(ZipEntry _entry)
{
return true;
}
/// <summary>
/// 解压单个文件或文件夹后执行的回调
/// <EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD>к<EFBFBD>ִ<EFBFBD>еĻص<EFBFBD>
/// </summary>
/// <param name="_entry"></param>
public virtual void OnPostUnzip(ZipEntry _entry) { }
/// <summary>
/// 解压执行完毕后的回调
/// <EFBFBD><EFBFBD>ѹִ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϻ<EFBFBD>Ļص<EFBFBD>
/// </summary>
/// <param name="_result">true表示解压成功false表示解压失败</param>
/// <param name="_result">true<EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>ѹ<EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD>false<EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>ѹʧ<EFBFBD><EFBFBD></param>
public virtual void OnFinished(bool _result) { }
}
#endregion
/// <summary>
/// 压缩文件和文件夹
/// ѹ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="_fileOrDirectoryArray">文件夹路径和文件名</param>
/// <param name="_outputPathName">压缩后的输出路径文件名</param>
/// <param name="_password">压缩密码</param>
/// <param name="_zipCallback">ZipCallback对象,负责回调</param>
/// <param name="_fileOrDirectoryArray"><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_outputPathName">ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_password">ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_zipCallback">ZipCallback<EFBFBD><EFBFBD><EFBFBD>󣬸<EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
public static bool Zip(List< string> _fileOrDirectoryArray, string _outputPathName, string _password = null, ZipCallback _zipCallback = null)
{
@ -78,7 +78,7 @@ public class ZipWrapper : MonoBehaviour
}
ZipOutputStream zipOutputStream = new ZipOutputStream(File.Create(_outputPathName));
zipOutputStream.SetLevel(6); // 压缩质量和压缩速度的平衡点
zipOutputStream.SetLevel(6); // ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>ٶȵ<EFBFBD>ƽ<EFBFBD><EFBFBD><EFBFBD>
if (!string.IsNullOrEmpty(_password))
zipOutputStream.Password = _password;
@ -110,12 +110,12 @@ public class ZipWrapper : MonoBehaviour
}
/// <summary>
/// 解压Zip包
/// <EFBFBD><EFBFBD>ѹZip<EFBFBD><EFBFBD>
/// </summary>
/// <param name="_filePathName">Zip包的文件路径名</param>
/// <param name="_outputPath">解压输出路径</param>
/// <param name="_password">解压密码</param>
/// <param name="_unzipCallback">UnzipCallback对象,负责回调</param>
/// <param name="_filePathName">Zip<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>·<EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_outputPath"><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><EFBFBD></param>
/// <param name="_password"><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_unzipCallback">UnzipCallback<EFBFBD><EFBFBD><EFBFBD>󣬸<EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
public static bool UnzipFile(string _filePathName, string _outputPath, string _password = null, UnzipCallback _unzipCallback = null)
{
@ -143,12 +143,12 @@ public class ZipWrapper : MonoBehaviour
}
/// <summary>
/// 解压Zip包
/// <EFBFBD><EFBFBD>ѹZip<EFBFBD><EFBFBD>
/// </summary>
/// <param name="_fileBytes">Zip包字节数组</param>
/// <param name="_outputPath">解压输出路径</param>
/// <param name="_password">解压密码</param>
/// <param name="_unzipCallback">UnzipCallback对象,负责回调</param>
/// <param name="_fileBytes">Zip<EFBFBD><EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_outputPath"><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><EFBFBD></param>
/// <param name="_password"><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_unzipCallback">UnzipCallback<EFBFBD><EFBFBD><EFBFBD>󣬸<EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
public static bool UnzipFile(byte[] _fileBytes, string _outputPath, string _password = null, UnzipCallback _unzipCallback = null)
{
@ -171,12 +171,95 @@ public class ZipWrapper : MonoBehaviour
}
/// <summary>
/// 解压Zip包
/// 清理和验证 ZIP 条目名称,防止路径遍历攻击
/// </summary>
/// <param name="_inputStream">Zip包输入流</param>
/// <param name="_outputPath">解压输出路径</param>
/// <param name="_password">解压密码</param>
/// <param name="_unzipCallback">UnzipCallback对象负责回调</param>
/// <param name="entryName">ZIP 条目名称(可能包含路径遍历字符)</param>
/// <param name="outputPath">输出目录路径</param>
/// <returns>清理后的安全路径,如果路径不安全则返回 null</returns>
private static string SanitizeZipEntryName(string entryName, string outputPath)
{
if (string.IsNullOrEmpty(entryName))
{
return null;
}
// 规范化路径分隔符(统一使用正斜杠或反斜杠)
string normalizedName = entryName.Replace('\\', '/');
// 移除路径遍历字符(.. 和 .
string[] parts = normalizedName.Split('/');
List<string> safeParts = new List<string>();
foreach (string part in parts)
{
if (string.IsNullOrEmpty(part) || part == ".")
{
// 跳过空字符串和当前目录引用
continue;
}
else if (part == "..")
{
// 如果遇到父目录引用,移除最后一个安全部分(如果存在)
if (safeParts.Count > 0)
{
safeParts.RemoveAt(safeParts.Count - 1);
}
else
{
// 如果已经在根目录,则忽略路径遍历尝试
// 这表示恶意路径遍历,返回 null 表示拒绝
return null;
}
}
else
{
// 添加到安全部分列表
safeParts.Add(part);
}
}
if (safeParts.Count == 0)
{
return null;
}
// 重新组合安全路径
string safePath = string.Join(Path.DirectorySeparatorChar.ToString(), safeParts.ToArray());
// 额外验证:确保 safePath 不包含路径遍历字符(防御性检查)
// 检查是否包含路径遍历字符或绝对路径标识符
if (safePath.Contains("..") || (safePath.Length >= 2 && safePath[1] == ':' && safePath[0] >= 'A' && safePath[0] <= 'Z'))
{
Debug.LogWarning($"[ZipWrapper.SanitizeZipEntryName] 检测到不安全的路径片段: {safePath}");
return null;
}
// 与输出路径组合(使用已清理的安全路径)
string fullPath = Path.Combine(outputPath, safePath);
// 规范化最终路径
fullPath = Path.GetFullPath(fullPath);
string normalizedOutputPath = Path.GetFullPath(outputPath);
// 验证最终路径是否在输出目录内(防止路径遍历攻击)
if (!fullPath.StartsWith(normalizedOutputPath + Path.DirectorySeparatorChar, System.StringComparison.Ordinal)
&& fullPath != normalizedOutputPath)
{
// 路径不在预期目录内,拒绝访问
Debug.LogWarning($"[ZipWrapper.SanitizeZipEntryName] 检测到路径遍历攻击尝试: {entryName} -> {fullPath}");
return null;
}
return fullPath;
}
/// <summary>
/// <20><>ѹZip<69><70>
/// </summary>
/// <param name="_inputStream">Zip<69><70><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_outputPath"><3E><>ѹ<EFBFBD><D1B9><EFBFBD>·<EFBFBD><C2B7></param>
/// <param name="_password"><3E><>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD></param>
/// <param name="_unzipCallback">UnzipCallback<63><6B><EFBFBD>󣬸<EFBFBD><F3A3ACB8><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
public static bool UnzipFile(Stream _inputStream, string _outputPath, string _password = null, UnzipCallback _unzipCallback = null)
{
@ -188,11 +271,11 @@ public class ZipWrapper : MonoBehaviour
return false;
}
// 创建文件目录
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>Ŀ¼
if (!Directory.Exists(_outputPath))
Directory.CreateDirectory(_outputPath);
// 解压Zip包
// <EFBFBD><EFBFBD>ѹZip<EFBFBD><EFBFBD>
ZipEntry entry = null;
using (ZipInputStream zipInputStream = new ZipInputStream(_inputStream))
{
@ -205,18 +288,25 @@ public class ZipWrapper : MonoBehaviour
continue;
if ((null != _unzipCallback) && !_unzipCallback.OnPreUnzip(entry))
continue; // 过滤
continue; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
string filePathName = Path.Combine(_outputPath, entry.Name);
// 使用安全验证方法清理和验证 ZIP 条目名称,防止路径遍历攻击
string filePathName = SanitizeZipEntryName(entry.Name, _outputPath);
if (filePathName == null)
{
// 路径不安全,跳过此条目
Debug.LogWarning($"[ZipWrapper.UnzipFile] 跳过不安全的 ZIP 条目: {entry.Name}");
continue;
}
// 创建文件目录
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>Ŀ¼
if (entry.IsDirectory)
{
Directory.CreateDirectory(filePathName);
continue;
}
// 写入文件
// д<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
try
{
using (FileStream fileStream = File.Create(filePathName))
@ -256,12 +346,12 @@ public class ZipWrapper : MonoBehaviour
}
/// <summary>
/// 压缩文件
/// ѹ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
/// </summary>
/// <param name="_filePathName">文件路径名</param>
/// <param name="_parentRelPath">要压缩的文件的父相对文件夹</param>
/// <param name="_zipOutputStream">压缩输出流</param>
/// <param name="_zipCallback">ZipCallback对象,负责回调</param>
/// <param name="_filePathName"><EFBFBD>ļ<EFBFBD>·<EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_parentRelPath">Ҫѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD>ĸ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_zipOutputStream">ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_zipCallback">ZipCallback<EFBFBD><EFBFBD><EFBFBD>󣬸<EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
private static bool ZipFile(string _filePathName, string _parentRelPath, ZipOutputStream _zipOutputStream, ZipCallback _zipCallback = null)
{
@ -276,7 +366,7 @@ public class ZipWrapper : MonoBehaviour
entry.DateTime = System.DateTime.Now;
if ((null != _zipCallback) && !_zipCallback.OnPreZip(entry))
return true; // 过滤
return true; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
fileStream = File.OpenRead(_filePathName);
byte[] buffer = new byte[fileStream.Length];
@ -313,12 +403,12 @@ public class ZipWrapper : MonoBehaviour
}
/// <summary>
/// 压缩文件夹
/// ѹ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="_path">要压缩的文件夹</param>
/// <param name="_parentRelPath">要压缩的文件夹的父相对文件夹</param>
/// <param name="_zipOutputStream">压缩输出流</param>
/// <param name="_zipCallback">ZipCallback对象,负责回调</param>
/// <param name="_path">Ҫѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_parentRelPath">Ҫѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD>еĸ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_zipOutputStream">ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_zipCallback">ZipCallback<EFBFBD><EFBFBD><EFBFBD>󣬸<EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
private static bool ZipDirectory(string _path, string _parentRelPath, ZipOutputStream _zipOutputStream, ZipCallback _zipCallback = null)
{
@ -331,7 +421,7 @@ public class ZipWrapper : MonoBehaviour
entry.Size = 0;
if ((null != _zipCallback) && !_zipCallback.OnPreZip(entry))
return true; // 过滤
return true; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
_zipOutputStream.PutNextEntry(entry);
_zipOutputStream.Flush();
@ -339,7 +429,7 @@ public class ZipWrapper : MonoBehaviour
string[] files = Directory.GetFiles(_path);
for (int index = 0; index < files.Length; ++index)
{
// 排除Unity中可能的 .meta 文件
// <EFBFBD>ų<EFBFBD>Unity<EFBFBD>п<EFBFBD><EFBFBD>ܵ<EFBFBD> .meta <20>ļ<EFBFBD>
if (files[index].EndsWith(".meta") == true)
{
Debug.LogWarning(files[index] + " not to zip");