| // 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; |
| } |
| |
| |
| } |