| // ZipDirEntry.cs | |
| // ------------------------------------------------------------------ | |
| // | |
| // Copyright (c) 2006-2011 Dino Chiesa . | |
| // All rights reserved. | |
| // | |
| // This code module is part of DotNetZip, a zipfile class library. | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // This code is licensed under the Microsoft Public License. | |
| // See the file License.txt for the license details. | |
| // More info on: http://dotnetzip.codeplex.com | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // last saved (in emacs): | |
| // Time-stamp: <2011-July-11 12:03:03> | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // This module defines members of the ZipEntry class for reading the | |
| // Zip file central directory. | |
| // | |
| // Created: Tue, 27 Mar 2007 15:30 | |
| // | |
| // ------------------------------------------------------------------ | |
| using System; | |
| using System.Collections.Generic; | |
| namespace OfficeOpenXml.Packaging.Ionic.Zip | |
| { | |
| partial class ZipEntry | |
| { | |
| /// <summary> | |
| /// True if the referenced entry is a directory. | |
| /// </summary> | |
| internal bool AttributesIndicateDirectory | |
| { | |
| get { return ((_InternalFileAttrs == 0) && ((_ExternalFileAttrs & 0x0010) == 0x0010)); } | |
| } | |
| internal void ResetDirEntry() | |
| { | |
| // __FileDataPosition is the position of the file data for an entry. | |
| // It is _RelativeOffsetOfLocalHeader + size of local header. | |
| // We cannot know the __FileDataPosition until we read the local | |
| // header. | |
| // The local header is not necessarily the same length as the record | |
| // in the central directory. | |
| // Set to -1, to indicate we need to read this later. | |
| this.__FileDataPosition = -1; | |
| // set _LengthOfHeader to 0, to indicate we need to read later. | |
| this._LengthOfHeader = 0; | |
| } | |
| /// <summary> | |
| /// Provides a human-readable string with information about the ZipEntry. | |
| /// </summary> | |
| public string Info | |
| { | |
| get | |
| { | |
| var builder = new System.Text.StringBuilder(); | |
| builder | |
| .Append(string.Format(" ZipEntry: {0}\n", this.FileName)) | |
| .Append(string.Format(" Version Made By: {0}\n", this._VersionMadeBy)) | |
| .Append(string.Format(" Needed to extract: {0}\n", this.VersionNeeded)); | |
| if (this._IsDirectory) | |
| builder.Append(" Entry type: directory\n"); | |
| else | |
| { | |
| builder.Append(string.Format(" File type: {0}\n", this._IsText? "text":"binary")) | |
| .Append(string.Format(" Compression: {0}\n", this.CompressionMethod)) | |
| .Append(string.Format(" Compressed: 0x{0:X}\n", this.CompressedSize)) | |
| .Append(string.Format(" Uncompressed: 0x{0:X}\n", this.UncompressedSize)) | |
| .Append(string.Format(" CRC32: 0x{0:X8}\n", this._Crc32)); | |
| } | |
| builder.Append(string.Format(" Disk Number: {0}\n", this._diskNumber)); | |
| if (this._RelativeOffsetOfLocalHeader > 0xFFFFFFFF) | |
| builder | |
| .Append(string.Format(" Relative Offset: 0x{0:X16}\n", this._RelativeOffsetOfLocalHeader)); | |
| else | |
| builder | |
| .Append(string.Format(" Relative Offset: 0x{0:X8}\n", this._RelativeOffsetOfLocalHeader)); | |
| builder | |
| .Append(string.Format(" Bit Field: 0x{0:X4}\n", this._BitField)) | |
| .Append(string.Format(" Encrypted?: {0}\n", this._sourceIsEncrypted)) | |
| .Append(string.Format(" Timeblob: 0x{0:X8}\n", this._TimeBlob)) | |
| .Append(string.Format(" Time: {0}\n", Ionic.Zip.SharedUtilities.PackedToDateTime(this._TimeBlob))); | |
| builder.Append(string.Format(" Is Zip64?: {0}\n", this._InputUsesZip64)); | |
| if (!string.IsNullOrEmpty(this._Comment)) | |
| { | |
| builder.Append(string.Format(" Comment: {0}\n", this._Comment)); | |
| } | |
| builder.Append("\n"); | |
| return builder.ToString(); | |
| } | |
| } | |
| // workitem 10330 | |
| private class CopyHelper | |
| { | |
| private static System.Text.RegularExpressions.Regex re = | |
| new System.Text.RegularExpressions.Regex(" \\(copy (\\d+)\\)$"); | |
| private static int callCount = 0; | |
| internal static string AppendCopyToFileName(string f) | |
| { | |
| callCount++; | |
| if (callCount > 25) | |
| throw new OverflowException("overflow while creating filename"); | |
| int n = 1; | |
| int r = f.LastIndexOf("."); | |
| if (r == -1) | |
| { | |
| // there is no extension | |
| System.Text.RegularExpressions.Match m = re.Match(f); | |
| if (m.Success) | |
| { | |
| n = Int32.Parse(m.Groups[1].Value) + 1; | |
| string copy = String.Format(" (copy {0})", n); | |
| f = f.Substring(0, m.Index) + copy; | |
| } | |
| else | |
| { | |
| string copy = String.Format(" (copy {0})", n); | |
| f = f + copy; | |
| } | |
| } | |
| else | |
| { | |
| //System.Console.WriteLine("HasExtension"); | |
| System.Text.RegularExpressions.Match m = re.Match(f.Substring(0, r)); | |
| if (m.Success) | |
| { | |
| n = Int32.Parse(m.Groups[1].Value) + 1; | |
| string copy = String.Format(" (copy {0})", n); | |
| f = f.Substring(0, m.Index) + copy + f.Substring(r); | |
| } | |
| else | |
| { | |
| string copy = String.Format(" (copy {0})", n); | |
| f = f.Substring(0, r) + copy + f.Substring(r); | |
| } | |
| //System.Console.WriteLine("returning f({0})", f); | |
| } | |
| return f; | |
| } | |
| } | |
| /// <summary> | |
| /// Reads one entry from the zip directory structure in the zip file. | |
| /// </summary> | |
| /// | |
| /// <param name="zf"> | |
| /// The zipfile for which a directory entry will be read. From this param, the | |
| /// method gets the ReadStream and the expected text encoding | |
| /// (ProvisionalAlternateEncoding) which is used if the entry is not marked | |
| /// UTF-8. | |
| /// </param> | |
| /// | |
| /// <param name="previouslySeen"> | |
| /// a list of previously seen entry names; used to prevent duplicates. | |
| /// </param> | |
| /// | |
| /// <returns>the entry read from the archive.</returns> | |
| internal static ZipEntry ReadDirEntry(ZipFile zf, | |
| Dictionary<String,Object> previouslySeen) | |
| { | |
| System.IO.Stream s = zf.ReadStream; | |
| System.Text.Encoding expectedEncoding = (zf.AlternateEncodingUsage == ZipOption.Always) | |
| ? zf.AlternateEncoding | |
| : ZipFile.DefaultEncoding; | |
| int signature = Ionic.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); | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s); | |
| // 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 && | |
| signature != ZipConstants.ZipEntrySignature // workitem 8299 | |
| ) | |
| { | |
| throw new BadReadException(String.Format(" 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.AlternateEncoding = expectedEncoding; | |
| zde._Source = ZipEntrySource.ZipFile; | |
| zde._container = new ZipContainer(zf); | |
| unchecked | |
| { | |
| 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 = (Int16)(block[i++] + block[i++] * 256); | |
| zde._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256; | |
| zde._LastModified = Ionic.Zip.SharedUtilities.PackedToDateTime(zde._TimeBlob); | |
| zde._timestamp |= ZipEntryTimestamp.DOS; | |
| 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); | |
| } | |
| // preserve | |
| zde._CompressionMethod_FromZipFile = zde._CompressionMethod; | |
| zde._filenameLength = (short)(block[i++] + block[i++] * 256); | |
| zde._extraFieldLength = (short)(block[i++] + block[i++] * 256); | |
| zde._commentLength = (short)(block[i++] + block[i++] * 256); | |
| zde._diskNumber = (UInt32)(block[i++] + block[i++] * 256); | |
| 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); | |
| // workitem 7801 | |
| zde.IsText = ((zde._InternalFileAttrs & 0x01) == 0x01); | |
| 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._FileNameInArchive = Ionic.Zip.SharedUtilities.Utf8StringFromBuffer(block); | |
| } | |
| else | |
| { | |
| zde._FileNameInArchive = Ionic.Zip.SharedUtilities.StringFromBuffer(block, expectedEncoding); | |
| } | |
| // workitem 10330 | |
| // insure unique entry names | |
| while (previouslySeen.ContainsKey(zde._FileNameInArchive)) | |
| { | |
| zde._FileNameInArchive = CopyHelper.AppendCopyToFileName(zde._FileNameInArchive); | |
| zde._metadataChanged = true; | |
| } | |
| if (zde.AttributesIndicateDirectory) | |
| zde.MarkAsDirectory(); // may append a slash to filename if nec. | |
| // workitem 6898 | |
| else if (zde._FileNameInArchive.EndsWith("/")) zde.MarkAsDirectory(); | |
| zde._CompressedFileDataSize = zde._CompressedSize; | |
| if ((zde._BitField & 0x01) == 0x01) | |
| { | |
| // this may change after processing the Extra field | |
| zde._Encryption_FromZipFile = zde._Encryption = | |
| EncryptionAlgorithm.PkzipWeak; | |
| zde._sourceIsEncrypted = true; | |
| } | |
| if (zde._extraFieldLength > 0) | |
| { | |
| zde._InputUsesZip64 = (zde._CompressedSize == 0xFFFFFFFF || | |
| zde._UncompressedSize == 0xFFFFFFFF || | |
| zde._RelativeOffsetOfLocalHeader == 0xFFFFFFFF); | |
| // Console.WriteLine(" Input uses Z64?: {0}", zde._InputUsesZip64); | |
| bytesRead += zde.ProcessExtraField(s, zde._extraFieldLength); | |
| zde._CompressedFileDataSize = 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 AESCRYPTO | |
| else if (zde.Encryption == EncryptionAlgorithm.WinZipAes128 || | |
| zde.Encryption == EncryptionAlgorithm.WinZipAes256) | |
| { | |
| zde._CompressedFileDataSize = zde.CompressedSize - | |
| (ZipEntry.GetLengthOfCryptoHeaderBytes(zde.Encryption) + 10); | |
| zde._LengthOfTrailer = 10; | |
| } | |
| #endif | |
| // tally the trailing descriptor | |
| if ((zde._BitField & 0x0008) == 0x0008) | |
| { | |
| // sig, CRC, Comp and Uncomp sizes | |
| if (zde._InputUsesZip64) | |
| zde._LengthOfTrailer += 24; | |
| else | |
| zde._LengthOfTrailer += 16; | |
| } | |
| // workitem 12744 | |
| zde.AlternateEncoding = ((zde._BitField & 0x0800) == 0x0800) | |
| ? System.Text.Encoding.UTF8 | |
| :expectedEncoding; | |
| zde.AlternateEncodingUsage = ZipOption.Always; | |
| 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 = Ionic.Zip.SharedUtilities.Utf8StringFromBuffer(block); | |
| } | |
| else | |
| { | |
| zde._Comment = Ionic.Zip.SharedUtilities.StringFromBuffer(block, expectedEncoding); | |
| } | |
| } | |
| //zde._LengthOfDirEntry = bytesRead; | |
| return zde; | |
| } | |
| /// <summary> | |
| /// Returns true if the passed-in value is a valid signature for a ZipDirEntry. | |
| /// </summary> | |
| /// <param name="signature">the candidate 4-byte signature value.</param> | |
| /// <returns>true, if the signature is valid according to the PKWare spec.</returns> | |
| internal static bool IsNotValidZipDirEntrySig(int signature) | |
| { | |
| return (signature != ZipConstants.ZipDirEntrySignature); | |
| } | |
| private Int16 _VersionMadeBy; | |
| private Int16 _InternalFileAttrs; | |
| private Int32 _ExternalFileAttrs; | |
| //private Int32 _LengthOfDirEntry; | |
| private Int16 _filenameLength; | |
| private Int16 _extraFieldLength; | |
| private Int16 _commentLength; | |
| } | |
| } |