P36 ZIP 사전 점검 실습

목표
  가상 ZIP을 풀지 않고 항목 수·정확한 이름 중복·선언 크기·정책 한도를 점검합니다.
  메타데이터 정책을 통과했을 때만 --crc로 ZipExtFile 반환 바이트와 CRC를 확인합니다.
  내부 파일의 디스크 추출·내용 앱 실행·자동 업로드·네트워크 요청은 없습니다.

시작 위치와 환경
  바깥 P36_Practice_ZIP_Preflight.zip만 새 빈 로컬 연습 폴더에 직접 풉니다.
  이 README.txt가 있는 최상위 폴더에서 명령을 한 줄씩 실행합니다.
  fixtures 안의 ZIP은 추출하거나 내부 항목을 실행하지 않습니다.
  CPython 3.12.14 / Linux 6.18.44 / x86_64에서 검증했습니다.
  Windows·macOS·탐색기·Excel·다른 압축 앱은 실행하지 않았습니다.
  표준 라이브러리만 사용하며 추가 패키지나 API 키가 필요 없습니다.
  python3가 같은 인터프리터라면 명령 첫 단어만 바꿀 수 있습니다.

검사 및 정상 실행
  python --version
  python tests/verify_checksums.py
  python -m unittest discover -s tests -v
  python tests/run_fixture_checks.py
  python code/zip_preflight.py fixtures/main.zip --out main_metadata.json
  python code/zip_preflight.py fixtures/main.zip --out main_crc.json --crc

기대값
  main.zip은 ASCII/LF로 만든 memo.txt 6 + table.csv 19 + notes.txt 6 = 31바이트입니다.
  디렉터리 항목 0, 파일 3개이며 ZIP 전체는 333바이트입니다.
  기본 실행: PASS, crc=not_requested, 관측 0, 항목별 관측 null, 종료 0
  --crc 실행: PASS, started_entries=3, passed_entries=3, 관측 31, 종료 0
  expected/main_metadata.json 및 expected/main_crc.json과 모든 고정 필드를 대조합니다.
  기대 JSON은 source_sha256만 생략했습니다. 실제 입력 바이트의 SHA-256을 별도로 계산하여
  보고서의 해당 필드 및 SHA256SUMS.txt와 대조합니다. source_unchanged도 true여야 합니다.
  expected/hand_calculation.txt는 코드의 실제 출력을 복사하지 않은 손계산 계약입니다.

정책 옵션 기본값 및 허용 범위
  --max-archive-bytes 65536  : ZIP 원본 상한, ZipFile 파싱 전 적용
  --max-entries 16          : 디렉터리 표시를 포함한 모든 항목 수
  --max-file-bytes 4096     : 항목별 선언 크기와 반환 관측치
  --max-total-bytes 8192    : 모든 선언 크기 합 및 반환 관측치 합
  --max-ratio 100           : 선언 크기 <= 값 * max(압축된 내용 크기, 1)
  각 옵션은 양의 정수로 1부터 기본값까지만 지정합니다. 상한 확대는 허용하지 않습니다.
  허용 압축방식은 STORED 및 DEFLATED이며 암호화 플래그가 있으면 보류합니다.
  --max-entries 3 --max-file-bytes 19 --max-total-bytes 31은 main의 정확 경계입니다.
  --max-total-bytes 30은 main의 CRC 시작 전 HOLD, 종료 1입니다.

반례 명령 예시
  python code/zip_preflight.py fixtures/duplicate.zip --out duplicate.json --crc
  python code/zip_preflight.py fixtures/total_limit.zip --out total_limit.json --crc
  python code/zip_preflight.py fixtures/too_many.zip --out too_many.json --crc
  python code/zip_preflight.py fixtures/encrypted_flag.zip --out encrypted_flag.json --crc
  python code/zip_preflight.py fixtures/unsupported_method.zip --out unsupported_method.json --crc
  python code/zip_preflight.py fixtures/crc_corrupt.zip --out crc_corrupt.json --crc
  python code/zip_preflight.py fixtures/misdeclared_large.zip --out misdeclared_large.json --crc
  python code/zip_preflight.py fixtures/spoofed_prefix.zip --out spoofed_prefix.json --crc

정확한 의미
  중복/개수/크기/비율/암호/미지원/이름/링크 우려: HOLD, 종료 1, 완성 보류 JSON 존재
  CRC를 요청했어도 정책 실패이면 blocked_by_policy, 내부 open 0, 관측 null
  CRC 손상: 앞 2개 통과, 관측 25바이트, 세 번째 오류, complete=false, 종료 1
  선언 4 대 실제 반환 3: observed_size_mismatch, 종료 1
  --crc complete/passed는 읽기 대상인 파일 항목 전체에 한정합니다.
  디렉터리 표시 항목은 열지 않고 directory_not_read로 남깁니다.
  파일 항목 없이 디렉터리 표시만 있으면 no_file_entries, performed=false, complete=false입니다.
  관측치는 호출자에게 반환된 바이트 수입니다. 오류 뒤 관측 0도 내용이 빈 증거가 아닙니다.

필수 한계 반례
  spoofed_prefix.zip은 물리적으로 ABC 세 바이트가 있지만 헤더 크기2·CRC(AB)를 선언합니다.
  검증한 ZipExtFile은 AB만 반환하여 이 도구도 PASS, 관측2, complete=true, 종료0입니다.
  따라서 관측 완료는 전체 물리 payload 길이의 독립 증명이 아닙니다.
  CRC는 손상 점검이고 악성코드 검사·작성자 인증·추출 또는 실행 승인이 아닙니다.
  모든 거짓 메타데이터·CPU 비용·메모리 디렉터리 파싱·내부 버퍼를 완전히 통제하지 못합니다.
  실제 적대적 ZIP이나 출처 불명 파일은 이 예제에 넣지 마세요.
  입력은 작고 통제된 합성 자료만 사용합니다. 암호/압축방식 반례는 헤더 숫자 패치이지
  실제 암호화 또는 미지원 코덱으로 만든 ZIP이 아닙니다.

실행 오류와 저장 정책
  종료 2: 잘못된 옵션/없는 입력/ZIP 파싱/상한/경로/변경/저장 오류, 새 최종 JSON 없음
  기존 출력이 있으면 보존하며 OUTPUT_EXISTS로 종료 2입니다.
  입력과 출력 동일 경로, 입력·부모 링크/reparse, 하드 링크 입력, .. 별칭은 거부합니다.
  출력 부모 폴더는 이미 있어야 하며 .json 확장자를 사용합니다.
  같은 폴더의 임시 JSON을 완성·fsync한 후 os.link로 새 이름만 확정합니다.
  하드 링크 미지원에서는 중단하고 덮어쓰기 방식으로 대체하지 않습니다.
  확정 후 임시 파일 정리만 실패하면 완성 JSON이 남고 TEMP_CLEANUP_WARNING을 표시합니다.
  강제 종료·정전은 시험하지 않았으며 .p36- 임시 파일이 남을 수 있습니다.
  셸의 >로 저장하지 마세요. 반례 종료1에서 &&가 다음 명령을 중단할 수 있습니다.
  새 출력 이름으로 재실행하고 이전 결과는 보존합니다.

검증
  unittest 38개 / CLI 재검산 26건 / 입력 파일 16개 (ZIP 15개·비ZIP 텍스트 반례 1개) / 독립 기대 JSON 18개
  CLI 재검산기는 임시 연습 사본에서 실행해 출력 파일을 이 패키지에 미리 넣지 않습니다.
  모든 입력 ZIP의 전후 SHA-256이 같아야 합니다. 체크섬은 원본 인증 또는 안전 판정이 아닙니다.
  입력을 다른 프로그램이 바꾸지 않는 로컬 폴더를 전제로 하며 동시 교체·네트워크·동기화·
  강제 종료 복구는 보장하지 않습니다. 읽기 전용이어도 OS 접근시각은 바뀔 수 있습니다.
  read 제한 모의시험은 통제된 읽기 객체의 반환치를 사용하며 실제 공격 시험은 아닙니다.

선택적 fixture 재생성
  python code/make_fixtures.py --dest recreated_fixtures
  대상 폴더는 아직 없어야 합니다. 기존 fixtures를 덮어쓰지 않습니다.
  고정 STORED fixture는 같은 bytes로 재생성됩니다. DEFLATED 비율 fixture의 압축 바이트는
  다른 zlib 버전에서 달라질 수 있으므로 제공 fixture 및 체크섬을 기본 대조값으로 씁니다.
  이 도구는 새 가상 ZIP만 만들고 내부 파일을 추출하지 않습니다.

구성
  code/      전체 검사 코드와 fixture 생성 도구
  fixtures/  정상·정책·손상·알려진 한계의 작은 합성 입력 16개 (ZIP 15개·비ZIP 텍스트 반례 1개)
  expected/  독립 기대 JSON 18개와 손계산
  tests/     unittest·CLI 대조·체크섬 검사
  SHA256SUMS.txt  자기 자신과 실행 후 새 보고서를 제외한 제공 파일의 SHA-256
