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