Linux

집나간 zip 파일 찾아요

페페로니피자 2024. 9. 2. 03:38
반응형

1) 서론

혹시 겉만 보고 무언가를 선택했다가 실망했던 경험이 있으신가요?

 

마트에서 종종 과일을 살 때 겉과 속이 다른 것에 실패하는 경험을 했었는데요. 빨갛게 잘 익어 보이는 자두나 사과를 구매했지만 막상 먹어보면 맛이 없는 경우가 있습니다. 오히려 색깔도 연하고 보기에 그저 그런 것이 의외로 맛있을 때도 있는데요.

 

어릴 때 어머니와 마트를 가면 손바닥으로 수박을 통통 두드려보는 것을 종종 봤습니다. 실제로 잘 익은 수박은 수분이 많기 때문에 맑은 '통통' 소리가 난다는 과학적 근거가 있다고 합니다. 어머니들은 이미 생활 속에서의 경험으로 얻은 지혜를 가지고 계셨을 수도 있습니다.

 

이처럼 겉으로만 보고 무언가를 판단하게 되면 안됩니다. 정말 그 내부가 어떻게 되어 있는지 알아야 하는데요.

 

최근 zip 파일 압축 해제 중 만난 오류의 원인을 분석하고, 해결한 과정을 공유드립니다.

 


2) 라이브러리는 Zip4j를 사용했어요

JVM 환경에서 zip 파일을 다룰 수 있는 라이브러리는 많습니다.

 

Java 언어에 이미 내장된 java.util.zip, Apache commons compress, Zip4j 등 여러 라이브러리가 있습니다.

 

Java 언어에 내장된 라이브러리는 매우 신뢰할 수 있지만, 개발자가 직접 작성하고 다뤄야 하는 코드의 양이 많습니다. 읽고 쓰기를 위한 stream, 실제 파일에 write 하는 등 다뤄야 하는 것이 많습니다. 혹시나 비밀번호라도 추가해야 하면 아찔합니다.

 

Apache commons compress 라이브러리는 비교적 간편하고 신뢰할 수 있지만, stream 자원을 직접 다뤄야 하는 등 여전히 아주 편리하다는 느낌을 받지는 못 합니다.

 

마지막으로 Zip4j는 파일, 폴더 단위를 압축, 해제, 비밀번호 추가 등 함수 단위로 간편하게 사용할 수 있게 해 두었습니다. 자원의 해제도 라이브러리 내부에서 직접 다뤄줍니다.

 

최근까지도 버전 업그레이드가 되고 있고, 사내에서도 널리 사용하고 있기에 레퍼런스도 많습니다.

 

내부 구현을 확인했을 때에도 java 내부 라이브러리를 wrapping 하고 있고, 자바는 JDK 버전에 따른 하위 호환성을 매우 잘 대응하기 때문에 문제없을 것이라고 판단합니다. 다만 혹시 모를 라이브러리 교체가 발생할 수도 있으니 라이브러리를 wrapping 하여 라이브러리 혹은 util 형태로 제공하는 것은 중요합니다.


3) zip 파일인데 해제가 안돼요

외부 기관으로부터 zip 파일을 주기적으로 수신하고 있고, 어느 날 파일을 압축 해제 시도하는데 오류가 발생합니다.

Caused by: net.lingala.zip4j.exception.ZipException: Zip file size less than minimum expected zip file size. Probably not a zip file or a corrupted zip file
	at net.lingala.zip4j.headers.HeaderReader.readAllHeaders(HeaderReader.java:69)
	at net.lingala.zip4j.ZipFile.readZipInfo(ZipFile.java:1142)

 

실제 파일을 확인해 보니 아래와 같습니다. 0byte 사이즈의 zip 파일입니다.

-rw-r--r--    1 소유자  소유그룹     0B  9  2 01:30 text.zip

 

외부 기관으로부터 받은 파일의 확인은 생각보다 어렵습니다. 상대 기관 담당자가 퇴근했을 수도 있고, 주말일 수도 있습니다. 어떤 구현으로 어떻게 만들어졌는지 알 수 없습니다. 특히 상대 기관으로의 문제 제기는 확실한 근거를 가지고 시도해야 합니다.

 

