move files around

This commit is contained in:
Adam Hathcock
2015-12-30 11:19:42 +00:00
parent a4c2d27985
commit 49707498a5
306 changed files with 49104 additions and 49455 deletions

View File

@@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Reader;
namespace SharpCompress.Archive
{
public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtractionListener
where TEntry : IArchiveEntry
where TVolume : IVolume
{
private readonly LazyReadOnlyCollection<TVolume> lazyVolumes;
private readonly LazyReadOnlyCollection<TEntry> lazyEntries;
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionBegin;
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionEnd;
public event EventHandler<CompressedBytesReadEventArgs> CompressedBytesRead;
public event EventHandler<FilePartExtractionBeginEventArgs> FilePartExtractionBegin;
protected string Password { get; private set; }
#if !DOTNET51
internal AbstractArchive(ArchiveType type, FileInfo fileInfo, Options options, string password)
{
Type = type;
Password = password;
if (!fileInfo.Exists)
{
throw new ArgumentException("File does not exist: " + fileInfo.FullName);
}
options = (Options) FlagUtility.SetFlag(options, Options.KeepStreamsOpen, false);
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(fileInfo, options));
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
}
protected abstract IEnumerable<TVolume> LoadVolumes(FileInfo file, Options options);
#endif
internal AbstractArchive(ArchiveType type, IEnumerable<Stream> streams, Options options, string password)
{
Type = type;
Password = password;
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(streams.Select(CheckStreams), options));
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
}
internal AbstractArchive(ArchiveType type)
{
Type = type;
lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
}
public ArchiveType Type { get; private set; }
void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry)
{
if (EntryExtractionBegin != null)
{
EntryExtractionBegin(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
}
}
void IArchiveExtractionListener.FireEntryExtractionEnd(IArchiveEntry entry)
{
if (EntryExtractionEnd != null)
{
EntryExtractionEnd(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
}
}
private static Stream CheckStreams(Stream stream)
{
if (!stream.CanSeek || !stream.CanRead)
{
throw new ArgumentException("Archive streams must be Readable and Seekable");
}
return stream;
}
/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
/// </summary>
public virtual ICollection<TEntry> Entries
{
get { return lazyEntries; }
}
/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive.
/// </summary>
public ICollection<TVolume> Volumes
{
get { return lazyVolumes; }
}
/// <summary>
/// The total size of the files compressed in the archive.
/// </summary>
public virtual long TotalSize
{
get { return Entries.Aggregate(0L, (total, cf) => total + cf.CompressedSize); }
}
/// <summary>
/// The total size of the files as uncompressed in the archive.
/// </summary>
public virtual long TotalUncompressSize
{
get { return Entries.Aggregate(0L, (total, cf) => total + cf.Size); }
}
protected abstract IEnumerable<TVolume> LoadVolumes(IEnumerable<Stream> streams, Options options);
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);
IEnumerable<IArchiveEntry> IArchive.Entries
{
get { return Entries.Cast<IArchiveEntry>(); }
}
IEnumerable<IVolume> IArchive.Volumes
{
get { return lazyVolumes.Cast<IVolume>(); }
}
private bool disposed;
public virtual void Dispose()
{
if (!disposed)
{
lazyVolumes.ForEach(v => v.Dispose());
lazyEntries.GetLoaded().Cast<Entry>().ForEach(x => x.Close());
disposed = true;
}
}
void IArchiveExtractionListener.EnsureEntriesLoaded()
{
lazyEntries.EnsureFullyLoaded();
lazyVolumes.EnsureFullyLoaded();
}
void IExtractionListener.FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes)
{
if (CompressedBytesRead != null)
{
CompressedBytesRead(this, new CompressedBytesReadEventArgs()
{
CurrentFilePartCompressedBytesRead = currentPartCompressedBytes,
CompressedBytesRead = compressedReadBytes
});
}
}
void IExtractionListener.FireFilePartExtractionBegin(string name, long size, long compressedSize)
{
if (FilePartExtractionBegin != null)
{
FilePartExtractionBegin(this, new FilePartExtractionBeginEventArgs()
{
CompressedSize = compressedSize,
Size = size,
Name = name,
});
}
}
/// <summary>
/// Use this method to extract all entries in an archive in order.
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
/// extracted sequentially for the best performance.
///
/// This method will load all entry information from the archive.
///
/// WARNING: this will reuse the underlying stream for the archive. Errors may
/// occur if this is used at the same time as other extraction methods on this instance.
/// </summary>
/// <returns></returns>
public IReader ExtractAllEntries()
{
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
return CreateReaderForSolidExtraction();
}
protected abstract IReader CreateReaderForSolidExtraction();
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// </summary>
public virtual bool IsSolid
{
get { return false; }
}
/// <summary>
/// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive.
/// </summary>
public bool IsComplete
{
get
{
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
return Entries.All(x => x.IsComplete);
}
}
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
namespace SharpCompress.Archive
{
public abstract class AbstractWritableArchive<TEntry, TVolume> : AbstractArchive<TEntry, TVolume>, IWritableArchive
where TEntry : IArchiveEntry
where TVolume : IVolume
{
private readonly List<TEntry> newEntries = new List<TEntry>();
private readonly List<TEntry> removedEntries = new List<TEntry>();
private readonly List<TEntry> modifiedEntries = new List<TEntry>();
private bool hasModifications;
internal AbstractWritableArchive(ArchiveType type)
: base(type)
{
}
internal AbstractWritableArchive(ArchiveType type, Stream stream, Options options)
: base(type, stream.AsEnumerable(), options, null)
{
}
#if !DOTNET51
internal AbstractWritableArchive(ArchiveType type, FileInfo fileInfo, Options options)
: base(type, fileInfo, options, null)
{
}
#endif
public override ICollection<TEntry> Entries
{
get
{
if (hasModifications)
{
return modifiedEntries;
}
return base.Entries;
}
}
private void RebuildModifiedCollection()
{
hasModifications = true;
newEntries.RemoveAll(v => removedEntries.Contains(v));
modifiedEntries.Clear();
modifiedEntries.AddRange(OldEntries.Concat(newEntries));
}
private IEnumerable<TEntry> OldEntries
{
get { return base.Entries.Where(x => !removedEntries.Contains(x)); }
}
public void RemoveEntry(TEntry entry)
{
if (!removedEntries.Contains(entry))
{
removedEntries.Add(entry);
RebuildModifiedCollection();
}
}
void IWritableArchive.RemoveEntry(IArchiveEntry entry)
{
RemoveEntry((TEntry)entry);
}
public TEntry AddEntry(string key, Stream source,
long size = 0, DateTime? modified = null)
{
return AddEntry(key, source, false, size, modified);
}
IArchiveEntry IWritableArchive.AddEntry(string key, Stream source, bool closeStream, long size, DateTime? modified)
{
return AddEntry(key, source, closeStream, size, modified);
}
public TEntry AddEntry(string key, Stream source, bool closeStream,
long size = 0, DateTime? modified = null)
{
if (key.StartsWith("/")
|| key.StartsWith("\\"))
{
key = key.Substring(1);
}
if (DoesKeyMatchExisting(key))
{
throw new ArchiveException("Cannot add entry with duplicate key: " + key);
}
var entry = CreateEntry(key, source, size, modified, closeStream);
newEntries.Add(entry);
RebuildModifiedCollection();
return entry;
}
private bool DoesKeyMatchExisting(string key)
{
foreach (var path in Entries.Select(x => x.Key))
{
var p = path.Replace('/','\\');
if (p.StartsWith("\\"))
{
p = p.Substring(1);
}
return string.Equals(p, key, StringComparison.OrdinalIgnoreCase);
}
return false;
}
public void SaveTo(Stream stream, CompressionInfo compressionType)
{
//reset streams of new entries
newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
SaveTo(stream, compressionType, OldEntries, newEntries);
}
protected TEntry CreateEntry(string key, Stream source, long size, DateTime? modified,
bool closeStream)
{
if (!source.CanRead || !source.CanSeek)
{
throw new ArgumentException("Streams must be readable and seekable to use the Writing Archive API");
}
return CreateEntryInternal(key, source, size, modified, closeStream);
}
protected abstract TEntry CreateEntryInternal(string key, Stream source, long size, DateTime? modified,
bool closeStream);
protected abstract void SaveTo(Stream stream, CompressionInfo compressionType,
IEnumerable<TEntry> oldEntries, IEnumerable<TEntry> newEntries);
public override void Dispose()
{
base.Dispose();
newEntries.Cast<Entry>().ForEach(x => x.Close());
removedEntries.Cast<Entry>().ForEach(x => x.Close());
modifiedEntries.Cast<Entry>().ForEach(x => x.Close());
}
}
}

View File

@@ -0,0 +1,172 @@
using System;
using System.IO;
using SharpCompress.Archive.GZip;
using SharpCompress.Archive.Rar;
using SharpCompress.Archive.SevenZip;
using SharpCompress.Archive.Tar;
using SharpCompress.Archive.Zip;
using SharpCompress.Common;
namespace SharpCompress.Archive
{
public class ArchiveFactory
{
/// <summary>
/// Opens an Archive for random access
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <returns></returns>
public static IArchive Open(Stream stream, Options options = Options.KeepStreamsOpen)
{
stream.CheckNotNull("stream");
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
if (ZipArchive.IsZipFile(stream, null))
{
stream.Seek(0, SeekOrigin.Begin);
return ZipArchive.Open(stream, options, null);
}
stream.Seek(0, SeekOrigin.Begin);
if (TarArchive.IsTarFile(stream))
{
stream.Seek(0, SeekOrigin.Begin);
return TarArchive.Open(stream, options);
}
stream.Seek(0, SeekOrigin.Begin);
if (SevenZipArchive.IsSevenZipFile(stream))
{
stream.Seek(0, SeekOrigin.Begin);
return SevenZipArchive.Open(stream, options);
}
stream.Seek(0, SeekOrigin.Begin);
if (GZipArchive.IsGZipFile(stream))
{
stream.Seek(0, SeekOrigin.Begin);
return GZipArchive.Open(stream, options);
}
stream.Seek(0, SeekOrigin.Begin);
if(RarArchive.IsRarFile(stream, Options.LookForHeader | Options.KeepStreamsOpen))
{
stream.Seek(0, SeekOrigin.Begin);
return RarArchive.Open(stream, options);
}
throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip");
}
public static IWritableArchive Create(ArchiveType type)
{
switch (type)
{
case ArchiveType.Zip:
{
return ZipArchive.Create();
}
case ArchiveType.Tar:
{
return TarArchive.Create();
}
case ArchiveType.GZip:
{
return GZipArchive.Create();
}
default:
{
throw new NotSupportedException("Cannot create Archives of type: " + type);
}
}
}
#if !DOTNET51
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
public static IArchive Open(string filePath)
{
return Open(filePath, Options.None);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
public static IArchive Open(FileInfo fileInfo)
{
return Open(fileInfo, Options.None);
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static IArchive Open(string filePath, Options options)
{
filePath.CheckNotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), options);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static IArchive Open(FileInfo fileInfo, Options options)
{
fileInfo.CheckNotNull("fileInfo");
using (var stream = fileInfo.OpenRead())
{
if (ZipArchive.IsZipFile(stream, null))
{
stream.Dispose();
return ZipArchive.Open(fileInfo, options, null);
}
stream.Seek(0, SeekOrigin.Begin);
if (TarArchive.IsTarFile(stream))
{
stream.Dispose();
return TarArchive.Open(fileInfo, options);
}
stream.Seek(0, SeekOrigin.Begin);
if (SevenZipArchive.IsSevenZipFile(stream))
{
stream.Dispose();
return SevenZipArchive.Open(fileInfo, options);
}
stream.Seek(0, SeekOrigin.Begin);
if (GZipArchive.IsGZipFile(stream))
{
stream.Dispose();
return GZipArchive.Open(fileInfo, options);
}
stream.Seek(0, SeekOrigin.Begin);
if(RarArchive.IsRarFile(stream, Options.LookForHeader | Options.KeepStreamsOpen))
{
stream.Dispose();
return RarArchive.Open(fileInfo, options);
}
throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip");
}
}
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(string sourceArchive, string destinationDirectory,
ExtractOptions options = ExtractOptions.Overwrite)
{
using (IArchive archive = Open(sourceArchive))
{
foreach (IArchiveEntry entry in archive.Entries)
{
entry.WriteToDirectory(destinationDirectory, options);
}
}
}
#endif
}
}

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.GZip;
using SharpCompress.Reader;
using SharpCompress.Reader.GZip;
using SharpCompress.Writer.GZip;
namespace SharpCompress.Archive.GZip
{
public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
{
#if !DOTNET51
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
public static GZipArchive Open(string filePath)
{
return Open(filePath, Options.None);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
public static GZipArchive Open(FileInfo fileInfo)
{
return Open(fileInfo, Options.None);
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static GZipArchive Open(string filePath, Options options)
{
filePath.CheckNotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), options);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static GZipArchive Open(FileInfo fileInfo, Options options)
{
fileInfo.CheckNotNull("fileInfo");
return new GZipArchive(fileInfo, options);
}
#endif
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
public static GZipArchive Open(Stream stream)
{
stream.CheckNotNull("stream");
return Open(stream, Options.None);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
public static GZipArchive Open(Stream stream, Options options)
{
stream.CheckNotNull("stream");
return new GZipArchive(stream, options);
}
public static GZipArchive Create()
{
return new GZipArchive();
}
#if !DOTNET51
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
internal GZipArchive(FileInfo fileInfo, Options options)
: base(ArchiveType.GZip, fileInfo, options)
{
}
protected override IEnumerable<GZipVolume> LoadVolumes(FileInfo file, Options options)
{
return new GZipVolume(file, options).AsEnumerable();
}
public static bool IsGZipFile(string filePath)
{
return IsGZipFile(new FileInfo(filePath));
}
public static bool IsGZipFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
return false;
}
using (Stream stream = fileInfo.OpenRead())
{
return IsGZipFile(stream);
}
}
public void SaveTo(string filePath)
{
SaveTo(new FileInfo(filePath));
}
public void SaveTo(FileInfo fileInfo)
{
using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write))
{
SaveTo(stream);
}
}
#endif
public static bool IsGZipFile(Stream stream)
{
// read the header on the first read
byte[] header = new byte[10];
int n = stream.Read(header, 0, header.Length);
// workitem 8501: handle edge case (decompress empty stream)
if (n == 0)
return false;
if (n != 10)
return false;
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
return false;
return true;
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
internal GZipArchive(Stream stream, Options options)
: base(ArchiveType.GZip, stream, options)
{
}
internal GZipArchive()
: base(ArchiveType.GZip)
{
}
public void SaveTo(Stream stream)
{
SaveTo(stream, CompressionType.GZip);
}
protected override GZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified,
bool closeStream)
{
if (Entries.Any())
{
throw new InvalidOperationException("Only one entry is allowed in a GZip Archive");
}
return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
}
protected override void SaveTo(Stream stream, CompressionInfo compressionInfo,
IEnumerable<GZipArchiveEntry> oldEntries,
IEnumerable<GZipArchiveEntry> newEntries)
{
if (Entries.Count > 1)
{
throw new InvalidOperationException("Only one entry is allowed in a GZip Archive");
}
using (var writer = new GZipWriter(stream))
{
foreach (var entry in oldEntries.Concat(newEntries)
.Where(x => !x.IsDirectory))
{
using (var entryStream = entry.OpenEntryStream())
{
writer.Write(entry.Key, entryStream, entry.LastModifiedTime);
}
}
}
}
protected override IEnumerable<GZipVolume> LoadVolumes(IEnumerable<Stream> streams, Options options)
{
return new GZipVolume(streams.First(), options).AsEnumerable();
}
protected override IEnumerable<GZipArchiveEntry> LoadEntries(IEnumerable<GZipVolume> volumes)
{
Stream stream = volumes.Single().Stream;
yield return new GZipArchiveEntry(this, new GZipFilePart(stream));
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return GZipReader.Open(stream);
}
}
}

View File

