윈도우 탐색기에서 열리지 않는 zip 파일 - The Compressed (zipped) Folder '[...].zip' is invalid.
이상하군요, 웹 서버 측에서 코드로 압축해 내려준 ZIP 파일이 Windows Explorer에서 다음과 같은 오류를 내며 풀리지 않습니다.
Compressed (zipped) Folders Error
Windows cannot complete the extraction.
The Compressed (zipped) Folder 'C:\temp\test.zip' is invalid.
전에 한 번 유사한 경험을 해봐서,
자바와 닷넷의 압축 호환
; https://www.sysnet.pe.kr/2/0/724
C# - Deflate, GZip, Zip
; https://www.sysnet.pe.kr/2/0/723
이번에도 파고 들어보면 해결책이 나오지 않을까 싶어 좀 살펴봤습니다. ^^
우선, Alzip이나 PowerShell의 Expand-Archive 명령어로는 잘 풀립니다.
PS> Expand-Archive test.zip -DestinationPath c:\temp\test
도대체 무슨 차이일까요? 혹시 세세한 ZIP 포맷에 어떤 문제가 있을까 싶어 WSL을 경유해
zipdetails를 이용해 살펴보면,
// 탐색기에서 오류가 발생하는 zip 파일
$ zipdetails /mnt/c/temp/test.zip
000000 LOCAL HEADER #1 04034B50
000004 Extract Zip Spec 14 '2.0'
000005 Extract OS 00 'MS-DOS'
000006 General Purpose Flag 0808
[Bits 1-2] 0 'Normal Compression'
[Bit 3] 1 'Streamed'
[Bit 11] 1 'Language Encoding'
000008 Compression Method 0008 'Deflated'
00000A Last Mod Time 5632700B 'Wed Jan 18 14:00:22 2023'
00000E CRC 00000000
000012 Compressed Length 00000000
000016 Uncompressed Length 00000000
00001A Filename Length 001D
00001C Extra Length 0000
00001E Filename '/invalid_file/test/unk00.json'
00003B PAYLOAD
001A78 STREAMING DATA HEADER 08074B50
001A7C CRC F95C9612
001A80 Compressed Length 00001A3D
001A84 Uncompressed Length 0000776B
001A88 LOCAL HEADER #2 04034B50
001A8C Extract Zip Spec 14 '2.0'
001A8D Extract OS 00 'MS-DOS'
001A8E General Purpose Flag 0808
[Bits 1-2] 0 'Normal Compression'
[Bit 3] 1 'Streamed'
[Bit 11] 1 'Language Encoding'
001A90 Compression Method 0008 'Deflated'
001A92 Last Mod Time 5632700C 'Wed Jan 18 14:00:24 2023'
001A96 CRC 00000000
001A9A Compressed Length 00000000
001A9E Uncompressed Length 00000000
001AA2 Filename Length 001D
001AA4 Extra Length 0000
001AA6 Filename '/invalid_file/test/unk01.json'
001AC3 PAYLOAD
...[생략]...
// 탐색기에서 오류가 없는 zip 파일
$ zipdetails /mnt/c/temp/test2.zip
0000 LOCAL HEADER #1 04034B50
0004 Extract Zip Spec 14 '2.0'
0005 Extract OS 00 'MS-DOS'
0006 General Purpose Flag 0000
[Bits 1-2] 0 'Normal Compression'
0008 Compression Method 0008 'Deflated'
000A Last Mod Time 56CD5886 'Tue Jun 13 11:04:12 2023'
000E CRC FF16EA5E
0012 Compressed Length 00000109
0016 Uncompressed Length 000001EA
001A Filename Length 000D
001C Extra Length 0000
001E Filename 'testfile1.txt'
002B PAYLOAD
0134 LOCAL HEADER #2 04034B50
0138 Extract Zip Spec 14 '2.0'
0139 Extract OS 00 'MS-DOS'
013A General Purpose Flag 0000
[Bits 1-2] 0 'Normal Compression'
013C Compression Method 0008 'Deflated'
013E Last Mod Time 56CD58D5 'Tue Jun 13 11:06:42 2023'
0142 CRC 1D1EC9E8
0146 Compressed Length 00000381
014A Uncompressed Length 000004B8
014E Filename Length 000D
0150 Extra Length 0000
0152 Filename 'testfile2.txt'
015F PAYLOAD
...[생략]...
열리지 않는 파일이 "STREAMING DATA HEADER"를 가지고 있습니다. 과연 이게 문제일까요?
자, 그래서 테스트를 해봤습니다. 이를 위해 다음의 Q&A에 있는 C# 코드를 이용해,
.net core unzip a file and zip subfolder
; https://stackoverflow.com/questions/53645337/net-core-unzip-a-file-and-zip-subfolder
문제가 되었던 test.zip 파일을
ZipArchive로 열어, 그대로 다시 ZipArchive로 복사해 저장하는 역할만 하도록 바꿀 수 있습니다.
using System.IO.Compression;
namespace ConsoleApp1;
// https://stackoverflow.com/questions/53645337/net-core-unzip-a-file-and-zip-subfolder
internal class Program
{
static void Main(string[] args)
{
using (MemoryStream srcMemoryStream = new MemoryStream())
using (MemoryStream targetMemoryStream = new MemoryStream())
{
using (FileStream sourceZipFile = new FileStream(@"C:\temp\test.zip", FileMode.Open))
{
sourceZipFile.CopyTo(srcMemoryStream);
}
using (ZipArchive srcArchive = new ZipArchive(srcMemoryStream, ZipArchiveMode.Read))
using (ZipArchive destArchive = new ZipArchive(targetMemoryStream, ZipArchiveMode.Create, true))
{
srcArchive.Entries
.ToList()
.ForEach((entry) =>
{
ZipArchiveEntry newEntry = destArchive.CreateEntry(entry.FullName);
using (Stream srcEntry = entry.Open())
{
using (Stream destEntry = newEntry.Open())
{
srcEntry.CopyTo(destEntry);
}
}
});
}
using (FileStream fs = new FileStream(@"c:\temp\test3.zip", FileMode.Create))
{
targetMemoryStream.WriteTo(fs);
targetMemoryStream.Flush();
fs.Flush(true);
}
}
}
}
위의 결과로 저장된 test3.zip 파일은 윈도우 탐색기에서도 잘 열렸던 포맷 유형으로 바뀝니다.
$ zipdetails /mnt/c/temp/test3.zip
000000 LOCAL HEADER #1 04034B50
000004 Extract Zip Spec 14 '2.0'
000005 Extract OS 00 'MS-DOS'
000006 General Purpose Flag 0000
[Bits 1-2] 0 'Normal Compression'
000008 Compression Method 0008 'Deflated'
00000A Last Mod Time 56E37165 'Mon Jul 3 14:11:10 2023'
00000E CRC F95C9612
000012 Compressed Length 00001A95
000016 Uncompressed Length 0000776B
00001A Filename Length 001D
00001C Extra Length 0000
00001E Filename '/invalid_file/test/unk00.json'
00003B PAYLOAD
001AD0 LOCAL HEADER #2 04034B50
001AD4 Extract Zip Spec 14 '2.0'
001AD5 Extract OS 00 'MS-DOS'
001AD6 General Purpose Flag 0000
[Bits 1-2] 0 'Normal Compression'
001AD8 Compression Method 0008 'Deflated'
001ADA Last Mod Time 56E37165 'Mon Jul 3 14:11:10 2023'
001ADE CRC 3A6DE765
001AE2 Compressed Length 00001487
001AE6 Uncompressed Length 000061E2
001AEA Filename Length 001D
001AEC Extra Length 0000
001AEE Filename '/invalid_file/test/unk01.json'
그리고, 이렇게 변형된 파일 역시 윈도우 탐색기에서는 열리지 않았습니다. 다른 문제가 있다는 것이죠? ^^
본문의 예제 파일을 눈여겨보신 분이라면, "Filename"의 시작이 "/"로 되는 특징을 하나 발견할 수 있습니다. 실제로 이것 때문인지 (Windows 10부터 포함된) tar.exe로 test.zip 파일을 풀어보면 이런 메시지가 하나 보입니다.
c:\temp> tar -xf test.zip
tar: Removing leading '/' from member names
그러니까, 아마도 윈도우 탐색기는 리눅스의 "/" 루트로 시작하는 절대 경로가 있는 파일이라면 정상적으로 해석을 못하는 것이 아닌가 싶은데요, 실제로 이 확인을 우리가 만든 소스 코드에서 "/" 루트 경로를 삭제하는 코드를 추가하면,
string filePath = entry.FullName.TrimStart('/');
ZipArchiveEntry newEntry = destArchive.CreateEntry(filePath);
새롭게 저장한 zip 파일은 탐색기로 잘 열립니다. 그렇다면 반대로, 탐색기에서 압축한 파일을 위의 소스 코드에서 루트 문자를 추가해 다시 저장하는 식으로 바꾸면,
string filePath = "/" + entry.FullName; // The Compressed (zipped) Folder '[...].zip' is invalid.
ZipArchiveEntry newEntry = destArchive.CreateEntry(filePath);
탐색기로는 열리지 않는 zip 파일이 나옵니다. (
이 글에 첨부한 invalid_sample.zip은 그렇게 만든 것입니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]