mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 15:04:43 +00:00
Added multipart Zip support (z01...). Added IEntry.IsSolid and implemented Rar and 7Zi[ support.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,3 +18,4 @@ tools
|
||||
|
||||
.DS_Store
|
||||
*.snupkg
|
||||
/tests/TestArchives/6d23a38c-f064-4ef1-ad89-b942396f53b9/Scratch
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -50,6 +50,12 @@ namespace SharpCompress.Archives
|
||||
{
|
||||
}
|
||||
|
||||
internal AbstractWritableArchive(ArchiveType type, IEnumerable<Stream> streams, ReaderOptions readerFactoryOptions)
|
||||
: base(type, streams, readerFactoryOptions)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public override ICollection<TEntry> Entries
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -137,6 +137,16 @@ namespace SharpCompress.Archives
|
||||
switch (type.Value)
|
||||
{
|
||||
case ArchiveType.Zip:
|
||||
using (Stream prt2z = files[1].OpenRead())
|
||||
{
|
||||
try
|
||||
{
|
||||
prt2z.Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
|
||||
if (ZipArchive.IsZipFile(prt2z)) //if part2 is a zip then it's multi not split zip, z01,z02... zip is moved to the end when opened
|
||||
return ZipArchive.Open(fileInfos.Select(a => a.OpenRead()), options);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return ZipArchive.Open(new SplitStream(files), options);
|
||||
case ArchiveType.SevenZip:
|
||||
return SevenZipArchive.Open(new SplitStream(files), options);
|
||||
@@ -216,6 +226,12 @@ namespace SharpCompress.Archives
|
||||
type = ArchiveType.Tar;
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
if (type == null) //test multipartzip as it could find zips in other non compressed archive types?
|
||||
{
|
||||
if (ZipArchive.IsZipMulti(stream)) //test the zip (last) file of a multipart zip
|
||||
type = ArchiveType.Zip;
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return type != null;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace SharpCompress.Archives.Rar
|
||||
this.parts = parts.ToList();
|
||||
this.archive = archive;
|
||||
this.readerOptions = readerOptions;
|
||||
this.IsSolid = this.FileHeader.IsSolid;
|
||||
}
|
||||
|
||||
public override CompressionType CompressionType => CompressionType.Rar;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable disable
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -101,11 +101,23 @@ namespace SharpCompress.Archives.SevenZip
|
||||
{
|
||||
var stream = volumes.Single().Stream;
|
||||
LoadFactory(stream);
|
||||
var entries = new SevenZipArchiveEntry[database._files.Count];
|
||||
for (int i = 0; i < database._files.Count; i++)
|
||||
{
|
||||
var file = database._files[i];
|
||||
yield return new SevenZipArchiveEntry(this, new SevenZipFilePart(stream, database, i, file, ReaderOptions.ArchiveEncoding));
|
||||
entries[i] = new SevenZipArchiveEntry(this, new SevenZipFilePart(stream, database, i, file, ReaderOptions.ArchiveEncoding));
|
||||
}
|
||||
foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder))
|
||||
{
|
||||
var isSolid = false;
|
||||
foreach (var entry in group)
|
||||
{
|
||||
entry.IsSolid = isSolid;
|
||||
isSolid = true; //mark others in this group as solid - same as rar behaviour.
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private void LoadFactory(Stream stream)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -6,6 +6,7 @@ using SharpCompress.Common;
|
||||
using SharpCompress.Common.Zip;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Zip;
|
||||
using SharpCompress.Writers;
|
||||
@@ -47,6 +48,29 @@ namespace SharpCompress.Archives.Zip
|
||||
return new ZipArchive(fileInfo, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes multiple seekable Streams for a multi-part archive
|
||||
/// </summary>
|
||||
/// <param name="streams"></param>
|
||||
/// <param name="options"></param>
|
||||
public static ZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.CheckNotNull(nameof(streams));
|
||||
return new ZipArchive(streams, options ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes multiple seekable Streams for a multi-part archive
|
||||
/// </summary>
|
||||
/// <param name="streams"></param>
|
||||
/// <param name="options"></param>
|
||||
internal ZipArchive(IEnumerable<Stream> streams, ReaderOptions options)
|
||||
: base(ArchiveType.Zip, streams, options)
|
||||
{
|
||||
headerFactory = new SeekableZipHeaderFactory(options.Password, options.ArchiveEncoding);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Takes a seekable Stream as a source
|
||||
/// </summary>
|
||||
@@ -97,6 +121,35 @@ namespace SharpCompress.Archives.Zip
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsZipMulti(Stream stream, string? password = null)
|
||||
{
|
||||
StreamingZipHeaderFactory headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding());
|
||||
try
|
||||
{
|
||||
ZipHeader? header = headerFactory.ReadStreamHeader(stream).FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
|
||||
if (header is null)
|
||||
{
|
||||
if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe
|
||||
{
|
||||
SeekableZipHeaderFactory z = new SeekableZipHeaderFactory(password, new ArchiveEncoding());
|
||||
var x = z.ReadSeekableHeader(stream).FirstOrDefault();
|
||||
return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType);
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with a FileInfo object to an existing file.
|
||||
/// </summary>
|
||||
@@ -131,33 +184,41 @@ namespace SharpCompress.Archives.Zip
|
||||
|
||||
protected override IEnumerable<ZipVolume> LoadVolumes(IEnumerable<Stream> streams)
|
||||
{
|
||||
return new ZipVolume(streams.First(), ReaderOptions).AsEnumerable();
|
||||
var st = streams.ToList(); //swap the zip to the end
|
||||
var tmp = st[0];
|
||||
st.RemoveAt(0);
|
||||
st.Add(tmp);
|
||||
|
||||
foreach (var s in st)
|
||||
yield return new ZipVolume(s, base.ReaderOptions);
|
||||
}
|
||||
|
||||
protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
|
||||
{
|
||||
var volume = volumes.Single();
|
||||
Stream stream = volume.Stream;
|
||||
foreach (ZipHeader h in headerFactory.ReadSeekableHeader(stream))
|
||||
var vols = volumes.ToArray();
|
||||
foreach (ZipHeader h in headerFactory.ReadSeekableHeader(vols.Last().Stream))
|
||||
{
|
||||
if (h != null)
|
||||
{
|
||||
switch (h.ZipHeaderType)
|
||||
{
|
||||
case ZipHeaderType.DirectoryEntry:
|
||||
{
|
||||
yield return new ZipArchiveEntry(this,
|
||||
new SeekableZipFilePart(headerFactory,
|
||||
(DirectoryEntryHeader)h,
|
||||
stream));
|
||||
}
|
||||
break;
|
||||
{
|
||||
DirectoryEntryHeader deh = (DirectoryEntryHeader)h;
|
||||
Stream s;
|
||||
if (deh.RelativeOffsetOfEntryHeader + deh.CompressedSize > vols[deh.DiskNumberStart].Stream.Length)
|
||||
s = new SplitStream(vols.Skip(deh.DiskNumberStart).Select(a => a.Stream));
|
||||
else
|
||||
s = vols[deh.DiskNumberStart].Stream;
|
||||
yield return new ZipArchiveEntry(this, new SeekableZipFilePart(headerFactory, deh, s));
|
||||
}
|
||||
break;
|
||||
case ZipHeaderType.DirectoryEnd:
|
||||
{
|
||||
byte[] bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty<byte>();
|
||||
volume.Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
|
||||
yield break;
|
||||
}
|
||||
{
|
||||
byte[] bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty<byte>();
|
||||
volumes.Last().Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace SharpCompress.Common
|
||||
|
||||
internal abstract IEnumerable<FilePart> Parts { get; }
|
||||
|
||||
internal bool IsSolid { get; set; }
|
||||
public bool IsSolid { get; set; }
|
||||
|
||||
internal virtual void Close()
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace SharpCompress.Common
|
||||
bool IsDirectory { get; }
|
||||
bool IsEncrypted { get; }
|
||||
bool IsSplitAfter { get; }
|
||||
bool IsSolid { get; }
|
||||
DateTime? LastAccessedTime { get; }
|
||||
DateTime? LastModifiedTime { get; }
|
||||
long Size { get; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -14,31 +14,67 @@ namespace SharpCompress.IO
|
||||
private bool _isSplit;
|
||||
private long _prevSize;
|
||||
private long _pos;
|
||||
private FileInfo[] _files;
|
||||
private long _streamCount;
|
||||
private FileInfo[]? _files;
|
||||
private Stream[]? _streams;
|
||||
private Stream _stream;
|
||||
|
||||
|
||||
public SplitStream(IEnumerable<FileInfo> files)
|
||||
public SplitStream(IEnumerable<FileInfo> files) : this(null, files)
|
||||
{
|
||||
_files = files.ToArray();
|
||||
_isSplit = _files.Length > 1;
|
||||
_size = _files.Sum(a => a.Length);
|
||||
_idx = 0;
|
||||
_prevSize = 0;
|
||||
_stream = openStream(0);
|
||||
}
|
||||
|
||||
private Stream openStream(int idx)
|
||||
public SplitStream(IEnumerable<Stream> streams) : this(streams, null)
|
||||
{
|
||||
if (_stream != null)
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
private SplitStream(IEnumerable<Stream>? streams, IEnumerable<FileInfo>? files)
|
||||
{
|
||||
if (streams != null)
|
||||
{
|
||||
_streams = streams.ToArray();
|
||||
_isSplit = _streams.Length > 1;
|
||||
_size = _streams.Sum(a => a.Length);
|
||||
_streamCount = _streams.Length;
|
||||
}
|
||||
else if (files != null)
|
||||
{
|
||||
_files = files.ToArray();
|
||||
_isSplit = _files.Length > 1;
|
||||
_size = _files.Sum(a => a.Length);
|
||||
_streamCount = _files.Length;
|
||||
}
|
||||
_idx = 0;
|
||||
_prevSize = 0;
|
||||
_stream = OpenStream(0);
|
||||
}
|
||||
|
||||
private Stream OpenStream(int idx)
|
||||
{
|
||||
if (_streams != null)
|
||||
_stream = _streams[idx];
|
||||
else if (_files != null)
|
||||
{
|
||||
if (_stream != null)
|
||||
_stream.Dispose();
|
||||
|
||||
_stream = File.OpenRead(_files[idx].FullName);
|
||||
}
|
||||
|
||||
_stream = File.OpenRead(_files[idx].FullName);
|
||||
_idx = idx;
|
||||
_pos = 0;
|
||||
return _stream;
|
||||
}
|
||||
|
||||
private long StreamLen(int idx)
|
||||
{
|
||||
if (_streams != null)
|
||||
return _streams[idx].Length;
|
||||
else if (_files != null)
|
||||
return _files[idx].Length;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => true;
|
||||
@@ -76,7 +112,7 @@ namespace SharpCompress.IO
|
||||
count -= r;
|
||||
offset += r;
|
||||
|
||||
if (_isSplit && _pos == _files[_idx].Length)
|
||||
if (_isSplit && _pos == StreamLen(_idx))
|
||||
Seek(0, SeekOrigin.Current); //will load next file
|
||||
}
|
||||
|
||||
@@ -96,15 +132,15 @@ namespace SharpCompress.IO
|
||||
if (_isSplit)
|
||||
{
|
||||
_prevSize = 0;
|
||||
for (int i = 0; i < _files.Length; i++)
|
||||
for (int i = 0; i < _streamCount; i++)
|
||||
{
|
||||
if (_prevSize + _files[i].Length > pos)
|
||||
if (_prevSize + StreamLen(i) > pos)
|
||||
{
|
||||
if (_idx != i)
|
||||
_stream = openStream(i);
|
||||
_stream = OpenStream(i);
|
||||
break;
|
||||
}
|
||||
_prevSize += _files[i].Length;
|
||||
_prevSize += StreamLen(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -127,6 +127,35 @@ namespace SharpCompress.Test
|
||||
}
|
||||
}
|
||||
|
||||
protected void ArchiveStreamMultiRead(ReaderOptions readerOptions = null, params string[] testArchives)
|
||||
{
|
||||
ArchiveStreamMultiRead(readerOptions, testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)));
|
||||
}
|
||||
|
||||
protected void ArchiveStreamMultiRead(ReaderOptions readerOptions, IEnumerable<string> testArchives)
|
||||
{
|
||||
using (var archive = ArchiveFactory.Open(testArchives.Select(a => new FileInfo(a)), readerOptions))
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||
{
|
||||
entry.WriteToDirectory(SCRATCH_FILES_PATH,
|
||||
new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
VerifyFiles();
|
||||
}
|
||||
|
||||
protected void ArchiveOpenStreamRead(ReaderOptions readerOptions = null, params string[] testArchives)
|
||||
{
|
||||
ArchiveOpenStreamRead(readerOptions, testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using Xunit;
|
||||
@@ -7,6 +7,16 @@ namespace SharpCompress.Test.SevenZip
|
||||
{
|
||||
public class SevenZipArchiveTests : ArchiveTests
|
||||
{
|
||||
[Fact]
|
||||
public void SevenZipArchive_Solid_StreamRead()
|
||||
{
|
||||
ArchiveStreamRead("7Zip.solid.7z");
|
||||
}
|
||||
[Fact]
|
||||
public void SevenZipArchive_NonSolid_StreamRead()
|
||||
{
|
||||
ArchiveStreamRead("7Zip.nonsolid.7z");
|
||||
}
|
||||
[Fact]
|
||||
public void SevenZipArchive_LZMA_StreamRead()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -93,6 +93,33 @@ namespace SharpCompress.Test.Zip
|
||||
ArchiveFileRead("Zip.bzip2.zip");
|
||||
}
|
||||
[Fact]
|
||||
public void WinZip26_ArchiveFileRead()
|
||||
{
|
||||
ArchiveFileRead("WinZip26.zip");
|
||||
}
|
||||
[Fact]
|
||||
public void WinZip26_Multi_ArchiveFileRead()
|
||||
{
|
||||
ArchiveStreamMultiRead(null, "WinZip26.nocomp.multi.zip",
|
||||
"WinZip26.nocomp.multi.z01"); //min split size is 64k so no compression used
|
||||
}
|
||||
[Fact]
|
||||
public void WinZip26_X_BZip2_ArchiveFileRead()
|
||||
{
|
||||
ArchiveFileRead("WinZip26_BZip2.zipx");
|
||||
}
|
||||
[Fact]
|
||||
public void WinZip26_X_Lzma_ArchiveFileRead()
|
||||
{
|
||||
ArchiveFileRead("WinZip26_LZMA.zipx");
|
||||
}
|
||||
[Fact]
|
||||
public void WinZip26_X_Multi_ArchiveFileRead()
|
||||
{
|
||||
ArchiveStreamMultiRead(null, "WinZip26.nocomp.multi.zipx",
|
||||
"WinZip26.nocomp.multi.zx01"); //min split size is 64k so no compression used
|
||||
}
|
||||
[Fact]
|
||||
public void Zip_Deflate_Streamed2_ArchiveFileRead()
|
||||
{
|
||||
ArchiveFileRead("Zip.deflate.dd-.zip");
|
||||
@@ -118,6 +145,12 @@ namespace SharpCompress.Test.Zip
|
||||
"Zip.deflate.split.006");
|
||||
}
|
||||
[Fact]
|
||||
public void Zip_InfoZip_Multi_ArchiveFileRead()
|
||||
{
|
||||
ArchiveStreamMultiRead(null, "Infozip.nocomp.multi.zip",
|
||||
"Infozip.nocomp.multi.z01"); //min split size is 64k so no compression used
|
||||
}
|
||||
[Fact]
|
||||
public void Zip_Deflate64_ArchiveFileRead()
|
||||
{
|
||||
ArchiveFileRead("Zip.deflate64.zip");
|
||||
|
||||
BIN
tests/TestArchives/Archives/7Zip.nonsolid.7z
Normal file
BIN
tests/TestArchives/Archives/7Zip.nonsolid.7z
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/7Zip.solid.7z
Normal file
BIN
tests/TestArchives/Archives/7Zip.solid.7z
Normal file
Binary file not shown.
0
tests/TestArchives/Archives/DotNetZip-obhg3mhx.tmp
Normal file
0
tests/TestArchives/Archives/DotNetZip-obhg3mhx.tmp
Normal file
BIN
tests/TestArchives/Archives/Infozip.nocomp.multi.z01
Normal file
BIN
tests/TestArchives/Archives/Infozip.nocomp.multi.z01
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/Infozip.nocomp.multi.zip
Normal file
BIN
tests/TestArchives/Archives/Infozip.nocomp.multi.zip
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/WinZip26.nocomp.multi.z01
Normal file
BIN
tests/TestArchives/Archives/WinZip26.nocomp.multi.z01
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/WinZip26.nocomp.multi.zip
Normal file
BIN
tests/TestArchives/Archives/WinZip26.nocomp.multi.zip
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/WinZip26.nocomp.multi.zipx
Normal file
BIN
tests/TestArchives/Archives/WinZip26.nocomp.multi.zipx
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/WinZip26.nocomp.multi.zx01
Normal file
BIN
tests/TestArchives/Archives/WinZip26.nocomp.multi.zx01
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/WinZip26.zip
Normal file
BIN
tests/TestArchives/Archives/WinZip26.zip
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/WinZip26_BZip2.zipx
Normal file
BIN
tests/TestArchives/Archives/WinZip26_BZip2.zipx
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/WinZip26_LZMA.zipx
Normal file
BIN
tests/TestArchives/Archives/WinZip26_LZMA.zipx
Normal file
Binary file not shown.
Reference in New Issue
Block a user