private static void CheckContents(ZipFile zipFile) { Assert.AreEqual("Contents of \"A.txt\".", ReadFile(zipFile, "A.txt")); Assert.AreEqual("Contents of \"B.txt\".", ReadFile(zipFile, "B.txt")); Assert.AreEqual("Contents of \"C.txt\".", ReadFile(zipFile, "Folder/C.txt")); Assert.AreEqual(Lorem, ReadFile(zipFile, "Lorem.txt")); }
private static void CheckEntries(ZipFile zipFile) { // Depending on the ZIP tool: The folder "Folder\" is not always in Entries. Assert.True(zipFile.Entries.Count == 4 || zipFile.Entries.Count == 5); Assert.True(zipFile.ContainsEntry("A.txt")); Assert.True(zipFile.ContainsEntry("B.txt")); Assert.True(zipFile.ContainsEntry("Folder/C.txt")); Assert.True(zipFile.ContainsEntry("Lorem.txt")); }
private static string ReadFile(ZipFile zipFile, string filename) { using (var reader = new StreamReader(zipFile[filename].OpenReader())) { return reader.ReadToEnd(); } }
/// <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); // 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(CultureInfo.InvariantCulture, " 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("/", StringComparison.Ordinal)) 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; }
private static void ReadZipFileComment(ZipFile zf) { // read the comment here byte[] block = new byte[2]; zf.ReadStream.Read(block, 0, block.Length); Int16 commentLength = (short)(block[0] + block[1] * 256); if (commentLength > 0) { block = new byte[commentLength]; zf.ReadStream.Read(block, 0, block.Length); // workitem 10392 - prefer ProvisionalAlternateEncoding, // first. The fix for workitem 6513 tried to use UTF8 // only as necessary, but that is impossible to test // for, in this direction. There's no way to know what // characters the already-encoded bytes refer // to. Therefore, must do what the user tells us. string s1 = zf.AlternateEncoding.GetString(block, 0, block.Length); zf.Comment = s1; } }
private static void Zip64SeekToCentralDirectory(ZipFile zf) { Stream s = zf.ReadStream; byte[] block = new byte[16]; // seek back to find the ZIP64 EoCD. // I think this might not work for .NET CF ? s.Seek(-40, SeekOrigin.Current); s.Read(block, 0, 16); Int64 offset64 = BitConverter.ToInt64(block, 8); // change for workitem 8098 s.Seek(offset64, SeekOrigin.Begin); //zf.SeekFromOrigin(Offset64); uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s); if (datum != ZipConstants.Zip64EndOfCentralDirectoryRecordSignature) throw new BadReadException(String.Format(CultureInfo.InvariantCulture, " Bad signature (0x{0:X8}) looking for ZIP64 EoCD Record at position 0x{1:X8}", datum, s.Position)); s.Read(block, 0, 8); Int64 Size = BitConverter.ToInt64(block, 0); block = new byte[Size]; s.Read(block, 0, block.Length); offset64 = BitConverter.ToInt64(block, 36); // change for workitem 8098 s.Seek(offset64, SeekOrigin.Begin); //zf.SeekFromOrigin(Offset64); }
private static void ReadIntoInstance(ZipFile zf) { Stream s = zf.ReadStream; try { if (!s.CanSeek) { ReadIntoInstance_Orig(zf); return; } // change for workitem 8098 //zf._originPosition = s.Position; // Try reading the central directory, rather than scanning the file. uint datum = ReadFirstFourBytes(s); if (datum == ZipConstants.EndOfCentralDirectorySignature) return; // start at the end of the file... // seek backwards a bit, then look for the EoCD signature. int nTries = 0; bool success = false; // The size of the end-of-central-directory-footer plus 2 bytes is 18. // This implies an archive comment length of 0. We'll add a margin of // safety and start "in front" of that, when looking for the // EndOfCentralDirectorySignature long posn = s.Length - 64; long maxSeekback = Math.Max(s.Length - 0x4000, 10); do { if (posn < 0) posn = 0; // BOF s.Seek(posn, SeekOrigin.Begin); long bytesRead = SharedUtilities.FindSignature(s, (int)ZipConstants.EndOfCentralDirectorySignature); if (bytesRead != -1) success = true; else { if (posn==0) break; // started at the BOF and found nothing nTries++; // Weird: with NETCF, negative offsets from SeekOrigin.End DO // NOT WORK. So rather than seek a negative offset, we seek // from SeekOrigin.Begin using a smaller number. posn -= (32 * (nTries + 1) * nTries); } } while (!success && posn > maxSeekback); if (success) { // workitem 8299 zf._locEndOfCDS = s.Position - 4; byte[] block = new byte[16]; s.Read(block, 0, block.Length); zf._diskNumberWithCd = BitConverter.ToUInt16(block, 2); if (zf._diskNumberWithCd == 0xFFFF) throw new ZipException("Spanned archives with more than 65534 segments are not supported at this time."); zf._diskNumberWithCd++; // I think the number in the file differs from reality by 1 int i = 12; uint offset32 = (uint) BitConverter.ToUInt32(block, i); if (offset32 == 0xFFFFFFFF) { Zip64SeekToCentralDirectory(zf); } else { // change for workitem 8098 s.Seek(offset32, SeekOrigin.Begin); } ReadCentralDirectory(zf); } else { // Could not find the central directory. // Fallback to the old method. // workitem 8098: ok //s.Seek(zf._originPosition, SeekOrigin.Begin); s.Seek(0L, SeekOrigin.Begin); ReadIntoInstance_Orig(zf); } } catch (Exception ex1) { throw new ZipException("Cannot read that as a ZipFile", ex1); } }
// build the TOC by reading each entry in the file. private static void ReadIntoInstance_Orig(ZipFile zf) { //zf._entries = new System.Collections.Generic.List<ZipEntry>(); zf._entries = new System.Collections.Generic.Dictionary<String,ZipEntry>(); ZipEntry e; // work item 6647: PK00 (packed to removable disk) bool firstEntry = true; ZipContainer zc = new ZipContainer(zf); while ((e = ZipEntry.ReadEntry(zc, firstEntry)) != null) { zf._entries.Add(e.FileName,e); firstEntry = false; } // read the zipfile's central directory structure here. // workitem 9912 // But, because it may be corrupted, ignore errors. try { ZipEntry de; // in lieu of hashset, use a dictionary var previouslySeen = new Dictionary<String,Object>(); while ((de = ZipEntry.ReadDirEntry(zf, previouslySeen)) != null) { // Housekeeping: Since ZipFile exposes ZipEntry elements in the enumerator, // we need to copy the comment that we grab from the ZipDirEntry // into the ZipEntry, so the application can access the comment. // Also since ZipEntry is used to Write zip files, we need to copy the // file attributes to the ZipEntry as appropriate. ZipEntry e1 = zf._entries[de.FileName]; if (e1 != null) { e1._Comment = de.Comment; if (de.IsDirectory) e1.MarkAsDirectory(); } previouslySeen.Add(de.FileName,null); // to prevent dupes } // workitem 8299 if (zf._locEndOfCDS > 0) zf.ReadStream.Seek(zf._locEndOfCDS, SeekOrigin.Begin); ReadCentralDirectoryFooter(zf); } catch (ZipException) { } catch (IOException) { } }
private static void ReadCentralDirectoryFooter(ZipFile zf) { Stream s = zf.ReadStream; int signature = Ionic.Zip.SharedUtilities.ReadSignature(s); byte[] block = null; int j = 0; if (signature == ZipConstants.Zip64EndOfCentralDirectoryRecordSignature) { // We have a ZIP64 EOCD // This data block is 4 bytes sig, 8 bytes size, 44 bytes fixed data, // followed by a variable-sized extension block. We have read the sig already. // 8 - datasize (64 bits) // 2 - version made by // 2 - version needed to extract // 4 - number of this disk // 4 - number of the disk with the start of the CD // 8 - total number of entries in the CD on this disk // 8 - total number of entries in the CD // 8 - size of the CD // 8 - offset of the CD // ----------------------- // 52 bytes block = new byte[8 + 44]; s.Read(block, 0, block.Length); Int64 DataSize = BitConverter.ToInt64(block, 0); // == 44 + the variable length if (DataSize < 44) throw new ZipException("Bad size in the ZIP64 Central Directory."); //zf._versionMadeBy = BitConverter.ToUInt16(block, j); j += 2; //zf._versionNeededToExtract = BitConverter.ToUInt16(block, j); j += 2; zf._diskNumberWithCd = BitConverter.ToUInt32(block, j); j += 2; //zf._diskNumberWithCd++; // hack!! // read the extended block block = new byte[DataSize - 44]; s.Read(block, 0, block.Length); // discard the result signature = Ionic.Zip.SharedUtilities.ReadSignature(s); if (signature != ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature) throw new ZipException("Inconsistent metadata in the ZIP64 Central Directory."); block = new byte[16]; s.Read(block, 0, block.Length); // discard the result signature = Ionic.Zip.SharedUtilities.ReadSignature(s); } // Throw if this is not a signature for "end of central directory record" // This is a sanity check. if (signature != ZipConstants.EndOfCentralDirectorySignature) { s.Seek(-4, SeekOrigin.Current); throw new BadReadException(String.Format(CultureInfo.InvariantCulture, "Bad signature ({0:X8}) at position 0x{1:X8}", signature, s.Position)); } // read the End-of-Central-Directory-Record block = new byte[16]; zf.ReadStream.Read(block, 0, block.Length); // off sz data // ------------------------------------------------------- // 0 4 end of central dir signature (0x06054b50) // 4 2 number of this disk // 6 2 number of the disk with start of the central directory // 8 2 total number of entries in the central directory on this disk // 10 2 total number of entries in the central directory // 12 4 size of the central directory // 16 4 offset of start of central directory with respect to the starting disk number // 20 2 ZIP file comment length // 22 ?? ZIP file comment if (zf._diskNumberWithCd == 0) { zf._diskNumberWithCd = BitConverter.ToUInt16(block, 2); //zf._diskNumberWithCd++; // hack!! } // read the comment here ReadZipFileComment(zf); }
private static void ReadCentralDirectory(ZipFile zf) { // We must have the central directory footer record, in order to properly // read zip dir entries from the central directory. This because the logic // knows when to open a spanned file when the volume number for the central // directory differs from the volume number for the zip entry. The // _diskNumberWithCd was set when originally finding the offset for the // start of the Central Directory. ZipEntry de; // in lieu of hashset, use a dictionary var previouslySeen = new Dictionary<String,object>(); while ((de = ZipEntry.ReadDirEntry(zf, previouslySeen)) != null) { de.ResetDirEntry(); zf._entries.Add(de.FileName,de); previouslySeen.Add(de.FileName, null); // to prevent dupes } // workitem 8299 if (zf._locEndOfCDS > 0) zf.ReadStream.Seek(zf._locEndOfCDS, SeekOrigin.Begin); ReadCentralDirectoryFooter(zf); // We keep the read stream open after reading. }
private static ZipFile Read(Stream zipStream, System.Text.Encoding encoding) { if (zipStream == null) throw new ArgumentNullException("zipStream"); ZipFile zf = new ZipFile(); zf._alternateEncoding = encoding ?? ZipFile.DefaultEncoding; zf._alternateEncodingUsage = ZipOption.Always; zf._readstream = (zipStream.Position == 0L) ? zipStream : new OffsetStream(zipStream); ReadIntoInstance(zf); return zf; }
//-------------------------------------------------------------- /// <summary> /// Initializes a new instance of the <see cref="ZipStorage"/> class. /// </summary> /// <param name="storage">The storage that contains the ZIP archive.</param> /// <param name="fileName">The file name (incl. path) of the ZIP archive.</param> /// <remarks> /// An exception is raised if the ZIP archive could not be opened. /// </remarks> /// <exception cref="ArgumentNullException"> /// <paramref name="storage"/> or <paramref name="fileName"/> is null. /// </exception> public ZipStorage(Storage storage, string fileName) { if (storage == null) throw new ArgumentNullException("storage"); if (fileName == null) throw new ArgumentNullException("fileName"); Storage = storage; FileName = fileName; _zipStream = storage.OpenFile(fileName); try { _zipFile = ZipFile.Read(_zipStream); return; } catch { _zipStream.Dispose(); #if !ANDROID throw; #endif } #if ANDROID // Android asset streams do not support Stream.Length/Position or seeking. // We need to copy the asset first to normal file. string tempFileName = storage.GetRealPath(fileName) ?? fileName; tempFileName = tempFileName.Replace('\\', '_'); tempFileName = tempFileName.Replace('/', '_'); tempFileName = "DigitalRune_Temp_" + tempFileName; tempFileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), tempFileName); // We copy the files once for each session because the asset files could have change. // To check if a file has changed, we would have to compare the whole file because ZIP files // store their dictionary at the end of the file. :-( // Filenames of the temp files are stored in a list. Other ZipStorages for the same // file will use the existing temp file. lock(_tempFiles) { if (!_tempFiles.Contains(tempFileName)) { _tempFiles.Add(tempFileName); using (_zipStream = storage.OpenFile(fileName)) { using (var dest = File.Create(tempFileName)) _zipStream.CopyTo(dest); } } } _zipStream = File.OpenRead(tempFileName); try { _zipFile = ZipFile.Read(_zipStream); } catch { _zipStream.Dispose(); throw; } #endif }
public ZipContainer(ZipFile zf) { _zf = zf; }