Added multipart Zip support (z01...). Added IEntry.IsSolid and implemented Rar and 7Zi[ support.

This commit is contained in:
Craig
2022-04-25 01:16:53 +01:00
parent f717133947
commit 224614312f
24 changed files with 249 additions and 43 deletions

1
.gitignore vendored
View File

@@ -18,3 +18,4 @@ tools
.DS_Store
*.snupkg
/tests/TestArchives/6d23a38c-f064-4ef1-ad89-b942396f53b9/Scratch

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;
}
}
}
}

View File

@@ -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()
{

View File

@@ -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; }

View File

@@ -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);
}
}

View File

@@ -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)));

View File

@@ -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()
{

View File

@@ -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");

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.