Runtime: ZipArchive CreateMode tries to read Position on non-seekable Stream

Created on 7 Sep 2016  路  8Comments  路  Source: dotnet/runtime

Looking at https://msdn.microsoft.com/en-us/library/hh158263(v=vs.110).aspx i find this remark:

If the mode parameter is set to Read, the stream must support reading. If the mode parameter is set to Create, the stream must support writing. If the mode parameter is set to Update, the stream must support reading, writing, and seeking.

Based on that i would conclude that using CreateMode, i should be able to write to a non-seekable stream. The corefx tests for Create also seems to indicate that this is the intention at first glance.

However, even in CreateMode, the code attempts to read the Position from the stream it is writing to, unless i am mistaken that is typically not supported on a non-seekable stream?

In the abovementioned test a test WrappedStream class is used that can be configured as non-seekable. However, it does support getting a Position which i find a bit strange.

I can see that there is also a closed issue on Connect:
https://connect.microsoft.com/VisualStudio/feedback/details/816411/ziparchive-shouldnt-read-the-position-of-non-seekable-streams

Below is an example that will throw NotSupportedException:

using System;
using System.IO;
using System.IO.Compression;

namespace SeekeableStreamBug {
  public class NonSeekableMemoryStream: MemoryStream {
    public override bool CanSeek => false;

    public override long Position {
      get { throw new NotSupportedException(); }
      set { throw new NotSupportedException(); }
    }
  }

  public class Program {
    public static void Main( string[] args ) {
      using ( ZipArchive archive = new ZipArchive( new NonSeekableMemoryStream(), ZipArchiveMode.Create, false ) ) {
        var entry = archive.CreateEntry( "foo" );
        using ( var es = entry.Open() ) {
          es.Write( new byte[] { 4, 2 }, 0, 2 );
        }
      }
    }
  }
}

area-System.IO.Compression enhancement up-for-grabs

Most helpful comment

In that case, can there be a compiler warning or some other indication? Right now, as someone building for netstandard2.0 as a library, our library consumers will see different behavior in Net Framework (which still has the bug) and Core (which has the fix I believe as of Core 2).

All 8 comments

unless i am mistaken that is typically not supported on a non-seekable stream

Yes, that's the core of the problem. The MSDN documentation for Stream.Position says:

The stream must support seeking to get or set the position. Use the CanSeek property to determine whether the stream supports seeking.

To me, this says that streams that don't support seeking shouldn't implement Position. But ZipArchive just assumes that any stream used with the Create mode does support Position.

Since ZipArchive in Create mode only reads Position, I think the fix is to make ZipArchive track Position on its own, either always, or only when the underlying Stream's CanSeek returns false.


A workaround for this is to wrap the Stream in a Stream that tracks the position, like the one posted in my answer on SO.

To me, this says that streams that don't support seeking shouldn't implement Position

Totally as an aside from the main thread of discussion here, I don't think that's exactly what the docs mean, or at least not what they were intended to mean. What I believe they intend to say is that as a consumer of a stream, you shouldn't expect Position to be implemented if !CanSeek. As the implementer of a Stream, though, you're not required to throw NotSupportedException if !CanSeek... you could provide a valid non-throwing implementation of Position, but you shouldn't expect any consumers using the type polymorphically via the base Stream to use your implementation, given they can't expect it to be valid.

I think the fix is to make ZipArchive track Position on its own ... only when the underlying Stream's CanSeek returns false.

Seems reasonable to me assuming it doesn't incur any unexpected perf costs. Marked as up for grabs.

@StephenCleary works on it ...

Will this be backported to classic .NET framework?

In our .NET Standard library (which uses ZipArchive), when the library is loaded and called from PowerShell (not PowerShell core), we still end up hitting this, so we're still including the wrapper class.

.NET Framework had its last major version - see .NET Core is the future of .NET.
We won't port any features, or nice to have things to .NET Framework. It is in servicing / support mode.

In that case, can there be a compiler warning or some other indication? Right now, as someone building for netstandard2.0 as a library, our library consumers will see different behavior in Net Framework (which still has the bug) and Core (which has the fix I believe as of Core 2).

This is a bug, not a feature request. Why would that not fall under servicing or support?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

chunseoklee picture chunseoklee  路  3Comments

matty-hall picture matty-hall  路  3Comments

jchannon picture jchannon  路  3Comments

yahorsi picture yahorsi  路  3Comments

EgorBo picture EgorBo  路  3Comments