/// <summary> /// Wraps the ArchiveInformationEvent invocation inside a protected virtual method to let derived classes override it. /// This method guards against the possibility of a race condition if the last subscriber unsubscribes immediately /// after the null check and before the event is raised. So please don't simplify the invocation even if Visual Studio /// tells you to. /// </summary> /// <param name="e">Event arguments</param> protected virtual void OnArchiveInformationEvent(ArchiveInformationEventArgs e) { ArchiveInformationEventHandler handler = ArchiveInformationEvent; // Event will be null if there are no subscribers if (handler != null) { // Use the () operator to raise the event. handler(this, e); } }
private void OnNewArchiveInformationEvent(object sender, ArchiveInformationEventArgs eventArgs) { ArchiveInformationStatus.Update(eventArgs); ArchiveCurrentProcessing = Visibility.Visible; if (eventArgs.ArchiveType == ArchiveType.Noark5.ToString()) { AddmlDataObjectStatusVisibility = Visibility.Visible; } else { AddmlFlatFileStatusVisibility = Visibility.Visible; } }
/// <summary> /// Read the main header of the archive. /// </summary> /// <returns> /// The main header of the archive /// </returns> public JpaArchiveHeader ReadArchiveHeader() { JpaArchiveHeader archiveHeader = new JpaArchiveHeader(); // Initialize parts counter archiveHeader.TotalParts = 1; // Open the first part Open(1); archiveHeader.Signature = ReadAsciiString(3); if (archiveHeader.Signature != "JPA") { throw new InvalidArchiveException(String.Format(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_FILE_TYPE_SIGNATURE"), "JPA")); } archiveHeader.HeaderLength = ReadUShort(); archiveHeader.MajorVersion = ReadByte(); archiveHeader.MinorVersion = ReadByte(); archiveHeader.FileCount = ReadULong(); archiveHeader.UncompressedSize = ReadULong(); archiveHeader.CompressedSize = ReadULong(); if (archiveHeader.HeaderLength > 19) { // We need to loop while we have remaining header bytes ushort remainingBytes = (ushort)(archiveHeader.HeaderLength - 19); while (remainingBytes > 0) { // Do we have an extra header? The next three bytes must be JP followed by 0x01 and the header type byte[] headerSignature = ReadBytes(4); if ((headerSignature[0] != 0x4a) || (headerSignature[1] != 0x50) || (headerSignature[2] != 0x01)) { throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_JPA_EXTRA_HEADER")); } // The next two bytes tell us how long this header is, without the 4 byte signature and type but WITH the header length field ushort extraHeaderLength = ReadUShort(); // Subtract the read bytes from the remaining bytes in the header. remainingBytes -= (ushort)(4 + extraHeaderLength); // Read the extra header switch (headerSignature[3]) { case 0x01: // Spanned Archive Marker header archiveHeader.TotalParts = ReadUShort(); break; default: // I have no idea what this is! throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_JPA_EXTRA_HEADER")); } } } // Invoke the archiveInformation event. We need to do some work to get there, through... // -- Create a new archive information record ArchiveInformation info = new ArchiveInformation(); // -- Get the total archive size by looping all of its parts info.ArchiveSize = 0; for (int i = 1; i <= Parts; i++) { FileInfo fi = new FileInfo(ArchivePath); info.ArchiveSize += (ulong)fi.Length; } archiveHeader.TotalLength = info.ArchiveSize; // -- Incorporate bits from the file header info.FileCount = archiveHeader.FileCount; info.UncompressedSize = archiveHeader.UncompressedSize; info.CompressedSize = archiveHeader.CompressedSize; // -- Create the event arguments object ArchiveInformationEventArgs args = new ArchiveInformationEventArgs(info); // -- Finally, invoke the event OnArchiveInformationEvent(args); // Lastly, return the read archive header return(archiveHeader); }
public void Update(ArchiveInformationEventArgs archiveInformationEvent) { ArchiveFileName = archiveInformationEvent.ArchiveFileName; ArchiveType = archiveInformationEvent.ArchiveType; Uuid = archiveInformationEvent.Uuid; }
/// <summary> /// Reads the archive file's standard and end of archive headers and makes sure it's a valid JPS archive. /// </summary> /// <returns></returns> /// <exception cref="InvalidArchiveException"></exception> private JpsHeaderData ReadArchiveHeader() { // Open the first part Close(); Open(1); // Read the file signature. Must be "JPS" JpsHeaderData headerData; headerData.Signature = ReadAsciiString(3); if (headerData.Signature != "JPS") { throw new InvalidArchiveException( String.Format(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_FILE_TYPE_SIGNATURE"), "JPS")); } // Read the rest of the header headerData.MajorVersion = ReadByte(); headerData.MinorVersion = ReadByte(); headerData.SpannedArchive = ReadByte(); headerData.ExtraHeaderLength = ReadUShort(); // Make sure it's a supported JPS version bool oneNine = (headerData.MajorVersion == 1) && (headerData.MinorVersion == 9); bool oneTen = (headerData.MajorVersion == 1) && (headerData.MinorVersion == 10); bool twoZero = (headerData.MajorVersion == 2) && (headerData.MinorVersion == 0); if (!oneNine && !oneTen && !twoZero) { throw new InvalidArchiveException(String.Format( Language.ResourceManager.GetString("ERR_FORMAT_JPS_INVALID_VERSION"), headerData.MajorVersion, headerData.MinorVersion )); } // Versions 1.9 and 1.10 must not have any extra header data. if ((oneNine || oneTen) && (headerData.ExtraHeaderLength > 0)) { throw new InvalidArchiveException(String.Format( Language.ResourceManager.GetString("ERR_FORMAT_JPS_INVALID_EXTRA_HEADER_FOR_VERSION"), headerData.MajorVersion, headerData.MinorVersion )); } // JPS 2.0 MUST have an extra header. Make sure it exists. if (twoZero && (headerData.ExtraHeaderLength != 76)) { throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_JPS_EXTRAHEADER_WRONGLENGTH")); } // Read the JPS 2.0 extra header if (twoZero) { ReadPbkdf2ExtraArchiveHeader(); } // In JPS 2.0 we are going to use PBKDF2 to derive the key from the password, therefore legacy needs to be // disabled. if (twoZero) { _useLegacyKey = false; } // Open the last part and read the End Of Archive header data Close(); Open(Parts); InputStream.Seek(-17, SeekOrigin.End); headerData.EndOfArchiveSignature = ReadAsciiString(3); if (headerData.EndOfArchiveSignature != "JPE") { throw new InvalidArchiveException( String.Format(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_FILE_TYPE_SIGNATURE"), "JPS")); } // Read the rest of the end of archive header data headerData.NumberOfParts = ReadUShort(); headerData.NumberOfFiles = ReadULong(); headerData.UncompressedSize = ReadULong(); headerData.CompressedSize = ReadULong(); // Now we can reopen the first part and go past the header Open(1); SkipBytes(8 + headerData.ExtraHeaderLength); // Invoke the archiveInformation event. We need to do some work to get there, through... ArchiveInformation info = new ArchiveInformation(); info.ArchiveType = ArchiveType.Jps; // -- Get the total archive size by looping all of its parts info.ArchiveSize = 0; for (int i = 1; i <= Parts; i++) { FileInfo fi = new FileInfo(ArchivePath); info.ArchiveSize += (ulong)fi.Length; } headerData.TotalSize = info.ArchiveSize; // -- Incorporate bits from the file header info.CompressedSize = headerData.CompressedSize; info.UncompressedSize = headerData.UncompressedSize; info.FileCount = headerData.NumberOfFiles; // -- Create the event arguments object ArchiveInformationEventArgs args = new ArchiveInformationEventArgs(info); // -- Finally, invoke the event OnArchiveInformationEvent(args); return(headerData); }
/// <summary> /// Handle the unarchiver's archive information event. We need it to get the total size of /// the archive in bytes which we will then use to get the percentage of the file /// extracted for the progress bar display. /// </summary> /// <param name="sender"></param> /// <param name="a"></param> private void onArchiveInformationHandler(object sender, ArchiveInformationEventArgs a) { _totalArchiveSize = a.ArchiveInformation.ArchiveSize; }
/// <summary> /// Locates and reads the End of Central Directory record. It also reads through the entire central directory. /// This must be called at the beginning of extraction. /// </summary> /// <returns>The EOCD record of the archive</returns> /// <exception cref="InvalidArchiveException"></exception> private ZipEndOfCentralDirectoryRecord ReadEndOfCentralDirectory() { Open(Parts); long localOffset = InputStream.Length - 22; /** * The EOCD record is 22 to infinity bytes long. Its first 22 bytes are a pre-defined data record, whereas * the rest are the ZIP file comment. In order to determine its location relative to the archive's EOF I * chose to implement an inneficient backwards sliding window algorithm. We start by reading the last 22 * bytes of the archive. If the header is not found, we keep sliding backwards, one byte at a time until * we either locate the header or reach the BOF. The latter case means we don't have a valid archive. This * shouldn't happen, unless the archive was truncated in transit. */ try { do { InputStream.Seek(localOffset, SeekOrigin.Begin); byte[] buffer = ReadBytes(4); if (isEOCD(buffer)) { break; } localOffset--; } while (localOffset > 0); } catch (Exception) { throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_ZIP_EOCD_NOT_FOUND")); } // EOCD not found within the last part. That's a violation of the ZIP standard. if (localOffset < 0) { throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_ZIP_EOCD_NOT_FOUND")); } // Go back to the EOCD offset and let's read the contents ZipEndOfCentralDirectoryRecord eocdRecord = new ZipEndOfCentralDirectoryRecord(); InputStream.Seek(localOffset, SeekOrigin.Begin); eocdRecord.Signature = ReadULong(); eocdRecord.DiskNumber = ReadUShort(); eocdRecord.CDDisk = ReadUShort(); eocdRecord.DiskCDEntries = ReadUShort(); eocdRecord.NumFilesInCD = ReadUShort(); eocdRecord.CDLength = ReadULong(); eocdRecord.CDOffset = ReadULong(); eocdRecord.CommentLength = ReadUShort(); eocdRecord.Comment = ""; if (eocdRecord.CommentLength > 0) { eocdRecord.Comment = ReadUtf8String(eocdRecord.CommentLength); } // Now we can go to the beginning of the Central Directory and read its contents. We need to do that to get // the comrpessed and uncompressed size counts. var info = ReadCentralDirectoryContents(eocdRecord); // Invoke the archiveInformation event. We need to do some work to get there, through... // -- Get the total archive size by looping all of its parts info.ArchiveSize = 0; for (int i = 1; i <= Parts; i++) { FileInfo fi = new FileInfo(ArchivePath); info.ArchiveSize += (ulong)fi.Length; } eocdRecord.TotalSize = info.ArchiveSize; // -- Incorporate bits from the file header info.FileCount = eocdRecord.NumFilesInCD; // -- Create the event arguments object ArchiveInformationEventArgs args = new ArchiveInformationEventArgs(info); // -- Finally, invoke the event OnArchiveInformationEvent(args); return(eocdRecord); }