Files
sharpcompress/src/SharpCompress/Common/Zip/ZipFilePart.cs

510 lines
18 KiB
C#
Raw Normal View History

2022-06-19 22:05:52 +02:00
using System;
using System.Buffers.Binary;
2015-12-30 11:19:42 +00:00
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
2015-12-30 11:19:42 +00:00
using SharpCompress.Common.Zip.Headers;
2016-09-26 11:49:49 +01:00
using SharpCompress.Compressors;
using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.Deflate;
2018-02-26 23:49:09 +01:00
using SharpCompress.Compressors.Deflate64;
2024-07-24 08:31:44 +01:00
using SharpCompress.Compressors.Explode;
2016-09-26 11:49:49 +01:00
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.PPMd;
2024-07-24 08:31:44 +01:00
using SharpCompress.Compressors.Reduce;
using SharpCompress.Compressors.Shrink;
using SharpCompress.Compressors.Xz;
using SharpCompress.Compressors.ZStandard;
2015-12-30 11:19:42 +00:00
using SharpCompress.IO;
2022-12-20 15:06:44 +00:00
namespace SharpCompress.Common.Zip;
internal abstract class ZipFilePart : FilePart
2015-12-30 11:19:42 +00:00
{
2023-03-21 13:14:08 +00:00
internal ZipFilePart(ZipFileEntry header, Stream stream)
: base(header.ArchiveEncoding)
2015-12-30 11:19:42 +00:00
{
2022-12-20 15:06:44 +00:00
Header = header;
header.Part = this;
BaseStream = stream;
}
2015-12-30 11:19:42 +00:00
2022-12-20 15:06:44 +00:00
internal Stream BaseStream { get; }
internal ZipFileEntry Header { get; set; }
2015-12-30 11:19:42 +00:00
2024-04-18 14:25:10 +01:00
internal override string? FilePartName => Header.Name;
2015-12-30 11:19:42 +00:00
2022-12-20 15:06:44 +00:00
internal override Stream GetCompressedStream()
{
if (!Header.HasData)
2015-12-30 11:19:42 +00:00
{
2022-12-20 15:06:44 +00:00
return Stream.Null;
2015-12-30 11:19:42 +00:00
}
2022-12-20 15:06:44 +00:00
var decompressionStream = CreateDecompressionStream(
GetCryptoStream(CreateBaseStream()),
Header.CompressionMethod
);
if (LeaveStreamOpen)
{
return SharpCompressStream.Create(decompressionStream, leaveOpen: true);
2022-12-20 15:06:44 +00:00
}
return decompressionStream;
}
2015-12-30 11:19:42 +00:00
2022-12-20 15:06:44 +00:00
internal override Stream GetRawStream()
{
if (!Header.HasData)
2015-12-30 11:19:42 +00:00
{
2022-12-20 15:06:44 +00:00
return Stream.Null;
2015-12-30 11:19:42 +00:00
}
2022-12-20 15:06:44 +00:00
return CreateBaseStream();
}
2015-12-30 11:19:42 +00:00
2022-12-20 15:06:44 +00:00
protected abstract Stream CreateBaseStream();
2015-12-30 11:19:42 +00:00
2022-12-20 15:06:44 +00:00
protected bool LeaveStreamOpen =>
FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) || Header.IsZip64;
2015-12-30 11:19:42 +00:00
2022-12-20 15:06:44 +00:00
protected Stream CreateDecompressionStream(Stream stream, ZipCompressionMethod method)
{
switch (method)
2015-12-30 11:19:42 +00:00
{
2022-12-20 15:06:44 +00:00
case ZipCompressionMethod.None:
2015-12-30 11:19:42 +00:00
{
if (Header.CompressedSize is 0)
{
return new DataDescriptorStream(stream);
}
return stream;
2022-12-20 15:06:44 +00:00
}
case ZipCompressionMethod.Shrink:
{
2024-01-27 18:59:56 +01:00
return new ShrinkStream(
stream,
CompressionMode.Decompress,
Header.CompressedSize,
Header.UncompressedSize
);
}
case ZipCompressionMethod.Reduce1:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 1);
}
case ZipCompressionMethod.Reduce2:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 2);
}
case ZipCompressionMethod.Reduce3:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 3);
}
case ZipCompressionMethod.Reduce4:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 4);
}
case ZipCompressionMethod.Explode:
{
2024-07-24 08:31:44 +01:00
return new ExplodeStream(
stream,
Header.CompressedSize,
Header.UncompressedSize,
Header.Flags
);
}
2022-12-20 15:06:44 +00:00
case ZipCompressionMethod.Deflate:
{
return new DeflateStream(stream, CompressionMode.Decompress);
}
case ZipCompressionMethod.Deflate64:
{
return new Deflate64Stream(stream, CompressionMode.Decompress);
}
case ZipCompressionMethod.BZip2:
{
return new BZip2Stream(stream, CompressionMode.Decompress, false);
}
case ZipCompressionMethod.LZMA:
{
if (FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted))
2022-12-20 13:45:47 +00:00
{
2022-12-20 15:06:44 +00:00
throw new NotSupportedException("LZMA with pkware encryption.");
2022-12-20 13:45:47 +00:00
}
2022-12-20 15:06:44 +00:00
var reader = new BinaryReader(stream);
reader.ReadUInt16(); //LZMA version
var props = new byte[reader.ReadUInt16()];
reader.Read(props, 0, props.Length);
return new LzmaStream(
props,
stream,
Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1,
FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1)
? -1
: Header.UncompressedSize
);
}
case ZipCompressionMethod.Xz:
{
return new XZStream(stream);
}
case ZipCompressionMethod.ZStandard:
{
return new DecompressionStream(stream);
}
2022-12-20 15:06:44 +00:00
case ZipCompressionMethod.PPMd:
{
Span<byte> props = stackalloc byte[2];
stream.ReadFully(props);
return new PpmdStream(new PpmdProperties(props), stream, false);
}
case ZipCompressionMethod.WinzipAes:
{
2022-12-20 15:14:22 +00:00
var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes);
2022-12-20 15:06:44 +00:00
if (data is null)
2022-12-20 13:45:47 +00:00
{
2022-12-20 15:06:44 +00:00
throw new InvalidFormatException("No Winzip AES extra data found.");
2022-12-20 13:45:47 +00:00
}
2022-12-20 15:06:44 +00:00
if (data.Length != 7)
2022-12-20 13:45:47 +00:00
{
2022-12-20 15:06:44 +00:00
throw new InvalidFormatException("Winzip data length is not 7.");
2022-12-20 13:45:47 +00:00
}
2022-12-20 15:14:22 +00:00
var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes);
2022-12-20 15:06:44 +00:00
if (compressedMethod != 0x01 && compressedMethod != 0x02)
2022-12-20 13:45:47 +00:00
{
2022-12-20 15:06:44 +00:00
throw new InvalidFormatException(
"Unexpected vendor version number for WinZip AES metadata"
2022-12-20 13:45:47 +00:00
);
}
2022-12-20 15:14:22 +00:00
var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2));
2022-12-20 15:06:44 +00:00
if (vendorId != 0x4541)
2022-12-20 13:45:47 +00:00
{
2022-12-20 15:06:44 +00:00
throw new InvalidFormatException(
"Unexpected vendor ID for WinZip AES metadata"
2022-12-20 13:45:47 +00:00
);
}
2022-12-20 15:06:44 +00:00
return CreateDecompressionStream(
stream,
(ZipCompressionMethod)
BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5))
);
}
default:
{
2022-12-20 15:14:22 +00:00
throw new NotSupportedException("CompressionMethod: " + Header.CompressionMethod);
2015-12-30 11:19:42 +00:00
}
}
2022-12-20 15:06:44 +00:00
}
protected Stream GetCryptoStream(Stream plainStream)
{
var isFileEncrypted = FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted);
2015-12-30 11:19:42 +00:00
2022-12-20 15:06:44 +00:00
if (Header.CompressedSize == 0 && isFileEncrypted)
2016-11-24 17:11:43 +03:00
{
2022-12-20 15:06:44 +00:00
throw new NotSupportedException("Cannot encrypt file with unknown size at start.");
}
2022-12-20 15:06:44 +00:00
if (
(
Header.CompressedSize == 0
&& FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)
) || Header.IsZip64
)
{
plainStream = SharpCompressStream.Create(plainStream, leaveOpen: true); //make sure AES doesn't close
2022-12-20 15:06:44 +00:00
}
else
{
plainStream = new ReadOnlySubStream(plainStream, Header.CompressedSize); //make sure AES doesn't close
}
2016-11-24 17:11:43 +03:00
2022-12-20 15:06:44 +00:00
if (isFileEncrypted)
{
switch (Header.CompressionMethod)
2016-11-24 17:11:43 +03:00
{
2022-12-20 15:06:44 +00:00
case ZipCompressionMethod.None:
case ZipCompressionMethod.Shrink:
case ZipCompressionMethod.Reduce1:
case ZipCompressionMethod.Reduce2:
case ZipCompressionMethod.Reduce3:
case ZipCompressionMethod.Reduce4:
2022-12-20 15:06:44 +00:00
case ZipCompressionMethod.Deflate:
case ZipCompressionMethod.Deflate64:
case ZipCompressionMethod.BZip2:
case ZipCompressionMethod.LZMA:
case ZipCompressionMethod.PPMd:
{
return new PkwareTraditionalCryptoStream(
plainStream,
Header.ComposeEncryptionData(plainStream),
CryptoMode.Decrypt
);
}
2016-11-24 17:11:43 +03:00
2022-12-20 15:06:44 +00:00
case ZipCompressionMethod.WinzipAes:
2016-11-24 17:11:43 +03:00
{
2022-12-20 15:06:44 +00:00
if (Header.WinzipAesEncryptionData != null)
2022-12-20 13:45:47 +00:00
{
2022-12-20 15:06:44 +00:00
return new WinzipAesCryptoStream(
2022-12-20 13:45:47 +00:00
plainStream,
2022-12-20 15:06:44 +00:00
Header.WinzipAesEncryptionData,
Header.CompressedSize - 10
2022-12-20 13:45:47 +00:00
);
}
2022-12-20 15:06:44 +00:00
return plainStream;
}
2016-11-24 17:11:43 +03:00
2022-12-20 15:06:44 +00:00
default:
{
throw new InvalidOperationException("Header.CompressionMethod is invalid");
2016-11-24 17:11:43 +03:00
}
}
}
2022-12-20 15:06:44 +00:00
return plainStream;
2016-11-24 17:11:43 +03:00
}
internal override async Task<Stream?> GetCompressedStreamAsync(
CancellationToken cancellationToken = default
)
{
if (!Header.HasData)
{
return Stream.Null;
}
var decompressionStream = await CreateDecompressionStreamAsync(
await GetCryptoStreamAsync(CreateBaseStream(), cancellationToken)
.ConfigureAwait(false),
Header.CompressionMethod,
cancellationToken
)
.ConfigureAwait(false);
if (LeaveStreamOpen)
{
return SharpCompressStream.Create(decompressionStream, leaveOpen: true);
}
return decompressionStream;
}
protected async Task<Stream> GetCryptoStreamAsync(
Stream plainStream,
CancellationToken cancellationToken = default
)
{
var isFileEncrypted = FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted);
if (Header.CompressedSize == 0 && isFileEncrypted)
{
throw new NotSupportedException("Cannot encrypt file with unknown size at start.");
}
if (
(
Header.CompressedSize == 0
&& FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)
) || Header.IsZip64
)
{
plainStream = SharpCompressStream.Create(plainStream, leaveOpen: true); //make sure AES doesn't close
}
else
{
plainStream = new ReadOnlySubStream(plainStream, Header.CompressedSize); //make sure AES doesn't close
}
if (isFileEncrypted)
{
switch (Header.CompressionMethod)
{
case ZipCompressionMethod.None:
case ZipCompressionMethod.Shrink:
case ZipCompressionMethod.Reduce1:
case ZipCompressionMethod.Reduce2:
case ZipCompressionMethod.Reduce3:
case ZipCompressionMethod.Reduce4:
case ZipCompressionMethod.Deflate:
case ZipCompressionMethod.Deflate64:
case ZipCompressionMethod.BZip2:
case ZipCompressionMethod.LZMA:
case ZipCompressionMethod.PPMd:
{
return new PkwareTraditionalCryptoStream(
plainStream,
await Header
.ComposeEncryptionDataAsync(plainStream, cancellationToken)
.ConfigureAwait(false),
CryptoMode.Decrypt
);
}
case ZipCompressionMethod.WinzipAes:
{
if (Header.WinzipAesEncryptionData != null)
{
return new WinzipAesCryptoStream(
plainStream,
Header.WinzipAesEncryptionData,
Header.CompressedSize - 10
);
}
return plainStream;
}
default:
{
throw new InvalidOperationException("Header.CompressionMethod is invalid");
}
}
}
return plainStream;
}
protected async Task<Stream> CreateDecompressionStreamAsync(
Stream stream,
ZipCompressionMethod method,
CancellationToken cancellationToken = default
)
{
switch (method)
{
case ZipCompressionMethod.None:
{
if (Header.CompressedSize is 0)
{
return new DataDescriptorStream(stream);
}
return stream;
}
case ZipCompressionMethod.Shrink:
{
return new ShrinkStream(
stream,
CompressionMode.Decompress,
Header.CompressedSize,
Header.UncompressedSize
);
}
case ZipCompressionMethod.Reduce1:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 1);
}
case ZipCompressionMethod.Reduce2:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 2);
}
case ZipCompressionMethod.Reduce3:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 3);
}
case ZipCompressionMethod.Reduce4:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 4);
}
case ZipCompressionMethod.Explode:
{
return new ExplodeStream(
stream,
Header.CompressedSize,
Header.UncompressedSize,
Header.Flags
);
}
case ZipCompressionMethod.Deflate:
{
return new DeflateStream(stream, CompressionMode.Decompress);
}
case ZipCompressionMethod.Deflate64:
{
return new Deflate64Stream(stream, CompressionMode.Decompress);
}
case ZipCompressionMethod.BZip2:
{
return new BZip2Stream(stream, CompressionMode.Decompress, false);
}
case ZipCompressionMethod.LZMA:
{
if (FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted))
{
throw new NotSupportedException("LZMA with pkware encryption.");
}
var buffer = new byte[4];
await stream.ReadFullyAsync(buffer, 0, 4, cancellationToken).ConfigureAwait(false);
var version = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(0, 2));
var propsSize = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(2, 2));
var props = new byte[propsSize];
await stream
.ReadFullyAsync(props, 0, propsSize, cancellationToken)
.ConfigureAwait(false);
return new LzmaStream(
props,
stream,
Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1,
FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1)
? -1
: Header.UncompressedSize
);
}
case ZipCompressionMethod.Xz:
{
return new XZStream(stream);
}
case ZipCompressionMethod.ZStandard:
{
return new DecompressionStream(stream);
}
case ZipCompressionMethod.PPMd:
{
var props = new byte[2];
await stream.ReadFullyAsync(props, 0, 2, cancellationToken).ConfigureAwait(false);
return new PpmdStream(new PpmdProperties(props), stream, false);
}
case ZipCompressionMethod.WinzipAes:
{
var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes);
if (data is null)
{
throw new InvalidFormatException("No Winzip AES extra data found.");
}
2026-01-07 14:54:32 +00:00
if (data.Length != 7)
{
throw new InvalidFormatException("Winzip data length is not 7.");
}
2026-01-07 14:54:32 +00:00
var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes);
if (compressedMethod != 0x01 && compressedMethod != 0x02)
{
throw new InvalidFormatException(
"Unexpected vendor version number for WinZip AES metadata"
);
}
var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2));
if (vendorId != 0x4541)
{
throw new InvalidFormatException(
"Unexpected vendor ID for WinZip AES metadata"
);
}
return await CreateDecompressionStreamAsync(
stream,
(ZipCompressionMethod)
BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5)),
cancellationToken
);
}
default:
{
throw new NotSupportedException("CompressionMethod: " + Header.CompressionMethod);
}
}
}
}