A decorator stream. It wraps another stream, and performs bookkeeping to keep track of the stream Position.

In some cases, it is not possible to get the Position of a stream, let's say, on a write-only output stream like ASP.NET's Response.OutputStream, or on a different write-only stream provided as the destination for the zip by the application. In this case, programmers can use this counting stream to count the bytes read or written.

Consider the scenario of an application that saves a self-extracting archive (SFX), that uses a custom SFX stub.

Saving to a filesystem file, the application would open the filesystem file (getting a FileStream), save the custom sfx stub into it, and then call ZipFile.Save(), specifying the same FileStream. ZipFile.Save() does the right thing for the zipentry offsets, by inquiring the Position of the FileStream before writing any data, and then adding that initial offset into any ZipEntry offsets in the zip directory. Everything works fine.

Now suppose the application is an ASPNET application and it saves directly to Response.OutputStream. It's not possible for DotNetZip to inquire the Position, so the offsets for the SFX will be wrong.

The workaround is for the application to use this class to wrap HttpResponse.OutputStream, then write the SFX stub and the ZipFile into that wrapper stream. Because ZipFile.Save() can inquire the Position, it will then do the right thing with the offsets.

Inheritance: System.IO.Stream
        /// <summary>
        ///   Save the zip archive to the specified stream.
        /// </summary>
        ///
        /// <remarks>
        /// <para>
        ///   The <c>ZipFile</c> instance is written to storage - typically a zip file
        ///   in a filesystem, but using this overload, the storage can be anything
        ///   accessible via a writable stream - only when the caller calls <c>Save</c>.
        /// </para>
        ///
        /// <para>
        ///   Use this method to save the zip content to a stream directly.  A common
        ///   scenario is an ASP.NET application that dynamically generates a zip file
        ///   and allows the browser to download it. The application can call
        ///   <c>Save(Response.OutputStream)</c> to write a zipfile directly to the
        ///   output stream, without creating a zip file on the disk on the ASP.NET
        ///   server.
        /// </para>
        ///
        /// <para>
        ///   Be careful when saving a file to a non-seekable stream, including
        ///   <c>Response.OutputStream</c>. When DotNetZip writes to a non-seekable
        ///   stream, the zip archive is formatted in such a way that may not be
        ///   compatible with all zip tools on all platforms.  It's a perfectly legal
        ///   and compliant zip file, but some people have reported problems opening
        ///   files produced this way using the Mac OS archive utility.
        /// </para>
        ///
        /// </remarks>
        ///
        /// <example>
        ///
        ///   This example saves the zipfile content into a MemoryStream, and
        ///   then gets the array of bytes from that MemoryStream.
        ///
        /// <code lang="C#">
        /// using (var zip = new Ionic.Zip.ZipFile())
        /// {
        ///     zip.CompressionLevel= Ionic.Zlib.CompressionLevel.BestCompression;
        ///     zip.Password = "******";
        ///     zip.Encryption = EncryptionAlgorithm.WinZipAes128;
        ///     zip.AddFile(sourceFileName);
        ///     MemoryStream output = new MemoryStream();
        ///     zip.Save(output);
        ///
        ///     byte[] zipbytes = output.ToArray();
        /// }
        /// </code>
        /// </example>
        ///
        /// <example>
        /// <para>
        ///   This example shows a pitfall you should avoid. DO NOT read
        ///   from a stream, then try to save to the same stream.  DO
        ///   NOT DO THIS:
        /// </para>
        ///
        /// <code lang="C#">
        /// using (var fs = new FileSteeam(filename, FileMode.Open))
        /// {
        ///   using (var zip = Ionic.Zip.ZipFile.Read(inputStream))
        ///   {
        ///     zip.AddEntry("Name1.txt", "this is the content");
        ///     zip.Save(inputStream);  // NO NO NO!!
        ///   }
        /// }
        /// </code>
        ///
        /// <para>
        ///   Better like this:
        /// </para>
        ///
        /// <code lang="C#">
        /// using (var zip = Ionic.Zip.ZipFile.Read(filename))
        /// {
        ///     zip.AddEntry("Name1.txt", "this is the content");
        ///     zip.Save();  // YES!
        /// }
        /// </code>
        ///
        /// </example>
        ///
        /// <param name="outputStream">
        ///   The <c>System.IO.Stream</c> to write to. It must be
        ///   writable. If you created the ZipFile instanct by calling
        ///   ZipFile.Read(), this stream must not be the same stream
        ///   you passed to ZipFile.Read().
        /// </param>
        public void Save(Stream outputStream)
        {
            if (outputStream == null)
            {
                throw new ArgumentNullException("outputStream");
            }
            if (!outputStream.CanWrite)
            {
                throw new ArgumentException("Must be a writable stream.", "outputStream");
            }

            // if we had a filename to save to, we are now obliterating it.
            _name = null;

            _writestream = new CountingStream(outputStream);

            _contentsChanged   = true;
            _fileAlreadyExists = false;
            Save();
        }
        /// <summary>
        ///   Save the zip archive to the specified stream.
        /// </summary>
        ///
        /// <remarks>
        /// <para>
        ///   The <c>ZipFile</c> instance is written to storage - typically a zip file
        ///   in a filesystem, but using this overload, the storage can be anything
        ///   accessible via a writable stream - only when the caller calls <c>Save</c>.
        /// </para>
        ///
        /// <para>
        ///   Use this method to save the zip content to a stream directly.  A common
        ///   scenario is an ASP.NET application that dynamically generates a zip file
        ///   and allows the browser to download it. The application can call
        ///   <c>Save(Response.OutputStream)</c> to write a zipfile directly to the
        ///   output stream, without creating a zip file on the disk on the ASP.NET
        ///   server.
        /// </para>
        ///
        /// <para>
        ///   Be careful when saving a file to a non-seekable stream, including
        ///   <c>Response.OutputStream</c>. When DotNetZip writes to a non-seekable
        ///   stream, the zip archive is formatted in such a way that may not be
        ///   compatible with all zip tools on all platforms.  It's a perfectly legal
        ///   and compliant zip file, but some people have reported problems opening
        ///   files produced this way using the Mac OS archive utility.
        /// </para>
        ///
        /// </remarks>
        ///
        /// <example>
        ///
        ///   This example saves the zipfile content into a MemoryStream, and
        ///   then gets the array of bytes from that MemoryStream.
        ///
        /// <code lang="C#">
        /// using (var zip = new Ionic.Zip.ZipFile())
        /// {
        ///     zip.CompressionLevel= Ionic.Zlib.CompressionLevel.BestCompression;
        ///     zip.Password = "******";
        ///     zip.Encryption = EncryptionAlgorithm.WinZipAes128;
        ///     zip.AddFile(sourceFileName);
        ///     MemoryStream output = new MemoryStream();
        ///     zip.Save(output);
        ///
        ///     byte[] zipbytes = output.ToArray();
        /// }
        /// </code>
        /// </example>
        ///
        /// <example>
        /// <para>
        ///   This example shows a pitfall you should avoid. DO NOT read
        ///   from a stream, then try to save to the same stream.  DO
        ///   NOT DO THIS:
        /// </para>
        ///
        /// <code lang="C#">
        /// using (var fs = new FileSteeam(filename, FileMode.Open))
        /// {
        ///   using (var zip = Ionic.Zip.ZipFile.Read(inputStream))
        ///   {
        ///     zip.AddEntry("Name1.txt", "this is the content");
        ///     zip.Save(inputStream);  // NO NO NO!!
        ///   }
        /// }
        /// </code>
        ///
        /// <para>
        ///   Better like this:
        /// </para>
        ///
        /// <code lang="C#">
        /// using (var zip = Ionic.Zip.ZipFile.Read(filename))
        /// {
        ///     zip.AddEntry("Name1.txt", "this is the content");
        ///     zip.Save();  // YES!
        /// }
        /// </code>
        ///
        /// </example>
        ///
        /// <param name="outputStream">
        ///   The <c>System.IO.Stream</c> to write to. It must be
        ///   writable. If you created the ZipFile instanct by calling
        ///   ZipFile.Read(), this stream must not be the same stream
        ///   you passed to ZipFile.Read().
        /// </param>
        public void Save(Stream outputStream)
        {
            if (outputStream == null)
                throw new ArgumentNullException("outputStream");
            if (!outputStream.CanWrite)
                throw new ArgumentException("Must be a writable stream.", "outputStream");

            // if we had a filename to save to, we are now obliterating it.
            _name = null;

            _writestream = new CountingStream(outputStream);

            _contentsChanged = true;
            _fileAlreadyExists = false;
            Save();
        }
        private void CopyThroughWithNoChange(Stream outstream)
        {
            int n;
            byte[] bytes = new byte[BufferSize];
            var input = new CountingStream(this.ArchiveStream);

            // seek to the beginning of the entry data in the input stream
            input.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);

            if (this._TotalEntrySize == 0)
            {
                // We've never set the length of the entry.
                // Set it here.
                this._TotalEntrySize = this._LengthOfHeader + this._CompressedFileDataSize + _LengthOfTrailer;

                // The CompressedSize includes all the leading metadata associated
                // to encryption, if any, as well as the compressed data, or
                // compressed-then-encrypted data, and the trailer in case of AES.

                // The CompressedFileData size is the same, less the encryption
                // framing data (12 bytes header for PKZip; 10/18 bytes header and
                // 10 byte trailer for AES).

                // The _LengthOfHeader includes all the zip entry header plus the
                // crypto header, if any.  The _LengthOfTrailer includes the
                // 10-byte MAC for AES, where appropriate, and the bit-3
                // Descriptor, where applicable.
            }


            // workitem 5616
            // remember the offset, within the output stream, of this particular entry header.
            // This may have changed if any of the other entries changed (eg, if a different
            // entry was removed or added.)
            var counter = outstream as CountingStream;
            _RelativeOffsetOfLocalHeader = (counter != null)
                ? counter.ComputedPosition
                : outstream.Position;  // BytesWritten

            // copy through the header, filedata, trailer, everything...
            long remaining = this._TotalEntrySize;
            while (remaining > 0)
            {
                int len = (remaining > bytes.Length) ? bytes.Length : (int)remaining;

                // read
                n = input.Read(bytes, 0, len);
                //_CheckRead(n);

                // write
                outstream.Write(bytes, 0, n);
                remaining -= n;
                OnWriteBlock(input.BytesRead, this._TotalEntrySize);
                if (_ioOperationCanceled)
                    break;
            }
        }
        private void CopyThroughWithRecompute(Stream outstream)
        {
            int n;
            byte[] bytes = new byte[BufferSize];
            var input = new CountingStream(this.ArchiveStream);

            long origRelativeOffsetOfHeader = _RelativeOffsetOfLocalHeader;

            // The header length may change due to rename of file, add a comment, etc.
            // We need to retain the original.
            int origLengthOfHeader = LengthOfHeader; // including crypto bytes!

            // WriteHeader() has the side effect of changing _RelativeOffsetOfLocalHeader
            // and setting _LengthOfHeader.  While ReadHeader() reads the crypto header if
            // present, WriteHeader() does not write the crypto header.
            WriteHeader(outstream, 0);
            StoreRelativeOffset();

            if (!this.FileName.EndsWith("/"))
            {
                // Not a directory; there is file data.
                // Seek to the beginning of the entry data in the input stream.

                long pos = origRelativeOffsetOfHeader + origLengthOfHeader;
                int len = GetLengthOfCryptoHeaderBytes(_Encryption_FromZipFile);
                pos -= len; // want to keep the crypto header
                _LengthOfHeader += len;

                input.Seek(pos, SeekOrigin.Begin);

                // copy through everything after the header to the output stream
                long remaining = this._CompressedSize;

                while (remaining > 0)
                {
                    len = (remaining > bytes.Length) ? bytes.Length : (int)remaining;

                    // read
                    n = input.Read(bytes, 0, len);
                    //_CheckRead(n);

                    // write
                    outstream.Write(bytes, 0, n);
                    remaining -= n;
                    OnWriteBlock(input.BytesRead, this._CompressedSize);
                    if (_ioOperationCanceled)
                        break;
                }

                // bit 3 descriptor
                if ((this._BitField & 0x0008) == 0x0008)
                {
                    int size = 16;
                    if (_InputUsesZip64) size += 8;
                    byte[] Descriptor = new byte[size];
                    input.Read(Descriptor, 0, size);

                    if (_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Never)
                    {
                        // original descriptor was 24 bytes, now we need 16.
                        // Must check for underflow here.
                        // signature + CRC.
                        outstream.Write(Descriptor, 0, 8);

                        // Compressed
                        if (_CompressedSize > 0xFFFFFFFF)
                            throw new InvalidOperationException("ZIP64 is required");
                        outstream.Write(Descriptor, 8, 4);

                        // UnCompressed
                        if (_UncompressedSize > 0xFFFFFFFF)
                            throw new InvalidOperationException("ZIP64 is required");
                        outstream.Write(Descriptor, 16, 4);
                        _LengthOfTrailer -= 8;
                    }
                    else if (!_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Always)
                    {
                        // original descriptor was 16 bytes, now we need 24
                        // signature + CRC
                        byte[] pad = new byte[4];
                        outstream.Write(Descriptor, 0, 8);
                        // Compressed
                        outstream.Write(Descriptor, 8, 4);
                        outstream.Write(pad, 0, 4);
                        // UnCompressed
                        outstream.Write(Descriptor, 12, 4);
                        outstream.Write(pad, 0, 4);
                        _LengthOfTrailer += 8;
                    }
                    else
                    {
                        // same descriptor on input and output. Copy it through.
                        outstream.Write(Descriptor, 0, size);
                        //_LengthOfTrailer += size;
                    }
                }
            }

            _TotalEntrySize = _LengthOfHeader + _CompressedFileDataSize + _LengthOfTrailer;
        }
        /// <summary>
        ///   Prepare the given stream for output - wrap it in a CountingStream, and
        ///   then in a CRC stream, and an encryptor and deflator as appropriate.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///     Previously this was used in ZipEntry.Write(), but in an effort to
        ///     introduce some efficiencies in that method I've refactored to put the
        ///     code inline.  This method still gets called by ZipOutputStream.
        ///   </para>
        /// </remarks>
        internal void PrepOutputStream(Stream s,
                                       long streamLength,
                                       out CountingStream outputCounter,
                                       out Stream encryptor,
                                       out Stream compressor,
                                       out Crisis.Ionic.Crc.CrcCalculatorStream output)
        {
            TraceWriteLine("PrepOutputStream: e({0}) comp({1}) crypto({2}) zf({3})",
                           FileName,
                           CompressionLevel,
                           Encryption,
                           _container.Name);

            // Wrap a counting stream around the raw output stream:
            // This is the last thing that happens before the bits go to the
            // application-provided stream.
            outputCounter = new CountingStream(s);

            // Sometimes the incoming "raw" output stream is already a CountingStream.
            // Doesn't matter. Wrap it with a counter anyway. We need to count at both
            // levels.

            if (streamLength != 0L)
            {
                // Maybe wrap an encrypting stream around that:
                // This will happen BEFORE output counting, and AFTER deflation, if encryption
                // is used.
                encryptor = MaybeApplyEncryption(outputCounter);

                // Maybe wrap a compressing Stream around that.
                // This will happen BEFORE encryption (if any) as we write data out.
                compressor = MaybeApplyCompression(encryptor, streamLength);
            }
            else
            {
                encryptor = compressor = outputCounter;
            }
            // Wrap a CrcCalculatorStream around that.
            // This will happen BEFORE compression (if any) as we write data out.
            output = new Crisis.Ionic.Crc.CrcCalculatorStream(compressor, true);
        }
        internal void FinishOutputStream(Stream s,
                                         CountingStream entryCounter,
                                         Stream encryptor,
                                         Stream compressor,
                                         Crisis.Ionic.Crc.CrcCalculatorStream output)
        {
            if (output == null) return;

            output.Close();

            // by calling Close() on the deflate stream, we write the footer bytes, as necessary.
            if ((compressor as Crisis.Ionic.Zlib.DeflateStream) != null)
                compressor.Close();
#if BZIP
            else if ((compressor as Ionic.BZip2.BZip2OutputStream) != null)
                compressor.Close();
#if !NETCF
            else if ((compressor as Ionic.BZip2.ParallelBZip2OutputStream) != null)
                compressor.Close();
#endif
#endif

#if !NETCF
            else if ((compressor as Crisis.Ionic.Zlib.ParallelDeflateOutputStream) != null)
                compressor.Close();
#endif

            encryptor.Flush();
            encryptor.Close();

            _LengthOfTrailer = 0;

            _UncompressedSize = output.TotalBytesSlurped;

#if AESCRYPTO
            WinZipAesCipherStream wzacs = encryptor as WinZipAesCipherStream;
            if (wzacs != null && _UncompressedSize > 0)
            {
                s.Write(wzacs.FinalAuthentication, 0, 10);
                _LengthOfTrailer += 10;
            }
#endif
            _CompressedFileDataSize = entryCounter.BytesWritten;
            _CompressedSize = _CompressedFileDataSize;   // may be adjusted
            _Crc32 = output.Crc;

            // Set _RelativeOffsetOfLocalHeader now, to allow for re-streaming
            StoreRelativeOffset();
        }
        private void _WriteEntryData(Stream s)
        {
            // Read in the data from the input stream (often a file in the filesystem),
            // and write it to the output stream, calculating a CRC on it as we go.
            // We will also compress and encrypt as necessary.

            Stream input = null;
            long fdp = -1L;
            try
            {
                // Want to record the position in the zip file of the zip entry
                // data (as opposed to the metadata).  s.Position may fail on some
                // write-only streams, eg stdout or System.Web.HttpResponseStream.
                // We swallow that exception, because we don't care, in that case.
                // But, don't set __FileDataPosition directly.  It may be needed
                // to READ the zip entry from the zip file, if this is a
                // "re-stream" situation. In other words if the zip entry has
                // changed compression level, or compression method, or (maybe?)
                // encryption algorithm.  In that case if the original entry is
                // encrypted, we need __FileDataPosition to be the value for the
                // input zip file.  This s.Position is for the output zipfile.  So
                // we copy fdp to __FileDataPosition after this entry has been
                // (maybe) restreamed.
                fdp = s.Position;
            }
            catch (Exception) { }

            try
            {
                // Use fileLength for progress updates, and to decide whether we can
                // skip encryption and compression altogether (in case of length==zero)
                long fileLength = SetInputAndFigureFileLength(ref input);

                // Wrap a counting stream around the raw output stream:
                // This is the last thing that happens before the bits go to the
                // application-provided stream.
                //
                // Sometimes s is a CountingStream. Doesn't matter. Wrap it with a
                // counter anyway. We need to count at both levels.

                CountingStream entryCounter = new CountingStream(s);

                Stream encryptor;
                Stream compressor;

                if (fileLength != 0L)
                {
                    // Maybe wrap an encrypting stream around the counter: This will
                    // happen BEFORE output counting, and AFTER compression, if encryption
                    // is used.
                    encryptor = MaybeApplyEncryption(entryCounter);

                    // Maybe wrap a compressing Stream around that.
                    // This will happen BEFORE encryption (if any) as we write data out.
                    compressor = MaybeApplyCompression(encryptor, fileLength);
                }
                else
                {
                    encryptor = compressor = entryCounter;
                }

                // Wrap a CrcCalculatorStream around that.
                // This will happen BEFORE compression (if any) as we write data out.
                var output = new Crisis.Ionic.Crc.CrcCalculatorStream(compressor, true);

                // output.Write() causes this flow:
                // calc-crc -> compress -> encrypt -> count -> actually write

                if (this._Source == ZipEntrySource.WriteDelegate)
                {
                    // allow the application to write the data
                    this._WriteDelegate(this.FileName, output);
                }
                else
                {
                    // synchronously copy the input stream to the output stream-chain
                    byte[] buffer = new byte[BufferSize];
                    int n;
                    while ((n = SharedUtilities.ReadWithRetry(input, buffer, 0, buffer.Length, FileName)) != 0)
                    {
                        output.Write(buffer, 0, n);
                        OnWriteBlock(output.TotalBytesSlurped, fileLength);
                        if (_ioOperationCanceled)
                            break;
                    }
                }

                FinishOutputStream(s, entryCounter, encryptor, compressor, output);
            }
            finally
            {
                if (this._Source == ZipEntrySource.JitStream)
                {
                    // allow the application to close the stream
                    if (this._CloseDelegate != null)
                        this._CloseDelegate(this.FileName, input);
                }
                else if ((input as FileStream) != null)
                {
#if NETCF
                    input.Close();
#else
                    input.Dispose();
#endif
                }
            }

            if (_ioOperationCanceled)
                return;

            // set FDP now, to allow for re-streaming
            this.__FileDataPosition = fdp;
            PostProcessOutput(s);
        }