xz (.xz) Stream Inspector
An .xz file is framed, and that is the whole difference from gzip. Every stream ends with a 12-byte footer whose backward size points at an index, and the index holds one record per block: that block's unpadded size and its uncompressed size. So the real uncompressed size, the number of blocks and every seek point are readable from a few hundred bytes at the end of the file, which is why xz --list answers instantly on a 4 GB archive while gzip -l can only repeat the last four bytes of a .gz and hope they describe the whole thing. This page reads that same framing, walking backwards from the end exactly as xz does, and shows all of it: the fd 37 7a 58 5a 00 magic, the stream flags carrying the check type (None, CRC32, CRC64 — the default — or SHA-256) and the CRC32 that guards those two bytes; then, per block, the header size, whether the header declares its own compressed and uncompressed sizes, the stored check value, and the filter chain decoded properly. LZMA2's dictionary size is a six-bit code on a 4 KiB to 4 GiB ladder rather than a plain number, and a BCJ filter for x86, ARM, ARM64, PowerPC, IA-64, SPARC, ARM-Thumb or RISC-V can sit in front of it with its own start offset, so a file built with --x86 has two filters in its chain and the page spells the chain the way you would pass it back to xz. After the blocks come the index records, the index's own CRC32, and the footer's backward size, repeated stream flags and YZ magic. Nothing is decompressed. LZMA2 is not implemented here and does not need to be, which is the point — but it also means each block's check value is copied out rather than verified, because proving a CRC64 right would mean decoding the payload. The four CRC32s that protect the framing itself are recomputed, and a mismatch is reported instead of refused, so a file with a damaged index, or a footer whose flags disagree with the header, still shows you everything that is intact. The block count is worth more attention than it looks: one block cannot be decompressed on more than one core and nothing can seek into it, which is exactly what xz -T0 changes — it cuts the input into blocks of about three times the dictionary size, so an input smaller than that stays a single block however many threads you asked for. Several streams can also sit in one file, concatenated, separated by padding that must be a multiple of four zero bytes; those are listed one by one, as are trailing bytes that belong to no stream at all.
How to use
- Drop an .xz or .tar.xz onto the box. It is read inside the page: nothing is uploaded and nothing is decompressed.
- Start with the summary — streams, blocks, the true uncompressed size taken from the index, the ratio and which integrity check the file uses.
- Open a stream for its header, its block table and its index. The filter column is written the way you would pass it to xz, so --x86 --lzma2=dict=8MiB is the chain that built the block.
- Check the block count before planning a parallel decompress or a partial read: one block means one core and no seeking, however many threads the reader has.
- If the framing tile says mismatch, look for the red badge: the stream header, every block header, the index and the footer each carry their own CRC32, and the footer repeats the stream flags.
Frequently asked questions
- How can it report the uncompressed size without decompressing anything?
- Because the .xz format writes that number down. Each stream ends with an index listing, for every block, its unpadded size and its uncompressed size, and the footer's backward size says how far back the index starts. A reader seeks to the last 12 bytes, jumps to the index, adds up the records and is done — that is what xz --list does and what this page does. It is also why an .xz can be seeked into: the index says which block holds a given uncompressed offset. gzip has nothing comparable. Its ISIZE lives in the last four bytes of the file, describes only the final member and is taken modulo 2^32, so a multi-member .gz or one over 4 GB simply cannot report its size from the framing.
- Why does my file have a single block even though I compressed it with -T0?
- Because threaded compression splits the input into blocks of roughly three times the dictionary size, and at the default preset 6 the dictionary is 8 MiB, so nothing under about 24 MiB is ever cut in two. -T0 only asks for as many threads as there are cores; it cannot create a second block out of an input that fits in one. If you want blocks for their own sake — parallel decompression, or a reader that seeks — set them explicitly with --block-size or --block-list. The cost is compression ratio: each block starts with an empty dictionary, so matches never reach back across a block boundary.
- Which check type should I use, and what does it cost?
- The stream flags name one of four: None, CRC32, CRC64 or SHA-256, storing 0, 4, 8 or 32 bytes after every block. CRC64 is the default and is the right answer almost always — it is a few bytes per block and detects the accidental damage that storage and transfers actually produce. SHA-256 costs 32 bytes per block and considerably more CPU, and it is worth being clear about what it does not buy you: the digest sits inside the file, so anyone who can alter the payload can alter it too. That is tamper detection only against accidents, not against an attacker; a signature over the whole file is the tool for that. --check=none saves four to eight bytes and removes the only thing that would have told you the payload was damaged, which is defensible when the payload carries its own checksum and rarely otherwise.
- What is the difference between unpadded size, total size and compressed size?
- They are three views of the same block. The compressed size is just the LZMA2 output. The unpadded size — the number actually stored in the index — is the block header plus that compressed data plus the check value, with no padding counted. The total size is the unpadded size rounded up to a multiple of four, because every block is padded with zero bytes so the next one starts aligned. Subtracting gives you the padding, between zero and three bytes. This page shows the compressed data size and the padding separately, and cross-checks both against the block header whenever the header declares its own sizes, which is what the c and u in the sizes column mean.
- The page reports a mismatch but xz -t just says the data is corrupt. Which one is right?
- Both, at different depths. xz -t decodes the whole payload and verifies each block's check value, so it catches damage anywhere, but it stops at the first error and reports it in the same sentence no matter what was wrong. This page never decodes the payload, so it cannot see corruption inside the compressed data at all — but it recomputes each of the framing CRC32s separately, so it can say which structure is damaged: the stream header, one block's header, the index, or the footer. It also compares the stream flags in the header against the copy in the footer, a disagreement that is invisible to a size-based check and is a genuine corruption signal. Use this page to localise the damage and xz -t to decide whether the data itself is usable.
- Can it list the files inside a .tar.xz?
- No, and no reader can without decompressing. The tar archive — with its filenames, sizes and permissions — is the payload, and the payload is LZMA2-compressed as one opaque run of bytes. What you do learn from the framing is how big that tar will be when it comes out, which tells you whether it will fit before you start; how many blocks it holds, which tells you whether the extraction can use more than one core; and whether the container is intact. If the uncompressed size is a round multiple of 512, that is a good sign it really is a tar, since tar pads every member and the archive itself to that block size.
- What does the dictionary size in the filter chain actually tell me?
- How far back the compressor was allowed to look for a repeat, and therefore roughly how much memory the decompressor will need — a decoder allocates a buffer the size of the dictionary, so an .xz written with -9 (64 MiB) needs about 65 MB to open on a machine that may not have it, which is the usual reason an embedded or CI decompression fails while the same file opens fine on a laptop. LZMA2 stores it as a six-bit code: bit 0 chooses a mantissa of 2 or 3 and the rest is an exponent, so the ladder runs 4 KiB, 6 KiB, 8 KiB, 12 KiB and so on up to 3 GiB, with the single value 40 reserved for 4 GiB minus one. Read as a plain integer, the default 8 MiB dictionary would show up as 22.
Related tools
TAR Inspector
Open a .tar, .tar.gz or .tgz and list every entry — permissions, owner, size, timestamp and link target — without extracting it.
NumPy .npy / .npz Inspector
Read a .npy or .npz header in the browser: dtype, shape, byte order, field layout and a value preview, with no NumPy and no upload.
gzip (.gz) Inspector
Read a .gz header field by field, walk every member, and verify the trailer by recomputing CRC32 and the real size in your browser.
MIME Type Lookup
Find the MIME type for a file extension (or the other way around) and learn what each type is for.
CIDR Aggregator (Merge & Summarize IP Ranges)
Coalesce a messy list of IPv4/IPv6 addresses, CIDR blocks, and ranges into the smallest equivalent set of CIDR prefixes.
Email Header Analyzer
Paste a raw email header to walk the Received hops in order, see the per-hop delay, and read off the SPF/DKIM/DMARC verdicts.