예제 #1
0
        /// <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);
            }
        }
예제 #2
0
        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;
            }
        }
예제 #3
0
        /// <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);
        }
예제 #4
0
 public void Update(ArchiveInformationEventArgs archiveInformationEvent)
 {
     ArchiveFileName = archiveInformationEvent.ArchiveFileName;
     ArchiveType     = archiveInformationEvent.ArchiveType;
     Uuid            = archiveInformationEvent.Uuid;
 }
예제 #5
0
        /// <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);
        }
예제 #6
0
 /// <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;
 }
예제 #7
0
        /// <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);
        }