Skip to content

Fix EntryStream.Dispose() throwing NotSupportedException on non-seekable streams#1151

Merged
adamhathcock merged 6 commits intomasterfrom
copilot/fix-entrystream-flush-issue
Jan 22, 2026
Merged

Fix EntryStream.Dispose() throwing NotSupportedException on non-seekable streams#1151
adamhathcock merged 6 commits intomasterfrom
copilot/fix-entrystream-flush-issue

Conversation

Copy link
Contributor

Copilot AI commented Jan 22, 2026

Fix EntryStream.Dispose() NotSupportedException on non-seekable streams

Problem

Since version 0.41.0, EntryStream.Dispose() calls Flush() on DeflateStream and LzmaStream to handle over-read issues. However, this causes NotSupportedException in legitimate streaming scenarios with non-seekable streams (e.g., ASP.NET Core HttpRequest.Body).

Solution

Wrapped the Flush() and FlushAsync() calls in try-catch blocks that safely ignore NotSupportedException. This maintains the over-read correction behavior for streams that support it, while gracefully degrading for non-seekable streams.

Plan

  • Explore repository structure and understand the issue
  • Examine EntryStream.Dispose() implementation
  • Create focused test to reproduce the issue
  • Implement fix: wrap Flush() calls in try-catch to swallow NotSupportedException
  • Verify fix works with test
  • Run broader tests to ensure no regressions
  • Code review and address feedback
  • Security scan
  • Format code with CSharpier
  • Add async tests

Changes Made

  • Modified EntryStream.Dispose() to wrap Flush() calls in try-catch blocks
  • Modified EntryStream.DisposeAsync() to wrap FlushAsync() calls in try-catch blocks
  • Both sync and async versions now safely ignore NotSupportedException from Flush operations
  • Added sync tests EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_Deflate and EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_LZMA
  • Added async tests EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_Deflate_Async and EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_LZMA_Async
  • Updated test comments to include version context (0.41.0)
  • Formatted code with CSharpier

Test Results

  • ✅ New sync tests pass (2/2)
  • ✅ New async tests pass (2/2)
  • ✅ ZipReaderTests pass (35/35)
  • ✅ ZipReaderAsyncTests pass (26/26)
  • ✅ TarReaderTests pass (18/18)
  • ✅ GZipReaderTests pass (2/2)
  • ✅ DisposalTests pass (13/13)

Security Summary

  • ✅ CodeQL scan completed with 0 alerts
  • No security vulnerabilities introduced
Original prompt

This section details on the original issue you should resolve

<issue_title>EntryStream.Dispose() calls Flush() on Deflate/LZMA streams causing NotSupportedException on non-seekable streams</issue_title>
<issue_description>Hi,

Since SharpCompress 0.41.0, EntryStream.Dispose() calls Flush() on some internal decompression streams:

//Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised
if (_stream is IStreamStack ss)
{
    if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
    {
        deflateStream.Flush(); //Deflate over reads. Knock it back
    }
    else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream)
    {
        lzmaStream.Flush(); //Lzma over reads. Knock it back
    }
}

This causes a NotSupportedException in some legitimate streaming scenarios.


Context / real-world scenario

I'm using SharpCompress in a pure streaming pipeline in ASP.NET Core:

  • Source stream: HttpRequest.Body

  • Read via MultipartReader (multipart/form-data)

  • Archive entries are processed sequentially using ReaderFactory.Open(...).MoveToNextEntry()

  • Entry streams are non-seekable by design

In this setup, Flush() on DeflateStream / LzmaStream may internally try to access Position / Seek on the underlying stream stack, which is not supported and throws NotSupportedException.

This happens during EntryStream.Dispose(), which breaks the iteration and prevents moving to the next entry.


Why this is problematic

From a consumer point of view:

  • Dispose() is expected to be safe and non-throwing

  • Especially in streaming scenarios, Dispose() is required to advance to the next entry

  • Throwing NotSupportedException during Dispose() makes SharpCompress unusable in valid non-seekable streaming pipelines


