/// <summary> /// Reads one entry from the zip directory structure in the zip file. /// </summary> /// <param name="s">the stream from which to read.</param> /// <param name="expectedEncoding"> /// The text encoding to use if the entry is not marked UTF-8. /// </param> /// <returns>the entry read from the archive.</returns> public static ZipEntry ReadDirEntry(System.IO.Stream s, System.Text.Encoding expectedEncoding) { long cdrPosition = s.Position; int signature = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.ReadSignature(s); // return null if this is not a local file header signature if (IsNotValidZipDirEntrySig(signature)) { s.Seek(-4, System.IO.SeekOrigin.Current); // Getting "not a ZipDirEntry signature" here is not always wrong or an error. // This can happen when walking through a zipfile. After the last ZipDirEntry, // we expect to read an EndOfCentralDirectorySignature. When we get this is how we // know we've reached the end of the central directory. if (signature != ZipConstants.EndOfCentralDirectorySignature && signature != ZipConstants.Zip64EndOfCentralDirectoryRecordSignature) { throw new BadReadException(String.Format(" ZipEntry::ReadDirEntry(): Bad signature (0x{0:X8}) at position 0x{1:X8}", signature, s.Position)); } return null; } int bytesRead = 42 + 4; byte[] block = new byte[42]; int n = s.Read(block, 0, block.Length); if (n != block.Length) return null; int i = 0; ZipEntry zde = new ZipEntry(); zde._archiveStream = s; zde._cdrPosition = cdrPosition; zde._VersionMadeBy = (short)(block[i++] + block[i++] * 256); zde._VersionNeeded = (short)(block[i++] + block[i++] * 256); zde._BitField = (short)(block[i++] + block[i++] * 256); zde._CompressionMethod = (short)(block[i++] + block[i++] * 256); zde._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; zde._LastModified = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.PackedToDateTime(zde._TimeBlob); zde._Crc32 = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; zde._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); zde._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); //DateTime lastModified = Ionic.Utils.Zip.SharedUtilities.PackedToDateTime(lastModDateTime); //i += 24; zde._filenameLength = (short)(block[i++] + block[i++] * 256); zde._extraFieldLength = (short)(block[i++] + block[i++] * 256); zde._commentLength = (short)(block[i++] + block[i++] * 256); //Int16 diskNumber = (short)(block[i++] + block[i++] * 256); i += 2; zde._InternalFileAttrs = (short)(block[i++] + block[i++] * 256); zde._ExternalFileAttrs = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; zde._RelativeOffsetOfLocalHeader = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); block = new byte[zde._filenameLength]; n = s.Read(block, 0, block.Length); bytesRead += n; if ((zde._BitField & 0x0800) == 0x0800) { // UTF-8 is in use zde._LocalFileName = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.Utf8StringFromBuffer(block, block.Length); } else { zde._LocalFileName = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.StringFromBuffer(block, block.Length, expectedEncoding); } // Console.WriteLine("\nEntry : {0}", zde._LocalFileName); // Console.WriteLine(" V Madeby: 0x{0:X4}", zde._VersionMadeBy); // Console.WriteLine(" V Needed: 0x{0:X4}", zde._VersionNeeded); // Console.WriteLine(" BitField: 0x{0:X4}", zde._BitField); // Console.WriteLine(" Compression: 0x{0:X4}", zde._CompressionMethod); // Console.WriteLine(" Lastmod: {0}", zde._LastModified.ToString("u")); // Console.WriteLine(" CRC: 0x{0:X8}", zde._Crc32); // Console.WriteLine(" Comp: 0x{0:X8} ({0})", zde._CompressedSize); // Console.WriteLine(" Uncomp: 0x{0:X8} ({0})", zde._UncompressedSize); zde._FileNameInArchive = zde._LocalFileName; if (zde.AttributesIndicateDirectory) zde.MarkAsDirectory(); // may append a slash to filename if nec. // workitem 6898 if (zde._LocalFileName.EndsWith("/")) zde.MarkAsDirectory(); zde._CompressedFileDataSize = zde._CompressedSize; if ((zde._BitField & 0x01) == 0x01) { zde._Encryption = EncryptionAlgorithm.PkzipWeak; // this may change after processing the Extra field } if (zde._extraFieldLength > 0) { //Console.WriteLine("ZDE Extra Field length: {0}", zde._extraFieldLength); zde._InputUsesZip64 = ((uint)zde._CompressedSize == 0xFFFFFFFF || (uint)zde._UncompressedSize == 0xFFFFFFFF || (uint)zde._RelativeOffsetOfLocalHeader == 0xFFFFFFFF); bytesRead += zde.ProcessExtraField(zde._extraFieldLength); zde._CompressedFileDataSize = zde._CompressedSize; //Console.WriteLine(" Compressed: 0x{0:X8} ({0})", zde._CompressedSize); } // we've processed the extra field, so we know the encryption method is set now. if (zde._Encryption == EncryptionAlgorithm.PkzipWeak) { // the "encryption header" of 12 bytes precedes the file data zde._CompressedFileDataSize -= 12; } if (zde._commentLength > 0) { block = new byte[zde._commentLength]; n = s.Read(block, 0, block.Length); bytesRead += n; if ((zde._BitField & 0x0800) == 0x0800) { // UTF-8 is in use zde._Comment = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.Utf8StringFromBuffer(block, block.Length); } else { zde._Comment = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.StringFromBuffer(block, block.Length, expectedEncoding); } } zde._LengthOfDirEntry = bytesRead; return zde; }
/// <summary> /// Reads one ZipEntry from the given stream. If the entry is encrypted, we don't /// decrypt at this point. We also do not decompress. Mostly we read metadata. /// </summary> /// <param name="zf">the zipfile this entry belongs to.</param> /// <param name="first">true of this is the first entry being read from the stream.</param> /// <returns>the ZipEntry read from the stream.</returns> internal static ZipEntry Read(ZipFile zf, bool first) { System.IO.Stream s = zf.ReadStream; System.Text.Encoding defaultEncoding = zf.ProvisionalAlternateEncoding; ZipEntry entry = new ZipEntry(); entry._Source = EntrySource.Zipfile; entry._zipfile = zf; entry._archiveStream = s; if (first) HandlePK00Prefix(s); if (!ReadHeader(entry, defaultEncoding)) return null; // store the position in the stream for this entry entry.__FileDataPosition = entry.ArchiveStream.Position; // seek past the data without reading it. We will read on Extract() s.Seek(entry._CompressedFileDataSize, System.IO.SeekOrigin.Current); // workitem 6607 - don't seek for directories // finally, seek past the (already read) Data descriptor if necessary if (((entry._BitField & 0x0008) == 0x0008) && !entry.FileName.EndsWith("/")) { // _InputUsesZip64 is set in ReadHeader() int DescriptorSize = (entry._InputUsesZip64) ? 24 : 16; s.Seek(DescriptorSize, System.IO.SeekOrigin.Current); } // workitem 5306 // http://www.codeplex.com/DotNetZip/WorkItem/View.aspx?WorkItemId=5306 HandleUnexpectedDataDescriptor(entry); return entry; }
private static void HandleUnexpectedDataDescriptor(ZipEntry entry) { System.IO.Stream s = entry.ArchiveStream; // In some cases, the "data descriptor" is present, without a signature, even when bit 3 of the BitField is NOT SET. // This is the CRC, followed // by the compressed length and the uncompressed length (4 bytes for each // of those three elements). Need to check that here. // uint datum = (uint)ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.ReadInt(s); if (datum == entry._Crc32) { int sz = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.ReadInt(s); if (sz == entry._CompressedSize) { sz = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.ReadInt(s); if (sz == entry._UncompressedSize) { // ignore everything and discard it. } else s.Seek(-12, System.IO.SeekOrigin.Current); // unread the three blocks } else s.Seek(-8, System.IO.SeekOrigin.Current); // unread the two blocks } else s.Seek(-4, System.IO.SeekOrigin.Current); // unread the block }
private static bool ReadHeader(ZipEntry ze, System.Text.Encoding defaultEncoding) { int bytesRead = 0; ze._RelativeOffsetOfLocalHeader = (int)ze.ArchiveStream.Position; int signature = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.ReadSignature(ze.ArchiveStream); bytesRead += 4; // Return false if this is not a local file header signature. if (ZipEntry.IsNotValidSig(signature)) { // Getting "not a ZipEntry signature" is not always wrong or an error. // This will happen after the last entry in a zipfile. In that case, we // expect to read : // a ZipDirEntry signature (if a non-empty zip file) or // a ZipConstants.EndOfCentralDirectorySignature. // // Anything else is a surprise. ze.ArchiveStream.Seek(-4, System.IO.SeekOrigin.Current); // unread the signature if (ZipEntry.IsNotValidZipDirEntrySig(signature) && (signature != ZipConstants.EndOfCentralDirectorySignature)) { throw new BadReadException(String.Format(" ZipEntry::ReadHeader(): Bad signature (0x{0:X8}) at position 0x{1:X8}", signature, ze.ArchiveStream.Position)); } return false; } byte[] block = new byte[26]; int n = ze.ArchiveStream.Read(block, 0, block.Length); if (n != block.Length) return false; bytesRead += n; int i = 0; ze._VersionNeeded = (short)(block[i++] + block[i++] * 256); ze._BitField = (short)(block[i++] + block[i++] * 256); ze._CompressionMethod = (short)(block[i++] + block[i++] * 256); ze._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; // transform the time data into something usable (a DateTime) ze._LastModified = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.PackedToDateTime(ze._TimeBlob); // NB: if ((ze._BitField & 0x0008) != 0x0008), then the Compressed, uncompressed and // CRC values are not true values; the true values will follow the entry data. // Nevertheless, regardless of the statis of bit 3 in the bitfield, the slots for // the three amigos may contain marker values for ZIP64. So we must read them. { ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); // validate ZIP64? No. We don't need to be pedantic about it. //if (((uint)ze._CompressedSize == 0xFFFFFFFF && // (uint)ze._UncompressedSize != 0xFFFFFFFF) || // ((uint)ze._CompressedSize != 0xFFFFFFFF && // (uint)ze._UncompressedSize == 0xFFFFFFFF)) // throw new BadReadException(String.Format(" ZipEntry::Read(): Inconsistent uncompressed size (0x{0:X8}) for zip64, at position 0x{1:X16}", ze._UncompressedSize, ze.ArchiveStream.Position)); if ((uint)ze._CompressedSize == 0xFFFFFFFF || (uint)ze._UncompressedSize == 0xFFFFFFFF) ze._InputUsesZip64 = true; //throw new BadReadException(" DotNetZip does not currently support reading the ZIP64 format."); } // else // { // // The CRC, compressed size, and uncompressed size stored here are not valid. // // The actual values are stored later in the stream. // // Here, we advance the pointer to skip the dummy data. // i += 12; // } Int16 filenameLength = (short)(block[i++] + block[i++] * 256); Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256); block = new byte[filenameLength]; n = ze.ArchiveStream.Read(block, 0, block.Length); bytesRead += n; // if the UTF8 bit is set for this entry, we override the encoding the application requested. ze._actualEncoding = ((ze._BitField & 0x0800) == 0x0800) ? System.Text.Encoding.UTF8 : defaultEncoding; // need to use this form of GetString() for .NET CF ze._FileNameInArchive = ze._actualEncoding.GetString(block, 0, block.Length); // when creating an entry by reading, the LocalFileName is the same as the FileNameInArchive ze._LocalFileName = ze._FileNameInArchive; // workitem 6898 if (ze._LocalFileName.EndsWith("/")) ze.MarkAsDirectory(); bytesRead += ze.ProcessExtraField(extraFieldLength); ze._LengthOfTrailer = 0; // workitem 6607 - don't read for directories // actually get the compressed size and CRC if necessary if (!ze._LocalFileName.EndsWith("/") && (ze._BitField & 0x0008) == 0x0008) { // This descriptor exists only if bit 3 of the general // purpose bit flag is set (see below). It is byte aligned // and immediately follows the last byte of compressed data. // This descriptor is used only when it was not possible to // seek in the output .ZIP file, e.g., when the output .ZIP file // was standard output or a non-seekable device. For ZIP64(tm) format // archives, the compressed and uncompressed sizes are 8 bytes each. long posn = ze.ArchiveStream.Position; // Here, we're going to loop until we find a ZipEntryDataDescriptorSignature and // a consistent data record after that. To be consistent, the data record must // indicate the length of the entry data. bool wantMore = true; long SizeOfDataRead = 0; int tries = 0; while (wantMore) { tries++; // We call the FindSignature shared routine to find the specified signature // in the already-opened zip archive, starting from the current cursor // position in that filestream. There are two possibilities: either we // find the signature or we don't. If we cannot find it, then the routine // returns -1, and the ReadHeader() method returns false, indicating we // cannot read a legal entry header. If we have found it, then the // FindSignature() method returns the number of bytes in the stream we had // to seek forward, to find the sig. We need this to determine if the zip // entry is valid, later. long d = ESRI.ArcGIS.Client.Toolkit.DataSources.Kml.Zip.SharedUtilities.FindSignature(ze.ArchiveStream, ZipConstants.ZipEntryDataDescriptorSignature); if (d == -1) return false; // total size of data read (through all loops of this). SizeOfDataRead += d; if (ze._InputUsesZip64 == true) { // read 1x 4-byte (CRC) and 2x 8-bytes (Compressed Size, Uncompressed Size) block = new byte[20]; n = ze.ArchiveStream.Read(block, 0, block.Length); if (n != 20) return false; // do not increment bytesRead - it is for entry header only. // the data we have just read is a footer (falls after the file data) //bytesRead += n; i = 0; ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); ze._CompressedSize = BitConverter.ToInt64(block, i); i += 8; ze._UncompressedSize = BitConverter.ToInt64(block, i); i += 8; ze._LengthOfTrailer += 24; // bytes including sig, CRC, Comp and Uncomp sizes } else { // read 3x 4-byte fields (CRC, Compressed Size, Uncompressed Size) block = new byte[12]; n = ze.ArchiveStream.Read(block, 0, block.Length); if (n != 12) return false; // do not increment bytesRead - it is for entry header only. // the data we have just read is a footer (falls after the file data) //bytesRead += n; i = 0; ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256); ze._LengthOfTrailer += 16; // bytes including sig, CRC, Comp and Uncomp sizes } wantMore = (SizeOfDataRead != ze._CompressedSize); if (wantMore) { // Seek back to un-read the last 12 bytes - maybe THEY contain // the ZipEntryDataDescriptorSignature. // (12 bytes for the CRC, Comp and Uncomp size.) ze.ArchiveStream.Seek(-12, System.IO.SeekOrigin.Current); // Adjust the size to account for the false signature read in // FindSignature(). SizeOfDataRead += 4; } } //if (SizeOfDataRead != ze._CompressedSize) // throw new BadReadException("Data format error (bit 3 is set)"); // seek back to previous position, to prepare to read file data ze.ArchiveStream.Seek(posn, System.IO.SeekOrigin.Begin); } ze._CompressedFileDataSize = ze._CompressedSize; // bit 0 set indicates that some kind of encryption is in use if ((ze._BitField & 0x01) == 0x01) { #if AESCRYPTO if (ze.Encryption == EncryptionAlgorithm.WinZipAes128 || ze.Encryption == EncryptionAlgorithm.WinZipAes256) { // read in the WinZip AES metadata ze._aesCrypto = WinZipAesCrypto.ReadFromStream(null, ze._KeyStrengthInBits, ze.ArchiveStream); bytesRead += ze._aesCrypto.SizeOfEncryptionMetadata - 10; ze._CompressedFileDataSize = ze.CompressedSize - ze._aesCrypto.SizeOfEncryptionMetadata; ze._LengthOfTrailer += 10; } else #endif { // read in the header data for "weak" encryption ze._WeakEncryptionHeader = new byte[12]; bytesRead += ZipEntry.ReadWeakEncryptionHeader(ze._archiveStream, ze._WeakEncryptionHeader); // decrease the filedata size by 12 bytes ze._CompressedFileDataSize -= 12; } } // Remember the size of the blob for this entry. // We also have the starting position in the stream for this entry. ze._LengthOfHeader = bytesRead; ze._TotalEntrySize = ze._LengthOfHeader + ze._CompressedSize + ze._LengthOfTrailer; // We've read in the regular entry header, the extra field, and any encryption // header. The pointer in the file is now at the start of the filedata, which is // potentially compressed and encrypted. Just ahead in the file, there are // _CompressedFileDataSize bytes of data, followed by potentially a non-zero // length trailer, consisting of optionally, some encryption stuff (10 byte MAC for AES), and // the bit-3 trailer (16 or 24 bytes). return true; }