This commit is contained in:
parent
44f7d21ec7
commit
9eed870d09
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue