Skip to content

Commit

Permalink
Merge pull request #191 from jskeet/lzip
Browse files Browse the repository at this point in the history
Initial read-only support for LZip
  • Loading branch information
adamhathcock authored Oct 14, 2016
2 parents 66420cd + d540f78 commit 9628ff9
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/SharpCompress/Common/CompressionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum CompressionType
LZMA,
BCJ,
BCJ2,
LZip,
Unknown
}
}
153 changes: 153 additions & 0 deletions src/SharpCompress/Compressors/LZMA/LZipStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using System;
using System.IO;

namespace SharpCompress.Compressors.LZMA
{
// TODO:
// - Write as well as read
// - Multi-volume support
// - Use of the data size / member size values at the end of the stream

/// <summary>
/// Stream supporting the LZIP format, as documented at http://www.nongnu.org/lzip/manual/lzip_manual.html
/// </summary>
public class LZipStream : Stream
{
private readonly Stream stream;
private bool disposed;
private readonly bool leaveOpen;

public LZipStream(Stream stream, CompressionMode mode)
: this(stream, mode, false)
{
}

public LZipStream(Stream stream, CompressionMode mode, bool leaveOpen)
{
if (mode != CompressionMode.Decompress)
{
throw new NotImplementedException("Only LZip decompression is currently supported");
}
Mode = mode;
this.leaveOpen = leaveOpen;
int dictionarySize = ValidateAndReadSize(stream);
if (dictionarySize == 0)
{
throw new IOException("Not an LZip stream");
}
byte[] properties = GetProperties(dictionarySize);
this.stream = new LzmaStream(properties, stream);
}

#region Stream methods

protected override void Dispose(bool disposing)
{
if (disposed)
{
return;
}
disposed = true;
if (disposing && !leaveOpen)
{
stream.Dispose();
}
}

public CompressionMode Mode { get; }

public override bool CanRead => stream.CanRead;

public override bool CanSeek => false;

public override bool CanWrite => false;

public override void Flush()
{
stream.Flush();
}

// TODO: Both Length and Position are sometimes feasible, but would require
// reading the output length when we initialize.
public override long Length { get { throw new NotImplementedException(); } }

public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

public override int Read(byte[] buffer, int offset, int count) => stream.Read(buffer, offset, count);

public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}

public override void SetLength(long value)
{
throw new NotImplementedException();
}

public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
#endregion

/// <summary>
/// Determines if the given stream is positioned at the start of a v1 LZip
/// file, as indicated by the ASCII characters "LZIP" and a version byte
/// of 1, followed by at least one byte.
/// </summary>
/// <param name="stream">The stream to read from. Must not be null.</param>
/// <returns><c>true</c> if the given stream is an LZip file, <c>false</c> otherwise.</returns>
public static bool IsLZipFile(Stream stream) => ValidateAndReadSize(stream) != 0;

/// <summary>
/// Reads the 6-byte header of the stream, and returns 0 if either the header
/// couldn't be read or it isn't a validate LZIP header, or the dictionary
/// size if it *is* a valid LZIP file.
/// </summary>
private static int ValidateAndReadSize(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
// Read the header
byte[] header = new byte[6];
int n = stream.Read(header, 0, header.Length);

// TODO: Handle reading only part of the header?

if (n != 6)
{
return 0;
}

if (header[0] != 'L' || header[1] != 'Z' || header[2] != 'I' || header[3] != 'P' || header[4] != 1 /* version 1 */)
{
return 0;
}
int basePower = header[5] & 0x1F;
int subtractionNumerator = (header[5] & 0xE0) >> 5;
return (1 << basePower) - subtractionNumerator * (1 << (basePower - 4));
}

/// <summary>
/// Creates a byte array to communicate the parameters and dictionary size to LzmaStream.
/// </summary>
private static byte[] GetProperties(int dictionarySize) =>
new byte[]
{
// Parameters as per http://www.nongnu.org/lzip/manual/lzip_manual.html#Stream-format
// but encoded as a single byte in the format LzmaStream expects.
// literal_context_bits = 3
// literal_pos_state_bits = 0
// pos_state_bits = 2
93,
// Dictionary size as 4-byte little-endian value
(byte)(dictionarySize & 0xff),
(byte)((dictionarySize >> 8) & 0xff),
(byte)((dictionarySize >> 16) & 0xff),
(byte)((dictionarySize >> 24) & 0xff)
};
}
}
13 changes: 13 additions & 0 deletions src/SharpCompress/Readers/ReaderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using SharpCompress.Readers.Rar;
using SharpCompress.Readers.Tar;
using SharpCompress.Readers.Zip;
using SharpCompress.Compressors.LZMA;

namespace SharpCompress.Readers
{
Expand Down Expand Up @@ -64,6 +65,18 @@ public static IReader Open(Stream stream, ReaderOptions options = null)
}
}

rewindableStream.Rewind(false);
if (LZipStream.IsLZipFile(rewindableStream))
{
rewindableStream.Rewind(false);
LZipStream testStream = new LZipStream(rewindableStream, CompressionMode.Decompress, true);
if (TarArchive.IsTarFile(testStream))
{
rewindableStream.Rewind(true);
return new TarReader(rewindableStream, options, CompressionType.LZip);
}
}

rewindableStream.Rewind(false);
if (RarArchive.IsRarFile(rewindableStream, options))
{
Expand Down
18 changes: 18 additions & 0 deletions src/SharpCompress/Readers/Tar/TarReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.Deflate;
using SharpCompress.IO;
using SharpCompress.Compressors.LZMA;

namespace SharpCompress.Readers.Tar
{
Expand Down Expand Up @@ -38,6 +39,10 @@ internal override Stream RequestInitialStream()
{
return new GZipStream(stream, CompressionMode.Decompress);
}
case CompressionType.LZip:
{
return new LZipStream(stream, CompressionMode.Decompress);
}
case CompressionType.None:
{
return stream;
Expand Down Expand Up @@ -87,6 +92,19 @@ public static TarReader Open(Stream stream, ReaderOptions options = null)
}
throw new InvalidFormatException("Not a tar file.");
}

rewindableStream.Rewind(false);
if (LZipStream.IsLZipFile(rewindableStream))
{
rewindableStream.Rewind(false);
LZipStream testStream = new LZipStream(rewindableStream, CompressionMode.Decompress, false);
if (TarArchive.IsTarFile(testStream))
{
rewindableStream.Rewind(true);
return new TarReader(rewindableStream, options, CompressionType.LZip);
}
throw new InvalidFormatException("Not a tar file.");
}
rewindableStream.Rewind(true);
return new TarReader(rewindableStream, options, CompressionType.None);
}
Expand Down
6 changes: 6 additions & 0 deletions test/SharpCompress.Test/Tar/TarReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public void Tar_GZip_Reader()
Read("Tar.tar.gz", CompressionType.GZip);
}

[Fact]
public void Tar_LZip_Reader()
{
Read("Tar.tar.lz", CompressionType.LZip);
}

[Fact]
public void Tar_BZip2_Entry_Stream()
{
Expand Down
Binary file added test/TestArchives/Archives/Tar.tar.lz
Binary file not shown.

0 comments on commit 9628ff9

Please sign in to comment.