internal void WriteCentralDirectoryEntry(Stream s)
{
byte[] bytes = new byte[4096];
int i = 0;
// signature
bytes[i++] = (byte)(ZipConstants.ZipDirEntrySignature & 0x000000FF);
bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x0000FF00) >> 8);
bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x00FF0000) >> 16);
bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0xFF000000) >> 24);
// Version Made By
// workitem 7071
// We must not overwrite the VersionMadeBy field when writing out a zip
// archive. The VersionMadeBy tells the zip reader the meaning of the
// File attributes. Overwriting the VersionMadeBy will result in
// inconsistent metadata. Consider the scenario where the application
// opens and reads a zip file that had been created on Linux. Then the
// app adds one file to the Zip archive, and saves it. The file
// attributes for all the entries added on Linux will be significant for
// Linux. Therefore the VersionMadeBy for those entries must not be
// changed. Only the entries that are actually created on Windows NTFS
// should get the VersionMadeBy indicating Windows/NTFS.
bytes[i++] = (byte)(_VersionMadeBy & 0x00FF);
bytes[i++] = (byte)((_VersionMadeBy & 0xFF00) >> 8);
// Apparently we want to duplicate the extra field here; we cannot
// simply zero it out and assume tools and apps will use the right one.
////Int16 extraFieldLengthSave = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);
////_EntryHeader[28] = 0;
////_EntryHeader[29] = 0;
// Version Needed, Bitfield, compression method, lastmod,
// crc, compressed and uncompressed sizes, filename length and extra field length.
// These are all present in the local file header, but they may be zero values there.
// So we cannot just copy them.
// workitem 11969: Version Needed To Extract in central directory must be
// the same as the local entry or MS .NET System.IO.Zip fails read.
Int16 vNeeded = (Int16)(VersionNeeded != 0 ? VersionNeeded : 20);
// workitem 12964
if (_OutputUsesZip64==null)
{
// a zipentry in a zipoutputstream, with zero bytes written
_OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always);
}
Int16 versionNeededToExtract = (Int16)(_OutputUsesZip64.Value ? 45 : vNeeded);
#if BZIP
if (this.CompressionMethod == Ionic.Zip.CompressionMethod.BZip2)
versionNeededToExtract = 46;
#endif
bytes[i++] = (byte)(versionNeededToExtract & 0x00FF);
bytes[i++] = (byte)((versionNeededToExtract & 0xFF00) >> 8);
bytes[i++] = (byte)(_BitField & 0x00FF);
bytes[i++] = (byte)((_BitField & 0xFF00) >> 8);
bytes[i++] = (byte)(_CompressionMethod & 0x00FF);
bytes[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);
#if AESCRYPTO
if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
Encryption == EncryptionAlgorithm.WinZipAes256)
{
i -= 2;
bytes[i++] = 0x63;
bytes[i++] = 0;
}
#endif
bytes[i++] = (byte)(_TimeBlob & 0x000000FF);
bytes[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
bytes[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
bytes[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);
bytes[i++] = (byte)(_Crc32 & 0x000000FF);
bytes[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
bytes[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
bytes[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);
int j = 0;
if (_OutputUsesZip64.Value)
{
// CompressedSize (Int32) and UncompressedSize - all 0xFF
for (j = 0; j < 8; j++)
bytes[i++] = 0xFF;
}
else
{
bytes[i++] = (byte)(_CompressedSize & 0x000000FF);
bytes[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
bytes[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
bytes[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);
bytes[i++] = (byte)(_UncompressedSize & 0x000000FF);
bytes[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
bytes[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
bytes[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
}
byte[] fileNameBytes = GetEncodedFileNameBytes();
Int16 filenameLength = (Int16)fileNameBytes.Length;
bytes[i++] = (byte)(filenameLength & 0x00FF);
bytes[i++] = (byte)((filenameLength & 0xFF00) >> 8);
// do this again because now we have real data
_presumeZip64 = _OutputUsesZip64.Value;
// workitem 11131
//
// cannot generate the extra field again, here's why: In the case of a
// zero-byte entry, which uses encryption, DotNetZip will "remove" the
// encryption from the entry. It does this in PostProcessOutput; it
// modifies the entry header, and rewrites it, resetting the Bitfield
// (one bit indicates encryption), and potentially resetting the
// compression method - for AES the Compression method is 0x63, and it
// would get reset to zero (no compression). It then calls SetLength()
// to truncate the stream to remove the encryption header (12 bytes for
// AES256). But, it leaves the previously-generated "Extra Field"
// metadata (11 bytes) for AES in the entry header. This extra field
// data is now "orphaned" - it refers to AES encryption when in fact no
// AES encryption is used. But no problem, the PKWARE spec says that
// unrecognized extra fields can just be ignored. ok. After "removal"
// of AES encryption, the length of the Extra Field can remains the
// same; it's just that there will be 11 bytes in there that previously
// pertained to AES which are now unused. Even the field code is still
// there, but it will be unused by readers, as the encryption bit is not
// set.
//
// Re-calculating the Extra field now would produce a block that is 11
// bytes shorter, and that mismatch - between the extra field in the
// local header and the extra field in the Central Directory - would
// cause problems. (where? why? what problems?) So we can't do
// that. It's all good though, because though the content may have
// changed, the length definitely has not. Also, the _EntryHeader
// contains the "updated" extra field (after PostProcessOutput) at
// offset (30 + filenameLength).
_Extra = ConstructExtraField(true);
Int16 extraFieldLength = (Int16)((_Extra == null) ? 0 : _Extra.Length);
bytes[i++] = (byte)(extraFieldLength & 0x00FF);
bytes[i++] = (byte)((extraFieldLength & 0xFF00) >> 8);
// File (entry) Comment Length
// the _CommentBytes private field was set during WriteHeader()
int commentLength = (_CommentBytes == null) ? 0 : _CommentBytes.Length;
// the size of our buffer defines the max length of the comment we can write
if (commentLength + i > bytes.Length) commentLength = bytes.Length - i;
bytes[i++] = (byte)(commentLength & 0x00FF);
bytes[i++] = (byte)((commentLength & 0xFF00) >> 8);
// Disk number start
bool segmented = (this._container.ZipFile != null) &&
(this._container.ZipFile.MaxOutputSegmentSize != 0);
if (segmented) // workitem 13915
{
// Emit nonzero disknumber only if saving segmented archive.
bytes[i++] = (byte)(_diskNumber & 0x00FF);
bytes[i++] = (byte)((_diskNumber & 0xFF00) >> 8);
}
else
{
// If reading a segmneted archive and saving to a regular archive,
// ZipEntry._diskNumber will be non-zero but it should be saved as
// zero.
bytes[i++] = 0;
bytes[i++] = 0;
}
// internal file attrs
// workitem 7801
bytes[i++] = (byte)((_IsText) ? 1 : 0); // lo bit: filetype hint. 0=bin, 1=txt.
bytes[i++] = 0;
// external file attrs
// workitem 7071
bytes[i++] = (byte)(_ExternalFileAttrs & 0x000000FF);
bytes[i++] = (byte)((_ExternalFileAttrs & 0x0000FF00) >> 8);
bytes[i++] = (byte)((_ExternalFileAttrs & 0x00FF0000) >> 16);
bytes[i++] = (byte)((_ExternalFileAttrs & 0xFF000000) >> 24);
// workitem 11131
// relative offset of local header.
//
// If necessary to go to 64-bit value, then emit 0xFFFFFFFF,
// else write out the value.
//
// Even if zip64 is required for other reasons - number of the entry
// > 65534, or uncompressed size of the entry > MAX_INT32, the ROLH
// need not be stored in a 64-bit field .
if (_RelativeOffsetOfLocalHeader > 0xFFFFFFFFL) // _OutputUsesZip64.Value
{
bytes[i++] = 0xFF;
bytes[i++] = 0xFF;
bytes[i++] = 0xFF;
bytes[i++] = 0xFF;
}
else
{
bytes[i++] = (byte)(_RelativeOffsetOfLocalHeader & 0x000000FF);
bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0x0000FF00) >> 8);
bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0x00FF0000) >> 16);
bytes[i++] = (byte)((_RelativeOffsetOfLocalHeader & 0xFF000000) >> 24);
}
// actual filename
Buffer.BlockCopy(fileNameBytes, 0, bytes, i, filenameLength);
i += filenameLength;
// "Extra field"
if (_Extra != null)
{
// workitem 11131
//
// copy from EntryHeader if available - it may have been updated.
// if not, copy from Extra. This would be unnecessary if I just
// updated the Extra field when updating EntryHeader, in
// PostProcessOutput.
//?? I don't understand why I wouldn't want to just use
// the recalculated Extra field. ??
// byte[] h = _EntryHeader ?? _Extra;
// int offx = (h == _EntryHeader) ? 30 + filenameLength : 0;
// Buffer.BlockCopy(h, offx, bytes, i, extraFieldLength);
// i += extraFieldLength;
byte[] h = _Extra;
int offx = 0;
Buffer.BlockCopy(h, offx, bytes, i, extraFieldLength);
i += extraFieldLength;
}
// file (entry) comment
if (commentLength != 0)
{
// now actually write the comment itself into the byte buffer
Buffer.BlockCopy(_CommentBytes, 0, bytes, i, commentLength);
// for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
// bytes[i + j] = _CommentBytes[j];
i += commentLength;
}
s.Write(bytes, 0, i);
}