本文由網友投稿。
作者:江湖人士
原文標題:2022 年底 C# 解壓縮 zip 檔案遇到的一個 bug
原文連結:https://jhrs.com/2022/46060.html
最近在排查一個上傳功能時,用戶端上傳的是 zip 檔案,到伺服器端後使用 C# 解壓縮 zip 檔案的程式碼將上傳檔案解壓縮後驗證是否是允許上傳的檔案類型,並且要驗證亂改檔案副檔名啊、檔案頭什麼的都要走一遭,結果解壓縮 zip 檔案時就出問題了。
C# 解壓縮 zip 檔案
先說一下前文(或者上下文),在 IIS 上部署了一個檔案服務站點,用於上傳各類檔案,流程上是先上傳到站點根目錄裡面隨機建立的一個暫存目錄(這裡採用偷懶方案,直接使用 guid 作為目錄名稱建立),先通過檔案驗證後再將其透過程式碼剪下或者複製到正式存檔目錄,C# 複製或者移動檔案的程式碼可以參考江湖人士網的這篇文章。昨天快下班時發現上傳 zip 檔案時報錯,在檔案服務根站點建立了很多很多的 guid 開頭的目錄,我的天啊,這下慘了,事出反常必有妖啊,肯定是程式碼出錯了。

有 bug 的解壓縮程式碼
這都馬上 2022 年底了,出了這個 bug 後,趕緊搭建一個模擬環境跑一下,發現原來的程式碼確實有問題,原始程式碼如下:
/// <summary>
/// 解壓縮檔案
/// </summary>
/// <param name="saveDir">儲存目錄</param>
/// <param name="stream"></param>
public static void UnZipFiles(string saveDir, Stream stream)
{
using (ZipInputStream s = new ZipInputStream(stream))
{
ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
{
string directoryName = $"{saveDir}{Path.GetDirectoryName(theEntry.Name)}\\";
string fileName = Path.GetFileName(theEntry.Name);
Directory.CreateDirectory(directoryName);
using (FileStream streamWriter = File.Create(directoryName + fileName))
{
byte[] data = new byte[2048];
while (true)
{
int size = s.Read(data, 0, data.Length);
if (size > 0)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
}
}
}
}
對了,這裡需要說明一下,這是以前的老專案,因此壓縮、解壓縮使用了 ICSharpCode.SharpZipLib.Zip 這個元件。
當打開原始碼來看,一眼就發現了問題所在,邏輯不嚴謹導致,解壓縮檔案儲存目錄直接拼接。
如何修復此 bug?
知道了問題所在,修復自然簡單,呼叫Path.Combine方法即可,解壓縮時再判斷一下是目錄還是檔案即可,最終修復後的程式碼如下:
/// <summary>
/// 解壓縮檔案
/// </summary>
/// <param name="saveDir">儲存目錄</param>
/// <param name="stream"></param>
public static void UnZipFiles(string saveDir, Stream stream)
{
using (ZipInputStream s = new ZipInputStream(stream))
{
ZipEntry theEntry;
string directoryName, file, fileName;
while ((theEntry = s.GetNextEntry()) != null)
{
directoryName = Path.Combine(saveDir, Path.GetDirectoryName(theEntry.Name));
fileName = Path.GetFileName(theEntry.Name);
Directory.CreateDirectory(directoryName);
file = Path.Combine(directoryName, fileName);
if (theEntry.IsFile)
{
using (FileStream streamWriter = File.Create(file))
{
byte[] data = new byte[2048];
while (true)
{
int size = s.Read(data, 0, data.Length);
if (size > 0)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
}
}
}
}
}