기다릴 수 없습니다. 직접 파일을 검증해 봅니다.


4) zip 파일 사이즈가 0byte가 될 수 있을까요?

일반적인 상식으로 zip 파일의 사이즈는 0byte가 될 수 없습니다. 그런데 왜 그럴까요?

 

Zip 파일 포맷의 구조는 대략적으로 이렇습니다.

 

Local file headers

  • 각 파일별 metadata
  • Signature(4bytes)
  • Checksum(4bytes)
  • Version(2bytes): 압축 해제에 필요한 zip specification version
  • Compressed/Uncompressed size(4/4bytes): 압축과 해제했을 때 사이즈
  • Last modification time/date(2/2bytes): 마지막으로 수정한 일시
  • 등등

 

File data

  • Local file header에 해당하는 실제 압축된 데이터

 

Central Directory

  • 각 파일별 metadata의 상세한 정보들
  • Local file headers와 동일하게 Signature, Version 정보 등
  • Local file header offset: 파일을 읽기 위한 local file header의 위치
  • 등등

 

End of central directory record

  • Zip 파일 가장 끝에 위치
  • Central Directory 사이즈, 시작 위치 등 정보 등

 

즉, 정상적으로 압축된 zip 파일은 반드시 데이터가 존재하므로 size가 있어야 합니다.

0byte zip 파일은 존재할 수 없습니다.

 

테스트 파일을 만들어서 검증해 봅니다.

# .zip 포멧 이름만 변경한 0byte 파일
-rw-r--r--    1 yeon  staff     0B  9  2 01:30 invalid.zip

# 정상적으로 압축한 174bytes 파일
-rw-r--r--    1 yeon  staff   174B  9  2 02:52 valid.zip

 

"xxd valid.zip | less" 명령어를 이용해서 valid.zip 파일을 16진수로 변환합니다. 아래 사진과 같이 정상적인 zip 파일의 구조를 가지고 있는 것을 볼 수 있습니다.

 

invalid.zip(0byte) 파일은 아무런 정보도 보여주지 않는 것을 볼 수 있습니다.

 

unzip 명령어를 이용해서 압축 해제를 시도했을 때에도 당연하게 실패합니다.

 

아래처럼 0byte 텍스트 파일을 만들고 확장자만 .zip으로 변경하게 되면 동일한 0byte zip 파일 재현됩니다.

# 0byte text
-rw-r--r--    1 yeon  staff     0B  9  2 04:20 invalid.txt

$ mv invalid.txt invalid.zip

# 0byte zip
-rw-r--r--    1 yeon  staff     0B  9  2 04:20 invalid.zip

 

즉, 외부기관에서 전달한 파일은 정상적인 zip 파일이 아니며, 단순 0byte 텍스트 파일의 확장자만 변경한것으로 추측합니다. 보낼 데이터가 없다는것을 의도한것으로 보이지만 좋은 방법은 아닌것 같습니다.


5) 결론

해결은 간단하게 되었습니다.

 

Zip4j 라이브러리에서는 isValidZipFile 함수를 제공하고 있고 해결할 수 있습니다. 해당 함수는 zip 파일의 헤더를 확인해서 정상적으로 읽을 수 없다면 false 응답을 하는데요. 분석한대로 정상적인 zip 파일이라면 헤더가 존재하기 때문에 좋은 방법으로 보입니다.

 

만약 구조를 모른 채 0byte zip 파일에 대한 의심을 하지 않았다면 코드에 대해서만 계속 고민을 하고, 시간이 흐른 후 외부 기관의 답변을 기다려야 했을 수도 있습니다. 빠르게 분석하고 재현해서 해결해 볼 수 있다는 것이 좋은 경험이었습니다.

 

그리고 정상적인 파일들을 압축하여 zip 파일을 생성하는 것이 정상이지만, 대외 기관과 업무를 할 때에는 예상치 못 한 상황이 발생할 수 있으니 코드적으로도 예외처리 등을 하는 것에 대해서 중요성을 느꼈습니다.


6) 출처

james madison university

반응형