Skip to content

fix: never leave trailing bytes when the output stream cannot be truncated - #232

Merged
vaceslav merged 1 commit into
mainfrom
fix/output-truncation
Sep 27, 2026
Merged

vaceslav merged 1 commit into
mainfrom
fix/output-truncation

Conversation

@vaceslav

Copy link
Copy Markdown
Contributor

Changes

Follow-up to #221. A seekable output stream that cannot be truncated (SetLength throws NotSupportedException) and still holds longer earlier content used to keep trailing bytes of that content after the document. For ODT the result was a corrupt file reported as success. The existing test SeekableOutputThatCannotChangeItsLength_IsWrittenWithoutTruncation covered that behavior and is replaced.

New behavior: the stream is truncated before anything is written. If it cannot be truncated, processing returns a Failure and the output is left unchanged.

  • ODT (OdtPackage.Save): the package is already built in memory, so its length is known. If output.CanSeek and the earlier content after Position is longer than the package, the output is truncated at Position before the package is written. If truncation is not supported, Save throws the new internal OutputStreamNotTruncatableException and nothing is written, which keeps the "nothing written on failure" contract. If the package is at least as long as the earlier tail, it overwrites all of it, so SetLength is not needed and non-truncatable streams keep working. Non-seekable outputs are unchanged. The old post-write TruncateAfterPosition, which silently swallowed NotSupportedException, is removed.
  • DOCX (DocumentTemplateProcessor): the perf(odt): stream unprocessed package entries, bound XML part sizes, truncate seekable outputs #221 truncation now runs before the template copy, not after it. With a non-truncatable output that holds earlier content, processing fails before anything is written and the earlier content stays as it was. Before, the template was copied over part of it and the result was Processing failed: Specified method is not supported. DOCX already needed SetLength in practice: OpenXML/ZipArchive update mode calls it on save even for an empty output, so an empty non-truncatable output still fails there, as before. The edit-in-place contract for DOCX does not change.
  • Both processors, and the TemplateProcessor facade, return the same message: Invalid output stream: the output stream has earlier content after the current position that would remain after the document, and it cannot be truncated (SetLength is not supported). Pass an empty output stream or one that supports SetLength (for example a FileStream opened with FileMode.Create).
  • Docs: opendocument.md (output requirements, memory use) and quick-start.md (DOCX output streams).

Tests

  • ODT (OdtPackageBranchTests): non-truncatable output with longer earlier content → Failure, bytes unchanged, directly and through the facade. Non-truncatable output with shorter earlier content → success and SetLength is never called. Fixed-size MemoryStream (can shrink) with longer content → truncated and valid.
  • DOCX (StreamValidationTests): non-truncatable output with earlier content → Failure, bytes unchanged, directly and through the facade. Shorter earlier content → truncated and valid. The existing ExistingLongerOutputFile_IsTruncated tests for both formats still pass.
  • Release build with -p:ContinuousIntegrationBuild=true (0 warnings), all test projects on net10/9/8, dotnet format --verify-no-changes, dotnet pack, mkdocs build --strict.

Public API impact

None: no public API change. Behavior change only for seekable, non-truncatable output streams with earlier content. They now get a Failure instead of a corrupt output (ODT: previously a false success; DOCX: previously a failure after partially overwriting the stream).

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@vaceslav
vaceslav merged commit dcd7c6e into main Sep 27, 2026
12 checks passed
@vaceslav
vaceslav deleted the fix/output-truncation branch September 27, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants