/// <summary> /// Test a local header against that provided from the central directory /// </summary> /// <param name="entry"> /// The entry to test against /// </param> /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> /// <returns>The offset of the entries data in the file</returns> long TestLocalHeader( BlubbZipEntry entry, HeaderTest tests ) { lock( baseStream_ ) { bool testHeader = ( tests & HeaderTest.Header ) != 0; bool testData = ( tests & HeaderTest.Extract ) != 0; baseStream_.Seek( offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin ); if( (int)ReadLEUint() != BlubbZipConstants.LocalHeaderSignature ) { throw new BlubbZipException( string.Format( "Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset ) ); } short extractVersion = (short)ReadLEUshort(); short localFlags = (short)ReadLEUshort(); short compressionMethod = (short)ReadLEUshort(); short fileTime = (short)ReadLEUshort(); short fileDate = (short)ReadLEUshort(); uint crcValue = ReadLEUint(); long compressedSize = ReadLEUint(); long size = ReadLEUint(); int storedNameLength = ReadLEUshort(); int extraDataLength = ReadLEUshort(); byte[] nameData = new byte[ storedNameLength ]; StreamUtils.ReadFully( baseStream_, nameData ); byte[] extraData = new byte[ extraDataLength ]; StreamUtils.ReadFully( baseStream_, extraData ); BlubbZipExtraData localExtraData = new BlubbZipExtraData( extraData ); // Extra data / blubb64 checks if( localExtraData.Find( 1 ) ) { // TODO Check for tag values being distinct.. Multiple blubb64 tags means what? // Blubb64 extra data but 'extract version' is too low if( extractVersion < BlubbZipConstants.VersionBlubb64 ) { throw new BlubbZipException( string.Format( "Extra data contains Blubb64 information but version {0}.{1} is not high enough", extractVersion / 10, extractVersion % 10 ) ); } // Blubb64 extra data but size fields dont indicate its required. if( ( (uint)size != uint.MaxValue ) && ( (uint)compressedSize != uint.MaxValue ) ) { throw new BlubbZipException( "Entry sizes not correct for Blubb64" ); } size = localExtraData.ReadLong(); compressedSize = localExtraData.ReadLong(); if( ( localFlags & (int)GeneralBitFlags.Descriptor ) != 0 ) { // These may be valid if patched later if( ( size != -1 ) && ( size != entry.Size ) ) { throw new BlubbZipException( "Size invalid for descriptor" ); } if( ( compressedSize != -1 ) && ( compressedSize != entry.CompressedSize ) ) { throw new BlubbZipException( "Compressed size invalid for descriptor" ); } } } else { // No blubb64 extra data but entry requires it. if( ( extractVersion >= BlubbZipConstants.VersionBlubb64 ) && ( ( (uint)size == uint.MaxValue ) || ( (uint)compressedSize == uint.MaxValue ) ) ) { throw new BlubbZipException( "Required Blubb64 extended information missing" ); } } if( testData ) { if( entry.IsFile ) { if( !entry.IsCompressionMethodSupported() ) { throw new BlubbZipException( "Compression method not supported" ); } if( ( extractVersion > BlubbZipConstants.VersionMadeBy ) || ( ( extractVersion > 20 ) && ( extractVersion < BlubbZipConstants.VersionBlubb64 ) ) ) { throw new BlubbZipException( string.Format( "Version required to extract this entry not supported ({0})", extractVersion ) ); } if( ( localFlags & (int)( GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked ) ) != 0 ) { throw new BlubbZipException( "The library does not support the blubb version required to extract this entry" ); } } } if( testHeader ) { if( ( extractVersion <= 63 ) && ( extractVersion != 10 ) && ( extractVersion != 11 ) && ( extractVersion != 20 ) && ( extractVersion != 21 ) && ( extractVersion != 25 ) && ( extractVersion != 27 ) && ( extractVersion != 45 ) && ( extractVersion != 46 ) && ( extractVersion != 50 ) && ( extractVersion != 51 ) && ( extractVersion != 52 ) && ( extractVersion != 61 ) && ( extractVersion != 62 ) && ( extractVersion != 63 ) ) { throw new BlubbZipException( string.Format( "Version required to extract this entry is invalid ({0})", extractVersion ) ); } // Local entry flags dont have reserved bit set on. if( ( localFlags & (int)( GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15 ) ) != 0 ) { throw new BlubbZipException( "Reserved bit flags cannot be set." ); } // Encryption requires extract version >= 20 if( ( ( localFlags & (int)GeneralBitFlags.Encrypted ) != 0 ) && ( extractVersion < 20 ) ) { throw new BlubbZipException( string.Format( "Version required to extract this entry is too low for encryption ({0})", extractVersion ) ); } // Strong encryption requires encryption flag to be set and extract version >= 50. if( ( localFlags & (int)GeneralBitFlags.StrongEncryption ) != 0 ) { if( ( localFlags & (int)GeneralBitFlags.Encrypted ) == 0 ) { throw new BlubbZipException( "Strong encryption flag set but encryption flag is not set" ); } if( extractVersion < 50 ) { throw new BlubbZipException( string.Format( "Version required to extract this entry is too low for encryption ({0})", extractVersion ) ); } } // Patched entries require extract version >= 27 if( ( ( localFlags & (int)GeneralBitFlags.Patched ) != 0 ) && ( extractVersion < 27 ) ) { throw new BlubbZipException( string.Format( "Patched data requires higher version than ({0})", extractVersion ) ); } // Central header flags match local entry flags. if( localFlags != entry.Flags ) { throw new BlubbZipException( "Central header/local header flags mismatch" ); } // Central header compression method matches local entry if( entry.CompressionMethod != (CompressionMethod)compressionMethod ) { throw new BlubbZipException( "Central header/local header compression method mismatch" ); } if( entry.Version != extractVersion ) { throw new BlubbZipException( "Extract version mismatch" ); } // Strong encryption and extract version match if( ( localFlags & (int)GeneralBitFlags.StrongEncryption ) != 0 ) { if( extractVersion < 62 ) { throw new BlubbZipException( "Strong encryption flag set but version not high enough" ); } } if( ( localFlags & (int)GeneralBitFlags.HeaderMasked ) != 0 ) { if( ( fileTime != 0 ) || ( fileDate != 0 ) ) { throw new BlubbZipException( "Header masked set but date/time values non-zero" ); } } if( ( localFlags & (int)GeneralBitFlags.Descriptor ) == 0 ) { if( crcValue != (uint)entry.Crc ) { throw new BlubbZipException( "Central header/local header crc mismatch" ); } } // Crc valid for empty entry. // This will also apply to streamed entries where size isnt known and the header cant be patched if( ( size == 0 ) && ( compressedSize == 0 ) ) { if( crcValue != 0 ) { throw new BlubbZipException( "Invalid CRC for empty entry" ); } } // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings // Assuming a code page at this point is not valid? Best is to store the name length in the BlubbEntry probably if( entry.Name.Length > storedNameLength ) { throw new BlubbZipException( "File name length mismatch" ); } // Name data has already been read convert it and compare. string localName = BlubbZipConstants.ConvertToStringExt( localFlags, nameData ); // Central directory and local entry name match if( localName != entry.Name ) { throw new BlubbZipException( "Central header and local header file name mismatch" ); } // Directories have zero actual size but can have compressed size if( entry.IsDirectory ) { if( size > 0 ) { throw new BlubbZipException( "Directory cannot have size" ); } // There may be other cases where the compressed size can be greater than this? // If so until details are known we will be strict. if( entry.IsCrypted ) { if( compressedSize > BlubbZipConstants.CryptoHeaderSize + 2 ) { throw new BlubbZipException( "Directory compressed size invalid" ); } } else if( compressedSize > 2 ) { // When not compressed the directory size can validly be 2 bytes // if the true size wasnt known when data was originally being written. // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes throw new BlubbZipException( "Directory compressed size invalid" ); } } if( !BlubbZipNameTransform.IsValidName( localName, true ) ) { throw new BlubbZipException( "Name is invalid" ); } } // Tests that apply to both data and header. // Size can be verified only if it is known in the local header. // it will always be known in the central header. if( ( ( localFlags & (int)GeneralBitFlags.Descriptor ) == 0 ) || ( ( size > 0 ) || ( compressedSize > 0 ) ) ) { if( size != entry.Size ) { throw new BlubbZipException( string.Format( "Size mismatch between central header({0}) and local header({1})", entry.Size, size ) ); } if( compressedSize != entry.CompressedSize ) { throw new BlubbZipException( string.Format( "Compressed size mismatch between central header({0}) and local header({1})", entry.CompressedSize, compressedSize ) ); } } int extraLength = storedNameLength + extraDataLength; return offsetOfFirstEntry + entry.Offset + BlubbZipConstants.LocalHeaderBaseSize + extraLength; } }
/// <summary> /// Advances to the next entry in the archive /// </summary> /// <returns> /// The next <see cref="BlubbEntry">entry</see> in the archive or null if there are no more entries. /// </returns> /// <remarks> /// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called. /// </remarks> /// <exception cref="InvalidOperationException"> /// Input stream is closed /// </exception> /// <exception cref="BlubbException"> /// Password is not set, password is invalid, compression method is invalid, /// version required to extract is not supported /// </exception> public BlubbZipEntry GetNextEntry() { if (crc == null) { throw new InvalidOperationException("Closed."); } if (entry != null) { CloseEntry(); } int header = inputBuffer.ReadLeInt(); if (header == BlubbZipConstants.CentralHeaderSignature || header == BlubbZipConstants.EndOfCentralDirectorySignature || header == BlubbZipConstants.CentralHeaderDigitalSignature || header == BlubbZipConstants.ArchiveExtraDataSignature || header == BlubbZipConstants.Blubb64CentralFileHeaderSignature) { // No more individual entries exist Close(); return(null); } // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found // Spanning signature is same as descriptor signature and is untested as yet. if ((header == BlubbZipConstants.SpanningTempSignature) || (header == BlubbZipConstants.SpanningSignature)) { header = inputBuffer.ReadLeInt(); } if (header != BlubbZipConstants.LocalHeaderSignature) { throw new BlubbZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header)); } short versionRequiredToExtract = (short)inputBuffer.ReadLeShort(); flags = inputBuffer.ReadLeShort(); method = inputBuffer.ReadLeShort(); uint dostime = (uint)inputBuffer.ReadLeInt(); int crc2 = inputBuffer.ReadLeInt(); csize = inputBuffer.ReadLeInt(); size = inputBuffer.ReadLeInt(); int nameLen = inputBuffer.ReadLeShort(); int extraLen = inputBuffer.ReadLeShort(); bool isCrypted = (flags & 1) == 1; byte[] buffer = new byte[nameLen]; inputBuffer.ReadRawBuffer(buffer); string name = BlubbZipConstants.ConvertToStringExt(flags, buffer); entry = new BlubbZipEntry(name, versionRequiredToExtract); entry.Flags = flags; entry.CompressionMethod = (CompressionMethod)method; if ((flags & 8) == 0) { entry.Crc = crc2 & 0xFFFFFFFFL; entry.Size = size & 0xFFFFFFFFL; entry.CompressedSize = csize & 0xFFFFFFFFL; entry.CryptoCheckValue = (byte)((crc2 >> 24) & 0xff); } else { // This allows for GNU, WinBlubb and possibly other archives, the PKZIP spec // says these values are zero under these circumstances. if (crc2 != 0) { entry.Crc = crc2 & 0xFFFFFFFFL; } if (size != 0) { entry.Size = size & 0xFFFFFFFFL; } if (csize != 0) { entry.CompressedSize = csize & 0xFFFFFFFFL; } entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); } entry.DosTime = dostime; // If local header requires Blubb64 is true then the extended header should contain // both values. // Handle extra data if present. This can set/alter some fields of the entry. if (extraLen > 0) { byte[] extra = new byte[extraLen]; inputBuffer.ReadRawBuffer(extra); entry.ExtraData = extra; } entry.ProcessExtraData(true); if (entry.CompressedSize >= 0) { csize = entry.CompressedSize; } if (entry.Size >= 0) { size = entry.Size; } if (method == (int)CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - BlubbZipConstants.CryptoHeaderSize != size))) { throw new BlubbZipException("Stored, but compressed != uncompressed"); } // Determine how to handle reading of data if this is attempted. if (entry.IsCompressionMethodSupported()) { internalReader = new ReadDataHandler(InitialRead); } else { internalReader = new ReadDataHandler(ReadingNotSupported); } return(entry); }
void ExtractEntry(BlubbZipEntry entry) { bool doExtraction = entry.IsCompressionMethodSupported(); string targetName = entry.Name; if (doExtraction) { if (entry.IsFile) { targetName = extractNameTransform_.TransformFile(targetName); } else if (entry.IsDirectory) { targetName = extractNameTransform_.TransformDirectory(targetName); } doExtraction = !((targetName == null) || (targetName.Length == 0)); } // TODO: Fire delegate/throw exception were compression method not supported, or name is invalid? string dirName = null; if (doExtraction) { if (entry.IsDirectory) { dirName = targetName; } else { dirName = Path.GetDirectoryName(Path.GetFullPath(targetName)); } } if (doExtraction && !Directory.Exists(dirName)) { if (!entry.IsDirectory || CreateEmptyDirectories) { try { Directory.CreateDirectory(dirName); } catch (Exception ex) { doExtraction = false; if (events_ != null) { if (entry.IsDirectory) { continueRunning_ = events_.OnDirectoryFailure(targetName, ex); } else { continueRunning_ = events_.OnFileFailure(targetName, ex); } } else { continueRunning_ = false; throw; } } } } if (doExtraction && entry.IsFile) { ExtractFileEntry(entry, targetName); } }
/// <summary> /// Advances to the next entry in the archive /// </summary> /// <returns> /// The next <see cref="BlubbEntry">entry</see> in the archive or null if there are no more entries. /// </returns> /// <remarks> /// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called. /// </remarks> /// <exception cref="InvalidOperationException"> /// Input stream is closed /// </exception> /// <exception cref="BlubbException"> /// Password is not set, password is invalid, compression method is invalid, /// version required to extract is not supported /// </exception> public BlubbZipEntry GetNextEntry() { if( crc == null ) { throw new InvalidOperationException( "Closed." ); } if( entry != null ) { CloseEntry(); } int header = inputBuffer.ReadLeInt(); if( header == BlubbZipConstants.CentralHeaderSignature || header == BlubbZipConstants.EndOfCentralDirectorySignature || header == BlubbZipConstants.CentralHeaderDigitalSignature || header == BlubbZipConstants.ArchiveExtraDataSignature || header == BlubbZipConstants.Blubb64CentralFileHeaderSignature ) { // No more individual entries exist Close(); return null; } // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found // Spanning signature is same as descriptor signature and is untested as yet. if( ( header == BlubbZipConstants.SpanningTempSignature ) || ( header == BlubbZipConstants.SpanningSignature ) ) { header = inputBuffer.ReadLeInt(); } if( header != BlubbZipConstants.LocalHeaderSignature ) { throw new BlubbZipException( "Wrong Local header signature: 0x" + String.Format( "{0:X}", header ) ); } short versionRequiredToExtract = (short)inputBuffer.ReadLeShort(); flags = inputBuffer.ReadLeShort(); method = inputBuffer.ReadLeShort(); uint dostime = (uint)inputBuffer.ReadLeInt(); int crc2 = inputBuffer.ReadLeInt(); csize = inputBuffer.ReadLeInt(); size = inputBuffer.ReadLeInt(); int nameLen = inputBuffer.ReadLeShort(); int extraLen = inputBuffer.ReadLeShort(); bool isCrypted = ( flags & 1 ) == 1; byte[] buffer = new byte[ nameLen ]; inputBuffer.ReadRawBuffer( buffer ); string name = BlubbZipConstants.ConvertToStringExt( flags, buffer ); entry = new BlubbZipEntry( name, versionRequiredToExtract ); entry.Flags = flags; entry.CompressionMethod = (CompressionMethod)method; if( ( flags & 8 ) == 0 ) { entry.Crc = crc2 & 0xFFFFFFFFL; entry.Size = size & 0xFFFFFFFFL; entry.CompressedSize = csize & 0xFFFFFFFFL; entry.CryptoCheckValue = (byte)( ( crc2 >> 24 ) & 0xff ); } else { // This allows for GNU, WinBlubb and possibly other archives, the PKZIP spec // says these values are zero under these circumstances. if( crc2 != 0 ) { entry.Crc = crc2 & 0xFFFFFFFFL; } if( size != 0 ) { entry.Size = size & 0xFFFFFFFFL; } if( csize != 0 ) { entry.CompressedSize = csize & 0xFFFFFFFFL; } entry.CryptoCheckValue = (byte)( ( dostime >> 8 ) & 0xff ); } entry.DosTime = dostime; // If local header requires Blubb64 is true then the extended header should contain // both values. // Handle extra data if present. This can set/alter some fields of the entry. if( extraLen > 0 ) { byte[] extra = new byte[ extraLen ]; inputBuffer.ReadRawBuffer( extra ); entry.ExtraData = extra; } entry.ProcessExtraData( true ); if( entry.CompressedSize >= 0 ) { csize = entry.CompressedSize; } if( entry.Size >= 0 ) { size = entry.Size; } if( method == (int)CompressionMethod.Stored && ( !isCrypted && csize != size || ( isCrypted && csize - BlubbZipConstants.CryptoHeaderSize != size ) ) ) { throw new BlubbZipException( "Stored, but compressed != uncompressed" ); } // Determine how to handle reading of data if this is attempted. if( entry.IsCompressionMethodSupported() ) { internalReader = new ReadDataHandler( InitialRead ); } else { internalReader = new ReadDataHandler( ReadingNotSupported ); } return entry; }