internal void PostProcessOutput(Stream s)
{
var s1 = s as CountingStream;
// workitem 8931 - for WriteDelegate.
// The WriteDelegate changes things because there can be a zero-byte stream
// written. In all other cases DotNetZip knows the length of the stream
// before compressing and encrypting. In this case we have to circle back,
// and omit all the crypto stuff - the GP bitfield, and the crypto header.
if (_UncompressedSize == 0 && _CompressedSize == 0)
{
if (this._Source == ZipEntrySource.ZipOutputStream) return; // nothing to do...
if (_Password != null)
{
int headerBytesToRetract = 0;
if (Encryption == EncryptionAlgorithm.PkzipWeak)
headerBytesToRetract = 12;
#if AESCRYPTO
else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
Encryption == EncryptionAlgorithm.WinZipAes256)
{
headerBytesToRetract = _aesCrypto_forWrite._Salt.Length + _aesCrypto_forWrite.GeneratedPV.Length;
}
#endif
if (this._Source == ZipEntrySource.ZipOutputStream && !s.CanSeek)
throw new ZipException("Zero bytes written, encryption in use, and non-seekable output.");
if (Encryption != EncryptionAlgorithm.None)
{
// seek back in the stream to un-output the security metadata
s.Seek(-1 * headerBytesToRetract, SeekOrigin.Current);
s.SetLength(s.Position);
// workitem 10178
Crisis.Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
// workitem 11131
// adjust the count on the CountingStream as necessary
if (s1 != null) s1.Adjust(headerBytesToRetract);
// subtract the size of the security header from the _LengthOfHeader
_LengthOfHeader -= headerBytesToRetract;
__FileDataPosition -= headerBytesToRetract;
}
_Password = null;
// turn off the encryption bit
_BitField &= ~(0x0001);
// copy the updated bitfield value into the header
int j = 6;
_EntryHeader[j++] = (byte)(_BitField & 0x00FF);
_EntryHeader[j++] = (byte)((_BitField & 0xFF00) >> 8);
#if AESCRYPTO
if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
Encryption == EncryptionAlgorithm.WinZipAes256)
{
// Fix the extra field - overwrite the 0x9901 headerId
// with dummy data. (arbitrarily, 0x9999)
Int16 fnLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
int offx = 30 + fnLength;
int aesIndex = FindExtraFieldSegment(_EntryHeader, offx, 0x9901);
if (aesIndex >= 0)
{
_EntryHeader[aesIndex++] = 0x99;
_EntryHeader[aesIndex++] = 0x99;
}
}
#endif
}
CompressionMethod = 0;
Encryption = EncryptionAlgorithm.None;
}
else if (_zipCrypto_forWrite != null
#if AESCRYPTO
|| _aesCrypto_forWrite != null
#endif
)
{
if (Encryption == EncryptionAlgorithm.PkzipWeak)
{
_CompressedSize += 12; // 12 extra bytes for the encryption header
}
#if AESCRYPTO
else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
Encryption == EncryptionAlgorithm.WinZipAes256)
{
// adjust the compressed size to include the variable (salt+pv)
// security header and 10-byte trailer. According to the winzip AES
// spec, that metadata is included in the "Compressed Size" figure
// when encoding the zip archive.
_CompressedSize += _aesCrypto_forWrite.SizeOfEncryptionMetadata;
}
#endif
}
int i = 8;
_EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
_EntryHeader[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);
i = 14;
// CRC - the correct value now
_EntryHeader[i++] = (byte)(_Crc32 & 0x000000FF);
_EntryHeader[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
_EntryHeader[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
_EntryHeader[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
SetZip64Flags();
// (i==26) filename length (Int16)
Int16 filenameLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
Int16 extraFieldLength = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);
if (_OutputUsesZip64.Value)
{
// VersionNeededToExtract - set to 45 to indicate zip64
_EntryHeader[4] = (byte)(45 & 0x00FF);
_EntryHeader[5] = 0x00;
// workitem 7924 - don't need bit 3
// // workitem 7917
// // set bit 3 for ZIP64 compatibility with WinZip12
// _BitField |= 0x0008;
// _EntryHeader[6] = (byte)(_BitField & 0x00FF);
// CompressedSize and UncompressedSize - 0xFF
for (int j = 0; j < 8; j++)
_EntryHeader[i++] = 0xff;
// At this point we need to find the "Extra field" that follows the
// filename. We had already emitted it, but the data (uncomp, comp,
// ROLH) was not available at the time we did so. Here, we emit it
// again, with final values.
i = 30 + filenameLength;
_EntryHeader[i++] = 0x01; // zip64
_EntryHeader[i++] = 0x00;
i += 2; // skip over data size, which is 16+4
Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, _EntryHeader, i, 8);
i += 8;
Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, _EntryHeader, i, 8);
}
else
{
// VersionNeededToExtract - reset to 20 since no zip64
_EntryHeader[4] = (byte)(20 & 0x00FF);
_EntryHeader[5] = 0x00;
// CompressedSize - the correct value now
i = 18;
_EntryHeader[i++] = (byte)(_CompressedSize & 0x000000FF);
_EntryHeader[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
_EntryHeader[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
_EntryHeader[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
// UncompressedSize - the correct value now
_EntryHeader[i++] = (byte)(_UncompressedSize & 0x000000FF);
_EntryHeader[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
_EntryHeader[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
_EntryHeader[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
// The HeaderId in the extra field header, is already dummied out.
if (extraFieldLength != 0)
{
i = 30 + filenameLength;
// For zip archives written by this library, if the zip64
// header exists, it is the first header. Because of the logic
// used when first writing the _EntryHeader bytes, the
// HeaderId is not guaranteed to be any particular value. So
// we determine if the first header is a putative zip64 header
// by examining the datasize. UInt16 HeaderId =
// (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
if (DataSize == 16)
{
// reset to Header Id to dummy value, effectively dummy-ing out the zip64 metadata
_EntryHeader[i++] = 0x99;
_EntryHeader[i++] = 0x99;
}
}
}
#if AESCRYPTO
if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
Encryption == EncryptionAlgorithm.WinZipAes256)
{
// Must set compressionmethod to 0x0063 (decimal 99)
//
// and then set the compression method bytes inside the extra
// field to the actual compression method value.
i = 8;
_EntryHeader[i++] = 0x63;
_EntryHeader[i++] = 0;
i = 30 + filenameLength;
do
{
UInt16 HeaderId = (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
if (HeaderId != 0x9901)
{
// skip this header
i += DataSize + 4;
}
else
{
i += 9;
// actual compression method
_EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
_EntryHeader[i++] = (byte)(_CompressionMethod & 0xFF00);
}
} while (i < (extraFieldLength - 30 - filenameLength));
}
#endif
// finally, write the data.
// workitem 7216 - sometimes we don't seek even if we CAN. ASP.NET
// Response.OutputStream, or stdout are non-seekable. But we may also want
// to NOT seek in other cases, eg zip64. For all cases, we just check bit 3
// to see if we want to seek. There's one exception - if using a
// ZipOutputStream, and PKZip encryption is in use, then we set bit 3 even
// if the out is seekable. This is so the check on the last byte of the
// PKZip Encryption Header can be done on the current time, as opposed to
// the CRC, to prevent streaming the file twice. So, test for
// ZipOutputStream and seekable, and if so, seek back, even if bit 3 is set.
if ((_BitField & 0x0008) != 0x0008 ||
(this._Source == ZipEntrySource.ZipOutputStream && s.CanSeek))
{
// seek back and rewrite the entry header
var zss = s as ZipSegmentedStream;
if (zss != null && _diskNumber != zss.CurrentSegment)
{
// In this case the entry header is in a different file,
// which has already been closed. Need to re-open it.
using (Stream hseg = ZipSegmentedStream.ForUpdate(this._container.ZipFile.Name, _diskNumber))
{
hseg.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
hseg.Write(_EntryHeader, 0, _EntryHeader.Length);
}
}
else
{
// seek in the raw output stream, to the beginning of the header for
// this entry.
// workitem 8098: ok (output)
s.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
// write the updated header to the output stream
s.Write(_EntryHeader, 0, _EntryHeader.Length);
// adjust the count on the CountingStream as necessary
if (s1 != null) s1.Adjust(_EntryHeader.Length);
// seek in the raw output stream, to the end of the file data
// for this entry
s.Seek(_CompressedSize, SeekOrigin.Current);
}
}
// emit the descriptor - only if not a directory.
if (((_BitField & 0x0008) == 0x0008) && !IsDirectory)
{
byte[] Descriptor = new byte[16 + (_OutputUsesZip64.Value ? 8 : 0)];
i = 0;
// signature
Array.Copy(BitConverter.GetBytes(ZipConstants.ZipEntryDataDescriptorSignature), 0, Descriptor, i, 4);
i += 4;
// CRC - the correct value now
Array.Copy(BitConverter.GetBytes(_Crc32), 0, Descriptor, i, 4);
i += 4;
// workitem 7917
if (_OutputUsesZip64.Value)
{
// CompressedSize - the correct value now
Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, Descriptor, i, 8);
i += 8;
// UncompressedSize - the correct value now
Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, Descriptor, i, 8);
i += 8;
}
else
{
// CompressedSize - (lower 32 bits) the correct value now
Descriptor[i++] = (byte)(_CompressedSize & 0x000000FF);
Descriptor[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
Descriptor[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
Descriptor[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
// UncompressedSize - (lower 32 bits) the correct value now
Descriptor[i++] = (byte)(_UncompressedSize & 0x000000FF);
Descriptor[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
Descriptor[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
Descriptor[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
}
// finally, write the trailing descriptor to the output stream
s.Write(Descriptor, 0, Descriptor.Length);
_LengthOfTrailer += Descriptor.Length;
}
}