@@ -0,0 +1,31 @@
using System.IO;
using System.Linq;
using SharpCompress.Common.GZip;
namespace SharpCompress.Archive.GZip
{
public class GZipArchiveEntry : GZipEntry, IArchiveEntry
{
internal GZipArchiveEntry(GZipArchive archive, GZipFilePart part)
: base(part)
{
Archive = archive;
}
public virtual Stream OpenEntryStream()
{
return Parts.Single().GetCompressedStream();
}
#region IArchiveEntry Members
public IArchive Archive { get; private set; }
public bool IsComplete
{
get { return true; }
}
#endregion
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress.Archive.GZip
{
internal class GZipWritableArchiveEntry : GZipArchiveEntry, IWritableArchiveEntry
{
private readonly string path;
private readonly long size;
private readonly DateTime? lastModified;
private readonly bool closeStream;
private readonly Stream stream;
internal GZipWritableArchiveEntry(GZipArchive archive, Stream stream,
string path, long size, DateTime? lastModified, bool closeStream)
: base(archive, null)
{
this.stream = stream;
this.path = path;
this.size = size;
this.lastModified = lastModified;
this.closeStream = closeStream;
}
public override long Crc
{
get { return 0; }
}
public override string Key
{
get { return path; }
}
public override long CompressedSize
{
get { return 0; }
}
public override long Size
{
get { return size; }
}
public override DateTime? LastModifiedTime
{
get { return lastModified; }
}
public override DateTime? CreatedTime
{
get { return null; }
}
public override DateTime? LastAccessedTime
{
get { return null; }
}
public override DateTime? ArchivedTime
{
get { return null; }
}
public override bool IsEncrypted
{
get { return false; }
}
public override bool IsDirectory
{
get { return false; }
}
public override bool IsSplit
{
get { return false; }
}
internal override IEnumerable<FilePart> Parts
{
get { throw new NotImplementedException(); }
}
Stream IWritableArchiveEntry.Stream
{
get
{
return stream;
}
}
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return new NonDisposingStream(stream);
}
internal override void Close()
{
if (closeStream)
{
stream.Dispose();
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Linq;
using SharpCompress.Common;
namespace SharpCompress.Archive
{
public static class IArchiveExtensions
{
#if !DOTNET51
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(this IArchive archive, string destinationDirectory,
ExtractOptions options = ExtractOptions.Overwrite)
{
foreach (IArchiveEntry entry in archive.Entries.Where(x => !x.IsDirectory))
{
entry.WriteToDirectory(destinationDirectory, options);
}
}
#endif
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using SharpCompress.Common;
using SharpCompress.Reader;
namespace SharpCompress.Archive
{
public interface IArchive : IDisposable
{
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionBegin;
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionEnd;
event EventHandler<CompressedBytesReadEventArgs> CompressedBytesRead;
event EventHandler<FilePartExtractionBeginEventArgs> FilePartExtractionBegin;
IEnumerable<IArchiveEntry> Entries { get; }
IEnumerable<IVolume> Volumes { get; }
ArchiveType Type { get; }
/// <summary>
/// Use this method to extract all entries in an archive in order.
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
/// extracted sequentially for the best performance.
/// </summary>
IReader ExtractAllEntries();
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// Rar Archives can be SOLID while all 7Zip archives are considered SOLID.
/// </summary>
bool IsSolid { get; }
/// <summary>
/// This checks to see if all the known entries have IsComplete = true
/// </summary>
bool IsComplete { get; }
/// <summary>
/// The total size of the files compressed in the archive.
/// </summary>
long TotalSize { get; }
/// <summary>
/// The total size of the files as uncompressed in the archive.
/// </summary>
long TotalUncompressSize { get; }
}
}

View File

@@ -0,0 +1,123 @@
using System.IO;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress.Archive
{
public static class IArchiveEntryExtensions
{
public static void WriteTo(this IArchiveEntry archiveEntry, Stream streamToWriteTo)
{
if (archiveEntry.Archive.Type == ArchiveType.Rar && archiveEntry.Archive.IsSolid)
{
throw new InvalidFormatException("Cannot use Archive random access on SOLID Rar files.");
}
if (archiveEntry.IsDirectory)
{
throw new ExtractionException("Entry is a file directory and cannot be extracted.");
}
var streamListener = archiveEntry.Archive as IArchiveExtractionListener;
streamListener.EnsureEntriesLoaded();
streamListener.FireEntryExtractionBegin(archiveEntry);
streamListener.FireFilePartExtractionBegin(archiveEntry.Key, archiveEntry.Size, archiveEntry.CompressedSize);
var entryStream = archiveEntry.OpenEntryStream();
if (entryStream == null)
{
return;
}
using(entryStream)
using (Stream s = new ListeningStream(streamListener, entryStream))
{
s.TransferTo(streamToWriteTo);
}
streamListener.FireEntryExtractionEnd(archiveEntry);
}
#if !DOTNET51
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(this IArchiveEntry entry, string destinationDirectory,
ExtractOptions options = ExtractOptions.Overwrite)
{
string destinationFileName;
string file = Path.GetFileName(entry.Key);
if (options.HasFlag(ExtractOptions.ExtractFullPath))
{
string folder = Path.GetDirectoryName(entry.Key);
string destdir = Path.Combine(destinationDirectory, folder);
if (!Directory.Exists(destdir))
{
Directory.CreateDirectory(destdir);
}
destinationFileName = Path.Combine(destdir, file);
}
else
{
destinationFileName = Path.Combine(destinationDirectory, file);
}
entry.WriteToFile(destinationFileName, options);
}
/// <summary>
/// Extract to specific file
/// </summary>
public static void WriteToFile(this IArchiveEntry entry, string destinationFileName,
ExtractOptions options = ExtractOptions.Overwrite)
{
if (entry.IsDirectory)
{
return;
}
FileMode fm = FileMode.Create;
if (!options.HasFlag(ExtractOptions.Overwrite))
{
fm = FileMode.CreateNew;
}
using (FileStream fs = File.Open(destinationFileName, fm))
{
entry.WriteTo(fs);
}
if (options.HasFlag(ExtractOptions.PreserveFileTime) || options.HasFlag(ExtractOptions.PreserveAttributes))
{
// update file time to original packed time
FileInfo nf = new FileInfo(destinationFileName);
if (nf.Exists)
{
if (options.HasFlag(ExtractOptions.PreserveFileTime))
{
if (entry.CreatedTime.HasValue)
{
nf.CreationTime = entry.CreatedTime.Value;
}
if (entry.LastModifiedTime.HasValue)
{
nf.LastWriteTime = entry.LastModifiedTime.Value;
}
if (entry.LastAccessedTime.HasValue)
{
nf.LastAccessTime = entry.CreatedTime.Value;
}
}
if (options.HasFlag(ExtractOptions.PreserveAttributes))
{
if (entry.Attrib.HasValue)
{
nf.Attributes = (FileAttributes)System.Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
}
}
}
}
}
#endif
}
}

View File

@@ -0,0 +1,24 @@
using System.IO;
using SharpCompress.Common;
namespace SharpCompress.Archive
{
public interface IArchiveEntry : IEntry
{
/// <summary>
/// Opens the current entry as a stream that will decompress as it is read.
/// Read the entire stream or use SkipEntry on EntryStream.
/// </summary>
Stream OpenEntryStream();
/// <summary>
/// The archive can find all the parts of the archive needed to extract this entry.
/// </summary>
bool IsComplete { get; }
/// <summary>
/// The archive instance this entry belongs to
/// </summary>
IArchive Archive { get; }
}
}

View File

@@ -0,0 +1,11 @@
using SharpCompress.Common;
namespace SharpCompress.Archive
{
internal interface IArchiveExtractionListener : IExtractionListener
{
void EnsureEntriesLoaded();
void FireEntryExtractionBegin(IArchiveEntry entry);
void FireEntryExtractionEnd(IArchiveEntry entry);
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.IO;
using SharpCompress.Common;
namespace SharpCompress.Archive
{
public static class IWritableArchiveExtensions
{
public static void SaveTo(this IWritableArchive writableArchive,
Stream stream, CompressionType compressionType)
{
writableArchive.SaveTo(stream, new CompressionInfo {Type = compressionType});
}
#if !DOTNET51
public static void AddEntry(this IWritableArchive writableArchive,
string entryPath, string filePath)
{
var fileInfo = new FileInfo(filePath);
if (!fileInfo.Exists)
{
throw new FileNotFoundException("Could not AddEntry: " + filePath);
}
writableArchive.AddEntry(entryPath, new FileInfo(filePath).OpenRead(), true, fileInfo.Length,
fileInfo.LastWriteTime);
}
public static void SaveTo(this IWritableArchive writableArchive,
string filePath, CompressionType compressionType)
{
writableArchive.SaveTo(new FileInfo(filePath), new CompressionInfo {Type = compressionType});
}
public static void SaveTo(this IWritableArchive writableArchive,
FileInfo fileInfo, CompressionType compressionType)
{
using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write))
{
writableArchive.SaveTo(stream, new CompressionInfo {Type = compressionType});
}
}
public static void SaveTo(this IWritableArchive writableArchive,
string filePath, CompressionInfo compressionInfo)
{
writableArchive.SaveTo(new FileInfo(filePath), compressionInfo);
}
public static void SaveTo(this IWritableArchive writableArchive,
FileInfo fileInfo, CompressionInfo compressionInfo)
{
using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write))
{
writableArchive.SaveTo(stream, compressionInfo);
}
}
public static void AddAllFromDirectory(
this IWritableArchive writableArchive,
string filePath, string searchPattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories)
{
#if NET35
foreach (var path in Directory.GetFiles(filePath, searchPattern, searchOption))
#else
foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption))
#endif
{
var fileInfo = new FileInfo(path);
writableArchive.AddEntry(path.Substring(filePath.Length), fileInfo.OpenRead(), true, fileInfo.Length,
fileInfo.LastWriteTime);
}
}
public static IArchiveEntry AddEntry(this IWritableArchive writableArchive, string key, FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
throw new ArgumentException("FileInfo does not exist.");
}
return writableArchive.AddEntry(key, fileInfo.OpenRead(), true, fileInfo.Length, fileInfo.LastWriteTime);
}
#endif
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.IO;
using SharpCompress.Common;
namespace SharpCompress.Archive
{
public interface IWritableArchive : IArchive
{
void RemoveEntry(IArchiveEntry entry);
IArchiveEntry AddEntry(string key, Stream source, bool closeStream, long size = 0, DateTime? modified = null);
void SaveTo(Stream stream, CompressionInfo compressionType);
}
}

View File

@@ -0,0 +1,9 @@
using System.IO;
namespace SharpCompress.Archive
{
internal interface IWritableArchiveEntry
{
Stream Stream { get; }
}
}

View File

@@ -0,0 +1,48 @@
#if !DOTNET51
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Archive.Rar
{
/// <summary>
/// A rar part based on a FileInfo object
/// </summary>
internal class FileInfoRarArchiveVolume : RarVolume
{
internal FileInfoRarArchiveVolume(FileInfo fileInfo, string password, Options options)
: base(StreamingMode.Seekable, fileInfo.OpenRead(), password, FixOptions(options))
{
FileInfo = fileInfo;
FileParts = base.GetVolumeFileParts().ToReadOnly();
}
private static Options FixOptions(Options options)
{
//make sure we're closing streams with fileinfo
if (options.HasFlag(Options.KeepStreamsOpen))
{
options = (Options) FlagUtility.SetFlag(options, Options.KeepStreamsOpen, false);
}
return options;
}
internal ReadOnlyCollection<RarFilePart> FileParts { get; private set; }
internal FileInfo FileInfo { get; private set; }
internal override RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader)
{
return new FileInfoRarFilePart(this, markHeader, fileHeader, FileInfo);
}
internal override IEnumerable<RarFilePart> ReadFileParts()
{
return FileParts;
}
}
}
#endif

View File

@@ -0,0 +1,31 @@
#if !DOTNET51
using System.IO;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
namespace SharpCompress.Archive.Rar
{
internal class FileInfoRarFilePart : SeekableFilePart
{
private readonly FileInfoRarArchiveVolume volume;
internal FileInfoRarFilePart(FileInfoRarArchiveVolume volume, MarkHeader mh, FileHeader fh, FileInfo fi)
: base(mh, fh, volume.Stream, volume.Password)
{
this.volume = volume;
FileInfo = fi;
}
internal FileInfo FileInfo { get; private set; }
internal override string FilePartName
{
get
{
return "Rar File: " + FileInfo.FullName
+ " File Entry: " + FileHeader.FileName;
}
}
}
}
#endif

View File

@@ -0,0 +1,23 @@
using System.Linq;
namespace SharpCompress.Archive.Rar
{
public static class RarArchiveExtensions
{
/// <summary>
/// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing.
/// </summary>
public static bool IsFirstVolume(this RarArchive archive)
{
return archive.Volumes.First().IsFirstVolume;
}
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public static bool IsMultipartVolume(this RarArchive archive)
{
return archive.Volumes.First().IsMultiVolume;
}
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.Compressor.Rar;
using SharpCompress.IO;
using SharpCompress.Reader;
using SharpCompress.Reader.Rar;
namespace SharpCompress.Archive.Rar
{
public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
{
private readonly Unpack unpack = new Unpack();
internal Unpack Unpack
{
get { return unpack; }
}
#if !DOTNET51
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
/// <param name="password"></param>
internal RarArchive(FileInfo fileInfo, Options options, string password)
: base(ArchiveType.Rar, fileInfo, options, password)
{
}
protected override IEnumerable<RarVolume> LoadVolumes(FileInfo file, Options options)
{
return RarArchiveVolumeFactory.GetParts(file, Password, options);
}
#endif
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// </summary>
/// <param name="streams"></param>
/// <param name="options"></param>
/// <param name="password"></param>
internal RarArchive(IEnumerable<Stream> streams, Options options, string password)
: base(ArchiveType.Rar, streams, options, password)
{
}
protected override IEnumerable<RarArchiveEntry> LoadEntries(IEnumerable<RarVolume> volumes)
{
return RarArchiveEntryFactory.GetEntries(this, volumes);
}
protected override IEnumerable<RarVolume> LoadVolumes(IEnumerable<Stream> streams, Options options)
{
return RarArchiveVolumeFactory.GetParts(streams, Password, options);
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.First().Stream;
stream.Position = 0;
return RarReader.Open(stream);
}
public override bool IsSolid
{
get { return Volumes.First().IsSolidArchive; }
}
#region Creation
#if !DOTNET51
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
/// <param name="password"></param>
public static RarArchive Open(string filePath, Options options = Options.None, string password = null)
{
filePath.CheckNotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), options, password);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
/// <param name="password"></param>
public static RarArchive Open(FileInfo fileInfo, Options options = Options.None, string password = null)
{
fileInfo.CheckNotNull("fileInfo");
return new RarArchive(fileInfo, options, password);
}
#endif
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <param name="password"></param>
public static RarArchive Open(Stream stream, Options options = Options.KeepStreamsOpen, string password = null)
{
stream.CheckNotNull("stream");
return Open(stream.AsEnumerable(), options, password);
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// </summary>
/// <param name="streams"></param>
/// <param name="options"></param>
/// <param name="password"></param>
public static RarArchive Open(IEnumerable<Stream> streams, Options options = Options.KeepStreamsOpen, string password = null)
{
streams.CheckNotNull("streams");
return new RarArchive(streams, options, password);
}
#if !DOTNET51
public static bool IsRarFile(string filePath)
{
return IsRarFile(new FileInfo(filePath));
}
public static bool IsRarFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
return false;
}
using (Stream stream = fileInfo.OpenRead())
{
return IsRarFile(stream);
}
}
#endif
public static bool IsRarFile(Stream stream)
{
return IsRarFile(stream, Options.None);
}
public static bool IsRarFile(Stream stream, Options options)
{
try
{
var headerFactory = new RarHeaderFactory(StreamingMode.Seekable, options);
RarHeader header = headerFactory.ReadHeaders(stream).FirstOrDefault();
if (header == null)
{
return false;
}
return Enum.IsDefined(typeof (HeaderType), header.HeaderType);
}
catch
{
return false;
}
}
#endregion
}
}

View File

@@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.Compressor.Rar;
namespace SharpCompress.Archive.Rar
{
public class RarArchiveEntry : RarEntry, IArchiveEntry
{
private readonly ICollection<RarFilePart> parts;
private readonly RarArchive archive;
internal RarArchiveEntry(RarArchive archive, IEnumerable<RarFilePart> parts)
{
this.parts = parts.ToList();
this.archive = archive;
}
public override CompressionType CompressionType
{
get { return CompressionType.Rar; }
}
public IArchive Archive
{
get
{
return archive;
}
}
internal override IEnumerable<FilePart> Parts
{
get { return parts.Cast<FilePart>(); }
}
internal override FileHeader FileHeader
{
get { return parts.First().FileHeader; }
}
public override long Crc
{
get
{
CheckIncomplete();
return parts.Select(fp => fp.FileHeader)
.Single(fh => !fh.FileFlags.HasFlag(FileFlags.SPLIT_AFTER)).FileCRC;
}
}
public override long Size
{
get
{
CheckIncomplete();
return parts.First().FileHeader.UncompressedSize;
}
}
public override long CompressedSize
{
get
{
CheckIncomplete();
return parts.Aggregate(0L, (total, fp) => { return total + fp.FileHeader.CompressedSize; });
}
}
public Stream OpenEntryStream()
{
return new RarStream(archive.Unpack, FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive));
}
public bool IsComplete
{
get { return parts.Select(fp => fp.FileHeader).Any(fh => !fh.FileFlags.HasFlag(FileFlags.SPLIT_AFTER)); }
}
private void CheckIncomplete()
{
if (!IsComplete)
{
throw new IncompleteArchiveException("ArchiveEntry is incomplete and cannot perform this operation.");
}
}
}
}

View File

@@ -0,0 +1,49 @@
using System.Collections.Generic;
using SharpCompress.Common;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
namespace SharpCompress.Archive.Rar
{
internal static class RarArchiveEntryFactory
{
private static IEnumerable<RarFilePart> GetFileParts(IEnumerable<RarVolume> parts)
{
foreach (RarVolume rarPart in parts)
{
foreach (RarFilePart fp in rarPart.ReadFileParts())
{
yield return fp;
}
}
}
private static IEnumerable<IEnumerable<RarFilePart>> GetMatchedFileParts(IEnumerable<RarVolume> parts)
{
var groupedParts = new List<RarFilePart>();
foreach (RarFilePart fp in GetFileParts(parts))
{
groupedParts.Add(fp);
if (!FlagUtility.HasFlag((long) fp.FileHeader.FileFlags, (long) FileFlags.SPLIT_AFTER))
{
yield return groupedParts;
groupedParts = new List<RarFilePart>();
}
}
if (groupedParts.Count > 0)
{
yield return groupedParts;
}
}
internal static IEnumerable<RarArchiveEntry> GetEntries(RarArchive archive,
IEnumerable<RarVolume> rarParts)
{
foreach (var groupedParts in GetMatchedFileParts(rarParts))
{
yield return new RarArchiveEntry(archive, groupedParts);
}
}
}
}

View File

@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.Common;
using SharpCompress.Common.Rar;
namespace SharpCompress.Archive.Rar
{
internal static class RarArchiveVolumeFactory
{
internal static IEnumerable<RarVolume> GetParts(IEnumerable<Stream> streams, string password, Options options)
{
foreach (Stream s in streams)
{
if (!s.CanRead || !s.CanSeek)
{
throw new ArgumentException("Stream is not readable and seekable");
}
StreamRarArchiveVolume part = new StreamRarArchiveVolume(s, password, options);
yield return part;
}
}
#if !DOTNET51
internal static IEnumerable<RarVolume> GetParts(FileInfo fileInfo, string password, Options options)
{
FileInfoRarArchiveVolume part = new FileInfoRarArchiveVolume(fileInfo, password, options);
yield return part;
if (!part.ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.VOLUME))
{
yield break; //if file isn't volume then there is no reason to look
}
ArchiveHeader ah = part.ArchiveHeader;
fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart);
//we use fileinfo because rar is dumb and looks at file names rather than archive info for another volume
while (fileInfo != null && fileInfo.Exists)
{
part = new FileInfoRarArchiveVolume(fileInfo, password, options);
fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart);
yield return part;
}
}
private static FileInfo GetNextFileInfo(ArchiveHeader ah, FileInfoRarFilePart currentFilePart)
{
if (currentFilePart == null)
{
return null;
}
bool oldNumbering = !ah.ArchiveHeaderFlags.HasFlag(ArchiveFlags.NEWNUMBERING)
|| currentFilePart.MarkHeader.OldFormat;
if (oldNumbering)
{
return FindNextFileWithOldNumbering(currentFilePart.FileInfo);
}
else
{
return FindNextFileWithNewNumbering(currentFilePart.FileInfo);
}
}
private static FileInfo FindNextFileWithOldNumbering(FileInfo currentFileInfo)
{
// .rar, .r00, .r01, ...
string extension = currentFileInfo.Extension;
StringBuilder buffer = new StringBuilder(currentFileInfo.FullName.Length);
buffer.Append(currentFileInfo.FullName.Substring(0,
currentFileInfo.FullName.Length - extension.Length));
if (string.Compare(extension, ".rar", StringComparison.OrdinalIgnoreCase) == 0)
{
buffer.Append(".r00");
}
else
{
int num = 0;
if (int.TryParse(extension.Substring(2, 2), out num))
{
num++;
buffer.Append(".r");
if (num < 10)
{
buffer.Append('0');
}
buffer.Append(num);
}
else
{
ThrowInvalidFileName(currentFileInfo);
}
}
return new FileInfo(buffer.ToString());
}
private static FileInfo FindNextFileWithNewNumbering(FileInfo currentFileInfo)
{
// part1.rar, part2.rar, ...
string extension = currentFileInfo.Extension;
if (string.Compare(extension, ".rar", StringComparison.OrdinalIgnoreCase) != 0)
{
throw new ArgumentException("Invalid extension, expected 'rar': " + currentFileInfo.FullName);
}
int startIndex = currentFileInfo.FullName.LastIndexOf(".part");
if (startIndex < 0)
{
ThrowInvalidFileName(currentFileInfo);
}
StringBuilder buffer = new StringBuilder(currentFileInfo.FullName.Length);
buffer.Append(currentFileInfo.FullName, 0, startIndex);
int num = 0;
string numString = currentFileInfo.FullName.Substring(startIndex + 5,
currentFileInfo.FullName.IndexOf('.', startIndex + 5) -
startIndex - 5);
buffer.Append(".part");
if (int.TryParse(numString, out num))
{
num++;
for (int i = 0; i < numString.Length - num.ToString().Length; i++)
{
buffer.Append('0');
}
buffer.Append(num);
}
else
{
ThrowInvalidFileName(currentFileInfo);
}
buffer.Append(".rar");
return new FileInfo(buffer.ToString());
}
private static void ThrowInvalidFileName(FileInfo fileInfo)
{
throw new ArgumentException("Filename invalid or next archive could not be found:"
+ fileInfo.FullName);
}
#endif
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.IO;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
namespace SharpCompress.Archive.Rar
{
internal class SeekableFilePart : RarFilePart
{
private readonly Stream stream;
private readonly string password;
internal SeekableFilePart(MarkHeader mh, FileHeader fh, Stream stream, string password)
: base(mh, fh)
{
this.stream = stream;
this.password = password;
}
internal override Stream GetCompressedStream()
{
stream.Position = FileHeader.DataStartPosition;
if (FileHeader.Salt != null)
{
return new RarCryptoWrapper(stream, password, FileHeader.Salt);
}
return stream;
}
internal override string FilePartName
{
get { return "Unknown Stream - File Entry: " + FileHeader.FileName; }
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Archive.Rar
{
internal class StreamRarArchiveVolume : RarVolume
{
internal StreamRarArchiveVolume(Stream stream, string password, Options options)
: base(StreamingMode.Seekable, stream, password, options)
{
}
internal override IEnumerable<RarFilePart> ReadFileParts()
{
return GetVolumeFileParts();
}
internal override RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader)
{
return new SeekableFilePart(markHeader, fileHeader, Stream, Password);
}
}
}

View File

@@ -0,0 +1,252 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.SevenZip;
using SharpCompress.IO;
using SharpCompress.Reader;
namespace SharpCompress.Archive.SevenZip
{
public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVolume>
{
private ArchiveDatabase database;
#if !DOTNET51
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
public static SevenZipArchive Open(string filePath)
{
return Open(filePath, Options.None);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
public static SevenZipArchive Open(FileInfo fileInfo)
{
return Open(fileInfo, Options.None);
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static SevenZipArchive Open(string filePath, Options options)
{
filePath.CheckNotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), options);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static SevenZipArchive Open(FileInfo fileInfo, Options options)
{
fileInfo.CheckNotNull("fileInfo");
return new SevenZipArchive(fileInfo, options);
}
#endif
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
public static SevenZipArchive Open(Stream stream)
{
stream.CheckNotNull("stream");
return Open(stream, Options.None);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
public static SevenZipArchive Open(Stream stream, Options options)
{
stream.CheckNotNull("stream");
return new SevenZipArchive(stream, options);
}
#if !DOTNET51
internal SevenZipArchive(FileInfo fileInfo, Options options)
: base(ArchiveType.SevenZip, fileInfo, options, null)
{
}
protected override IEnumerable<SevenZipVolume> LoadVolumes(FileInfo file, Options options)
{
if (FlagUtility.HasFlag(options, Options.KeepStreamsOpen))
{
options = (Options)FlagUtility.SetFlag(options, Options.KeepStreamsOpen, false);
}
return new SevenZipVolume(file.OpenRead(), options).AsEnumerable();
}
public static bool IsSevenZipFile(string filePath)
{
return IsSevenZipFile(new FileInfo(filePath));
}
public static bool IsSevenZipFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
return false;
}
using (Stream stream = fileInfo.OpenRead())
{
return IsSevenZipFile(stream);
}
}
#endif
internal SevenZipArchive(Stream stream, Options options)
: base(ArchiveType.SevenZip, stream.AsEnumerable(), options, null)
{
}
internal SevenZipArchive()
: base(ArchiveType.SevenZip)
{
}
protected override IEnumerable<SevenZipVolume> LoadVolumes(IEnumerable<Stream> streams, Options options)
{
foreach (Stream s in streams)
{
if (!s.CanRead || !s.CanSeek)
{
throw new ArgumentException("Stream is not readable and seekable");
}
SevenZipVolume volume = new SevenZipVolume(s, options);
yield return volume;
}
}
protected override IEnumerable<SevenZipArchiveEntry> LoadEntries(IEnumerable<SevenZipVolume> volumes)
{
var stream = volumes.Single().Stream;
LoadFactory(stream);
for (int i = 0; i < database.Files.Count; i++)
{
var file = database.Files[i];
if (!file.IsDir)
{
yield return new SevenZipArchiveEntry(this, new SevenZipFilePart(stream, database, i, file));
}
}
}
private void LoadFactory(Stream stream)
{
if (database == null)
{
stream.Position = 0;
var reader = new ArchiveReader();
reader.Open(stream);
database = reader.ReadDatabase(null);
}
}
public static bool IsSevenZipFile(Stream stream)
{
try
{
return SignatureMatch(stream);
}
catch
{
return false;
}
}
private static readonly byte[] SIGNATURE = new byte[] {(byte) '7', (byte) 'z', 0xBC, 0xAF, 0x27, 0x1C};
private static bool SignatureMatch(Stream stream)
{
BinaryReader reader = new BinaryReader(stream);
byte[] signatureBytes = reader.ReadBytes(6);
return signatureBytes.BinaryEquals(SIGNATURE);
}
protected override IReader CreateReaderForSolidExtraction()
{
return new SevenZipReader(this);
}
public override bool IsSolid
{
get { return Entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder).Count() > 1; }
}
public override long TotalSize
{
get
{
int i = Entries.Count;
return database.PackSizes.Aggregate(0L, (total, packSize) => total + packSize);
}
}
private class SevenZipReader : AbstractReader<SevenZipEntry, SevenZipVolume>
{
private readonly SevenZipArchive archive;
private CFolder currentFolder;
private Stream currentStream;
private CFileItem currentItem;
internal SevenZipReader(SevenZipArchive archive)
: base(Options.KeepStreamsOpen, ArchiveType.SevenZip)
{
this.archive = archive;
}
public override SevenZipVolume Volume
{
get { return archive.Volumes.Single(); }
}
internal override IEnumerable<SevenZipEntry> GetEntries(Stream stream)
{
List<SevenZipArchiveEntry> entries = archive.Entries.ToList();
stream.Position = 0;
foreach (var dir in entries.Where(x => x.IsDirectory))
{
yield return dir;
}
foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder))
{
currentFolder = group.Key;
if (group.Key == null)
{
currentStream = Stream.Null;
}
else
{
currentStream = archive.database.GetFolderStream(stream, currentFolder, null);
}
foreach (var entry in group)
{
currentItem = entry.FilePart.Header;
yield return entry;
}
}
}
protected override EntryStream GetEntryStream()
{
return CreateEntryStream(new ReadOnlySubStream(currentStream, currentItem.Size));
}
}
}
}

