2022年底C# 解壓zip檔案遇到的一個Bug

2022年底C# 解壓zip檔案遇到的一個Bug

最近在排查一個上傳功能時,客戶端上傳的是zip檔案,到伺服器端後使用C# 解壓zip檔案的程式碼將上傳檔案解壓後驗證是否是允許上傳的檔案類型,並且要驗證亂改檔案副檔名啊、檔案頭什麼的都要走一遭,結果解壓zip檔案時就出問題了。

最後更新 2022/12/23 上午9:39
江湖人士
預計閱讀 3 分鐘
分類
.NET
標籤
.NET C#

本文由網友投稿。

作者:江湖人士

原文標題: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;
                        }
                    }
                }
            }
        }
    }
}
繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2026/2/7

AOT使用經驗總結

從專案建立伊始,就應養成良好的習慣,即只要添加了新功能或使用了較新的語法,就及時進行 AOT 發布測試。

繼續閱讀