/// <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> /// Starts a new Blubb entry. It automatically closes the previous /// entry if present. /// All entry elements bar name are optional, but must be correct if present. /// If the compression method is stored and the output is not patchable /// the compression for that entry is automatically changed to deflate level 0 /// </summary> /// <param name="entry"> /// the entry. /// </param> /// <exception cref="System.ArgumentNullException"> /// if entry passed is null. /// </exception> /// <exception cref="System.IO.IOException"> /// if an I/O error occured. /// </exception> /// <exception cref="System.InvalidOperationException"> /// if stream was finished /// </exception> /// <exception cref="BlubbException"> /// Too many entries in the Blubb file<br/> /// Entry name is too long<br/> /// Finish has already been called<br/> /// </exception> public void PutNextEntry(BlubbZipEntry entry) { if (entry == null) { throw new ArgumentNullException("entry"); } if (entries == null) { throw new InvalidOperationException("BlubbOutputStream was finished"); } if (curEntry != null) { CloseEntry(); } if (entries.Count == int.MaxValue) { throw new BlubbZipException("Too many entries for Blubb file"); } CompressionMethod method = entry.CompressionMethod; int compressionLevel = defaultCompressionLevel; // Clear flags that the library manages internally entry.Flags &= (int)GeneralBitFlags.UnicodeText; patchEntryHeader = false; bool headerInfoAvailable; // No need to compress - definitely no data. if (entry.Size == 0) { entry.CompressedSize = entry.Size; entry.Crc = 0; method = CompressionMethod.Stored; headerInfoAvailable = true; } else { headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc; // Switch to deflation if storing isnt possible. if (method == CompressionMethod.Stored) { if (!headerInfoAvailable) { if (!CanPatchEntries) { // Can't patch entries so storing is not possible. method = CompressionMethod.Deflated; compressionLevel = 0; } } else { // entry.size must be > 0 entry.CompressedSize = entry.Size; headerInfoAvailable = entry.HasCrc; } } } if (headerInfoAvailable == false) { if (CanPatchEntries == false) { // Only way to record size and compressed size is to append a data descriptor // after compressed data. // Stored entries of this form have already been converted to deflating. entry.Flags |= 8; } else { patchEntryHeader = true; } } if (Password != null) { entry.IsCrypted = true; if (entry.Crc < 0) { // Need to append a data descriptor as the crc isnt available for use // with encryption, the date is used instead. Setting the flag // indicates this to the decompressor. entry.Flags |= 8; } } entry.Offset = offset; entry.CompressionMethod = (CompressionMethod)method; curMethod = method; sizePatchPos = -1; if (useBlubb64_ == UseBlubb64.On || (entry.Size < 0 && useBlubb64_ == UseBlubb64.Dynamic)) { entry.ForceBlubb64(); } // Write the local file header WriteLeInt(BlubbZipConstants.LocalHeaderSignature); WriteLeShort(entry.Version); WriteLeShort(entry.Flags); WriteLeShort((byte)method); WriteLeInt((int)entry.DosTime); // TODO: Refactor header writing. Its done in several places. if (headerInfoAvailable == true) { WriteLeInt((int)entry.Crc); if (entry.LocalHeaderRequiresBlubb64) { WriteLeInt(-1); WriteLeInt(-1); } else { WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + BlubbZipConstants.CryptoHeaderSize : (int)entry.CompressedSize); WriteLeInt((int)entry.Size); } } else { if (patchEntryHeader == true) { crcPatchPos = baseOutputStream_.Position; } WriteLeInt(0); // Crc if (patchEntryHeader) { sizePatchPos = baseOutputStream_.Position; } // For local header both sizes appear in Blubb64 Extended Information if (entry.LocalHeaderRequiresBlubb64 || patchEntryHeader) { WriteLeInt(-1); WriteLeInt(-1); } else { WriteLeInt(0); // Compressed size WriteLeInt(0); // Uncompressed size } } byte[] name = BlubbZipConstants.ConvertToArray(entry.Flags, entry.Name); if (name.Length > 0xFFFF) { throw new BlubbZipException("Entry name too long."); } BlubbZipExtraData ed = new BlubbZipExtraData(entry.ExtraData); if (entry.LocalHeaderRequiresBlubb64) { ed.StartNewEntry(); if (headerInfoAvailable) { ed.AddLeLong(entry.Size); ed.AddLeLong(entry.CompressedSize); } else { ed.AddLeLong(-1); ed.AddLeLong(-1); } ed.AddNewEntry(1); if (!ed.Find(1)) { throw new BlubbZipException("Internal error cant find extra data"); } if (patchEntryHeader) { sizePatchPos = ed.CurrentReadIndex; } } else { ed.Delete(1); } byte[] extra = ed.GetEntryData(); WriteLeShort(name.Length); WriteLeShort(extra.Length); if (name.Length > 0) { baseOutputStream_.Write(name, 0, name.Length); } if (entry.LocalHeaderRequiresBlubb64 && patchEntryHeader) { sizePatchPos += baseOutputStream_.Position; } if (extra.Length > 0) { baseOutputStream_.Write(extra, 0, extra.Length); } offset += BlubbZipConstants.LocalHeaderBaseSize + name.Length + extra.Length; // Activate the entry. curEntry = entry; crc.Reset(); if (method == CompressionMethod.Deflated) { deflater_.Reset(); deflater_.SetLevel(compressionLevel); } size = 0; if (entry.IsCrypted == true) { if (entry.Crc < 0) // so testing Blubb will says its ok { WriteEncryptionHeader(entry.DosTime << 16); } else { WriteEncryptionHeader(entry.Crc); } } }
void WriteLocalEntryHeader( BlubbUpdate update ) { BlubbZipEntry entry = update.OutEntry; // TODO: Local offset will require adjusting for multi-disk blubb files. entry.Offset = baseStream_.Position; // TODO: Need to clear any entry flags that dont make sense or throw an exception here. if( update.Command != UpdateCommand.Copy ) { if( entry.CompressionMethod == CompressionMethod.Deflated ) { if( entry.Size == 0 ) { // No need to compress - no data. entry.CompressedSize = entry.Size; entry.Crc = 0; entry.CompressionMethod = CompressionMethod.Stored; } } else if( entry.CompressionMethod == CompressionMethod.Stored ) { entry.Flags &= ~(int)GeneralBitFlags.Descriptor; } if( HaveKeys ) { entry.IsCrypted = true; if( entry.Crc < 0 ) { entry.Flags |= (int)GeneralBitFlags.Descriptor; } } else { entry.IsCrypted = false; } switch( useBlubb64_ ) { case UseBlubb64.Dynamic: if( entry.Size < 0 ) { entry.ForceBlubb64(); } break; case UseBlubb64.On: entry.ForceBlubb64(); break; case UseBlubb64.Off: // Do nothing. The entry itself may be using Blubb64 independantly. break; } } // Write the local file header WriteLEInt( BlubbZipConstants.LocalHeaderSignature ); WriteLEShort( entry.Version ); WriteLEShort( entry.Flags ); WriteLEShort( (byte)entry.CompressionMethod ); WriteLEInt( (int)entry.DosTime ); if( !entry.HasCrc ) { // Note patch address for updating CRC later. update.CrcPatchOffset = baseStream_.Position; WriteLEInt( (int)0 ); } else { WriteLEInt( unchecked( (int)entry.Crc ) ); } if( entry.LocalHeaderRequiresBlubb64 ) { WriteLEInt( -1 ); WriteLEInt( -1 ); } else { if( ( entry.CompressedSize < 0 ) || ( entry.Size < 0 ) ) { update.SizePatchOffset = baseStream_.Position; } WriteLEInt( (int)entry.CompressedSize ); WriteLEInt( (int)entry.Size ); } byte[] name = BlubbZipConstants.ConvertToArray( entry.Flags, entry.Name ); if( name.Length > 0xFFFF ) { throw new BlubbZipException( "Entry name too long." ); } BlubbZipExtraData ed = new BlubbZipExtraData( entry.ExtraData ); if( entry.LocalHeaderRequiresBlubb64 ) { ed.StartNewEntry(); // Local entry header always includes size and compressed size. // NOTE the order of these fields is reversed when compared to the normal headers! ed.AddLeLong( entry.Size ); ed.AddLeLong( entry.CompressedSize ); ed.AddNewEntry( 1 ); } else { ed.Delete( 1 ); } entry.ExtraData = ed.GetEntryData(); WriteLEShort( name.Length ); WriteLEShort( entry.ExtraData.Length ); if( name.Length > 0 ) { baseStream_.Write( name, 0, name.Length ); } if( entry.LocalHeaderRequiresBlubb64 ) { if( !ed.Find( 1 ) ) { throw new BlubbZipException( "Internal error cannot find extra data" ); } update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; } if( entry.ExtraData.Length > 0 ) { baseStream_.Write( entry.ExtraData, 0, entry.ExtraData.Length ); } }
/// <summary> /// Process extra data fields updating the entry based on the contents. /// </summary> /// <param name="localHeader">True if the extra data fields should be handled /// for a local header, rather than for a central header. /// </param> internal void ProcessExtraData(bool localHeader) { BlubbZipExtraData extraData = new BlubbZipExtraData(this.extra); if (extraData.Find(0x0001)) { if ((versionToExtract & 0xff) < BlubbZipConstants.VersionBlubb64) { throw new BlubbZipException("Blubb64 Extended information found but version is not valid"); } // The recorded size will change but remember that this is blubb64. forceBlubb64_ = true; if (extraData.ValueLength < 4) { throw new BlubbZipException("Extra data extended Blubb64 information length is invalid"); } if (localHeader || size == uint.MaxValue) { size = (ulong)extraData.ReadLong(); } if (localHeader || compressedSize == uint.MaxValue) { compressedSize = (ulong)extraData.ReadLong(); } if (!localHeader && offset == uint.MaxValue) { offset = extraData.ReadLong(); } } else { if ( ((versionToExtract & 0xff) >= BlubbZipConstants.VersionBlubb64) && ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) ) { throw new BlubbZipException("Blubb64 Extended information required but is missing."); } } if (extraData.Find(10)) { // No room for any tags. if (extraData.ValueLength < 8) { throw new BlubbZipException("NTFS Extra data invalid"); } extraData.ReadInt(); // Reserved while (extraData.UnreadCount >= 4) { int ntfsTag = extraData.ReadShort(); int ntfsLength = extraData.ReadShort(); if (ntfsTag == 1) { if (ntfsLength >= 24) { long lastModification = extraData.ReadLong(); long lastAccess = extraData.ReadLong(); long createTime = extraData.ReadLong(); DateTime = System.DateTime.FromFileTime(lastModification); } break; } else { // An unknown NTFS tag so simply skip it. extraData.Skip(ntfsLength); } } } else if (extraData.Find(0x5455)) { int length = extraData.ValueLength; int flags = extraData.ReadByte(); // Can include other times but these are ignored. Length of data should // actually be 1 + 4 * no of bits in flags. if (((flags & 1) != 0) && (length >= 5)) { int iTime = extraData.ReadInt(); DateTime = (new System.DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime() + new TimeSpan(0, 0, 0, iTime, 0)).ToLocalTime(); } } }
// Write the local file header // TODO: BlubbHelperStream.WriteLocalHeader is not yet used and needs checking for BlubbFile and BlubbOuptutStream usage void WriteLocalHeader( BlubbZipEntry entry, EntryPatchData patchData ) { CompressionMethod method = entry.CompressionMethod; bool headerInfoAvailable = true; // How to get this? bool patchEntryHeader = false; WriteLEInt( BlubbZipConstants.LocalHeaderSignature ); WriteLEShort( entry.Version ); WriteLEShort( entry.Flags ); WriteLEShort( (byte)method ); WriteLEInt( (int)entry.DosTime ); if( headerInfoAvailable == true ) { WriteLEInt( (int)entry.Crc ); if( entry.LocalHeaderRequiresBlubb64 ) { WriteLEInt( -1 ); WriteLEInt( -1 ); } else { WriteLEInt( entry.IsCrypted ? (int)entry.CompressedSize + BlubbZipConstants.CryptoHeaderSize : (int)entry.CompressedSize ); WriteLEInt( (int)entry.Size ); } } else { if( patchData != null ) { patchData.CrcPatchOffset = stream_.Position; } WriteLEInt( 0 ); // Crc if( patchData != null ) { patchData.SizePatchOffset = stream_.Position; } // For local header both sizes appear in Blubb64 Extended Information if( entry.LocalHeaderRequiresBlubb64 && patchEntryHeader ) { WriteLEInt( -1 ); WriteLEInt( -1 ); } else { WriteLEInt( 0 ); // Compressed size WriteLEInt( 0 ); // Uncompressed size } } byte[] name = BlubbZipConstants.ConvertToArray( entry.Flags, entry.Name ); if( name.Length > 0xFFFF ) { throw new BlubbZipException( "Entry name too long." ); } BlubbZipExtraData ed = new BlubbZipExtraData( entry.ExtraData ); if( entry.LocalHeaderRequiresBlubb64 && ( headerInfoAvailable || patchEntryHeader ) ) { ed.StartNewEntry(); if( headerInfoAvailable ) { ed.AddLeLong( entry.Size ); ed.AddLeLong( entry.CompressedSize ); } else { ed.AddLeLong( -1 ); ed.AddLeLong( -1 ); } ed.AddNewEntry( 1 ); if( !ed.Find( 1 ) ) { throw new BlubbZipException( "Internal error cant find extra data" ); } if( patchData != null ) { patchData.SizePatchOffset = ed.CurrentReadIndex; } } else { ed.Delete( 1 ); } byte[] extra = ed.GetEntryData(); WriteLEShort( name.Length ); WriteLEShort( extra.Length ); if( name.Length > 0 ) { stream_.Write( name, 0, name.Length ); } if( entry.LocalHeaderRequiresBlubb64 && patchEntryHeader ) { patchData.SizePatchOffset += stream_.Position; } if( extra.Length > 0 ) { stream_.Write( extra, 0, extra.Length ); } }
/// <summary> /// Process extra data fields updating the entry based on the contents. /// </summary> /// <param name="localHeader">True if the extra data fields should be handled /// for a local header, rather than for a central header. /// </param> internal void ProcessExtraData( bool localHeader ) { BlubbZipExtraData extraData = new BlubbZipExtraData( this.extra ); if( extraData.Find( 0x0001 ) ) { if( ( versionToExtract & 0xff ) < BlubbZipConstants.VersionBlubb64 ) throw new BlubbZipException( "Blubb64 Extended information found but version is not valid" ); // The recorded size will change but remember that this is blubb64. forceBlubb64_ = true; if( extraData.ValueLength < 4 ) throw new BlubbZipException( "Extra data extended Blubb64 information length is invalid" ); if( localHeader || size == uint.MaxValue ) size = (ulong)extraData.ReadLong(); if( localHeader || compressedSize == uint.MaxValue ) compressedSize = (ulong)extraData.ReadLong(); if( !localHeader && offset == uint.MaxValue ) offset = extraData.ReadLong(); } else { if( ( ( versionToExtract & 0xff ) >= BlubbZipConstants.VersionBlubb64 ) && ( ( size == uint.MaxValue ) || ( compressedSize == uint.MaxValue ) ) ) { throw new BlubbZipException( "Blubb64 Extended information required but is missing." ); } } if( extraData.Find( 10 ) ) { // No room for any tags. if( extraData.ValueLength < 8 ) { throw new BlubbZipException( "NTFS Extra data invalid" ); } extraData.ReadInt(); // Reserved while( extraData.UnreadCount >= 4 ) { int ntfsTag = extraData.ReadShort(); int ntfsLength = extraData.ReadShort(); if( ntfsTag == 1 ) { if( ntfsLength >= 24 ) { long lastModification = extraData.ReadLong(); long lastAccess = extraData.ReadLong(); long createTime = extraData.ReadLong(); DateTime = System.DateTime.FromFileTime( lastModification ); } break; } else { // An unknown NTFS tag so simply skip it. extraData.Skip( ntfsLength ); } } } else if( extraData.Find( 0x5455 ) ) { int length = extraData.ValueLength; int flags = extraData.ReadByte(); // Can include other times but these are ignored. Length of data should // actually be 1 + 4 * no of bits in flags. if( ( ( flags & 1 ) != 0 ) && ( length >= 5 ) ) { int iTime = extraData.ReadInt(); DateTime = ( new System.DateTime( 1970, 1, 1, 0, 0, 0 ).ToUniversalTime() + new TimeSpan( 0, 0, 0, iTime, 0 ) ).ToLocalTime(); } } }
/// <summary> /// Starts a new Blubb entry. It automatically closes the previous /// entry if present. /// All entry elements bar name are optional, but must be correct if present. /// If the compression method is stored and the output is not patchable /// the compression for that entry is automatically changed to deflate level 0 /// </summary> /// <param name="entry"> /// the entry. /// </param> /// <exception cref="System.ArgumentNullException"> /// if entry passed is null. /// </exception> /// <exception cref="System.IO.IOException"> /// if an I/O error occured. /// </exception> /// <exception cref="System.InvalidOperationException"> /// if stream was finished /// </exception> /// <exception cref="BlubbException"> /// Too many entries in the Blubb file<br/> /// Entry name is too long<br/> /// Finish has already been called<br/> /// </exception> public void PutNextEntry( BlubbZipEntry entry ) { if( entry == null ) throw new ArgumentNullException( "entry" ); if( entries == null ) throw new InvalidOperationException( "BlubbOutputStream was finished" ); if( curEntry != null ) CloseEntry(); if( entries.Count == int.MaxValue ) throw new BlubbZipException( "Too many entries for Blubb file" ); CompressionMethod method = entry.CompressionMethod; int compressionLevel = defaultCompressionLevel; // Clear flags that the library manages internally entry.Flags &= (int)GeneralBitFlags.UnicodeText; patchEntryHeader = false; bool headerInfoAvailable; // No need to compress - definitely no data. if( entry.Size == 0 ) { entry.CompressedSize = entry.Size; entry.Crc = 0; method = CompressionMethod.Stored; headerInfoAvailable = true; } else { headerInfoAvailable = ( entry.Size >= 0 ) && entry.HasCrc; // Switch to deflation if storing isnt possible. if( method == CompressionMethod.Stored ) { if( !headerInfoAvailable ) { if( !CanPatchEntries ) { // Can't patch entries so storing is not possible. method = CompressionMethod.Deflated; compressionLevel = 0; } } else { // entry.size must be > 0 entry.CompressedSize = entry.Size; headerInfoAvailable = entry.HasCrc; } } } if( headerInfoAvailable == false ) { if( CanPatchEntries == false ) { // Only way to record size and compressed size is to append a data descriptor // after compressed data. // Stored entries of this form have already been converted to deflating. entry.Flags |= 8; } else { patchEntryHeader = true; } } if( Password != null ) { entry.IsCrypted = true; if( entry.Crc < 0 ) { // Need to append a data descriptor as the crc isnt available for use // with encryption, the date is used instead. Setting the flag // indicates this to the decompressor. entry.Flags |= 8; } } entry.Offset = offset; entry.CompressionMethod = (CompressionMethod)method; curMethod = method; sizePatchPos = -1; if( useBlubb64_ == UseBlubb64.On || ( entry.Size < 0 && useBlubb64_ == UseBlubb64.Dynamic ) ) entry.ForceBlubb64(); // Write the local file header WriteLeInt( BlubbZipConstants.LocalHeaderSignature ); WriteLeShort( entry.Version ); WriteLeShort( entry.Flags ); WriteLeShort( (byte)method ); WriteLeInt( (int)entry.DosTime ); // TODO: Refactor header writing. Its done in several places. if( headerInfoAvailable == true ) { WriteLeInt( (int)entry.Crc ); if( entry.LocalHeaderRequiresBlubb64 ) { WriteLeInt( -1 ); WriteLeInt( -1 ); } else { WriteLeInt( entry.IsCrypted ? (int)entry.CompressedSize + BlubbZipConstants.CryptoHeaderSize : (int)entry.CompressedSize ); WriteLeInt( (int)entry.Size ); } } else { if( patchEntryHeader == true ) { crcPatchPos = baseOutputStream_.Position; } WriteLeInt( 0 ); // Crc if( patchEntryHeader ) { sizePatchPos = baseOutputStream_.Position; } // For local header both sizes appear in Blubb64 Extended Information if( entry.LocalHeaderRequiresBlubb64 || patchEntryHeader ) { WriteLeInt( -1 ); WriteLeInt( -1 ); } else { WriteLeInt( 0 ); // Compressed size WriteLeInt( 0 ); // Uncompressed size } } byte[] name = BlubbZipConstants.ConvertToArray( entry.Flags, entry.Name ); if( name.Length > 0xFFFF ) throw new BlubbZipException( "Entry name too long." ); BlubbZipExtraData ed = new BlubbZipExtraData( entry.ExtraData ); if( entry.LocalHeaderRequiresBlubb64 ) { ed.StartNewEntry(); if( headerInfoAvailable ) { ed.AddLeLong( entry.Size ); ed.AddLeLong( entry.CompressedSize ); } else { ed.AddLeLong( -1 ); ed.AddLeLong( -1 ); } ed.AddNewEntry( 1 ); if( !ed.Find( 1 ) ) { throw new BlubbZipException( "Internal error cant find extra data" ); } if( patchEntryHeader ) { sizePatchPos = ed.CurrentReadIndex; } } else { ed.Delete( 1 ); } byte[] extra = ed.GetEntryData(); WriteLeShort( name.Length ); WriteLeShort( extra.Length ); if( name.Length > 0 ) { baseOutputStream_.Write( name, 0, name.Length ); } if( entry.LocalHeaderRequiresBlubb64 && patchEntryHeader ) { sizePatchPos += baseOutputStream_.Position; } if( extra.Length > 0 ) { baseOutputStream_.Write( extra, 0, extra.Length ); } offset += BlubbZipConstants.LocalHeaderBaseSize + name.Length + extra.Length; // Activate the entry. curEntry = entry; crc.Reset(); if( method == CompressionMethod.Deflated ) { deflater_.Reset(); deflater_.SetLevel( compressionLevel ); } size = 0; if( entry.IsCrypted == true ) { if( entry.Crc < 0 ) { // so testing Blubb will says its ok WriteEncryptionHeader( entry.DosTime << 16 ); } else { WriteEncryptionHeader( entry.Crc ); } } }
// Write the local file header // TODO: BlubbHelperStream.WriteLocalHeader is not yet used and needs checking for BlubbFile and BlubbOuptutStream usage void WriteLocalHeader(BlubbZipEntry entry, EntryPatchData patchData) { CompressionMethod method = entry.CompressionMethod; bool headerInfoAvailable = true; // How to get this? bool patchEntryHeader = false; WriteLEInt(BlubbZipConstants.LocalHeaderSignature); WriteLEShort(entry.Version); WriteLEShort(entry.Flags); WriteLEShort((byte)method); WriteLEInt((int)entry.DosTime); if (headerInfoAvailable == true) { WriteLEInt((int)entry.Crc); if (entry.LocalHeaderRequiresBlubb64) { WriteLEInt(-1); WriteLEInt(-1); } else { WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + BlubbZipConstants.CryptoHeaderSize : (int)entry.CompressedSize); WriteLEInt((int)entry.Size); } } else { if (patchData != null) { patchData.CrcPatchOffset = stream_.Position; } WriteLEInt(0); // Crc if (patchData != null) { patchData.SizePatchOffset = stream_.Position; } // For local header both sizes appear in Blubb64 Extended Information if (entry.LocalHeaderRequiresBlubb64 && patchEntryHeader) { WriteLEInt(-1); WriteLEInt(-1); } else { WriteLEInt(0); // Compressed size WriteLEInt(0); // Uncompressed size } } byte[] name = BlubbZipConstants.ConvertToArray(entry.Flags, entry.Name); if (name.Length > 0xFFFF) { throw new BlubbZipException("Entry name too long."); } BlubbZipExtraData ed = new BlubbZipExtraData(entry.ExtraData); if (entry.LocalHeaderRequiresBlubb64 && (headerInfoAvailable || patchEntryHeader)) { ed.StartNewEntry(); if (headerInfoAvailable) { ed.AddLeLong(entry.Size); ed.AddLeLong(entry.CompressedSize); } else { ed.AddLeLong(-1); ed.AddLeLong(-1); } ed.AddNewEntry(1); if (!ed.Find(1)) { throw new BlubbZipException("Internal error cant find extra data"); } if (patchData != null) { patchData.SizePatchOffset = ed.CurrentReadIndex; } } else { ed.Delete(1); } byte[] extra = ed.GetEntryData(); WriteLEShort(name.Length); WriteLEShort(extra.Length); if (name.Length > 0) { stream_.Write(name, 0, name.Length); } if (entry.LocalHeaderRequiresBlubb64 && patchEntryHeader) { patchData.SizePatchOffset += stream_.Position; } if (extra.Length > 0) { stream_.Write(extra, 0, extra.Length); } }