Expected behavior / suggestion

At minimum, EntryStream.Dispose() should:

  • Not throw if Flush() is not supported

  • Swallow or ignore NotSupportedException coming from Flush()

Example defensive pattern:

try
{
    deflateStream.Flush();
}
catch (NotSupportedException)
{
    // ignore: underlying stream does not support required operations
}

Or more generally: Dispose() should never fail due to optional stream realignment logic.


Workaround on consumer side

Currently I have to wrap Dispose() with a try/catch and manually dispose the base stream via IStreamStack, which works but feels like something the library should handle.


Summary

  • The new Flush() in EntryStream.Dispose() breaks valid non-seekable streaming scenarios.

  • The over-read problem is real, but the current solution is unsafe.

  • Dispose() should not throw in this case.</issue_description>

<agent_instructions>try out the suggestion</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits January 22, 2026 13:04
…eekable streams

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix EntryStream.Dispose() to prevent NotSupportedException Fix EntryStream.Dispose() throwing NotSupportedException on non-seekable streams Jan 22, 2026
Copilot AI requested a review from adamhathcock January 22, 2026 13:11
@adamhathcock
Copy link
Owner

@copilot format this

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 22, 2026

@copilot format this

Formatted code with CSharpier (commit 7cbdc5b). Removed trailing whitespace from test file.

@adamhathcock
Copy link
Owner

@copilot add async tests to go along with the sync tests added

1 similar comment
@adamhathcock
Copy link
Owner

@copilot add async tests to go along with the sync tests added

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 22, 2026

@copilot add async tests to go along with the sync tests added

Added async tests EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_Deflate_Async and EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_LZMA_Async (commit 9628f2d). Both tests pass.

@copilot add async tests to go along with the sync tests added

Added async tests EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_Deflate_Async and EntryStream_Dispose_DoesNotThrow_OnNonSeekableStream_LZMA_Async (commit 9628f2d). Both tests pass.

@adamhathcock adamhathcock marked this pull request as ready for review January 22, 2026 15:16
Copilot AI review requested due to automatic review settings January 22, 2026 15:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a regression introduced in version 0.41.0 where EntryStream.Dispose() throws NotSupportedException when working with non-seekable streams. The issue occurs because Flush() calls on decompression streams can fail on non-seekable underlying streams (e.g., ASP.NET Core HttpRequest.Body). The fix wraps these Flush() calls in try-catch blocks to gracefully handle the exception while maintaining the over-read correction behavior for seekable streams.

Changes:

  • Added exception handling around Flush() and FlushAsync() calls in EntryStream disposal methods
  • Added comprehensive sync and async tests covering both Deflate and LZMA compression formats
  • Updated project configuration to support conditional compilation for legacy .NET Framework

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/SharpCompress/Common/EntryStream.cs Wrapped Flush/FlushAsync calls in try-catch blocks to ignore NotSupportedException
tests/SharpCompress.Test/Zip/ZipReaderTests.cs Added sync tests for non-seekable stream disposal with Deflate and LZMA
tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs Added async tests for non-seekable stream disposal with Deflate and LZMA
tests/SharpCompress.Test/SharpCompress.Test.csproj Added LEGACY_DOTNET define for net48 and fixed Linux-specific Mono.Posix.NETStandard dependency

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@adamhathcock adamhathcock merged commit caa7acd into master Jan 22, 2026
11 checks passed
@adamhathcock adamhathcock deleted the copilot/fix-entrystream-flush-issue branch January 22, 2026 15:23
adamhathcock added a commit that referenced this pull request Jan 23, 2026
…ush-issue

Fix EntryStream.Dispose() throwing NotSupportedException on non-seekable streams
# Conflicts:
#	tests/SharpCompress.Test/Zip/ZipReaderTests.cs
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.

EntryStream.Dispose() calls Flush() on Deflate/LZMA streams causing NotSupportedException on non-seekable streams

3 participants