Guide

Corrupt files for testing error handling

What corrupt files actually look like — truncation, broken headers, damaged data, empty and malformed files — and how your code should reject them.

Every app that accepts files eventually receives a broken one. An upload drops at 70%, a phone writes half a video before the battery dies, or someone renames a text file to .pdf. If your code has only ever seen valid files, the first corrupt one tends to produce a 500 error, a hung worker or a thumbnail job that retries forever.

“Corrupt” covers several different failures, and each one breaks code in its own way. Here is what each looks like at the byte level, with a safe sample file for each.

1. Truncated files

This is the most common kind in practice. The start of the file is fine, so the signature check passes and the format is detected correctly. The problem only appears when a parser reaches the end and finds data missing.

  • Truncated PDF: the first 70% of a 10-page PDF, with the cross-reference table and trailer missing
  • Truncated JPG: the first half of a 640×480 JPEG, with no end-of-image marker
  • Truncated PNG: the IDAT stream ends early and the IEND chunk is missing
  • Truncated MP4: the moov index is intact but half the media data is gone
  • Truncated ZIP: missing its central directory, so listing fails
  • Truncated DOCX and XLSX: Office files are ZIPs, so they fail the same way
  • Truncated WAV: the header still declares the full data length

Truncation is tricky because many readers try to recover. Most image viewers show the top half of the JPEG and grey out the rest, and PDF readers often rebuild the missing cross-reference table. That is fine in a viewer. In a pipeline, though, decide on a policy: either accept partial files on purpose, or reject them. Don’t let your library pick silently.

If you need a corrupted PDF for testing

The truncated PDF is the realistic case: it starts with a valid %PDF-1.4 header, so it passes a magic-byte check and fails later. For the other PDF failures, use the text file named .pdf (no PDF signature at all), the zero-byte PDF, and the valid-but-empty blank page PDF, which has no text layer. Check them from the command line:

Shell
curl -O https://files.samplefiles.com/edge-cases/corrupt/truncated.pdf
qpdf --check truncated.pdf      # reports the damage and whether it could recover
pdfinfo truncated.pdf           # poppler: may still print metadata after warnings

2. Damaged headers

Here the content is intact but the bytes that identify the format have been overwritten, so detection fails before any decoding starts.

Your code should report “unrecognized or unsupported file”, not crash inside a decoder that was chosen from the extension.

3. Damaged data in the middle

These files have a valid structure and a valid header, with garbage somewhere inside. They are the hardest to catch with cheap checks.

Shell
unzip -t crc-mismatch.zip                                  # tests every entry's CRC
ffmpeg -v error -i corrupted-middle.mp3 -f null -          # decodes everything, prints only errors

4. Empty files

A zero-byte file is valid on disk and makes it through most upload stacks untouched. It then breaks the first piece of code that expects content. We have zero-byte files in 20 extensions, including JPG, MP4, DOCX and JSON.

“Empty” also has valid forms that must not be rejected as corrupt: a well-formed ZIP with no entries, a CSV with only a header, an empty XLSX workbook and a whitespace-only text file. Your error messages should tell these cases apart (“this file is empty” versus “this file has no rows”).

5. Malformed text formats

Text formats have no binary structure to break, so corruption shows up as syntax errors:

  • Invalid JSON: single quotes, an unquoted key and trailing commas
  • Malformed XML: an unclosed element, overlapping tags, an unquoted attribute and an undefined entity
  • Malformed CSV: a short row, a long row and an unterminated quote that swallows the rest of the file

The CSV case is worth a closer look. Many CSV parsers don’t fail on an unterminated quote. They return one giant final field instead, so check row counts and field counts, not just whether an exception was raised. The JSON parser guide covers JSON failures in more depth.

6. Wrong type entirely

A file can be perfectly valid and still be the wrong thing, such as a WebM video named .mp4 or an MP3 named .wav. These are covered in validating uploads by magic bytes.

What your code should do

Validate before you accept. Run the real decoder once, at upload time, instead of discovering the damage hours later in a background job. For images, a full decode catches truncation that a header check misses:

Python
from PIL import Image, UnidentifiedImageError

def check_image(path):
    try:
        with Image.open(path) as im:
            im.verify()   # structural check; the image can't be used afterwards
        with Image.open(path) as im:
            im.load()     # full decode: raises OSError on truncated data
    except (UnidentifiedImageError, OSError, SyntaxError, ValueError) as e:
        raise ValueError(f"Invalid image: {e}") from e

Don’t set Pillow’s ImageFile.LOAD_TRUNCATED_IMAGES = True globally to make errors go away. It turns rejections into silently half-grey images.

Return a 4xx with a useful message. “This PDF appears to be incomplete. Try uploading it again” is actionable. A stack trace or “Internal Server Error” isn’t, and it can leak library versions.

Bound the work. Run decoders with a timeout and a memory limit, ideally in a separate process, so a pathological file can’t take down the worker. Don’t retry a job that failed on a corrupt input: the file won’t fix itself.

Clean up. Delete partial output, such as half-extracted archives and thumbnails for the first page only, when processing fails.

Log enough to reproduce. Record the file’s size and SHA-256 so you can match a failure to a sample. Our checksum tool computes the same hashes in the browser.

All of these files are listed together on the corrupt file testing use case.

Sample files for this guide