This commit is contained in:
lujiajian 2025-11-04 17:52:22 +08:00
parent 44f7d21ec7
commit 9eed870d09
1 changed files with 151 additions and 53 deletions

View File

@ -67,7 +67,7 @@ public class ZipWrapper : MonoBehaviour
/// <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)
public static bool Zip(List<string> _fileOrDirectoryArray, string _outputPathName, string _password = null, ZipCallback _zipCallback = null)
{
if ((null == _fileOrDirectoryArray) || string.IsNullOrEmpty(_outputPathName))
{
@ -77,36 +77,43 @@ public class ZipWrapper : MonoBehaviour
return false;
}
ZipOutputStream zipOutputStream = new ZipOutputStream(File.Create(_outputPathName));
zipOutputStream.SetLevel(6); // ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><D1B9><EFBFBD>ٶȵ<D9B6>ƽ<EFBFBD><C6BD><EFBFBD>
if (!string.IsNullOrEmpty(_password))
zipOutputStream.Password = _password;
for (int index = 0; index < _fileOrDirectoryArray.Count; ++index)
// 使用 using 语句确保 FileStream 和 ZipOutputStream 资源被正确释放,即使在异常情况下也能释放
using (FileStream fileStream = File.Create(_outputPathName))
{
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)
using (ZipOutputStream zipOutputStream = new ZipOutputStream(fileStream))
{
if (null != _zipCallback)
_zipCallback.OnFinished(false);
zipOutputStream.SetLevel(6); // 压缩级别,压缩率与压缩速度的平衡点
if (!string.IsNullOrEmpty(_password))
zipOutputStream.Password = _password;
return false;
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();
// using 语句会自动调用 Close() 和 Dispose(),但显式调用 Close() 也无妨
zipOutputStream.Close();
if (null != _zipCallback)
_zipCallback.OnFinished(true);
return true;
}
}
zipOutputStream.Finish();
zipOutputStream.Close();
if (null != _zipCallback)
_zipCallback.OnFinished(true);
return true;
}
/// <summary>
@ -129,7 +136,11 @@ public class ZipWrapper : MonoBehaviour
try
{
return UnzipFile(File.OpenRead(_filePathName), _outputPath, _password, _unzipCallback);
// 使用 using 语句确保 FileStream 资源被正确释放
using (FileStream fileStream = File.OpenRead(_filePathName))
{
return UnzipFile(fileStream, _outputPath, _password, _unzipCallback);
}
}
catch (System.Exception _e)
{
@ -213,6 +224,21 @@ public class ZipWrapper : MonoBehaviour
}
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);
}
@ -234,6 +260,14 @@ public class ZipWrapper : MonoBehaviour
return null;
}
// 在 Path.Combine 之前,额外验证 safePath 是否为绝对路径
// 如果 safePath 是绝对路径,则拒绝,防止路径遍历攻击
if (Path.IsPathRooted(safePath))
{
Debug.LogWarning($"[ZipWrapper.SanitizeZipEntryName] 检测到绝对路径,拒绝路径遍历攻击: {entryName} -> {safePath}");
return null;
}
// 与输出路径组合(使用已清理的安全路径)
string fullPath = Path.Combine(outputPath, safePath);
@ -299,17 +333,88 @@ public class ZipWrapper : MonoBehaviour
continue;
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>Ŀ¼
if (entry.IsDirectory)
// 额外的安全验证:在使用 filePathName 创建文件前,再次验证路径的安全性
// 确保 filePathName 是规范化后的绝对路径,且确实在输出目录内
string normalizedFilePath = Path.GetFullPath(filePathName);
string normalizedOutputPath = Path.GetFullPath(_outputPath);
// 验证路径是否在输出目录内(防止路径遍历攻击)
if (!normalizedFilePath.StartsWith(normalizedOutputPath + Path.DirectorySeparatorChar, System.StringComparison.OrdinalIgnoreCase)
&& normalizedFilePath != normalizedOutputPath)
{
Directory.CreateDirectory(filePathName);
Debug.LogWarning($"[ZipWrapper.UnzipFile] 检测到路径遍历攻击,拒绝访问: {entry.Name} -> {normalizedFilePath}");
continue;
}
// д<><D0B4><EFBFBD>ļ<EFBFBD>
// 确保路径中不包含不安全的字符
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
{
using (FileStream fileStream = File.Create(filePathName))
// 最终安全验证:在使用 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;
}
// 使用规范化后的安全路径创建文件
using (FileStream fileStream = File.Create(finalFilePath))
{
byte[] bytes = new byte[1024];
while (true)
@ -358,7 +463,6 @@ public class ZipWrapper : MonoBehaviour
//Crc32 crc32 = new Crc32();
ZipEntry entry = null;
FileStream fileStream = null;
try
{
string entryName = _parentRelPath + '/' + Path.GetFileName(_filePathName);
@ -366,35 +470,29 @@ public class ZipWrapper : MonoBehaviour
entry.DateTime = System.DateTime.Now;
if ((null != _zipCallback) && !_zipCallback.OnPreZip(entry))
return true; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
return true; // 跳过
fileStream = File.OpenRead(_filePathName);
byte[] buffer = new byte[fileStream.Length];
fileStream.Read(buffer, 0, buffer.Length);
fileStream.Close();
// 使用 using 语句确保 FileStream 资源被正确释放
using (FileStream fileStream = File.OpenRead(_filePathName))
{
byte[] buffer = new byte[fileStream.Length];
fileStream.Read(buffer, 0, buffer.Length);
entry.Size = buffer.Length;
entry.Size = buffer.Length;
//crc32.Reset();
//crc32.Update(buffer);
//entry.Crc = crc32.Value;
//crc32.Reset();
//crc32.Update(buffer);
//entry.Crc = crc32.Value;
_zipOutputStream.PutNextEntry(entry);
_zipOutputStream.Write(buffer, 0, buffer.Length);
_zipOutputStream.PutNextEntry(entry);
_zipOutputStream.Write(buffer, 0, buffer.Length);
}
}
catch (System.Exception _e)
{
Debug.LogError("[ZipUtility.ZipFile]: " + _e.ToString());
return false;
}
finally
{
if (null != fileStream)
{
fileStream.Close();
fileStream.Dispose();
}
}
if (null != _zipCallback)
_zipCallback.OnPostZip(entry);