View File

@@ -0,0 +1,33 @@
using System.IO;
using SharpCompress.Common.SevenZip;
namespace SharpCompress.Archive.SevenZip
{
public class SevenZipArchiveEntry : SevenZipEntry, IArchiveEntry
{
internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part)
: base(part)
{
Archive = archive;
}
public Stream OpenEntryStream()
{
return FilePart.GetCompressedStream();
}
public IArchive Archive { get; private set; }
public bool IsComplete
{
get { return true; }
}
/// <summary>
/// This is a 7Zip Anti item
/// </summary>
public bool IsAnti
{
get { return FilePart.Header.IsAnti; }
}
}
}

View File

@@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.Tar;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.IO;
using SharpCompress.Reader;
using SharpCompress.Reader.Tar;
using SharpCompress.Writer.Tar;
namespace SharpCompress.Archive.Tar
{
public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
{
#if !DOTNET51
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
public static TarArchive Open(string filePath)
{
return Open(filePath, Options.None);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
public static TarArchive Open(FileInfo fileInfo)
{
return Open(fileInfo, Options.None);
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static TarArchive Open(string filePath, Options options)
{
filePath.CheckNotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), options);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static TarArchive Open(FileInfo fileInfo, Options options)
{
fileInfo.CheckNotNull("fileInfo");
return new TarArchive(fileInfo, options);
}
#endif
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
public static TarArchive Open(Stream stream)
{
stream.CheckNotNull("stream");
return Open(stream, Options.None);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
public static TarArchive Open(Stream stream, Options options)
{
stream.CheckNotNull("stream");
return new TarArchive(stream, options);
}
#if !DOTNET51
public static bool IsTarFile(string filePath)
{
return IsTarFile(new FileInfo(filePath));
}
public static bool IsTarFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
return false;
}
using (Stream stream = fileInfo.OpenRead())
{
return IsTarFile(stream);
}
}
#endif
public static bool IsTarFile(Stream stream)
{
try
{
TarHeader tar = new TarHeader();
tar.Read(new BinaryReader(stream));
return tar.Name.Length > 0 && Enum.IsDefined(typeof (EntryType), tar.EntryType);
}
catch
{
}
return false;
}
#if !DOTNET51
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
internal TarArchive(FileInfo fileInfo, Options options)
: base(ArchiveType.Tar, fileInfo, options)
{
}
protected override IEnumerable<TarVolume> LoadVolumes(FileInfo file, Options options)
{
if (FlagUtility.HasFlag(options, Options.KeepStreamsOpen))
{
options = (Options)FlagUtility.SetFlag(options, Options.KeepStreamsOpen, false);
}
return new TarVolume(file.OpenRead(), options).AsEnumerable();
}
#endif
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
internal TarArchive(Stream stream, Options options)
: base(ArchiveType.Tar, stream, options)
{
}
internal TarArchive()
: base(ArchiveType.Tar)
{
}
protected override IEnumerable<TarVolume> LoadVolumes(IEnumerable<Stream> streams, Options options)
{
return new TarVolume(streams.First(), options).AsEnumerable();
}
protected override IEnumerable<TarArchiveEntry> LoadEntries(IEnumerable<TarVolume> volumes)
{
Stream stream = volumes.Single().Stream;
TarHeader previousHeader = null;
foreach (TarHeader header in TarHeaderFactory.ReadHeader(StreamingMode.Seekable, stream))
{
if (header != null)
{
if (header.EntryType == EntryType.LongName)
{
previousHeader = header;
}
else
{
if (previousHeader != null)
{
var entry = new TarArchiveEntry(this, new TarFilePart(previousHeader, stream),
CompressionType.None);
var oldStreamPos = stream.Position;
using(var entryStream = entry.OpenEntryStream())
using(var memoryStream = new MemoryStream())
{
entryStream.TransferTo(memoryStream);
memoryStream.Position = 0;
var bytes = memoryStream.ToArray();
header.Name = ArchiveEncoding.Default.GetString(bytes, 0, bytes.Length).TrimNulls();
}
stream.Position = oldStreamPos;
previousHeader = null;
}
yield return new TarArchiveEntry(this, new TarFilePart(header, stream), CompressionType.None);
}
}
}
}
public static TarArchive Create()
{
return new TarArchive();
}
protected override TarArchiveEntry CreateEntryInternal(string filePath, Stream source,
long size, DateTime? modified, bool closeStream)
{
return new TarWritableArchiveEntry(this, source, CompressionType.Unknown, filePath, size, modified,
closeStream);
}
protected override void SaveTo(Stream stream, CompressionInfo compressionInfo,
IEnumerable<TarArchiveEntry> oldEntries,
IEnumerable<TarArchiveEntry> newEntries)
{
using (var writer = new TarWriter(stream, compressionInfo))
{
foreach (var entry in oldEntries.Concat(newEntries)
.Where(x => !x.IsDirectory))
{
using (var entryStream = entry.OpenEntryStream())
{
writer.Write(entry.Key, entryStream, entry.LastModifiedTime, entry.Size);
}
}
}
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return TarReader.Open(stream);
}
}
}

View File

@@ -0,0 +1,31 @@
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.Tar;
namespace SharpCompress.Archive.Tar
{
public class TarArchiveEntry : TarEntry, IArchiveEntry
{
internal TarArchiveEntry(TarArchive archive, TarFilePart part, CompressionType compressionType)
: base(part, compressionType)
{
Archive = archive;
}
public virtual Stream OpenEntryStream()
{
return Parts.Single().GetCompressedStream();
}
#region IArchiveEntry Members
public IArchive Archive { get; private set; }
public bool IsComplete
{
get { return true; }
}
#endregion
}
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress.Archive.Tar
{
internal class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiveEntry
{
private readonly string path;
private readonly long size;
private readonly DateTime? lastModified;
private readonly bool closeStream;
private readonly Stream stream;
internal TarWritableArchiveEntry(TarArchive archive, Stream stream, CompressionType compressionType,
string path, long size, DateTime? lastModified, bool closeStream)
: base(archive, null, compressionType)
{
this.stream = stream;
this.path = path;
this.size = size;
this.lastModified = lastModified;
this.closeStream = closeStream;
}
public override long Crc
{
get { return 0; }
}
public override string Key
{
get { return path; }
}
public override long CompressedSize
{
get { return 0; }
}
public override long Size
{
get { return size; }
}
public override DateTime? LastModifiedTime
{
get { return lastModified; }
}
public override DateTime? CreatedTime
{
get { return null; }
}
public override DateTime? LastAccessedTime
{
get { return null; }
}
public override DateTime? ArchivedTime
{
get { return null; }
}
public override bool IsEncrypted
{
get { return false; }
}
public override bool IsDirectory
{
get { return false; }
}
public override bool IsSplit
{
get { return false; }
}
internal override IEnumerable<FilePart> Parts
{
get { throw new NotImplementedException(); }
}
Stream IWritableArchiveEntry.Stream
{
get
{
return stream;
}
}
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return new NonDisposingStream(stream);
}
internal override void Close()
{
if (closeStream)
{
stream.Dispose();
}
}
}
}

View File

