Ejemplo n.º 1
0
        /// <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 Alienlab.Zip.ZipFile())
        /// {
        ///     zip.CompressionLevel= Alienlab.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 = Alienlab.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 = Alienlab.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 Alienlab.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 Alienlab.Crc.CrcCalculatorStream(compressor, true);
        }
        internal void FinishOutputStream(Stream s,
                                         CountingStream entryCounter,
                                         Stream encryptor,
                                         Stream compressor,
                                         Alienlab.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 Alienlab.Zlib.DeflateStream) != null)
                compressor.Close();
#if BZIP
            else if ((compressor as Alienlab.BZip2.BZip2OutputStream) != null)
                compressor.Close();
#if !NETCF
            else if ((compressor as Alienlab.BZip2.ParallelBZip2OutputStream) != null)
                compressor.Close();
#endif
#endif

#if !NETCF
            else if ((compressor as Alienlab.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 Alienlab.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);
        }