fix(zip): write an entry's size into its local header - #755
Merged
Conversation
Saving went through miniz's streaming append, which leaves the crc and both sizes to a trailing data descriptor and zeroes them in the local header. LibreOffice rejects that on a stored entry, and every odf package stores `mimetype` — so an odt edited and saved by the apps came back as "the file is damaged, repair it?". `MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE` makes miniz come back to the local header once the entry is written, which the write sink has to follow: it now seeks to the offset miniz names instead of appending blindly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KiPhteiEmPp8iPQLBfnthA
andiwand
force-pushed
the
fix/zip-save-local-header-sizes
branch
from
August 28, 2026 05:16
a21c992 to
339c1da
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
The report
A user edited an
.odton the tablet, saved it, copied it back to the PC, and LibreOffice 26.2 says the file is damaged and offers to repair it. (Same mail also asks about ODS editing and about typing into an empty table cell — both known, neither touched here.)What was wrong
ZipArchive::saveappends every entry throughmz_zip_writer_add_read_buf_callback. That is miniz's streaming form: it writes the local header before it knows the entry's size, so crc and both sizes go out as zero and are repeated afterwards in a trailing data descriptor (general purpose bit 3).LibreOffice tolerates that on a deflated entry — it writes such entries itself — but rejects it on a stored one. Every odf package stores
mimetype, so every odt/ods/odp/odg we saved was unopenable. A docx got away with it because nothing in one is stored.Bisected against LibreOffice by rebuilding the saved package with one framing property changed at a time: descriptors on deflated entries only → opens; descriptor on
mimetypealone → "source file could not be loaded". Versions, external attributes and entry order made no difference.The fix
Pass
MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE, so miniz returns to the local header and fills in the real crc and sizes instead of emitting a descriptor. That needs the sink to honour the offset it is given, which the write callback ignored — it now seeks, relative to wherever the stream started.savetherefore wants a seekable stream, noted on the declaration;util::file::createopens one, and the tests now open theirs binary.Verified
Document::save, all open in LibreOffice 26.2 headless. Before:Error: source file could not be loaded.ZipArchive.save_sizes_local_headerswalks the saved local headers by the lengths they carry — which only terminates because they carry them — and checks bit 3 is clear and crc/size are filled. It fails on the old writer.odr_test: 1241 passed, the 6 usual skips.