@@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
using SharpCompress.Common.Zip;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressor.Deflate;
using SharpCompress.Reader;
using SharpCompress.Reader.Zip;
using SharpCompress.Writer.Zip;
namespace SharpCompress.Archive.Zip
{
public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
{
private readonly SeekableZipHeaderFactory headerFactory;
/// <summary>
/// Gets or sets the compression level applied to files added to the archive,
/// if the compression method is set to deflate
/// </summary>
public CompressionLevel DeflateCompressionLevel { get; set; }
#if !DOTNET51
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="password"></param>
public static ZipArchive Open(string filePath, string password = null)
{
return Open(filePath, Options.None, password);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="password"></param>
public static ZipArchive Open(FileInfo fileInfo, string password = null)
{
return Open(fileInfo, Options.None, password);
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
/// <param name="password"></param>
public static ZipArchive Open(string filePath, Options options, string password = null)
{
filePath.CheckNotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), options, password);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
/// <param name="password"></param>
public static ZipArchive Open(FileInfo fileInfo, Options options, string password = null)
{
fileInfo.CheckNotNull("fileInfo");
return new ZipArchive(fileInfo, options, password);
}
#endif
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="password"></param>
public static ZipArchive Open(Stream stream, string password = null)
{
stream.CheckNotNull("stream");
return Open(stream, Options.None, password);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <param name="password"></param>
public static ZipArchive Open(Stream stream, Options options, string password = null)
{
stream.CheckNotNull("stream");
return new ZipArchive(stream, options, password);
}
#if !DOTNET51
public static bool IsZipFile(string filePath, string password = null)
{
return IsZipFile(new FileInfo(filePath), password);
}
public static bool IsZipFile(FileInfo fileInfo, string password = null)
{
if (!fileInfo.Exists)
{
return false;
}
using (Stream stream = fileInfo.OpenRead())
{
return IsZipFile(stream, password);
}
}
#endif
public static bool IsZipFile(Stream stream, string password = null)
{
StreamingZipHeaderFactory headerFactory = new StreamingZipHeaderFactory(password);
try
{
ZipHeader header =
headerFactory.ReadStreamHeader(stream).FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
if (header == null)
{
return false;
}
return Enum.IsDefined(typeof (ZipHeaderType), header.ZipHeaderType);
}
catch (CryptographicException)
{
return true;
}
catch
{
return false;
}
}
#if !DOTNET51
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
/// <param name="password"></param>
internal ZipArchive(FileInfo fileInfo, Options options, string password = null)
: base(ArchiveType.Zip, fileInfo, options)
{
headerFactory = new SeekableZipHeaderFactory(password);
}
protected override IEnumerable<ZipVolume> LoadVolumes(FileInfo file, Options options)
{
if (FlagUtility.HasFlag(options, Options.KeepStreamsOpen))
{
options = (Options)FlagUtility.SetFlag(options, Options.KeepStreamsOpen, false);
}
return new ZipVolume(file.OpenRead(), options).AsEnumerable();
}
#endif
internal ZipArchive()
: base(ArchiveType.Zip)
{
}
/// <summary>
/// Takes multiple seekable Streams for a multi-part archive
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <param name="password"></param>
internal ZipArchive(Stream stream, Options options, string password = null)
: base(ArchiveType.Zip, stream, options)
{
headerFactory = new SeekableZipHeaderFactory(password);
}
protected override IEnumerable<ZipVolume> LoadVolumes(IEnumerable<Stream> streams, Options options)
{
return new ZipVolume(streams.First(), options).AsEnumerable();
}
protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
{
var volume = volumes.Single();
Stream stream = volume.Stream;
foreach (ZipHeader h in headerFactory.ReadSeekableHeader(stream))
{
if (h != null)
{
switch (h.ZipHeaderType)
{
case ZipHeaderType.DirectoryEntry:
{
yield return new ZipArchiveEntry(this,
new SeekableZipFilePart(headerFactory,
h as DirectoryEntryHeader,
stream));
}
break;
case ZipHeaderType.DirectoryEnd:
{
byte[] bytes = (h as DirectoryEndHeader).Comment;
volume.Comment = ArchiveEncoding.Default.GetString(bytes, 0, bytes.Length);
yield break;
}
}
}
}
}
protected override void SaveTo(Stream stream, CompressionInfo compressionInfo,
IEnumerable<ZipArchiveEntry> oldEntries,
IEnumerable<ZipArchiveEntry> newEntries)
{
using (var writer = new ZipWriter(stream, compressionInfo, string.Empty))
{
foreach (var entry in oldEntries.Concat(newEntries)
.Where(x => !x.IsDirectory))
{
using (var entryStream = entry.OpenEntryStream())
{
writer.Write(entry.Key, entryStream, entry.LastModifiedTime, string.Empty);
}
}
}
}
protected override ZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified,
bool closeStream)
{
return new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
}
public static ZipArchive Create()
{
return new ZipArchive();
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return ZipReader.Open(stream);
}
}
}

View File

@@ -0,0 +1,36 @@
using System.IO;
using System.Linq;
using SharpCompress.Common.Zip;
namespace SharpCompress.Archive.Zip
{
public class ZipArchiveEntry : ZipEntry, IArchiveEntry
{
internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart part)
: base(part)
{
Archive = archive;
}
public virtual Stream OpenEntryStream()
{
return Parts.Single().GetCompressedStream();
}
#region IArchiveEntry Members
public IArchive Archive { get; private set; }
public bool IsComplete
{
get { return true; }
}
#endregion
public string Comment
{
get { return (Parts.Single() as SeekableZipFilePart).Comment; }
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress.Archive.Zip
{
internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
{
private readonly string path;
private readonly long size;
private readonly DateTime? lastModified;
private readonly bool closeStream;
private readonly Stream stream;
private bool isDisposed;
internal ZipWritableArchiveEntry(ZipArchive archive, Stream stream, string path, long size,
DateTime? lastModified, bool closeStream)
: base(archive, null)
{
this.stream = stream;
this.path = path;
this.size = size;
this.lastModified = lastModified;
this.closeStream = closeStream;
}
public override long Crc
{
get { return 0; }
}
public override string Key
{
get { return path; }
}
public override long CompressedSize
{
get { return 0; }
}
public override long Size
{
get { return size; }
}
public override DateTime? LastModifiedTime
{
get { return lastModified; }
}
public override DateTime? CreatedTime
{
get { return null; }
}
public override DateTime? LastAccessedTime
{
get { return null; }
}
public override DateTime? ArchivedTime
{
get { return null; }
}
public override bool IsEncrypted
{
get { return false; }
}
public override bool IsDirectory
{
get { return false; }
}
public override bool IsSplit
{
get { return false; }
}
internal override IEnumerable<FilePart> Parts
{
get { throw new NotImplementedException(); }
}
Stream IWritableArchiveEntry.Stream
{
get
{
return stream;
}
}
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return new NonDisposingStream(stream);
}
internal override void Close()
{
if (closeStream && !isDisposed)
{
stream.Dispose();
isDisposed = true;
}
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("SharpCompress")]
[assembly: AssemblyProduct("SharpCompress")]
[assembly: InternalsVisibleTo("SharpCompress.Test")]
[assembly: InternalsVisibleTo("SharpCompress.Test.Portable")]
[assembly: CLSCompliant(true)]

View File

@@ -0,0 +1,23 @@
using System.Text;
namespace SharpCompress.Common
{
public static class ArchiveEncoding
{
/// <summary>
/// Default encoding to use when archive format doesn't specify one.
/// </summary>
public static Encoding Default { get; set; }
/// <summary>
/// Encoding used by encryption schemes which don't comply with RFC 2898.
/// </summary>
public static Encoding Password { get; set; }
static ArchiveEncoding()
{
Default = Encoding.UTF8;
Password = Encoding.UTF8;
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace SharpCompress.Common
{
public class ArchiveException : Exception
{
public ArchiveException(string message)
: base(message)
{
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace SharpCompress.Common
{
public class ArchiveExtractionEventArgs<T> : EventArgs
{
internal ArchiveExtractionEventArgs(T entry)
{
Item = entry;
}
public T Item { get; private set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace SharpCompress.Common
{
public enum ArchiveType
{
Rar,
Zip,
Tar,
SevenZip,
GZip,
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace SharpCompress.Common
{
public class CompressedBytesReadEventArgs : EventArgs
{
/// <summary>
/// Compressed bytes read for the current entry
/// </summary>
public long CompressedBytesRead { get; internal set; }
/// <summary>
/// Current file part read for Multipart files (e.g. Rar)
/// </summary>
public long CurrentFilePartCompressedBytesRead { get; internal set; }
}
}

View File

@@ -0,0 +1,30 @@
using SharpCompress.Compressor.Deflate;
namespace SharpCompress.Common
{
/// <summary>
/// Detailed compression properties when saving.
/// </summary>
public class CompressionInfo
{
public CompressionInfo()
{
DeflateCompressionLevel = CompressionLevel.Default;
}
/// <summary>
/// The algorthm to use. Must be valid for the format type.
/// </summary>
public CompressionType Type { get; set; }
/// <summary>
/// When CompressionType.Deflate is used, this property is referenced. Defaults to CompressionLevel.Default.
/// </summary>
public CompressionLevel DeflateCompressionLevel { get; set; }
public static implicit operator CompressionInfo(CompressionType compressionType)
{
return new CompressionInfo() {Type = compressionType};
}
}
}

View File

@@ -0,0 +1,16 @@
namespace SharpCompress.Common
{
public enum CompressionType
{
None,
GZip,
BZip2,
PPMd,
Deflate,
Rar,
LZMA,
BCJ,
BCJ2,
Unknown,
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace SharpCompress.Common
{
public class CryptographicException : Exception
{
public CryptographicException(string message)
: base(message)
{
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
namespace SharpCompress.Common
{
public abstract class Entry : IEntry
{
/// <summary>
/// The File's 32 bit CRC Hash
/// </summary>
public abstract long Crc { get; }
/// <summary>
/// The string key of the file internal to the Archive.
/// </summary>
public abstract string Key { get; }
/// <summary>
/// The compressed file size
/// </summary>
public abstract long CompressedSize { get; }
/// <summary>
/// The compression type
/// </summary>
public abstract CompressionType CompressionType { get; }
/// <summary>
/// The uncompressed file size
/// </summary>
public abstract long Size { get; }
/// <summary>
/// The entry last modified time in the archive, if recorded
/// </summary>
public abstract DateTime? LastModifiedTime { get; }
/// <summary>
/// The entry create time in the archive, if recorded
/// </summary>
public abstract DateTime? CreatedTime { get; }
/// <summary>
/// The entry last accessed time in the archive, if recorded
/// </summary>
public abstract DateTime? LastAccessedTime { get; }
/// <summary>
/// The entry time when archived, if recorded
/// </summary>
public abstract DateTime? ArchivedTime { get; }
/// <summary>
/// Entry is password protected and encrypted and cannot be extracted.
/// </summary>
public abstract bool IsEncrypted { get; }
/// <summary>
/// Entry is password protected and encrypted and cannot be extracted.
/// </summary>
public abstract bool IsDirectory { get; }
/// <summary>
/// Entry is split among multiple volumes
/// </summary>
public abstract bool IsSplit { get; }
internal abstract IEnumerable<FilePart> Parts { get; }
internal bool IsSolid { get; set; }
internal virtual void Close()
{
}
/// <summary>
/// Entry file attribute.
/// </summary>
public virtual int? Attrib
{
get { throw new NotImplementedException(); }
}
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.IO;
using SharpCompress.Reader;
namespace SharpCompress.Common
{
public class EntryStream : Stream
{
public IReader Reader { get; private set; }
private Stream stream;
private bool completed;
private bool isDisposed;
internal EntryStream(IReader reader, Stream stream)
{
this.Reader = reader;
this.stream = stream;
}
/// <summary>
/// When reading a stream from OpenEntryStream, the stream must be completed so use this to finish reading the entire entry.
/// </summary>
public void SkipEntry()
{
var buffer = new byte[4096];
while (Read(buffer, 0, buffer.Length) > 0)
{
}
completed = true;
}
protected override void Dispose(bool disposing)
{
if (!(completed || Reader.Cancelled))
{
SkipEntry();
}
if (isDisposed)
{
return;
}
isDisposed = true;
base.Dispose(disposing);
stream.Dispose();
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override void Flush()
{
throw new NotSupportedException();
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override int Read(byte[] buffer, int offset, int count)
{
int read = stream.Read(buffer, offset, count);
if (read <= 0)
{
completed = true;
}
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace SharpCompress.Common
{
[Flags]
public enum ExtractOptions
{
None = 0,
/// <summary>
/// overwrite target if it exists
/// </summary>
Overwrite = 1 << 0,
/// <summary>
/// extract with internal directory structure
/// </summary>
ExtractFullPath = 1 << 1,
/// <summary>
/// preserve file time
/// </summary>
PreserveFileTime = 1 << 2,
/// <summary>
/// preserve windows file attributes
/// </summary>
PreserveAttributes = 1 << 3,
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace SharpCompress.Common
{
public class ExtractionException : Exception
{
public ExtractionException(string message)
: base(message)
{
}
public ExtractionException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@@ -0,0 +1,12 @@
using System.IO;
namespace SharpCompress.Common
{
public abstract class FilePart
{
internal abstract string FilePartName { get; }
internal abstract Stream GetCompressedStream();
internal abstract Stream GetRawStream();
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace SharpCompress.Common
{
public class FilePartExtractionBeginEventArgs : EventArgs
{
/// <summary>
/// File name for the part for the current entry
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Uncompressed size of the current entry in the part
/// </summary>
public long Size { get; internal set; }
/// <summary>
/// Compressed size of the current entry in the part
/// </summary>
public long CompressedSize { get; internal set; }
}
}

View File

@@ -0,0 +1,109 @@
using System;
namespace SharpCompress.Common
{
internal static class FlagUtility
{
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(long bitField, T flag)
where T : struct
{
return HasFlag(bitField, flag);
}
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(ulong bitField, T flag)
where T : struct
{
return HasFlag(bitField, flag);
}
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag(ulong bitField, ulong flag)
{
return ((bitField & flag) == flag);
}
public static bool HasFlag(short bitField, short flag)
{
return ((bitField & flag) == flag);
}
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(T bitField, T flag)
where T : struct
{
return HasFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag));
}
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag(long bitField, long flag)
{
return ((bitField & flag) == flag);
}
/// <summary>
/// Sets a bit-field to either on or off for the specified flag.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to change</param>
/// <param name="on">bool</param>
/// <returns>The flagged variable with the flag changed</returns>
public static long SetFlag(long bitField, long flag, bool @on)
{
if (@on)
{
return bitField | flag;
}
return bitField & (~flag);
}
/// <summary>
/// Sets a bit-field to either on or off for the specified flag.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to change</param>
/// <param name="on">bool</param>
/// <returns>The flagged variable with the flag changed</returns>
public static long SetFlag<T>(T bitField, T flag, bool @on)
where T : struct
{
return SetFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag), @on);
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace SharpCompress.Common.GZip
{
public class GZipEntry : Entry
{
private readonly GZipFilePart filePart;
internal GZipEntry(GZipFilePart filePart)
{
this.filePart = filePart;
}
public override CompressionType CompressionType
{
get { return CompressionType.GZip; }
}
public override long Crc
{
get { return 0; }
}
public override string Key
{
get { return filePart.FilePartName; }
}
public override long CompressedSize
{
get { return 0; }
}
public override long Size
{
get { return 0; }
}
public override DateTime? LastModifiedTime
{
get { return filePart.DateModified; }
}
public override DateTime? CreatedTime
{
get { return null; }
}
public override DateTime? LastAccessedTime
{
get { return null; }
}
public override DateTime? ArchivedTime
{
get { return null; }
}
public override bool IsEncrypted
{
get { return false; }
}
public override bool IsDirectory
{
get { return false; }
}
public override bool IsSplit
{
get { return false; }
}
internal override IEnumerable<FilePart> Parts
{
get { return filePart.AsEnumerable<FilePart>(); }
}
internal static IEnumerable<GZipEntry> GetEntries(Stream stream)
{
yield return new GZipEntry(new GZipFilePart(stream));
}
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.IO;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Compressor;
using SharpCompress.Compressor.Deflate;
namespace SharpCompress.Common.GZip
{
internal class GZipFilePart : FilePart
{
private string name;
private readonly Stream stream;
internal GZipFilePart(Stream stream)
{
ReadAndValidateGzipHeader(stream);
this.stream = stream;
}
internal DateTime? DateModified { get; private set; }
internal override string FilePartName
{
get { return name; }
}
internal override Stream GetCompressedStream()
{
return new DeflateStream(stream, CompressionMode.Decompress, CompressionLevel.Default, false);
}
internal override Stream GetRawStream()
{
return stream;
}
private void ReadAndValidateGzipHeader(Stream stream)
{
// read the header on the first read
byte[] header = new byte[10];
int n = stream.Read(header, 0, header.Length);
// workitem 8501: handle edge case (decompress empty stream)
if (n == 0)
return;
if (n != 10)
throw new ZlibException("Not a valid GZIP stream.");
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
throw new ZlibException("Bad GZIP header.");
Int32 timet = BitConverter.ToInt32(header, 4);
DateModified = TarHeader.Epoch.AddSeconds(timet);
if ((header[3] & 0x04) == 0x04)
{
// read and discard extra field
n = stream.Read(header, 0, 2); // 2-byte length field
Int16 extraLength = (Int16) (header[0] + header[1]*256);
byte[] extra = new byte[extraLength];
n = stream.Read(extra, 0, extra.Length);
if (n != extraLength)
{
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
}
}
if ((header[3] & 0x08) == 0x08)
name = ReadZeroTerminatedString(stream);
if ((header[3] & 0x10) == 0x010)
ReadZeroTerminatedString(stream);
if ((header[3] & 0x02) == 0x02)
stream.ReadByte(); // CRC16, ignore
}
private static string ReadZeroTerminatedString(Stream stream)
{
byte[] buf1 = new byte[1];
var list = new System.Collections.Generic.List<byte>();
bool done = false;
do
{
// workitem 7740
int n = stream.Read(buf1, 0, 1);
if (n != 1)
{
throw new ZlibException("Unexpected EOF reading GZIP header.");
}
if (buf1[0] == 0)
{
done = true;
}
else
{
list.Add(buf1[0]);
}
} while (!done);
byte[] a = list.ToArray();
return ArchiveEncoding.Default.GetString(a, 0, a.Length);
}
}
}

View File

@@ -0,0 +1,34 @@
using System.IO;
namespace SharpCompress.Common.GZip
{
public class GZipVolume : Volume
{
#if !DOTNET51
private readonly FileInfo fileInfo;
#endif
public GZipVolume(Stream stream, Options options)
: base(stream, options)
{
}
#if !DOTNET51
public GZipVolume(FileInfo fileInfo, Options options)
: base(fileInfo.OpenRead(), options)
{
this.fileInfo = fileInfo;
}
#endif
public override bool IsFirstVolume
{
get { return true; }
}
public override bool IsMultiVolume
{
get { return true; }
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace SharpCompress.Common
{
public interface IEntry
{
CompressionType CompressionType { get; }
DateTime? ArchivedTime { get; }
long CompressedSize { get; }
long Crc { get; }
DateTime? CreatedTime { get; }
string Key { get; }
bool IsDirectory { get; }
bool IsEncrypted { get; }
bool IsSplit { get; }
DateTime? LastAccessedTime { get; }
DateTime? LastModifiedTime { get; }
long Size { get; }
int? Attrib { get; }
}
}

View File

@@ -0,0 +1,8 @@
namespace SharpCompress.Common
{
internal interface IExtractionListener
{
void FireFilePartExtractionBegin(string name, long size, long compressedSize);
void FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes);
}
}

View File

@@ -0,0 +1,11 @@
using System;
#if !DOTNET51
using System.IO;
#endif
namespace SharpCompress.Common
{
public interface IVolume : IDisposable
{
}
}

View File

@@ -0,0 +1,10 @@
namespace SharpCompress.Common
{
public class IncompleteArchiveException : ArchiveException
{
public IncompleteArchiveException(string message)
: base(message)
{
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace SharpCompress.Common
{
public class InvalidFormatException : ExtractionException
{
public InvalidFormatException(string message)
: base(message)
{
}
public InvalidFormatException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace SharpCompress.Common
{
public class MultiVolumeExtractionException : ExtractionException
{
public MultiVolumeExtractionException(string message)
: base(message)
{
}
public MultiVolumeExtractionException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@@ -0,0 +1,10 @@
namespace SharpCompress.Common
{
public class MultipartStreamRequiredException : ExtractionException
{
public MultipartStreamRequiredException(string message)
: base(message)
{
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
namespace SharpCompress.Common
{
[Flags]
public enum Options
{
/// <summary>
/// No options specified
/// </summary>
None = 0,
/// <summary>
/// SharpCompress will keep the supplied streams open
/// </summary>
KeepStreamsOpen = 1,
/// <summary>
/// Look for RarArchive (Check for self-extracting archives or cases where RarArchive isn't at the start of the file)
/// </summary>
LookForHeader = 2,
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace SharpCompress.Common
{
public class PasswordProtectedException : ExtractionException
{
public PasswordProtectedException(string message)
: base(message)
{
}
public PasswordProtectedException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@@ -0,0 +1,20 @@
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class SignHeader : RarHeader
{
protected override void ReadFromReader(MarkingBinaryReader reader)
{
CreationTime = reader.ReadInt32();
ArcNameSize = reader.ReadInt16();
UserNameSize = reader.ReadInt16();
}
internal int CreationTime { get; private set; }
internal short ArcNameSize { get; private set; }
internal short UserNameSize { get; private set; }
}
}

View File

@@ -0,0 +1,36 @@
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class ArchiveHeader : RarHeader
{
internal const short mainHeaderSizeWithEnc = 7;
internal const short mainHeaderSize = 6;
protected override void ReadFromReader(MarkingBinaryReader reader)
{
HighPosAv = reader.ReadInt16();
PosAv = reader.ReadInt32();
if (ArchiveHeaderFlags.HasFlag(ArchiveFlags.ENCRYPTVER))
{
EncryptionVersion = reader.ReadByte();
}
}
internal ArchiveFlags ArchiveHeaderFlags
{
get { return (ArchiveFlags) base.Flags; }
}
internal short HighPosAv { get; private set; }
internal int PosAv { get; private set; }
internal byte EncryptionVersion { get; private set; }
public bool HasPassword
{
get { return ArchiveHeaderFlags.HasFlag(ArchiveFlags.PASSWORD); }
}
}
}

View File

@@ -0,0 +1,22 @@
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class CommentHeader : RarHeader
{
protected override void ReadFromReader(MarkingBinaryReader reader)
{
UnpSize = reader.ReadInt16();
UnpVersion = reader.ReadByte();
UnpMethod = reader.ReadByte();
CommCRC = reader.ReadInt16();
}
internal short UnpSize { get; private set; }
internal byte UnpVersion { get; private set; }
internal byte UnpMethod { get; private set; }
internal short CommCRC { get; private set; }
}
}

View File

@@ -0,0 +1,28 @@
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class EndArchiveHeader : RarHeader
{
protected override void ReadFromReader(MarkingBinaryReader reader)
{
if (EndArchiveFlags.HasFlag(EndArchiveFlags.EARC_DATACRC))
{
ArchiveCRC = reader.ReadInt32();
}
if (EndArchiveFlags.HasFlag(EndArchiveFlags.EARC_VOLNUMBER))
{
VolumeNumber = reader.ReadInt16();
}
}
internal EndArchiveFlags EndArchiveFlags
{
get { return (EndArchiveFlags) base.Flags; }
}
internal int? ArchiveCRC { get; private set; }
internal short? VolumeNumber { get; private set; }
}
}

View File

@@ -0,0 +1,233 @@
using System;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class FileHeader : RarHeader
{
private const byte SALT_SIZE = 8;
private const byte NEWLHD_SIZE = 32;
protected override void ReadFromReader(MarkingBinaryReader reader)
{
uint lowUncompressedSize = reader.ReadUInt32();
HostOS = (HostOS)reader.ReadByte();
FileCRC = reader.ReadUInt32();
FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadInt32());
RarVersion = reader.ReadByte();
PackingMethod = reader.ReadByte();
short nameSize = reader.ReadInt16();
FileAttributes = reader.ReadInt32();
uint highCompressedSize = 0;
uint highUncompressedkSize = 0;
if (FileFlags.HasFlag(FileFlags.LARGE))
{
highCompressedSize = reader.ReadUInt32();
highUncompressedkSize = reader.ReadUInt32();
}
else
{
if (lowUncompressedSize == 0xffffffff)
{
lowUncompressedSize = 0xffffffff;
highUncompressedkSize = int.MaxValue;
}
}
CompressedSize = UInt32To64(highCompressedSize, AdditionalSize);
UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize);
nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize;
byte[] fileNameBytes = reader.ReadBytes(nameSize);
switch (HeaderType)
{
case HeaderType.FileHeader:
{
if (FileFlags.HasFlag(FileFlags.UNICODE))
{
int length = 0;
while (length < fileNameBytes.Length
&& fileNameBytes[length] != 0)
{
length++;
}
if (length != nameSize)
{
length++;
FileName = FileNameDecoder.Decode(fileNameBytes, length);
}
else
{
FileName = DecodeDefault(fileNameBytes);
}
}
else
{
FileName = DecodeDefault(fileNameBytes);
}
FileName = ConvertPath(FileName, HostOS);
}
break;
case HeaderType.NewSubHeader:
{
int datasize = HeaderSize - NEWLHD_SIZE - nameSize;
if (FileFlags.HasFlag(FileFlags.SALT))
{
datasize -= SALT_SIZE;
}
if (datasize > 0)
{
SubData = reader.ReadBytes(datasize);
}
if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes))
{
RecoverySectors = SubData[8] + (SubData[9] << 8)
+ (SubData[10] << 16) + (SubData[11] << 24);
}
}
break;
}
if (FileFlags.HasFlag(FileFlags.SALT))
{
Salt = reader.ReadBytes(SALT_SIZE);
}
if (FileFlags.HasFlag(FileFlags.EXTTIME))
{
// verify that the end of the header hasn't been reached before reading the Extended Time.
// some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate.
if (ReadBytes + reader.CurrentReadByteCount <= HeaderSize - 2)
{
ushort extendedFlags = reader.ReadUInt16();
FileLastModifiedTime = ProcessExtendedTime(extendedFlags, FileLastModifiedTime, reader, 0);
FileCreatedTime = ProcessExtendedTime(extendedFlags, null, reader, 1);
FileLastAccessedTime = ProcessExtendedTime(extendedFlags, null, reader, 2);
FileArchivedTime = ProcessExtendedTime(extendedFlags, null, reader, 3);
}
}
}
//only the full .net framework will do other code pages than unicode/utf8
private string DecodeDefault(byte[] bytes)
{
return ArchiveEncoding.Default.GetString(bytes, 0, bytes.Length);
}
private long UInt32To64(uint x, uint y)
{
long l = x;
l <<= 32;
return l + y;
}
private static DateTime? ProcessExtendedTime(ushort extendedFlags, DateTime? time, MarkingBinaryReader reader,
int i)
{
uint rmode = (uint)extendedFlags >> (3 - i) * 4;
if ((rmode & 8) == 0)
{
return null;
}
if (i != 0)
{
uint DosTime = reader.ReadUInt32();
time = Utility.DosDateToDateTime(DosTime);
}
if ((rmode & 4) == 0)
{
time = time.Value.AddSeconds(1);
}
uint nanosecondHundreds = 0;
int count = (int)rmode & 3;
for (int j = 0; j < count; j++)
{
byte b = reader.ReadByte();
nanosecondHundreds |= (((uint)b) << ((j + 3 - count) * 8));
}
//10^-7 to 10^-3
return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4));
}
private static string ConvertPath(string path, HostOS os)
{
#if DOTNET51
return path.Replace('\\', '/');
#else
switch (os)
{
case HostOS.MacOS:
case HostOS.Unix:
{
if (Path.DirectorySeparatorChar == '\\')
{
return path.Replace('/', '\\');
}
}
break;
default:
{
if (Path.DirectorySeparatorChar == '/')
{
return path.Replace('\\', '/');
}
}
break;
}
return path;
#endif
}
internal long DataStartPosition { get; set; }
internal HostOS HostOS { get; private set; }
internal uint FileCRC { get; private set; }
internal DateTime? FileLastModifiedTime { get; private set; }
internal DateTime? FileCreatedTime { get; private set; }
internal DateTime? FileLastAccessedTime { get; private set; }
internal DateTime? FileArchivedTime { get; private set; }
internal byte RarVersion { get; private set; }
internal byte PackingMethod { get; private set; }
internal int FileAttributes { get; private set; }
internal FileFlags FileFlags
{
get { return (FileFlags)base.Flags; }
}
internal long CompressedSize { get; private set; }
internal long UncompressedSize { get; private set; }
internal string FileName { get; private set; }
internal byte[] SubData { get; private set; }
internal int RecoverySectors { get; private set; }
internal byte[] Salt { get; private set; }
public override string ToString()
{
return FileName;
}
public Stream PackedStream { get; set; }
}
}

View File

@@ -0,0 +1,78 @@
using System.Text;
namespace SharpCompress.Common.Rar.Headers
{
/// <summary>
/// This is for the crazy Rar encoding that I don't understand
/// </summary>
internal static class FileNameDecoder
{
internal static int GetChar(byte[] name, int pos)
{
return name[pos] & 0xff;
}
internal static string Decode(byte[] name, int encPos)
{
int decPos = 0;
int flags = 0;
int flagBits = 0;
int low = 0;
int high = 0;
int highByte = GetChar(name, encPos++);
StringBuilder buf = new StringBuilder();
while (encPos < name.Length)
{
if (flagBits == 0)
{
flags = GetChar(name, encPos++);
flagBits = 8;
}
switch (flags >> 6)
{
case 0:
buf.Append((char) (GetChar(name, encPos++)));
++decPos;
break;
case 1:
buf.Append((char) (GetChar(name, encPos++) + (highByte << 8)));
++decPos;
break;
case 2:
low = GetChar(name, encPos);
high = GetChar(name, encPos + 1);
buf.Append((char) ((high << 8) + low));
++decPos;
encPos += 2;
break;
case 3:
int length = GetChar(name, encPos++);
if ((length & 0x80) != 0)
{
int correction = GetChar(name, encPos++);
for (length = (length & 0x7f) + 2; length > 0 && decPos < name.Length; length--, decPos++)
{
low = (GetChar(name, decPos) + correction) & 0xff;
buf.Append((char) ((highByte << 8) + low));
}
}
else
{
for (length += 2; length > 0 && decPos < name.Length; length--, decPos++)
{
buf.Append((char) (GetChar(name, decPos)));
}
}
break;
}
flags = (flags << 2) & 0xff;
flagBits -= 2;
}
return buf.ToString();
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
namespace SharpCompress.Common.Rar.Headers
{
internal enum HeaderType
{
MarkHeader = 0x72,
ArchiveHeader = 0x73,
FileHeader = 0x74,
CommHeader = 0x75,
AvHeader = 0x76,
SubHeader = 0x77,
ProtectHeader = 0x78,
SignHeader = 0x79,
NewSubHeader = 0x7a,
EndArchiveHeader = 0x7b,
}
internal enum HeaderFlags : short
{
LONG_BLOCK = -0x8000,
}
[Flags]
internal enum ArchiveFlags
{
VOLUME = 0x0001,
COMMENT = 0x0002,
LOCK = 0x0004,
SOLID = 0x0008,
NEWNUMBERING = 0x0010,
AV = 0x0020,
PROTECT = 0x0040,
PASSWORD = 0x0080,
FIRSTVOLUME = 0x0100,
ENCRYPTVER = 0x0200,
}
internal enum HostOS
{
MSDOS = 0,
OS2 = 1,
Win32 = 2,
Unix = 3,
MacOS = 4,
BeOS = 5
}
[Flags]
internal enum FileFlags : ushort
{
SPLIT_BEFORE = 0x0001,
SPLIT_AFTER = 0x0002,
PASSWORD = 0x0004,
COMMENT = 0x0008,
SOLID = 0x0010,
WINDOWMASK = 0x00e0,
WINDOW64 = 0x0000,
WINDOW128 = 0x0020,
WINDOW256 = 0x0040,
WINDOW512 = 0x0060,
WINDOW1024 = 0x0080,
WINDOW2048 = 0x00a0,
WINDOW4096 = 0x00c0,
DIRECTORY = 0x00e0,
LARGE = 0x0100,
UNICODE = 0x0200,
SALT = 0x0400,
VERSION = 0x0800,
EXTTIME = 0x1000,
EXTFLAGS = 0x2000,
}
[Flags]
internal enum EndArchiveFlags
{
EARC_NEXT_VOLUME = 0x0001,
EARC_DATACRC = 0x0002,
EARC_REVSPACE = 0x0004,
EARC_VOLNUMBER = 0x0008,
}
}

View File

@@ -0,0 +1,59 @@
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class MarkHeader : RarHeader
{
protected override void ReadFromReader(MarkingBinaryReader reader)
{
}
internal bool IsValid()
{
if (!(HeadCRC == 0x6152))
{
return false;
}
if (!(HeaderType == HeaderType.MarkHeader))
{
return false;
}
if (!(Flags == 0x1a21))
{
return false;
}
if (!(HeaderSize == BaseBlockSize))
{
return false;
}
return true;
}
internal bool IsSignature()
{
bool valid = false;
/*byte[] d = new byte[BaseBlock.BaseBlockSize];
BinaryWriter writer = new BinaryWriter();
writer.Write(HeadCRC);
writer.Write((byte)HeaderType);
writer.Write(flags);
writer.Write(HeaderSize);
writer.Flush
if (d[0] == 0x52) {
if (d[1]==0x45 && d[2]==0x7e && d[3]==0x5e) {
oldFormat=true;
valid=true;
}
else if (d[1]==0x61 && d[2]==0x72 && d[3]==0x21 && d[4]==0x1a &&
d[5]==0x07 && d[6]==0x00) {
oldFormat=false;
valid=true;
}
}*/
return valid;
}
internal bool OldFormat { get; private set; }
}
}

View File

@@ -0,0 +1,59 @@
using System;
namespace SharpCompress.Common.Rar.Headers
{
internal class NewSubHeaderType : IEquatable<NewSubHeaderType>
{
internal static readonly NewSubHeaderType SUBHEAD_TYPE_CMT = new NewSubHeaderType('C', 'M', 'T');
//internal static final NewSubHeaderType SUBHEAD_TYPE_ACL = new NewSubHeaderType(new byte[]{'A','C','L'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_STREAM = new NewSubHeaderType(new byte[]{'S','T','M'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_UOWNER = new NewSubHeaderType(new byte[]{'U','O','W'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_AV = new NewSubHeaderType(new byte[]{'A','V'});
internal static readonly NewSubHeaderType SUBHEAD_TYPE_RR = new NewSubHeaderType('R', 'R');
//internal static final NewSubHeaderType SUBHEAD_TYPE_OS2EA = new NewSubHeaderType(new byte[]{'E','A','2'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_BEOSEA = new NewSubHeaderType(new byte[]{'E','A','B','E'});
private byte[] bytes;
private NewSubHeaderType(params char[] chars)
{
bytes = new byte[chars.Length];
for (int i = 0; i < chars.Length; ++i)
{
bytes[i] = (byte) chars[i];
}
}
internal bool Equals(byte[] bytes)
{
if (this.bytes.Length != bytes.Length)
{
return false;
}
for (int i = 0; i < bytes.Length; ++i)
{
if (this.bytes[i] != bytes[i])
{
return false;
}
}
return true;
}
#region IEquatable<NewSubHeaderType> Members
public bool Equals(NewSubHeaderType other)
{
return Equals(other.bytes);
}
#endregion
}
}

View File

@@ -0,0 +1,22 @@
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
// ProtectHeader is part of the Recovery Record feature
internal class ProtectHeader : RarHeader
{
protected override void ReadFromReader(MarkingBinaryReader reader)
{
Version = reader.ReadByte();
RecSectors = reader.ReadUInt16();
TotalBlocks = reader.ReadUInt32();
Mark = reader.ReadBytes(8);
}
internal uint DataSize { get { return AdditionalSize; } }
internal byte Version { get; private set; }
internal ushort RecSectors { get; private set; }
internal uint TotalBlocks { get; private set; }
internal byte[] Mark { get; private set; }
}
}

View File

@@ -0,0 +1,95 @@
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class RarHeader
{
internal const short BaseBlockSize = 7;
internal const short LONG_BLOCK = -0x8000;
private void FillBase(RarHeader baseHeader)
{
HeadCRC = baseHeader.HeadCRC;
HeaderType = baseHeader.HeaderType;
Flags = baseHeader.Flags;
HeaderSize = baseHeader.HeaderSize;
AdditionalSize = baseHeader.AdditionalSize;
ReadBytes = baseHeader.ReadBytes;
}
internal static RarHeader Create(MarkingBinaryReader reader)
{
try
{
RarHeader header = new RarHeader();
reader.Mark();
header.ReadFromReader(reader);
header.ReadBytes += reader.CurrentReadByteCount;
return header;
}
catch (EndOfStreamException)
{
return null;
}
}
protected virtual void ReadFromReader(MarkingBinaryReader reader)
{
HeadCRC = reader.ReadInt16();
HeaderType = (HeaderType)(reader.ReadByte() & 0xff);
Flags = reader.ReadInt16();
HeaderSize = reader.ReadInt16();
if (FlagUtility.HasFlag(Flags, LONG_BLOCK))
{
AdditionalSize = reader.ReadUInt32();
}
}
internal T PromoteHeader<T>(MarkingBinaryReader reader)
where T : RarHeader, new()
{
T header = new T();
header.FillBase(this);
reader.Mark();
header.ReadFromReader(reader);
header.ReadBytes += reader.CurrentReadByteCount;
int headerSizeDiff = header.HeaderSize - (int)header.ReadBytes;
if (headerSizeDiff > 0)
{
reader.ReadBytes(headerSizeDiff);
}
return header;
}
protected virtual void PostReadingBytes(MarkingBinaryReader reader)
{
}
/// <summary>
/// This is the number of bytes read when reading the header
/// </summary>
protected long ReadBytes { get; private set; }
protected short HeadCRC { get; private set; }
internal HeaderType HeaderType { get; private set; }
/// <summary>
/// Untyped flags. These should be typed when Promoting to another header
/// </summary>
protected short Flags { get; private set; }
protected short HeaderSize { get; private set; }
/// <summary>
/// This additional size of the header could be file data
/// </summary>
protected uint AdditionalSize { get; private set; }
}
}

View File

@@ -0,0 +1,243 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class RarHeaderFactory
{
private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136
internal RarHeaderFactory(StreamingMode mode, Options options, string password = null)
{
StreamingMode = mode;
Options = options;
Password = password;
}
private Options Options { get; set; }
public string Password { get; private set; }
internal StreamingMode StreamingMode { get; private set; }
internal bool IsEncrypted { get; private set; }
internal IEnumerable<RarHeader> ReadHeaders(Stream stream)
{
if (Options.HasFlag(Options.LookForHeader))
{
stream = CheckSFX(stream);
}
RarHeader header;
while ((header = ReadNextHeader(stream)) != null)
{
yield return header;
if (header.HeaderType == HeaderType.EndArchiveHeader)
{
yield break; // the end?
}
}
}
private Stream CheckSFX(Stream stream)
{
RewindableStream rewindableStream = GetRewindableStream(stream);
stream = rewindableStream;
BinaryReader reader = new BinaryReader(rewindableStream);
try
{
int count = 0;
while (true)
{
byte firstByte = reader.ReadByte();
if (firstByte == 0x52)
{
MemoryStream buffer = new MemoryStream();
byte[] nextThreeBytes = reader.ReadBytes(3);
if ((nextThreeBytes[0] == 0x45)
&& (nextThreeBytes[1] == 0x7E)
&& (nextThreeBytes[2] == 0x5E))
{
//old format and isvalid
buffer.WriteByte(0x52);
buffer.Write(nextThreeBytes, 0, 3);
rewindableStream.Rewind(buffer);
break;
}
byte[] secondThreeBytes = reader.ReadBytes(3);
if ((nextThreeBytes[0] == 0x61)
&& (nextThreeBytes[1] == 0x72)
&& (nextThreeBytes[2] == 0x21)
&& (secondThreeBytes[0] == 0x1A)
&& (secondThreeBytes[1] == 0x07)
&& (secondThreeBytes[2] == 0x00))
{
//new format and isvalid
buffer.WriteByte(0x52);
buffer.Write(nextThreeBytes, 0, 3);
buffer.Write(secondThreeBytes, 0, 3);
rewindableStream.Rewind(buffer);
break;
}
buffer.Write(nextThreeBytes, 0, 3);
buffer.Write(secondThreeBytes, 0, 3);
rewindableStream.Rewind(buffer);
}
if (count > MAX_SFX_SIZE)
{
break;
}
}
}
catch (Exception e)
{
if (!Options.HasFlag(Options.KeepStreamsOpen))
{
#if NET35
reader.Close();
#else
reader.Dispose();
#endif
}
throw new InvalidFormatException("Error trying to read rar signature.", e);
}
return stream;
}
private RewindableStream GetRewindableStream(Stream stream)
{
RewindableStream rewindableStream = stream as RewindableStream;
if (rewindableStream == null)
{
rewindableStream = new RewindableStream(stream);
}
return rewindableStream;
}
private RarHeader ReadNextHeader(Stream stream)
{
var reader = new RarCryptoBinaryReader(stream, Password);
if (IsEncrypted)
{
if (Password == null)
{
throw new CryptographicException("Encrypted Rar archive has no password specified.");
}
reader.SkipQueue();
byte[] salt = reader.ReadBytes(8);
reader.InitializeAes(salt);
}
RarHeader header = RarHeader.Create(reader);
if (header == null)
{
return null;
}
switch (header.HeaderType)
{
case HeaderType.ArchiveHeader:
{
var ah = header.PromoteHeader<ArchiveHeader>(reader);
IsEncrypted = ah.HasPassword;
return ah;
}
case HeaderType.MarkHeader:
{
return header.PromoteHeader<MarkHeader>(reader);
}
case HeaderType.ProtectHeader:
{
ProtectHeader ph = header.PromoteHeader<ProtectHeader>(reader);
// skip the recovery record data, we do not use it.
switch (StreamingMode)
{
case StreamingMode.Seekable:
{
reader.BaseStream.Position += ph.DataSize;
}
break;
case StreamingMode.Streaming:
{
reader.BaseStream.Skip(ph.DataSize);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
return ph;
}
case HeaderType.NewSubHeader:
{
FileHeader fh = header.PromoteHeader<FileHeader>(reader);
switch (StreamingMode)
{
case StreamingMode.Seekable:
{
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
//skip the data because it's useless?
reader.BaseStream.Skip(fh.CompressedSize);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
return fh;
}
case HeaderType.FileHeader:
{
FileHeader fh = header.PromoteHeader<FileHeader>(reader);
switch (StreamingMode)
{
case StreamingMode.Seekable:
{
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
if (fh.Salt == null)
{
fh.PackedStream = ms;
}
else
{
fh.PackedStream = new RarCryptoWrapper(ms, Password, fh.Salt);
}
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
return fh;
}
case HeaderType.EndArchiveHeader:
{
return header.PromoteHeader<EndArchiveHeader>(reader);
}
default:
{
throw new InvalidFormatException("Invalid Rar Header: " + header.HeaderType.ToString());
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
{
internal class AVHeader : RarHeader
{
protected override void ReadFromReader(MarkingBinaryReader reader)
{
UnpackVersion = reader.ReadByte();
Method = reader.ReadByte();
AVVersion = reader.ReadByte();
AVInfoCRC = reader.ReadInt32();
}
internal int AVInfoCRC { get; private set; }
internal byte UnpackVersion { get; private set; }
internal byte Method { get; private set; }
internal byte AVVersion { get; private set; }
}
}

View File

@@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar
{
internal class RarCryptoBinaryReader : MarkingBinaryReader
{
private RarRijndael rijndael;
private byte[] salt;
private readonly string password;
private readonly Queue<byte> data = new Queue<byte>();
public RarCryptoBinaryReader(Stream stream, string password )
: base(stream)
{
this.password = password;
}
protected bool UseEncryption
{
get { return salt != null; }
}
internal void InitializeAes(byte[] salt)
{
this.salt = salt;
rijndael = RarRijndael.InitializeFrom(password, salt);
}
public override byte[] ReadBytes(int count)
{
if (UseEncryption)
{
return ReadAndDecryptBytes(count);
}
return base.ReadBytes(count);
}
private byte[] ReadAndDecryptBytes(int count)
{
int queueSize = data.Count;
int sizeToRead = count - queueSize;
if (sizeToRead > 0)
{
int alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
for (int i = 0; i < alignedSize / 16; i++)
{
//long ax = System.currentTimeMillis();
byte[] cipherText = base.ReadBytes(16);
var readBytes = rijndael.ProcessBlock(cipherText);
foreach (var readByte in readBytes)
data.Enqueue(readByte);
}
}
var decryptedBytes = new byte[count];
for (int i = 0; i < count; i++)
{
decryptedBytes[i] = data.Dequeue();
}
return decryptedBytes;
}
public void ClearQueue()
{
data.Clear();
}
public void SkipQueue()
{
var position = BaseStream.Position;
BaseStream.Position = position + data.Count;
ClearQueue();
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace SharpCompress.Common.Rar
{
internal class RarCryptoWrapper : Stream
{
private readonly Stream actualStream;
private readonly byte[] salt;
private RarRijndael rijndael;
private readonly Queue<byte> data = new Queue<byte>();
public RarCryptoWrapper(Stream actualStream, string password, byte[] salt)
{
this.actualStream = actualStream;
this.salt = salt;
rijndael = RarRijndael.InitializeFrom(password, salt);
}
public override void Flush()
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (salt == null)
{
return actualStream.Read(buffer, offset, count);
}
return ReadAndDecrypt(buffer, offset, count);
}
public int ReadAndDecrypt(byte[] buffer, int offset, int count)
{
int queueSize = data.Count;
int sizeToRead = count - queueSize;
if (sizeToRead > 0)
{
int alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
for (int i = 0; i < alignedSize / 16; i++)
{
//long ax = System.currentTimeMillis();
byte[] cipherText = new byte[RarRijndael.CRYPTO_BLOCK_SIZE];
actualStream.Read(cipherText, 0, RarRijndael.CRYPTO_BLOCK_SIZE);
var readBytes = rijndael.ProcessBlock(cipherText);
foreach (var readByte in readBytes)
data.Enqueue(readByte);
}
for (int i = 0; i < count; i++)
buffer[offset + i] = data.Dequeue();
}
return count;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position { get; set; }
protected override void Dispose(bool disposing)
{
if (rijndael != null)
{
rijndael.Dispose();
rijndael = null;
}
base.Dispose(disposing);
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using SharpCompress.Common.Rar.Headers;
namespace SharpCompress.Common.Rar
{
public abstract class RarEntry : Entry
{
internal abstract FileHeader FileHeader { get; }
/// <summary>
/// The File's 32 bit CRC Hash
/// </summary>
public override long Crc
{
get { return FileHeader.FileCRC; }
}
/// <summary>
/// The path of the file internal to the Rar Archive.
/// </summary>
public override string Key
{
get { return FileHeader.FileName; }
}
/// <summary>
/// The entry last modified time in the archive, if recorded
/// </summary>
public override DateTime? LastModifiedTime
{
get { return FileHeader.FileLastModifiedTime; }
}
/// <summary>
/// The entry create time in the archive, if recorded
/// </summary>
public override DateTime? CreatedTime
{
get { return FileHeader.FileCreatedTime; }
}
/// <summary>
/// The entry last accessed time in the archive, if recorded
/// </summary>
public override DateTime? LastAccessedTime
{
get { return FileHeader.FileLastAccessedTime; }
}
/// <summary>
/// The entry time whend archived, if recorded
/// </summary>
public override DateTime? ArchivedTime
{
get { return FileHeader.FileArchivedTime; }
}
/// <summary>
/// Entry is password protected and encrypted and cannot be extracted.
/// </summary>
public override bool IsEncrypted
{
get { return FileHeader.FileFlags.HasFlag(FileFlags.PASSWORD); }
}
/// <summary>
/// Entry is password protected and encrypted and cannot be extracted.
/// </summary>
public override bool IsDirectory
{
get { return FileHeader.FileFlags.HasFlag(FileFlags.DIRECTORY); }
}
public override bool IsSplit
{
get { return FileHeader.FileFlags.HasFlag(FileFlags.SPLIT_AFTER); }
}
public override string ToString()
{
return string.Format("Entry Path: {0} Compressed Size: {1} Uncompressed Size: {2} CRC: {3}",
Key, CompressedSize, Size, Crc);
}
}
}

View File

@@ -0,0 +1,26 @@
using System.IO;
using SharpCompress.Common.Rar.Headers;
namespace SharpCompress.Common.Rar
{
/// <summary>
/// This represents a single file part that exists in a rar volume. A compressed file is one or many file parts that are spread across one or may rar parts.
/// </summary>
internal abstract class RarFilePart : FilePart
{
internal RarFilePart(MarkHeader mh, FileHeader fh)
{
MarkHeader = mh;
FileHeader = fh;
}
internal MarkHeader MarkHeader { get; private set; }
internal FileHeader FileHeader { get; private set; }
internal override Stream GetRawStream()
{
return null;
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
namespace SharpCompress.Common.Rar
{
internal class RarRijndael : IDisposable
{
internal const int CRYPTO_BLOCK_SIZE = 16;
private readonly string password;
private readonly byte[] salt;
private byte[] aesInitializationVector;
private RijndaelEngine rijndael;
private RarRijndael(string password, byte[] salt)
{
this.password = password;
this.salt = salt;
}
private byte[] ComputeHash(byte[] input)
{
var sha = new Sha1Digest();
sha.BlockUpdate(input, 0, input.Length);
byte[] result = new byte[sha.GetDigestSize()];
sha.DoFinal(result, 0);
return result;
}
private void Initialize()
{
rijndael = new RijndaelEngine();
aesInitializationVector = new byte[CRYPTO_BLOCK_SIZE];
int rawLength = 2*password.Length;
byte[] rawPassword = new byte[rawLength + 8];
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
for (int i = 0; i < password.Length; i++)
{
rawPassword[i*2] = passwordBytes[i];
rawPassword[i*2 + 1] = 0;
}
for (int i = 0; i < salt.Length; i++)
{
rawPassword[i + rawLength] = salt[i];
}
const int noOfRounds = (1 << 18);
IList<byte> bytes = new List<byte>();
byte[] digest;
//TODO slow code below, find ways to optimize
for (int i = 0; i < noOfRounds; i++)
{
bytes.AddRange(rawPassword);
bytes.AddRange(new[]
{
(byte) i, (byte) (i >> 8), (byte) (i >> CRYPTO_BLOCK_SIZE)
});
if (i%(noOfRounds/CRYPTO_BLOCK_SIZE) == 0)
{
digest = ComputeHash(bytes.ToArray());
aesInitializationVector[i/(noOfRounds/CRYPTO_BLOCK_SIZE)] = digest[19];
}
}
digest = ComputeHash(bytes.ToArray());
//slow code ends
byte[] aesKey = new byte[CRYPTO_BLOCK_SIZE];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
aesKey[i*4 + j] = (byte)
(((digest[i*4]*0x1000000) & 0xff000000 |
(uint) ((digest[i*4 + 1]*0x10000) & 0xff0000) |
(uint) ((digest[i*4 + 2]*0x100) & 0xff00) |
(uint) (digest[i*4 + 3] & 0xff)) >> (j*8));
rijndael.Init(false, new KeyParameter(aesKey));
}
public static RarRijndael InitializeFrom(string password, byte[] salt)
{
var rijndael = new RarRijndael(password, salt);
rijndael.Initialize();
return rijndael;
}
public byte[] ProcessBlock(byte[] cipherText)
{
var plainText = new byte[CRYPTO_BLOCK_SIZE];
var decryptedBytes = new List<byte>();
rijndael.ProcessBlock(cipherText, 0, plainText, 0);
for (int j = 0; j < plainText.Length; j++)
decryptedBytes.Add((byte) (plainText[j] ^ aesInitializationVector[j%16])); //32:114, 33:101
for (int j = 0; j < aesInitializationVector.Length; j++)
aesInitializationVector[j] = cipherText[j];
return decryptedBytes.ToArray();
}
public void Dispose()
{
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar
{
/// <summary>
/// A RarArchiveVolume is a single rar file that may or may not be a split RarArchive. A Rar Archive is one to many Rar Parts
/// </summary>
public abstract class RarVolume : Volume
{
private readonly RarHeaderFactory headerFactory;
internal RarVolume(StreamingMode mode, Stream stream, string password, Options options)
: base(stream, options)
{
headerFactory = new RarHeaderFactory(mode, options, password);
Password = password;
}
internal string Password { get; private set; }
internal StreamingMode Mode
{
get { return headerFactory.StreamingMode; }
}
internal abstract IEnumerable<RarFilePart> ReadFileParts();
internal abstract RarFilePart CreateFilePart(FileHeader fileHeader, MarkHeader markHeader);
internal IEnumerable<RarFilePart> GetVolumeFileParts()
{
MarkHeader previousMarkHeader = null;
foreach (RarHeader header in headerFactory.ReadHeaders(this.Stream))
{
switch (header.HeaderType)
{
case HeaderType.ArchiveHeader:
{
ArchiveHeader = header as ArchiveHeader;
}
break;
case HeaderType.MarkHeader:
{
previousMarkHeader = header as MarkHeader;
}
break;
case HeaderType.FileHeader:
{
FileHeader fh = header as FileHeader;
RarFilePart fp = CreateFilePart(fh, previousMarkHeader);
yield return fp;
}
break;
}
}
}
internal ArchiveHeader ArchiveHeader { get; private set; }
private void EnsureArchiveHeaderLoaded()
{
if (ArchiveHeader == null)
{
if (Mode == StreamingMode.Streaming)
{
throw new InvalidOperationException("ArchiveHeader should never been null in a streaming read.");
}
//we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream
GetVolumeFileParts().First();
Stream.Position = 0;
}
}
/// <summary>
/// RarArchive is the first volume of a multi-part archive.
/// Only Rar 3.0 format and higher
/// </summary>
public override bool IsFirstVolume
{
get
{
EnsureArchiveHeaderLoaded();
return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.FIRSTVOLUME);
}
}
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public override bool IsMultiVolume
{
get
{
EnsureArchiveHeaderLoaded();
return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.VOLUME);
}
}
/// <summary>
/// RarArchive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// Currently, SharpCompress cannot decompress SOLID archives.
/// </summary>
public bool IsSolidArchive
{
get
{
EnsureArchiveHeaderLoaded();
return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.SOLID);
}
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace SharpCompress.Common
{
public class ReaderExtractionEventArgs<T> : EventArgs
{
internal ReaderExtractionEventArgs(T entry)
{
Item = entry;
}
public T Item { get; private set; }
}
}

View File

@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Compressor.LZMA;
using SharpCompress.Compressor.LZMA.Utilites;
namespace SharpCompress.Common.SevenZip
{
internal class ArchiveDatabase
{
internal byte MajorVersion;
internal byte MinorVersion;
internal long StartPositionAfterHeader;
internal long DataStartPosition;
internal List<long> PackSizes = new List<long>();
internal List<uint?> PackCRCs = new List<uint?>();
internal List<CFolder> Folders = new List<CFolder>();
internal List<int> NumUnpackStreamsVector;
internal List<CFileItem> Files = new List<CFileItem>();
internal List<long> PackStreamStartPositions = new List<long>();
internal List<int> FolderStartFileIndex = new List<int>();
internal List<int> FileIndexToFolderIndexMap = new List<int>();
internal void Clear()
{
PackSizes.Clear();
PackCRCs.Clear();
Folders.Clear();
NumUnpackStreamsVector = null;
Files.Clear();
PackStreamStartPositions.Clear();
FolderStartFileIndex.Clear();
FileIndexToFolderIndexMap.Clear();
}
internal bool IsEmpty()
{
return PackSizes.Count == 0
&& PackCRCs.Count == 0
&& Folders.Count == 0
&& NumUnpackStreamsVector.Count == 0
&& Files.Count == 0;
}
private void FillStartPos()
{
PackStreamStartPositions.Clear();
long startPos = 0;
for (int i = 0; i < PackSizes.Count; i++)
{
PackStreamStartPositions.Add(startPos);
startPos += PackSizes[i];
}
}
private void FillFolderStartFileIndex()
{
FolderStartFileIndex.Clear();
FileIndexToFolderIndexMap.Clear();
int folderIndex = 0;
int indexInFolder = 0;
for (int i = 0; i < Files.Count; i++)
{
CFileItem file = Files[i];
bool emptyStream = !file.HasStream;
if (emptyStream && indexInFolder == 0)
{
FileIndexToFolderIndexMap.Add(-1);
continue;
}
if (indexInFolder == 0)
{
// v3.13 incorrectly worked with empty folders
// v4.07: Loop for skipping empty folders
for (; ; )
{
if (folderIndex >= Folders.Count)
throw new InvalidOperationException();
FolderStartFileIndex.Add(i); // check it
if (NumUnpackStreamsVector[folderIndex] != 0)
break;
folderIndex++;
}
}
FileIndexToFolderIndexMap.Add(folderIndex);
if (emptyStream)
continue;
indexInFolder++;
if (indexInFolder >= NumUnpackStreamsVector[folderIndex])
{
folderIndex++;
indexInFolder = 0;
}
}
}
public void Fill()
{
FillStartPos();
FillFolderStartFileIndex();
}
internal long GetFolderStreamPos(CFolder folder, int indexInFolder)
{
int index = folder.FirstPackStreamId + indexInFolder;
return DataStartPosition + PackStreamStartPositions[index];
}
internal long GetFolderFullPackSize(int folderIndex)
{
int packStreamIndex = Folders[folderIndex].FirstPackStreamId;
CFolder folder = Folders[folderIndex];
long size = 0;
for (int i = 0; i < folder.PackStreams.Count; i++)
size += PackSizes[packStreamIndex + i];
return size;
}
internal Stream GetFolderStream(Stream stream, CFolder folder, IPasswordProvider pw)
{
int packStreamIndex = folder.FirstPackStreamId;
long folderStartPackPos = GetFolderStreamPos(folder, 0);
List<long> packSizes = new List<long>();
for (int j = 0; j < folder.PackStreams.Count; j++)
packSizes.Add(PackSizes[packStreamIndex + j]);
return DecoderStreamHelper.CreateDecoderStream(stream, folderStartPackPos, packSizes.ToArray(), folder, pw);
}
private long GetFolderPackStreamSize(int folderIndex, int streamIndex)
{
return PackSizes[Folders[folderIndex].FirstPackStreamId + streamIndex];
}
private long GetFilePackSize(int fileIndex)
{
int folderIndex = FileIndexToFolderIndexMap[fileIndex];
if (folderIndex != -1)
if (FolderStartFileIndex[folderIndex] == fileIndex)
return GetFolderFullPackSize(folderIndex);
return 0;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
namespace SharpCompress.Common.SevenZip
{
internal class CBindPair
{
internal int InIndex;
internal int OutIndex;
}
}

View File

@@ -0,0 +1,10 @@
namespace SharpCompress.Common.SevenZip
{
internal class CCoderInfo
{
internal CMethodId MethodId;
internal byte[] Props;
internal int NumInStreams;
internal int NumOutStreams;
}
}

View File

@@ -0,0 +1,42 @@
using System;
namespace SharpCompress.Common.SevenZip
{
internal class CFileItem
{
public long Size { get; internal set; }
public uint? Attrib { get; internal set; }
public uint? Crc { get; internal set; }
public string Name { get; internal set; }
public bool HasStream { get; internal set; }
public bool IsDir { get; internal set; }
public bool CrcDefined
{
get { return Crc != null; }
}
public bool AttribDefined
{
get { return Attrib != null; }
}
public void SetAttrib(uint attrib)
{
this.Attrib = attrib;
}
public DateTime? CTime { get; internal set; }
public DateTime? ATime { get; internal set; }
public DateTime? MTime { get; internal set; }
public long? StartPos { get; internal set; }
public bool IsAnti { get; internal set; }
internal CFileItem()
{
HasStream = true;
}
}
}

View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using SharpCompress.Compressor.LZMA;
namespace SharpCompress.Common.SevenZip
{
internal class CFolder
{
internal List<CCoderInfo> Coders = new List<CCoderInfo>();
internal List<CBindPair> BindPairs = new List<CBindPair>();
internal List<int> PackStreams = new List<int>();
internal int FirstPackStreamId;
internal List<long> UnpackSizes = new List<long>();
internal uint? UnpackCRC;
internal bool UnpackCRCDefined
{
get { return UnpackCRC != null; }
}
public long GetUnpackSize()
{
if (UnpackSizes.Count == 0)
return 0;
for (int i = UnpackSizes.Count - 1; i >= 0; i--)
if (FindBindPairForOutStream(i) < 0)
return UnpackSizes[i];
throw new Exception();
}
public int GetNumOutStreams()
{
int count = 0;
for (int i = 0; i < Coders.Count; i++)
count += Coders[i].NumOutStreams;
return count;
}
public int FindBindPairForInStream(int inStreamIndex)
{
for (int i = 0; i < BindPairs.Count; i++)
if (BindPairs[i].InIndex == inStreamIndex)
return i;
return -1;
}
public int FindBindPairForOutStream(int outStreamIndex)
{
for (int i = 0; i < BindPairs.Count; i++)
if (BindPairs[i].OutIndex == outStreamIndex)
return i;
return -1;
}
public int FindPackStreamArrayIndex(int inStreamIndex)
{
for (int i = 0; i < PackStreams.Count; i++)
if (PackStreams[i] == inStreamIndex)
return i;
return -1;
}
public bool IsEncrypted()
{
for (int i = Coders.Count - 1; i >= 0; i--)
if (Coders[i].MethodId == CMethodId.kAES)
return true;
return false;
}
public bool CheckStructure()
{
const int kNumCodersMax = 32; // don't change it
const int kMaskSize = 32; // it must be >= kNumCodersMax
const int kNumBindsMax = 32;
if (Coders.Count > kNumCodersMax || BindPairs.Count > kNumBindsMax)
return false;
{
var v = new BitVector(BindPairs.Count + PackStreams.Count);
for (int i = 0; i < BindPairs.Count; i++)
if (v.GetAndSet(BindPairs[i].InIndex))
return false;
for (int i = 0; i < PackStreams.Count; i++)
if (v.GetAndSet(PackStreams[i]))
return false;
}
{
var v = new BitVector(UnpackSizes.Count);
for (int i = 0; i < BindPairs.Count; i++)
if (v.GetAndSet(BindPairs[i].OutIndex))
return false;
}
uint[] mask = new uint[kMaskSize];
{
List<int> inStreamToCoder = new List<int>();
List<int> outStreamToCoder = new List<int>();
for (int i = 0; i < Coders.Count; i++)
{
CCoderInfo coder = Coders[i];
for (int j = 0; j < coder.NumInStreams; j++)
inStreamToCoder.Add(i);
for (int j = 0; j < coder.NumOutStreams; j++)
outStreamToCoder.Add(i);
}
for (int i = 0; i < BindPairs.Count; i++)
{
CBindPair bp = BindPairs[i];
mask[inStreamToCoder[bp.InIndex]] |= (1u << outStreamToCoder[bp.OutIndex]);
}
}
for (int i = 0; i < kMaskSize; i++)
for (int j = 0; j < kMaskSize; j++)
if (((1u << j) & mask[i]) != 0)
mask[i] |= mask[j];
for (int i = 0; i < kMaskSize; i++)
if (((1u << i) & mask[i]) != 0)
return false;
return true;
}
}
}

View File

@@ -0,0 +1,55 @@
namespace SharpCompress.Common.SevenZip
{
internal struct CMethodId
{
public const ulong kCopyId = 0;
public const ulong kLzmaId = 0x030101;
public const ulong kLzma2Id = 0x21;
public const ulong kAESId = 0x06F10701;
public static readonly CMethodId kCopy = new CMethodId(kCopyId);
public static readonly CMethodId kLzma = new CMethodId(kLzmaId);
public static readonly CMethodId kLzma2 = new CMethodId(kLzma2Id);
public static readonly CMethodId kAES = new CMethodId(kAESId);
public readonly ulong Id;
public CMethodId(ulong id)
{
this.Id = id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is CMethodId && (CMethodId) obj == this;
}
public bool Equals(CMethodId other)
{
return Id == other.Id;
}
public static bool operator ==(CMethodId left, CMethodId right)
{
return left.Id == right.Id;
}
public static bool operator !=(CMethodId left, CMethodId right)
{
return left.Id != right.Id;
}
public int GetLength()
{
int bytes = 0;
for (ulong value = Id; value != 0; value >>= 8)
bytes++;
return bytes;
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Compressor.LZMA;
namespace SharpCompress.Common.SevenZip
{
internal struct CStreamSwitch : IDisposable
{
private ArchiveReader _archive;
private bool _needRemove;
private bool _active;
public void Dispose()
{
if (_active)
{
_active = false;
#if DEBUG
Log.WriteLine("[end of switch]");
#endif
}
if (_needRemove)
{
_needRemove = false;
_archive.DeleteByteStream();
}
}
public void Set(ArchiveReader archive, byte[] dataVector)
{
Dispose();
_archive = archive;
_archive.AddByteStream(dataVector, 0, dataVector.Length);
_needRemove = true;
_active = true;
}
public void Set(ArchiveReader archive, List<byte[]> dataVector)
{
Dispose();
_active = true;
byte external = archive.ReadByte();
if (external != 0)
{
int dataIndex = archive.ReadNum();
if (dataIndex < 0 || dataIndex >= dataVector.Count)
throw new InvalidOperationException();
#if DEBUG
Log.WriteLine("[switch to stream {0}]", dataIndex);
#endif
_archive = archive;
_archive.AddByteStream(dataVector[dataIndex], 0, dataVector[dataIndex].Length);
_needRemove = true;
_active = true;
}
else
{
#if DEBUG
Log.WriteLine("[inline data]");
#endif
}
}
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.IO;
using System.Text;
using SharpCompress.Compressor.LZMA;
namespace SharpCompress.Common.SevenZip
{
internal class DataReader
{
#region Static Methods
public static uint Get32(byte[] buffer, int offset)
{
return (uint) buffer[offset]
+ ((uint) buffer[offset + 1] << 8)
+ ((uint) buffer[offset + 2] << 16)
+ ((uint) buffer[offset + 3] << 24);
}
public static ulong Get64(byte[] buffer, int offset)
{
return (ulong) buffer[offset]
+ ((ulong) buffer[offset + 1] << 8)
+ ((ulong) buffer[offset + 2] << 16)
+ ((ulong) buffer[offset + 3] << 24)
+ ((ulong) buffer[offset + 4] << 32)
+ ((ulong) buffer[offset + 5] << 40)
+ ((ulong) buffer[offset + 6] << 48)
+ ((ulong) buffer[offset + 7] << 56);
}
#endregion
#region Variables
private byte[] _buffer;
private int _origin;
private int _offset;
private int _ending;
#endregion
#region Public Methods
public DataReader(byte[] buffer, int offset, int length)
{
_buffer = buffer;
_origin = offset;
_offset = offset;
_ending = offset + length;
}
public int Offset
{
get { return _offset; }
}
public Byte ReadByte()
{
if (_offset >= _ending)
throw new EndOfStreamException();
return _buffer[_offset++];
}
public void ReadBytes(byte[] buffer, int offset, int length)
{
if (length > _ending - _offset)
throw new EndOfStreamException();
while (length-- > 0)
buffer[offset++] = _buffer[_offset++];
}
public void SkipData(long size)
{
if (size > _ending - _offset)
throw new EndOfStreamException();
_offset += (int) size;
#if DEBUG
Log.WriteLine("SkipData {0}", size);
#endif
}
public void SkipData()
{
SkipData(checked((long) ReadNumber()));
}
public ulong ReadNumber()
{
if (_offset >= _ending)
throw new EndOfStreamException();
byte firstByte = _buffer[_offset++];
byte mask = 0x80;
ulong value = 0;
for (int i = 0; i < 8; i++)
{
if ((firstByte & mask) == 0)
{
ulong highPart = firstByte & (mask - 1u);
value += highPart << (i*8);
return value;
}
if (_offset >= _ending)
throw new EndOfStreamException();
value |= (ulong) _buffer[_offset++] << (8*i);
mask >>= 1;
}
return value;
}
public int ReadNum()
{
ulong value = ReadNumber();
if (value > Int32.MaxValue)
throw new NotSupportedException();
return (int) value;
}
public uint ReadUInt32()
{
if (_offset + 4 > _ending)
throw new EndOfStreamException();
uint res = Get32(_buffer, _offset);
_offset += 4;
return res;
}
public ulong ReadUInt64()
{
if (_offset + 8 > _ending)
throw new EndOfStreamException();
ulong res = Get64(_buffer, _offset);
_offset += 8;
return res;
}
public string ReadString()
{
int ending = _offset;
for (;;)
{
if (ending + 2 > _ending)
throw new EndOfStreamException();
if (_buffer[ending] == 0 && _buffer[ending + 1] == 0)
break;
ending += 2;
}
string str = Encoding.Unicode.GetString(_buffer, _offset, ending - _offset);
_offset = ending + 2;
return str;
}
#endregion
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
namespace SharpCompress.Common.SevenZip
{
public class SevenZipEntry : Entry
{
internal SevenZipEntry(SevenZipFilePart filePart)
{
this.FilePart = filePart;
}
internal SevenZipFilePart FilePart { get; private set; }
public override CompressionType CompressionType
{
get { return FilePart.CompressionType; }
}
public override long Crc
{
get { return FilePart.Header.Crc ?? 0; }
}
public override string Key
{
get { return FilePart.Header.Name; }
}
public override long CompressedSize
{
get { return 0; }
}
public override long Size
{
get { return (long) FilePart.Header.Size; }
}
public override DateTime? LastModifiedTime
{
get { return FilePart.Header.MTime; }
}
public override DateTime? CreatedTime
{
get { return null; }
}
public override DateTime? LastAccessedTime
{
get { return null; }
}
public override DateTime? ArchivedTime
{
get { return null; }
}
public override bool IsEncrypted
{
get { return false; }
}
public override bool IsDirectory
{
get { return FilePart.Header.IsDir; }
}
public override bool IsSplit
{
get { return false; }
}
public override int? Attrib
{
get { return (int) FilePart.Header.Attrib; }
}
internal override IEnumerable<FilePart> Parts
{
get { return FilePart.AsEnumerable<FilePart>(); }
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.IO;
using System.Linq;
using SharpCompress.IO;
namespace SharpCompress.Common.SevenZip
{
internal class SevenZipFilePart : FilePart
{
private CompressionType? type;
private Stream stream;
private ArchiveDatabase database;
internal SevenZipFilePart(Stream stream, ArchiveDatabase database, int index, CFileItem fileEntry)
{
this.stream = stream;
this.database = database;
Index = index;
Header = fileEntry;
if (Header.HasStream)
{
Folder = database.Folders[database.FileIndexToFolderIndexMap[index]];
}
}
internal Stream BaseStream { get; private set; }
internal CFileItem Header { get; private set; }
internal CFolder Folder { get; private set; }
internal int Index { get; private set; }
internal override string FilePartName
{
get { return Header.Name; }
}
internal override Stream GetRawStream()
{
return null;
}
internal override Stream GetCompressedStream()
{
if (!Header.HasStream)
{
return null;
}
var folderStream = database.GetFolderStream(stream, Folder, null);
int firstFileIndex = database.FolderStartFileIndex[database.Folders.IndexOf(Folder)];
int skipCount = Index - firstFileIndex;
long skipSize = 0;
for (int i = 0; i < skipCount; i++)
{
skipSize += database.Files[firstFileIndex + i].Size;
}
if (skipSize > 0)
{
folderStream.Skip(skipSize);
}
return new ReadOnlySubStream(folderStream, Header.Size);
}
public CompressionType CompressionType
{
get
{
if (type == null)
{
type = GetCompression();
}
return type.Value;
}
}
//copied from DecoderRegistry
private const uint k_Copy = 0x0;
private const uint k_Delta = 3;
private const uint k_LZMA2 = 0x21;
private const uint k_LZMA = 0x030101;
private const uint k_PPMD = 0x030401;
private const uint k_BCJ = 0x03030103;
private const uint k_BCJ2 = 0x0303011B;
private const uint k_Deflate = 0x040108;
private const uint k_BZip2 = 0x040202;
internal CompressionType GetCompression()
{
var coder = Folder.Coders.First();
switch (coder.MethodId.Id)
{
case k_LZMA:
case k_LZMA2:
{
return CompressionType.LZMA;
}
case k_PPMD:
{
return CompressionType.PPMd;
}
case k_BZip2:
{
return CompressionType.BZip2;
}
default:
throw new NotImplementedException();
}
}
}
}

View File

@@ -0,0 +1,12 @@
using System.IO;
namespace SharpCompress.Common.SevenZip
{
public class SevenZipVolume : Volume
{
public SevenZipVolume(Stream stream, Options options)
: base(stream, options)
{
}
}
}

View File

@@ -0,0 +1,18 @@
namespace SharpCompress.Common.Tar.Headers
{
internal enum EntryType : byte
{
File = 0,
OldFile = (byte) '0',
HardLink = (byte) '1',
SymLink = (byte) '2',
CharDevice = (byte) '3',
BlockDevice = (byte) '4',
Directory = (byte) '5',
Fifo = (byte) '6',
LongLink = (byte) 'K',
LongName = (byte) 'L',
SparseFile = (byte) 'S',
VolumeHeader = (byte) 'V',
}
}

View File

@@ -0,0 +1,223 @@
using System;
using System.IO;
using System.Text;
namespace SharpCompress.Common.Tar.Headers
{
internal class TarHeader
{
internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
internal string Name { get; set; }
//internal int Mode { get; set; }
//internal int UserId { get; set; }
//internal string UserName { get; set; }
//internal int GroupId { get; set; }
//internal string GroupName { get; set; }
internal long Size { get; set; }
internal DateTime LastModifiedTime { get; set; }
internal EntryType EntryType { get; set; }
internal Stream PackedStream { get; set; }
internal void Write(Stream output)
{
byte[] buffer = new byte[512];
WriteOctalBytes(511, buffer, 100, 8); // file mode
WriteOctalBytes(0, buffer, 108, 8); // owner ID
WriteOctalBytes(0, buffer, 116, 8); // group ID
//Encoding.UTF8.GetBytes("magic").CopyTo(buffer, 257);
if (Name.Length > 100)
{
// Set mock filename and filetype to indicate the next block is the actual name of the file
WriteStringBytes("././@LongLink", buffer, 0, 100);
buffer[156] = (byte)EntryType.LongName;
WriteOctalBytes(Name.Length + 1, buffer, 124, 12);
}
else
{
WriteStringBytes(Name, buffer, 0, 100);
WriteOctalBytes(Size, buffer, 124, 12);
var time = (long)(LastModifiedTime.ToUniversalTime() - Epoch).TotalSeconds;
WriteOctalBytes(time, buffer, 136, 12);
buffer[156] = (byte)EntryType;
if (Size >= 0x1FFFFFFFF)
{
byte[] bytes = BitConverter.GetBytes(Utility.HostToNetworkOrder(Size));
var bytes12 = new byte[12];
bytes.CopyTo(bytes12, 12 - bytes.Length);
bytes12[0] |= 0x80;
bytes12.CopyTo(buffer, 124);
}
}
int crc = RecalculateChecksum(buffer);
WriteOctalBytes(crc, buffer, 148, 8);
output.Write(buffer, 0, buffer.Length);
if (Name.Length > 100)
{
WriteLongFilenameHeader(output);
Name = Name.Substring(0, 100);
Write(output);
}
}
private void WriteLongFilenameHeader(Stream output)
{
byte[] nameBytes = ArchiveEncoding.Default.GetBytes(Name);
output.Write(nameBytes, 0, nameBytes.Length);
// pad to multiple of 512 bytes, and make sure a terminating null is added
int numPaddingBytes = 512 - (nameBytes.Length % 512);
if (numPaddingBytes == 0)
numPaddingBytes = 512;
output.Write(new byte[numPaddingBytes], 0, numPaddingBytes);
}
internal bool Read(BinaryReader reader)
{
byte[] buffer = reader.ReadBytes(512);
if (buffer.Length == 0)
{
return false;
}
if (buffer.Length < 512)
{
throw new InvalidOperationException();
}
Name = ArchiveEncoding.Default.GetString(buffer, 0, 100).TrimNulls();
//Mode = ReadASCIIInt32Base8(buffer, 100, 7);
//UserId = ReadASCIIInt32Base8(buffer, 108, 7);
//GroupId = ReadASCIIInt32Base8(buffer, 116, 7);
EntryType = (EntryType) buffer[156];
if ((buffer[124] & 0x80) == 0x80) // if size in binary
{
long sizeBigEndian = BitConverter.ToInt64(buffer, 0x80);
Size = Utility.NetworkToHostOrder(sizeBigEndian);
}
else
{
Size = ReadASCIIInt64Base8(buffer, 124, 11);
}
long unixTimeStamp = ReadASCIIInt64Base8(buffer, 136, 11);
LastModifiedTime = Epoch.AddSeconds(unixTimeStamp).ToLocalTime();
Magic = ArchiveEncoding.Default.GetString(buffer, 257, 6).TrimNulls();
if (!string.IsNullOrEmpty(Magic) && "ustar".Equals(Magic))
{
string namePrefix = ArchiveEncoding.Default.GetString(buffer, 345, 157);
namePrefix = namePrefix.TrimNulls();
if (!string.IsNullOrEmpty(namePrefix))
{
Name = namePrefix + "/" + Name;
}
}
if (EntryType != EntryType.LongName && Name.Length == 0)
{
return false;
}
return true;
}
private static void WriteStringBytes(string name, byte[] buffer, int offset, int length)
{
int i;
for (i = 0; i < length - 1 && i < name.Length; ++i)
{
buffer[offset + i] = (byte) name[i];
}
for (; i < length; ++i)
{
buffer[offset + i] = 0;
}
}
private static void WriteOctalBytes(long value, byte[] buffer, int offset, int length)
{
string val = Convert.ToString(value, 8);
int shift = length - val.Length - 1;
for (int i = 0; i < shift; i++)
{
buffer[offset + i] = (byte) ' ';
}
for (int i = 0; i < val.Length; i++)
{
buffer[offset + i + shift] = (byte) val[i];
}
}
private static int ReadASCIIInt32Base8(byte[] buffer, int offset, int count)
{
string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
if (string.IsNullOrEmpty(s))
{
return 0;
}
return Convert.ToInt32(s, 8);
}
private static long ReadASCIIInt64Base8(byte[] buffer, int offset, int count)
{
string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
if (string.IsNullOrEmpty(s))
{
return 0;
}
return Convert.ToInt64(s, 8);
}
private static long ReadASCIIInt64(byte[] buffer, int offset, int count)
{
string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
if (string.IsNullOrEmpty(s))
{
return 0;
}
return Convert.ToInt64(s);
}
internal static int RecalculateChecksum(byte[] buf)
{
// Set default value for checksum. That is 8 spaces.
Encoding.UTF8.GetBytes(" ").CopyTo(buf, 148);
// Calculate checksum
int headerChecksum = 0;
foreach (byte b in buf)
{
headerChecksum += b;
}
return headerChecksum;
}
internal static int RecalculateAltChecksum(byte[] buf)
{
Encoding.UTF8.GetBytes(" ").CopyTo(buf, 148);
int headerChecksum = 0;
foreach (byte b in buf)
{
if ((b & 0x80) == 0x80)
{
headerChecksum -= b ^ 0x80;
}
else
{
headerChecksum += b;
}
}
return headerChecksum;
}
public long? DataStartPosition { get; set; }
public string Magic { get; set; }
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Common.Tar
{
public class TarEntry : Entry
{
private readonly TarFilePart filePart;
private readonly CompressionType type;
internal TarEntry(TarFilePart filePart, CompressionType type)
{
this.filePart = filePart;
this.type = type;
}
public override CompressionType CompressionType
{
get { return type; }
}
public override long Crc
{
get { return 0; }
}
public override string Key
{
get { return filePart.Header.Name; }
}
public override long CompressedSize
{
get { return filePart.Header.Size; }
}
public override long Size
{
get { return filePart.Header.Size; }
}
public override DateTime? LastModifiedTime
{
get { return filePart.Header.LastModifiedTime; }
}
public override DateTime? CreatedTime
{
get { return null; }
}
public override DateTime? LastAccessedTime
{
get { return null; }
}
public override DateTime? ArchivedTime
{
get { return null; }
}
public override bool IsEncrypted
{
get { return false; }
}
public override bool IsDirectory
{
get { return filePart.Header.EntryType == EntryType.Directory; }
}
public override bool IsSplit
{
get { return false; }
}
internal override IEnumerable<FilePart> Parts
{
get { return filePart.AsEnumerable<FilePart>(); }
}
internal static IEnumerable<TarEntry> GetEntries(StreamingMode mode, Stream stream,
CompressionType compressionType)
{
foreach (TarHeader h in TarHeaderFactory.ReadHeader(mode, stream))
{
if (h != null)
{
if (mode == StreamingMode.Seekable)
{
yield return new TarEntry(new TarFilePart(h, stream), compressionType);
}
else
{
yield return new TarEntry(new TarFilePart(h, null), compressionType);
}
}
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System.IO;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Common.Tar
{
internal class TarFilePart : FilePart
{
private readonly Stream seekableStream;
internal TarFilePart(TarHeader header, Stream seekableStream)
{
this.seekableStream = seekableStream;
Header = header;
}
internal TarHeader Header { get; private set; }
internal override string FilePartName
{
get { return Header.Name; }
}
internal override Stream GetCompressedStream()
{
if (seekableStream != null)
{
seekableStream.Position = Header.DataStartPosition.Value;
return new ReadOnlySubStream(seekableStream, Header.Size);
}
return Header.PackedStream;
}
internal override Stream GetRawStream()
{
return null;
}
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Common.Tar
{
internal static class TarHeaderFactory
{
internal static IEnumerable<TarHeader> ReadHeader(StreamingMode mode, Stream stream)
{
while (true)
{
TarHeader header = null;
try
{
BinaryReader reader = new BinaryReader(stream);
header = new TarHeader();
if (!header.Read(reader))
{
yield break;
}
switch (mode)
{
case StreamingMode.Seekable:
{
header.DataStartPosition = reader.BaseStream.Position;
//skip to nearest 512
reader.BaseStream.Position += PadTo512(header.Size);
}
break;
case StreamingMode.Streaming:
{
header.PackedStream = new TarReadOnlySubStream(stream, header.Size);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
}
catch
{
header = null;
}
yield return header;
}
}
private static long PadTo512(long size)
{
int zeros = (int) (size%512);
if (zeros == 0)
{
return size;
}
return 512 - zeros + size;
}
}
}

View File

@@ -0,0 +1,105 @@
using System.IO;
namespace SharpCompress.Common.Tar
{
internal class TarReadOnlySubStream : Stream
{
private bool isDisposed;
private long amountRead;
public TarReadOnlySubStream(Stream stream, long bytesToRead)
{
Stream = stream;
BytesLeftToRead = bytesToRead;
}
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
isDisposed = true;
if (disposing)
{
long skipBytes = amountRead % 512;
if (skipBytes == 0)
{
return;
}
skipBytes = 512 - skipBytes;
if (skipBytes == 0)
{
return;
}
var buffer = new byte[skipBytes];
Stream.ReadFully(buffer);
}
}
private long BytesLeftToRead { get; set; }
public Stream Stream { get; private set; }
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override void Flush()
{
throw new System.NotSupportedException();
}
public override long Length
{
get { throw new System.NotSupportedException(); }
}
public override long Position
{
get { throw new System.NotSupportedException(); }
set { throw new System.NotSupportedException(); }
}
public override int Read(byte[] buffer, int offset, int count)
{
if (BytesLeftToRead < count)
{
count = (int)BytesLeftToRead;
}
int read = Stream.Read(buffer, offset, count);
if (read > 0)
{
BytesLeftToRead -= read;
amountRead += read;
}
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new System.NotSupportedException();
}
public override void SetLength(long value)
{
throw new System.NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new System.NotSupportedException();
}
}
}

View File

@@ -0,0 +1,12 @@
using System.IO;
namespace SharpCompress.Common.Tar
{
public class TarVolume : Volume
{
public TarVolume(Stream stream, Options options)
: base(stream, options)
{
}
}
}

View File

@@ -0,0 +1,51 @@
using System.IO;
using SharpCompress.IO;
namespace SharpCompress.Common
{
public abstract class Volume : IVolume
{
private readonly Stream actualStream;
internal Volume(Stream stream, Options options)
{
actualStream = stream;
Options = options;
}
internal Stream Stream
{
get { return new NonDisposingStream(actualStream); }
}
internal Options Options { get; private set; }
/// <summary>
/// RarArchive is the first volume of a multi-part archive.
/// Only Rar 3.0 format and higher
/// </summary>
public virtual bool IsFirstVolume
{
get { return true; }
}
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public virtual bool IsMultiVolume
{
get { return true; }
}
private bool disposed;
public void Dispose()
{
if (!Options.HasFlag(Options.KeepStreamsOpen) && !disposed)
{
actualStream.Dispose();
disposed = true;
}
}
}
}

View File

@@ -0,0 +1,52 @@
using System.IO;
namespace SharpCompress.Common.Zip.Headers
{
internal class DirectoryEndHeader : ZipHeader
{
public DirectoryEndHeader()
: base(ZipHeaderType.DirectoryEnd)
{
}
internal override void Read(BinaryReader reader)
{
VolumeNumber = reader.ReadUInt16();
FirstVolumeWithDirectory = reader.ReadUInt16();
TotalNumberOfEntriesInDisk = reader.ReadUInt16();
TotalNumberOfEntries = reader.ReadUInt16();
DirectorySize = reader.ReadUInt32();
DirectoryStartOffsetRelativeToDisk = reader.ReadUInt32();
CommentLength = reader.ReadUInt16();
Comment = reader.ReadBytes(CommentLength);
}
internal override void Write(BinaryWriter writer)
{
writer.Write(VolumeNumber);
writer.Write(FirstVolumeWithDirectory);
writer.Write(TotalNumberOfEntriesInDisk);
writer.Write(TotalNumberOfEntries);
writer.Write(DirectorySize);
writer.Write(DirectoryStartOffsetRelativeToDisk);
writer.Write(CommentLength);
writer.Write(Comment);
}
public ushort VolumeNumber { get; private set; }
public ushort FirstVolumeWithDirectory { get; private set; }
public ushort TotalNumberOfEntriesInDisk { get; private set; }
public uint DirectorySize { get; private set; }
public uint DirectoryStartOffsetRelativeToDisk { get; private set; }
public ushort CommentLength { get; private set; }
public byte[] Comment { get; private set; }
public ushort TotalNumberOfEntries { get; private set; }
}
}

Some files were not shown because too many files have changed in this diff Show More