Test-TaizhouWarehousePhaseII/3d/Assets/Zion/Scripts/配送/Manager/ZipWrapper.cs

558 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using ICSharpCode.SharpZipLib.Zip;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class ZipWrapper : MonoBehaviour
{
#region ZipCallback
public abstract class ZipCallback
{
/// <summary>
/// ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>ǰִ<C7B0>еĻص<C4BB>
/// </summary>
/// <param name="_entry"></param>
/// <returns><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>true<75><65><EFBFBD><EFBFBD>ѹ<EFBFBD><D1B9><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>У<EFBFBD><D0A3><EFBFBD>֮<EFBFBD><D6AE>ѹ<EFBFBD><D1B9><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD></returns>
public virtual bool OnPreZip(ZipEntry _entry)
{
return true;
}
/// <summary>
/// ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>к<EFBFBD>ִ<EFBFBD>еĻص<C4BB>
/// </summary>
/// <param name="_entry"></param>
public virtual void OnPostZip(ZipEntry _entry) { }
/// <summary>
/// ѹ<><D1B9>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD>Ϻ<EFBFBD>Ļص<C4BB>
/// </summary>
/// <param name="_result">true<75><65>ʾѹ<CABE><D1B9><EFBFBD>ɹ<EFBFBD><C9B9><EFBFBD>false<73><65>ʾѹ<CABE><D1B9>ʧ<EFBFBD><CAA7></param>
public virtual void OnFinished(bool _result) { }
}
#endregion
#region UnzipCallback
public abstract class UnzipCallback
{
/// <summary>
/// <20><>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>ǰִ<C7B0>еĻص<C4BB>
/// </summary>
/// <param name="_entry"></param>
/// <returns><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>true<75><65><EFBFBD><EFBFBD>ѹ<EFBFBD><D1B9><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>У<EFBFBD><D0A3><EFBFBD>֮<EFBFBD><D6AE>ѹ<EFBFBD><D1B9><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD></returns>
public virtual bool OnPreUnzip(ZipEntry _entry)
{
return true;
}
/// <summary>
/// <20><>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>к<EFBFBD>ִ<EFBFBD>еĻص<C4BB>
/// </summary>
/// <param name="_entry"></param>
public virtual void OnPostUnzip(ZipEntry _entry) { }
/// <summary>
/// <20><>ѹִ<D1B9><D6B4><EFBFBD><EFBFBD>Ϻ<EFBFBD>Ļص<C4BB>
/// </summary>
/// <param name="_result">true<75><65>ʾ<EFBFBD><CABE>ѹ<EFBFBD>ɹ<EFBFBD><C9B9><EFBFBD>false<73><65>ʾ<EFBFBD><CABE>ѹʧ<D1B9><CAA7></param>
public virtual void OnFinished(bool _result) { }
}
#endregion
/// <summary>
/// ѹ<><D1B9><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>
/// </summary>
/// <param name="_fileOrDirectoryArray"><3E>ļ<EFBFBD><C4BC><EFBFBD>·<EFBFBD><C2B7><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD></param>
/// <param name="_outputPathName">ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD></param>
/// <param name="_password">ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_zipCallback">ZipCallback<63><6B><EFBFBD>󣬸<EFBFBD><F3A3ACB8><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
public static bool Zip(List<string> _fileOrDirectoryArray, string _outputPathName, string _password = null, ZipCallback _zipCallback = null)
{
if ((null == _fileOrDirectoryArray) || string.IsNullOrEmpty(_outputPathName))
{
if (null != _zipCallback)
_zipCallback.OnFinished(false);
return false;
}
// 使用 using 语句确保 FileStream 和 ZipOutputStream 资源被正确释放,即使在异常情况下也能释放
// 注意Unity 平台不支持 System.Security.AccessControl因此使用标准文件创建方式
// 在实际部署时,建议通过操作系统级别的文件权限设置来限制文件访问
using (FileStream fileStream = File.Create(_outputPathName))
{
using (ZipOutputStream zipOutputStream = new ZipOutputStream(fileStream))
{
zipOutputStream.SetLevel(6); // 压缩级别,压缩率与压缩速度的平衡点
if (!string.IsNullOrEmpty(_password))
zipOutputStream.Password = _password;
for (int index = 0; index < _fileOrDirectoryArray.Count; ++index)
{
bool result = false;
string fileOrDirectory = _fileOrDirectoryArray[index];
if (Directory.Exists(fileOrDirectory))
result = ZipDirectory(fileOrDirectory, string.Empty, zipOutputStream, _zipCallback);
else if (File.Exists(fileOrDirectory))
result = ZipFile(fileOrDirectory, string.Empty, zipOutputStream, _zipCallback);
if (!result)
{
if (null != _zipCallback)
_zipCallback.OnFinished(false);
return false;
}
}
zipOutputStream.Finish();
zipOutputStream.Close();
if (null != _zipCallback)
_zipCallback.OnFinished(true);
return true;
}
}
}
/// <summary>
/// <20><>ѹZip<69><70>
/// </summary>
/// <param name="_filePathName">Zip<69><70><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>·<EFBFBD><C2B7><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(string _filePathName, string _outputPath, string _password = null, UnzipCallback _unzipCallback = null)
{
if (string.IsNullOrEmpty(_filePathName) || string.IsNullOrEmpty(_outputPath))
{
if (null != _unzipCallback)
_unzipCallback.OnFinished(false);
return false;
}
try
{
// 使用 using 语句确保 FileStream 资源被正确释放
using (FileStream fileStream = File.OpenRead(_filePathName))
{
return UnzipFile(fileStream, _outputPath, _password, _unzipCallback);
}
}
catch (System.Exception _e)
{
Debug.LogError("[ZipUtility.UnzipFile]: " + _e.ToString());
if (null != _unzipCallback)
_unzipCallback.OnFinished(false);
return false;
}
}
/// <summary>
/// <20><>ѹZip<69><70>
/// </summary>
/// <param name="_fileBytes">Zip<69><70><EFBFBD>ֽ<EFBFBD><D6BD><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(byte[] _fileBytes, string _outputPath, string _password = null, UnzipCallback _unzipCallback = null)
{
if ((null == _fileBytes) || string.IsNullOrEmpty(_outputPath))
{
if (null != _unzipCallback)
_unzipCallback.OnFinished(false);
return false;
}
bool result = UnzipFile(new MemoryStream(_fileBytes), _outputPath, _password, _unzipCallback);
if (!result)
{
if (null != _unzipCallback)
_unzipCallback.OnFinished(false);
}
return result;
}
/// <summary>
/// 清理和验证 ZIP 条目名称,防止路径遍历攻击
/// </summary>
/// <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
{
// 验证路径片段是否安全:检查是否包含路径遍历字符或绝对路径标识符
// 防止恶意构造的路径片段(如 "C:" 或包含 ".." 的片段)
if (part.Contains("..") || part.Contains("\\") || part.Contains(":"))
{
Debug.LogWarning($"[ZipWrapper.SanitizeZipEntryName] 检测到不安全的路径片段: {part}");
return null;
}
// 验证路径片段是否为绝对路径Windows 驱动器号)
if (part.Length >= 2 && part[1] == ':' && part[0] >= 'A' && part[0] <= 'Z')
{
Debug.LogWarning($"[ZipWrapper.SanitizeZipEntryName] 检测到绝对路径片段(驱动器号): {part}");
return null;
}
// 添加到安全部分列表
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;
}
// 在 Path.Combine 之前,额外验证 safePath 是否为绝对路径
// 如果 safePath 是绝对路径,则拒绝,防止路径遍历攻击
if (Path.IsPathRooted(safePath))
{
Debug.LogWarning($"[ZipWrapper.SanitizeZipEntryName] 检测到绝对路径,拒绝路径遍历攻击: {entryName} -> {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)
{
if ((null == _inputStream) || string.IsNullOrEmpty(_outputPath))
{
if (null != _unzipCallback)
_unzipCallback.OnFinished(false);
return false;
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>Ŀ¼
if (!Directory.Exists(_outputPath))
Directory.CreateDirectory(_outputPath);
// <20><>ѹZip<69><70>
ZipEntry entry = null;
using (ZipInputStream zipInputStream = new ZipInputStream(_inputStream))
{
if (!string.IsNullOrEmpty(_password))
zipInputStream.Password = _password;
while (null != (entry = zipInputStream.GetNextEntry()))
{
if (string.IsNullOrEmpty(entry.Name))
continue;
if ((null != _unzipCallback) && !_unzipCallback.OnPreUnzip(entry))
continue; // <20><><EFBFBD><EFBFBD>
// 使用安全验证方法清理和验证 ZIP 条目名称,防止路径遍历攻击
string filePathName = SanitizeZipEntryName(entry.Name, _outputPath);
if (filePathName == null)
{
// 路径不安全,跳过此条目
Debug.LogWarning($"[ZipWrapper.UnzipFile] 跳过不安全的 ZIP 条目: {entry.Name}");
continue;
}
// 额外的安全验证:在使用 filePathName 创建文件前,再次验证路径的安全性
// 确保 filePathName 是规范化后的绝对路径,且确实在输出目录内
string normalizedFilePath = Path.GetFullPath(filePathName);
string normalizedOutputPath = Path.GetFullPath(_outputPath);
// 验证路径是否在输出目录内(防止路径遍历攻击)
if (!normalizedFilePath.StartsWith(normalizedOutputPath + Path.DirectorySeparatorChar, System.StringComparison.OrdinalIgnoreCase)
&& normalizedFilePath != normalizedOutputPath)
{
Debug.LogWarning($"[ZipWrapper.UnzipFile] 检测到路径遍历攻击,拒绝访问: {entry.Name} -> {normalizedFilePath}");
continue;
}
// 确保路径中不包含不安全的字符
if (normalizedFilePath.Contains("..") || Path.IsPathRooted(entry.Name))
{
Debug.LogWarning($"[ZipWrapper.UnzipFile] 检测到不安全的路径字符,拒绝访问: {entry.Name}");
continue;
}
// 创建父目录(如果需要)
string directoryPath = Path.GetDirectoryName(normalizedFilePath);
if (!string.IsNullOrEmpty(directoryPath))
{
// 再次验证父目录路径的安全性,确保在输出目录内
string normalizedDirectoryPath = Path.GetFullPath(directoryPath);
if (normalizedDirectoryPath.StartsWith(normalizedOutputPath + Path.DirectorySeparatorChar, System.StringComparison.OrdinalIgnoreCase)
|| normalizedDirectoryPath == normalizedOutputPath)
{
if (!Directory.Exists(normalizedDirectoryPath))
{
Directory.CreateDirectory(normalizedDirectoryPath);
}
}
else
{
Debug.LogWarning($"[ZipWrapper.UnzipFile] 父目录路径不在输出目录内,拒绝创建: {directoryPath}");
continue;
}
}
// 如果是目录条目,创建目录后跳过
if (entry.IsDirectory)
{
// 再次验证目录路径的安全性,确保在输出目录内
if (normalizedFilePath.StartsWith(normalizedOutputPath + Path.DirectorySeparatorChar, System.StringComparison.OrdinalIgnoreCase)
|| normalizedFilePath == normalizedOutputPath)
{
Directory.CreateDirectory(normalizedFilePath);
}
else
{
Debug.LogWarning($"[ZipWrapper.UnzipFile] 目录路径不在输出目录内,拒绝创建: {normalizedFilePath}");
}
continue;
}
// 写入文件
try
{
// 最终安全验证:在使用 File.Create 前,最后一次验证路径的安全性
// 确保 normalizedFilePath 仍然是规范化的绝对路径,且在输出目录内
string finalFilePath = Path.GetFullPath(normalizedFilePath);
string finalOutputPath = Path.GetFullPath(_outputPath);
// 再次验证路径是否在输出目录内(防止路径遍历攻击)
if (!finalFilePath.StartsWith(finalOutputPath + Path.DirectorySeparatorChar, System.StringComparison.OrdinalIgnoreCase)
&& finalFilePath != finalOutputPath)
{
Debug.LogWarning($"[ZipWrapper.UnzipFile] 最终验证失败:路径不在输出目录内,拒绝创建文件: {entry.Name} -> {finalFilePath}");
continue;
}
// 确保路径中不包含路径遍历字符
if (finalFilePath.Contains(".."))
{
Debug.LogWarning($"[ZipWrapper.UnzipFile] 最终验证失败:检测到路径遍历字符,拒绝创建文件: {entry.Name}");
continue;
}
// 使用规范化后的安全路径创建文件
// 注意Unity 平台不支持 System.Security.AccessControl因此使用标准文件创建方式
// 在实际部署时,建议通过操作系统级别的文件权限设置来限制文件访问
using (FileStream fileStream = File.Create(finalFilePath))
{
byte[] bytes = new byte[1024];
while (true)
{
int count = zipInputStream.Read(bytes, 0, bytes.Length);
if (count > 0)
fileStream.Write(bytes, 0, count);
else
{
if (null != _unzipCallback)
_unzipCallback.OnPostUnzip(entry);
break;
}
}
}
}
catch (System.Exception _e)
{
Debug.LogError("[ZipUtility.UnzipFile]: " + _e.ToString());
if (null != _unzipCallback)
_unzipCallback.OnFinished(false);
return false;
}
}
}
if (null != _unzipCallback)
_unzipCallback.OnFinished(true);
return true;
}
/// <summary>
/// ѹ<><D1B9><EFBFBD>ļ<EFBFBD>
/// </summary>
/// <param name="_filePathName"><3E>ļ<EFBFBD>·<EFBFBD><C2B7><EFBFBD><EFBFBD></param>
/// <param name="_parentRelPath">Ҫѹ<D2AA><D1B9><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>ĸ<EFBFBD><C4B8><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD></param>
/// <param name="_zipOutputStream">ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_zipCallback">ZipCallback<63><6B><EFBFBD>󣬸<EFBFBD><F3A3ACB8><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
private static bool ZipFile(string _filePathName, string _parentRelPath, ZipOutputStream _zipOutputStream, ZipCallback _zipCallback = null)
{
ZipEntry entry = null;
try
{
string entryName = _parentRelPath + '/' + Path.GetFileName(_filePathName);
entry = new ZipEntry(entryName);
entry.DateTime = System.DateTime.Now;
// 使用 using 语句确保 FileStream 资源被正确释放
using (FileStream fileStream = File.OpenRead(_filePathName))
{
byte[] buffer = new byte[fileStream.Length];
fileStream.Read(buffer, 0, buffer.Length);
entry.Size = buffer.Length;
_zipOutputStream.PutNextEntry(entry);
_zipOutputStream.Write(buffer, 0, buffer.Length);
}
}
catch (System.Exception _e)
{
Debug.LogError("[ZipUtility.ZipFile]: " + _e.ToString());
return false;
}
if (null != _zipCallback)
_zipCallback.OnPostZip(entry);
return true;
}
/// <summary>
/// ѹ<><D1B9><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>
/// </summary>
/// <param name="_path">Ҫѹ<D2AA><D1B9><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD></param>
/// <param name="_parentRelPath">Ҫѹ<D2AA><D1B9><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>еĸ<D0B5><C4B8><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD></param>
/// <param name="_zipOutputStream">ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="_zipCallback">ZipCallback<63><6B><EFBFBD>󣬸<EFBFBD><F3A3ACB8><EFBFBD>ص<EFBFBD></param>
/// <returns></returns>
private static bool ZipDirectory(string _path, string _parentRelPath, ZipOutputStream _zipOutputStream, ZipCallback _zipCallback = null)
{
ZipEntry entry = null;
try
{
string entryName = Path.Combine(_parentRelPath, Path.GetFileName(_path) + '/');
entry = new ZipEntry(entryName);
entry.DateTime = System.DateTime.Now;
entry.Size = 0;
_zipOutputStream.PutNextEntry(entry);
_zipOutputStream.Flush();
string[] files = Directory.GetFiles(_path);
for (int index = 0; index < files.Length; ++index)
{
// <20>ų<EFBFBD>Unity<74>п<EFBFBD><D0BF>ܵ<EFBFBD> .meta <20>ļ<EFBFBD>
if (files[index].EndsWith(".meta") == true)
{
Debug.LogWarning(files[index] + " not to zip");
continue;
}
ZipFile(files[index], Path.Combine(_parentRelPath, Path.GetFileName(_path)), _zipOutputStream, _zipCallback);
}
}
catch (System.Exception _e)
{
Debug.LogError("[ZipUtility.ZipDirectory]: " + _e.ToString());
return false;
}
string[] directories = Directory.GetDirectories(_path);
for (int index = 0; index < directories.Length; ++index)
{
if (!ZipDirectory(directories[index], Path.Combine(_parentRelPath, Path.GetFileName(_path)), _zipOutputStream, _zipCallback))
{
return false;
}
}
if (null != _zipCallback)
_zipCallback.OnPostZip(entry);
return true;
}
}