190527PX Added zip export to database, set .net version to 4.5
This commit is contained in:
parent
e955e2ffd9
commit
dcecb0caea
|
@ -2,5 +2,5 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
<startup>
|
||||||
|
|
||||||
<supportedRuntime version="v2.0.50727"/></startup>
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.BZip2
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An example class to demonstrate compression and decompression of BZip2 streams.
|
||||||
|
/// </summary>
|
||||||
|
public static class BZip2
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Decompress the <paramref name="inStream">input</paramref> writing
|
||||||
|
/// uncompressed data to the <paramref name="outStream">output stream</paramref>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inStream">The readable stream containing data to decompress.</param>
|
||||||
|
/// <param name="outStream">The output stream to receive the decompressed data.</param>
|
||||||
|
/// <param name="isStreamOwner">Both streams are closed on completion if true.</param>
|
||||||
|
public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner)
|
||||||
|
{
|
||||||
|
if (inStream == null || outStream == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Null Stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (BZip2InputStream bzipInput = new BZip2InputStream(inStream))
|
||||||
|
{
|
||||||
|
bzipInput.IsStreamOwner = isStreamOwner;
|
||||||
|
Core.StreamUtils.Copy(bzipInput, outStream, new byte[4096]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (isStreamOwner)
|
||||||
|
{
|
||||||
|
// inStream is closed by the BZip2InputStream if stream owner
|
||||||
|
outStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compress the <paramref name="inStream">input stream</paramref> sending
|
||||||
|
/// result data to <paramref name="outStream">output stream</paramref>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inStream">The readable stream to compress.</param>
|
||||||
|
/// <param name="outStream">The output stream to receive the compressed data.</param>
|
||||||
|
/// <param name="isStreamOwner">Both streams are closed on completion if true.</param>
|
||||||
|
/// <param name="level">Block size acts as compression level (1 to 9) with 1 giving
|
||||||
|
/// the lowest compression and 9 the highest.</param>
|
||||||
|
public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int level)
|
||||||
|
{
|
||||||
|
if (inStream == null || outStream == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Null Stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (BZip2OutputStream bzipOutput = new BZip2OutputStream(outStream, level))
|
||||||
|
{
|
||||||
|
bzipOutput.IsStreamOwner = isStreamOwner;
|
||||||
|
Core.StreamUtils.Copy(inStream, bzipOutput, new byte[4096]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (isStreamOwner)
|
||||||
|
{
|
||||||
|
// outStream is closed by the BZip2OutputStream if stream owner
|
||||||
|
inStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
namespace ICSharpCode.SharpZipLib.BZip2
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines internal values for both compression and decompression
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class BZip2Constants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Random numbers used to randomise repetitive blocks
|
||||||
|
/// </summary>
|
||||||
|
public readonly static int[] RandomNumbers = {
|
||||||
|
619, 720, 127, 481, 931, 816, 813, 233, 566, 247,
|
||||||
|
985, 724, 205, 454, 863, 491, 741, 242, 949, 214,
|
||||||
|
733, 859, 335, 708, 621, 574, 73, 654, 730, 472,
|
||||||
|
419, 436, 278, 496, 867, 210, 399, 680, 480, 51,
|
||||||
|
878, 465, 811, 169, 869, 675, 611, 697, 867, 561,
|
||||||
|
862, 687, 507, 283, 482, 129, 807, 591, 733, 623,
|
||||||
|
150, 238, 59, 379, 684, 877, 625, 169, 643, 105,
|
||||||
|
170, 607, 520, 932, 727, 476, 693, 425, 174, 647,
|
||||||
|
73, 122, 335, 530, 442, 853, 695, 249, 445, 515,
|
||||||
|
909, 545, 703, 919, 874, 474, 882, 500, 594, 612,
|
||||||
|
641, 801, 220, 162, 819, 984, 589, 513, 495, 799,
|
||||||
|
161, 604, 958, 533, 221, 400, 386, 867, 600, 782,
|
||||||
|
382, 596, 414, 171, 516, 375, 682, 485, 911, 276,
|
||||||
|
98, 553, 163, 354, 666, 933, 424, 341, 533, 870,
|
||||||
|
227, 730, 475, 186, 263, 647, 537, 686, 600, 224,
|
||||||
|
469, 68, 770, 919, 190, 373, 294, 822, 808, 206,
|
||||||
|
184, 943, 795, 384, 383, 461, 404, 758, 839, 887,
|
||||||
|
715, 67, 618, 276, 204, 918, 873, 777, 604, 560,
|
||||||
|
951, 160, 578, 722, 79, 804, 96, 409, 713, 940,
|
||||||
|
652, 934, 970, 447, 318, 353, 859, 672, 112, 785,
|
||||||
|
645, 863, 803, 350, 139, 93, 354, 99, 820, 908,
|
||||||
|
609, 772, 154, 274, 580, 184, 79, 626, 630, 742,
|
||||||
|
653, 282, 762, 623, 680, 81, 927, 626, 789, 125,
|
||||||
|
411, 521, 938, 300, 821, 78, 343, 175, 128, 250,
|
||||||
|
170, 774, 972, 275, 999, 639, 495, 78, 352, 126,
|
||||||
|
857, 956, 358, 619, 580, 124, 737, 594, 701, 612,
|
||||||
|
669, 112, 134, 694, 363, 992, 809, 743, 168, 974,
|
||||||
|
944, 375, 748, 52, 600, 747, 642, 182, 862, 81,
|
||||||
|
344, 805, 988, 739, 511, 655, 814, 334, 249, 515,
|
||||||
|
897, 955, 664, 981, 649, 113, 974, 459, 893, 228,
|
||||||
|
433, 837, 553, 268, 926, 240, 102, 654, 459, 51,
|
||||||
|
686, 754, 806, 760, 493, 403, 415, 394, 687, 700,
|
||||||
|
946, 670, 656, 610, 738, 392, 760, 799, 887, 653,
|
||||||
|
978, 321, 576, 617, 626, 502, 894, 679, 243, 440,
|
||||||
|
680, 879, 194, 572, 640, 724, 926, 56, 204, 700,
|
||||||
|
707, 151, 457, 449, 797, 195, 791, 558, 945, 679,
|
||||||
|
297, 59, 87, 824, 713, 663, 412, 693, 342, 606,
|
||||||
|
134, 108, 571, 364, 631, 212, 174, 643, 304, 329,
|
||||||
|
343, 97, 430, 751, 497, 314, 983, 374, 822, 928,
|
||||||
|
140, 206, 73, 263, 980, 736, 876, 478, 430, 305,
|
||||||
|
170, 514, 364, 692, 829, 82, 855, 953, 676, 246,
|
||||||
|
369, 970, 294, 750, 807, 827, 150, 790, 288, 923,
|
||||||
|
804, 378, 215, 828, 592, 281, 565, 555, 710, 82,
|
||||||
|
896, 831, 547, 261, 524, 462, 293, 465, 502, 56,
|
||||||
|
661, 821, 976, 991, 658, 869, 905, 758, 745, 193,
|
||||||
|
768, 550, 608, 933, 378, 286, 215, 979, 792, 961,
|
||||||
|
61, 688, 793, 644, 986, 403, 106, 366, 905, 644,
|
||||||
|
372, 567, 466, 434, 645, 210, 389, 550, 919, 135,
|
||||||
|
780, 773, 635, 389, 707, 100, 626, 958, 165, 504,
|
||||||
|
920, 176, 193, 713, 857, 265, 203, 50, 668, 108,
|
||||||
|
645, 990, 626, 197, 510, 357, 358, 850, 858, 364,
|
||||||
|
936, 638
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When multiplied by compression parameter (1-9) gives the block size for compression
|
||||||
|
/// 9 gives the best compression but uses the most memory.
|
||||||
|
/// </summary>
|
||||||
|
public const int BaseBlockSize = 100000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int MaximumAlphaSize = 258;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int MaximumCodeLength = 23;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int RunA = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int RunB = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int GroupCount = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int GroupSize = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int NumberOfIterations = 4;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int MaximumSelectors = (2 + (900000 / GroupSize));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Backend constant
|
||||||
|
/// </summary>
|
||||||
|
public const int OvershootBytes = 20;
|
||||||
|
|
||||||
|
private BZip2Constants()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.BZip2
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// BZip2Exception represents exceptions specific to BZip2 classes and code.
|
||||||
|
/// </summary>
|
||||||
|
public class BZip2Exception : SharpZipBaseException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="BZip2Exception" />.
|
||||||
|
/// </summary>
|
||||||
|
public BZip2Exception()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="BZip2Exception" /> with its message string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
public BZip2Exception(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="BZip2Exception" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
/// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param>
|
||||||
|
public BZip2Exception(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,163 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Checksum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Computes Adler32 checksum for a stream of data. An Adler32
|
||||||
|
/// checksum is not as reliable as a CRC32 checksum, but a lot faster to
|
||||||
|
/// compute.
|
||||||
|
///
|
||||||
|
/// The specification for Adler32 may be found in RFC 1950.
|
||||||
|
/// ZLIB Compressed Data Format Specification version 3.3)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// From that document:
|
||||||
|
///
|
||||||
|
/// "ADLER32 (Adler-32 checksum)
|
||||||
|
/// This contains a checksum value of the uncompressed data
|
||||||
|
/// (excluding any dictionary data) computed according to Adler-32
|
||||||
|
/// algorithm. This algorithm is a 32-bit extension and improvement
|
||||||
|
/// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073
|
||||||
|
/// standard.
|
||||||
|
///
|
||||||
|
/// Adler-32 is composed of two sums accumulated per byte: s1 is
|
||||||
|
/// the sum of all bytes, s2 is the sum of all s1 values. Both sums
|
||||||
|
/// are done modulo 65521. s1 is initialized to 1, s2 to zero. The
|
||||||
|
/// Adler-32 checksum is stored as s2*65536 + s1 in most-
|
||||||
|
/// significant-byte first (network) order."
|
||||||
|
///
|
||||||
|
/// "8.2. The Adler-32 algorithm
|
||||||
|
///
|
||||||
|
/// The Adler-32 algorithm is much faster than the CRC32 algorithm yet
|
||||||
|
/// still provides an extremely low probability of undetected errors.
|
||||||
|
///
|
||||||
|
/// The modulo on unsigned long accumulators can be delayed for 5552
|
||||||
|
/// bytes, so the modulo operation time is negligible. If the bytes
|
||||||
|
/// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position
|
||||||
|
/// and order sensitive, unlike the first sum, which is just a
|
||||||
|
/// checksum. That 65521 is prime is important to avoid a possible
|
||||||
|
/// large class of two-byte errors that leave the check unchanged.
|
||||||
|
/// (The Fletcher checksum uses 255, which is not prime and which also
|
||||||
|
/// makes the Fletcher check insensitive to single byte changes 0 -
|
||||||
|
/// 255.)
|
||||||
|
///
|
||||||
|
/// The sum s1 is initialized to 1 instead of zero to make the length
|
||||||
|
/// of the sequence part of s2, so that the length does not have to be
|
||||||
|
/// checked separately. (Any sequence of zeroes has a Fletcher
|
||||||
|
/// checksum of zero.)"
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream"/>
|
||||||
|
/// <see cref="ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream"/>
|
||||||
|
public sealed class Adler32 : IChecksum
|
||||||
|
{
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// largest prime smaller than 65536
|
||||||
|
/// </summary>
|
||||||
|
private static readonly uint BASE = 65521;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CRC data checksum so far.
|
||||||
|
/// </summary>
|
||||||
|
private uint checkValue;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a default instance of <see cref="Adler32"></see>
|
||||||
|
/// </summary>
|
||||||
|
public Adler32()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the Adler32 data checksum as if no update was ever called.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
checkValue = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Adler32 data checksum computed so far.
|
||||||
|
/// </summary>
|
||||||
|
public long Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return checkValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the checksum with the byte b.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bval">
|
||||||
|
/// The data value to add. The high byte of the int is ignored.
|
||||||
|
/// </param>
|
||||||
|
public void Update(int bval)
|
||||||
|
{
|
||||||
|
// We could make a length 1 byte array and call update again, but I
|
||||||
|
// would rather not have that overhead
|
||||||
|
uint s1 = checkValue & 0xFFFF;
|
||||||
|
uint s2 = checkValue >> 16;
|
||||||
|
|
||||||
|
s1 = (s1 + ((uint)bval & 0xFF)) % BASE;
|
||||||
|
s2 = (s1 + s2) % BASE;
|
||||||
|
|
||||||
|
checkValue = (s2 << 16) + s1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the Adler32 data checksum with the bytes taken from
|
||||||
|
/// a block of data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Contains the data to update the checksum with.</param>
|
||||||
|
public void Update(byte[] buffer)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
Update(new ArraySegment<byte>(buffer, 0, buffer.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update Adler32 data checksum based on a portion of a block of data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "segment">
|
||||||
|
/// The chunk of data to add
|
||||||
|
/// </param>
|
||||||
|
public void Update(ArraySegment<byte> segment)
|
||||||
|
{
|
||||||
|
//(By Per Bothner)
|
||||||
|
uint s1 = checkValue & 0xFFFF;
|
||||||
|
uint s2 = checkValue >> 16;
|
||||||
|
var count = segment.Count;
|
||||||
|
var offset = segment.Offset;
|
||||||
|
while (count > 0)
|
||||||
|
{
|
||||||
|
// We can defer the modulo operation:
|
||||||
|
// s1 maximally grows from 65521 to 65521 + 255 * 3800
|
||||||
|
// s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31
|
||||||
|
int n = 3800;
|
||||||
|
if (n > count)
|
||||||
|
{
|
||||||
|
n = count;
|
||||||
|
}
|
||||||
|
count -= n;
|
||||||
|
while (--n >= 0)
|
||||||
|
{
|
||||||
|
s1 = s1 + (uint)(segment.Array[offset++] & 0xff);
|
||||||
|
s2 = s2 + s1;
|
||||||
|
}
|
||||||
|
s1 %= BASE;
|
||||||
|
s2 %= BASE;
|
||||||
|
}
|
||||||
|
checkValue = (s2 << 16) | s1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Checksum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CRC-32 with unreversed data and reversed output
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial:
|
||||||
|
/// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0.
|
||||||
|
///
|
||||||
|
/// Polynomials over GF(2) are represented in binary, one bit per coefficient,
|
||||||
|
/// with the lowest powers in the most significant bit. Then adding polynomials
|
||||||
|
/// is just exclusive-or, and multiplying a polynomial by x is a right shift by
|
||||||
|
/// one. If we call the above polynomial p, and represent a byte as the
|
||||||
|
/// polynomial q, also with the lowest power in the most significant bit (so the
|
||||||
|
/// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,
|
||||||
|
/// where a mod b means the remainder after dividing a by b.
|
||||||
|
///
|
||||||
|
/// This calculation is done using the shift-register method of multiplying and
|
||||||
|
/// taking the remainder. The register is initialized to zero, and for each
|
||||||
|
/// incoming bit, x^32 is added mod p to the register if the bit is a one (where
|
||||||
|
/// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by
|
||||||
|
/// x (which is shifting right by one and adding x^32 mod p if the bit shifted
|
||||||
|
/// out is a one). We start with the highest power (least significant bit) of
|
||||||
|
/// q and repeat for all eight bits of q.
|
||||||
|
///
|
||||||
|
/// The table is simply the CRC of all possible eight bit values. This is all
|
||||||
|
/// the information needed to generate CRC's on data a byte at a time for all
|
||||||
|
/// combinations of CRC register values and incoming bytes.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class BZip2Crc : IChecksum
|
||||||
|
{
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private const uint crcInit = 0xFFFFFFFF;
|
||||||
|
//const uint crcXor = 0x00000000;
|
||||||
|
|
||||||
|
private static readonly uint[] crcTable = {
|
||||||
|
0X00000000, 0X04C11DB7, 0X09823B6E, 0X0D4326D9,
|
||||||
|
0X130476DC, 0X17C56B6B, 0X1A864DB2, 0X1E475005,
|
||||||
|
0X2608EDB8, 0X22C9F00F, 0X2F8AD6D6, 0X2B4BCB61,
|
||||||
|
0X350C9B64, 0X31CD86D3, 0X3C8EA00A, 0X384FBDBD,
|
||||||
|
0X4C11DB70, 0X48D0C6C7, 0X4593E01E, 0X4152FDA9,
|
||||||
|
0X5F15ADAC, 0X5BD4B01B, 0X569796C2, 0X52568B75,
|
||||||
|
0X6A1936C8, 0X6ED82B7F, 0X639B0DA6, 0X675A1011,
|
||||||
|
0X791D4014, 0X7DDC5DA3, 0X709F7B7A, 0X745E66CD,
|
||||||
|
0X9823B6E0, 0X9CE2AB57, 0X91A18D8E, 0X95609039,
|
||||||
|
0X8B27C03C, 0X8FE6DD8B, 0X82A5FB52, 0X8664E6E5,
|
||||||
|
0XBE2B5B58, 0XBAEA46EF, 0XB7A96036, 0XB3687D81,
|
||||||
|
0XAD2F2D84, 0XA9EE3033, 0XA4AD16EA, 0XA06C0B5D,
|
||||||
|
0XD4326D90, 0XD0F37027, 0XDDB056FE, 0XD9714B49,
|
||||||
|
0XC7361B4C, 0XC3F706FB, 0XCEB42022, 0XCA753D95,
|
||||||
|
0XF23A8028, 0XF6FB9D9F, 0XFBB8BB46, 0XFF79A6F1,
|
||||||
|
0XE13EF6F4, 0XE5FFEB43, 0XE8BCCD9A, 0XEC7DD02D,
|
||||||
|
0X34867077, 0X30476DC0, 0X3D044B19, 0X39C556AE,
|
||||||
|
0X278206AB, 0X23431B1C, 0X2E003DC5, 0X2AC12072,
|
||||||
|
0X128E9DCF, 0X164F8078, 0X1B0CA6A1, 0X1FCDBB16,
|
||||||
|
0X018AEB13, 0X054BF6A4, 0X0808D07D, 0X0CC9CDCA,
|
||||||
|
0X7897AB07, 0X7C56B6B0, 0X71159069, 0X75D48DDE,
|
||||||
|
0X6B93DDDB, 0X6F52C06C, 0X6211E6B5, 0X66D0FB02,
|
||||||
|
0X5E9F46BF, 0X5A5E5B08, 0X571D7DD1, 0X53DC6066,
|
||||||
|
0X4D9B3063, 0X495A2DD4, 0X44190B0D, 0X40D816BA,
|
||||||
|
0XACA5C697, 0XA864DB20, 0XA527FDF9, 0XA1E6E04E,
|
||||||
|
0XBFA1B04B, 0XBB60ADFC, 0XB6238B25, 0XB2E29692,
|
||||||
|
0X8AAD2B2F, 0X8E6C3698, 0X832F1041, 0X87EE0DF6,
|
||||||
|
0X99A95DF3, 0X9D684044, 0X902B669D, 0X94EA7B2A,
|
||||||
|
0XE0B41DE7, 0XE4750050, 0XE9362689, 0XEDF73B3E,
|
||||||
|
0XF3B06B3B, 0XF771768C, 0XFA325055, 0XFEF34DE2,
|
||||||
|
0XC6BCF05F, 0XC27DEDE8, 0XCF3ECB31, 0XCBFFD686,
|
||||||
|
0XD5B88683, 0XD1799B34, 0XDC3ABDED, 0XD8FBA05A,
|
||||||
|
0X690CE0EE, 0X6DCDFD59, 0X608EDB80, 0X644FC637,
|
||||||
|
0X7A089632, 0X7EC98B85, 0X738AAD5C, 0X774BB0EB,
|
||||||
|
0X4F040D56, 0X4BC510E1, 0X46863638, 0X42472B8F,
|
||||||
|
0X5C007B8A, 0X58C1663D, 0X558240E4, 0X51435D53,
|
||||||
|
0X251D3B9E, 0X21DC2629, 0X2C9F00F0, 0X285E1D47,
|
||||||
|
0X36194D42, 0X32D850F5, 0X3F9B762C, 0X3B5A6B9B,
|
||||||
|
0X0315D626, 0X07D4CB91, 0X0A97ED48, 0X0E56F0FF,
|
||||||
|
0X1011A0FA, 0X14D0BD4D, 0X19939B94, 0X1D528623,
|
||||||
|
0XF12F560E, 0XF5EE4BB9, 0XF8AD6D60, 0XFC6C70D7,
|
||||||
|
0XE22B20D2, 0XE6EA3D65, 0XEBA91BBC, 0XEF68060B,
|
||||||
|
0XD727BBB6, 0XD3E6A601, 0XDEA580D8, 0XDA649D6F,
|
||||||
|
0XC423CD6A, 0XC0E2D0DD, 0XCDA1F604, 0XC960EBB3,
|
||||||
|
0XBD3E8D7E, 0XB9FF90C9, 0XB4BCB610, 0XB07DABA7,
|
||||||
|
0XAE3AFBA2, 0XAAFBE615, 0XA7B8C0CC, 0XA379DD7B,
|
||||||
|
0X9B3660C6, 0X9FF77D71, 0X92B45BA8, 0X9675461F,
|
||||||
|
0X8832161A, 0X8CF30BAD, 0X81B02D74, 0X857130C3,
|
||||||
|
0X5D8A9099, 0X594B8D2E, 0X5408ABF7, 0X50C9B640,
|
||||||
|
0X4E8EE645, 0X4A4FFBF2, 0X470CDD2B, 0X43CDC09C,
|
||||||
|
0X7B827D21, 0X7F436096, 0X7200464F, 0X76C15BF8,
|
||||||
|
0X68860BFD, 0X6C47164A, 0X61043093, 0X65C52D24,
|
||||||
|
0X119B4BE9, 0X155A565E, 0X18197087, 0X1CD86D30,
|
||||||
|
0X029F3D35, 0X065E2082, 0X0B1D065B, 0X0FDC1BEC,
|
||||||
|
0X3793A651, 0X3352BBE6, 0X3E119D3F, 0X3AD08088,
|
||||||
|
0X2497D08D, 0X2056CD3A, 0X2D15EBE3, 0X29D4F654,
|
||||||
|
0XC5A92679, 0XC1683BCE, 0XCC2B1D17, 0XC8EA00A0,
|
||||||
|
0XD6AD50A5, 0XD26C4D12, 0XDF2F6BCB, 0XDBEE767C,
|
||||||
|
0XE3A1CBC1, 0XE760D676, 0XEA23F0AF, 0XEEE2ED18,
|
||||||
|
0XF0A5BD1D, 0XF464A0AA, 0XF9278673, 0XFDE69BC4,
|
||||||
|
0X89B8FD09, 0X8D79E0BE, 0X803AC667, 0X84FBDBD0,
|
||||||
|
0X9ABC8BD5, 0X9E7D9662, 0X933EB0BB, 0X97FFAD0C,
|
||||||
|
0XAFB010B1, 0XAB710D06, 0XA6322BDF, 0XA2F33668,
|
||||||
|
0XBCB4666D, 0XB8757BDA, 0XB5365D03, 0XB1F740B4
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CRC data checksum so far.
|
||||||
|
/// </summary>
|
||||||
|
private uint checkValue;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a default instance of <see cref="BZip2Crc"></see>
|
||||||
|
/// </summary>
|
||||||
|
public BZip2Crc()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the CRC data checksum as if no update was ever called.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
checkValue = crcInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the CRC data checksum computed so far.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Reversed Out = true</remarks>
|
||||||
|
public long Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Tehcnically, the output should be:
|
||||||
|
//return (long)(~checkValue ^ crcXor);
|
||||||
|
// but x ^ 0 = x, so there is no point in adding
|
||||||
|
// the XOR operation
|
||||||
|
return (long)(~checkValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the checksum with the int bval.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "bval">
|
||||||
|
/// the byte is taken as the lower 8 bits of bval
|
||||||
|
/// </param>
|
||||||
|
/// <remarks>Reversed Data = false</remarks>
|
||||||
|
public void Update(int bval)
|
||||||
|
{
|
||||||
|
checkValue = unchecked(crcTable[(byte)(((checkValue >> 24) & 0xFF) ^ bval)] ^ (checkValue << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the CRC data checksum with the bytes taken from
|
||||||
|
/// a block of data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Contains the data to update the CRC with.</param>
|
||||||
|
public void Update(byte[] buffer)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
Update(new ArraySegment<byte>(buffer, 0, buffer.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update CRC data checksum based on a portion of a block of data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "segment">
|
||||||
|
/// The chunk of data to add
|
||||||
|
/// </param>
|
||||||
|
public void Update(ArraySegment<byte> segment)
|
||||||
|
{
|
||||||
|
var count = segment.Count;
|
||||||
|
var offset = segment.Offset;
|
||||||
|
|
||||||
|
while (--count >= 0)
|
||||||
|
Update(segment.Array[offset++]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Checksum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CRC-32 with reversed data and unreversed output
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial:
|
||||||
|
/// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0.
|
||||||
|
///
|
||||||
|
/// Polynomials over GF(2) are represented in binary, one bit per coefficient,
|
||||||
|
/// with the lowest powers in the most significant bit. Then adding polynomials
|
||||||
|
/// is just exclusive-or, and multiplying a polynomial by x is a right shift by
|
||||||
|
/// one. If we call the above polynomial p, and represent a byte as the
|
||||||
|
/// polynomial q, also with the lowest power in the most significant bit (so the
|
||||||
|
/// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,
|
||||||
|
/// where a mod b means the remainder after dividing a by b.
|
||||||
|
///
|
||||||
|
/// This calculation is done using the shift-register method of multiplying and
|
||||||
|
/// taking the remainder. The register is initialized to zero, and for each
|
||||||
|
/// incoming bit, x^32 is added mod p to the register if the bit is a one (where
|
||||||
|
/// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by
|
||||||
|
/// x (which is shifting right by one and adding x^32 mod p if the bit shifted
|
||||||
|
/// out is a one). We start with the highest power (least significant bit) of
|
||||||
|
/// q and repeat for all eight bits of q.
|
||||||
|
///
|
||||||
|
/// The table is simply the CRC of all possible eight bit values. This is all
|
||||||
|
/// the information needed to generate CRC's on data a byte at a time for all
|
||||||
|
/// combinations of CRC register values and incoming bytes.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class Crc32 : IChecksum
|
||||||
|
{
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private static readonly uint crcInit = 0xFFFFFFFF;
|
||||||
|
private static readonly uint crcXor = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
private static readonly uint[] crcTable = {
|
||||||
|
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419,
|
||||||
|
0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
|
||||||
|
0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07,
|
||||||
|
0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||||
|
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
|
||||||
|
0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
|
||||||
|
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
|
||||||
|
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||||
|
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
|
||||||
|
0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
|
||||||
|
0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
|
||||||
|
0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||||
|
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
|
||||||
|
0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
|
||||||
|
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
|
||||||
|
0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||||
|
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
|
||||||
|
0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
|
||||||
|
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
|
||||||
|
0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||||
|
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
|
||||||
|
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
|
||||||
|
0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
|
||||||
|
0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||||
|
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
|
||||||
|
0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
|
||||||
|
0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
|
||||||
|
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||||
|
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
|
||||||
|
0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
|
||||||
|
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
|
||||||
|
0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||||
|
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
|
||||||
|
0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
|
||||||
|
0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
|
||||||
|
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||||
|
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
|
||||||
|
0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
|
||||||
|
0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
|
||||||
|
0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||||
|
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
|
||||||
|
0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
|
||||||
|
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
|
||||||
|
0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||||
|
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
|
||||||
|
0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
|
||||||
|
0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
|
||||||
|
0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||||
|
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
|
||||||
|
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
|
||||||
|
0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
|
||||||
|
0x2D02EF8D
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CRC data checksum so far.
|
||||||
|
/// </summary>
|
||||||
|
private uint checkValue;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
internal static uint ComputeCrc32(uint oldCrc, byte bval)
|
||||||
|
{
|
||||||
|
return (uint)(Crc32.crcTable[(oldCrc ^ bval) & 0xFF] ^ (oldCrc >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a default instance of <see cref="Crc32"></see>
|
||||||
|
/// </summary>
|
||||||
|
public Crc32()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the CRC data checksum as if no update was ever called.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
checkValue = crcInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the CRC data checksum computed so far.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Reversed Out = false</remarks>
|
||||||
|
public long Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (long)(checkValue ^ crcXor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the checksum with the int bval.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "bval">
|
||||||
|
/// the byte is taken as the lower 8 bits of bval
|
||||||
|
/// </param>
|
||||||
|
/// <remarks>Reversed Data = true</remarks>
|
||||||
|
public void Update(int bval)
|
||||||
|
{
|
||||||
|
checkValue = unchecked(crcTable[(checkValue ^ bval) & 0xFF] ^ (checkValue >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the CRC data checksum with the bytes taken from
|
||||||
|
/// a block of data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Contains the data to update the CRC with.</param>
|
||||||
|
public void Update(byte[] buffer)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
Update(new ArraySegment<byte>(buffer, 0, buffer.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update CRC data checksum based on a portion of a block of data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "segment">
|
||||||
|
/// The chunk of data to add
|
||||||
|
/// </param>
|
||||||
|
public void Update(ArraySegment<byte> segment)
|
||||||
|
{
|
||||||
|
var count = segment.Count;
|
||||||
|
var offset = segment.Offset;
|
||||||
|
|
||||||
|
while (--count >= 0)
|
||||||
|
Update(segment.Array[offset++]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Checksum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface to compute a data checksum used by checked input/output streams.
|
||||||
|
/// A data checksum can be updated by one byte or with a byte array. After each
|
||||||
|
/// update the value of the current checksum can be returned by calling
|
||||||
|
/// <code>getValue</code>. The complete checksum object can also be reset
|
||||||
|
/// so it can be used again with new data.
|
||||||
|
/// </summary>
|
||||||
|
public interface IChecksum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the data checksum as if no update was ever called.
|
||||||
|
/// </summary>
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the data checksum computed so far.
|
||||||
|
/// </summary>
|
||||||
|
long Value
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds one byte to the data checksum.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "bval">
|
||||||
|
/// the data value to add. The high byte of the int is ignored.
|
||||||
|
/// </param>
|
||||||
|
void Update(int bval);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the data checksum with the bytes taken from the array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// buffer an array of bytes
|
||||||
|
/// </param>
|
||||||
|
void Update(byte[] buffer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the byte array to the data checksum.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "segment">
|
||||||
|
/// The chunk of data to add
|
||||||
|
/// </param>
|
||||||
|
void Update(ArraySegment<byte> segment);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SharpZipBaseException is the base exception class for SharpZipLib.
|
||||||
|
/// All library exceptions are derived from this.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>NOTE: Not all exceptions thrown will be derived from this class.
|
||||||
|
/// A variety of other exceptions are possible for example <see cref="ArgumentNullException"></see></remarks>
|
||||||
|
public class SharpZipBaseException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the SharpZipBaseException class.
|
||||||
|
/// </summary>
|
||||||
|
public SharpZipBaseException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the SharpZipBaseException class with a specified error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
public SharpZipBaseException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the SharpZipBaseException class with a specified
|
||||||
|
/// error message and a reference to the inner exception that is the cause of this exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
/// <param name="innerException">The inner exception</param>
|
||||||
|
public SharpZipBaseException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that an error occured during decoding of a input stream due to corrupt
|
||||||
|
/// data or (unintentional) library incompability.
|
||||||
|
/// </summary>
|
||||||
|
public class StreamDecodingException : SharpZipBaseException
|
||||||
|
{
|
||||||
|
private const string GenericMessage = "Input stream could not be decoded";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the StreamDecodingException with a generic message
|
||||||
|
/// </summary>
|
||||||
|
public StreamDecodingException() : base(GenericMessage) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the StreamDecodingException class with a specified error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
public StreamDecodingException(string message) : base(message) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the StreamDecodingException class with a specified
|
||||||
|
/// error message and a reference to the inner exception that is the cause of this exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
/// <param name="innerException">The inner exception</param>
|
||||||
|
public StreamDecodingException(string message, Exception innerException) : base(message, innerException) { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the input stream could not decoded due to known library incompability or missing features
|
||||||
|
/// </summary>
|
||||||
|
public class StreamUnsupportedException : StreamDecodingException
|
||||||
|
{
|
||||||
|
private const string GenericMessage = "Input stream is in a unsupported format";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the StreamUnsupportedException with a generic message
|
||||||
|
/// </summary>
|
||||||
|
public StreamUnsupportedException() : base(GenericMessage) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the StreamUnsupportedException class with a specified error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
public StreamUnsupportedException(string message) : base(message) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the StreamUnsupportedException class with a specified
|
||||||
|
/// error message and a reference to the inner exception that is the cause of this exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
/// <param name="innerException">The inner exception</param>
|
||||||
|
public StreamUnsupportedException(string message, Exception innerException) : base(message, innerException) { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the input stream could not decoded due to the stream ending before enough data had been provided
|
||||||
|
/// </summary>
|
||||||
|
public class UnexpectedEndOfStreamException : StreamDecodingException
|
||||||
|
{
|
||||||
|
private const string GenericMessage = "Input stream ended unexpectedly";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the UnexpectedEndOfStreamException with a generic message
|
||||||
|
/// </summary>
|
||||||
|
public UnexpectedEndOfStreamException() : base(GenericMessage) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the UnexpectedEndOfStreamException class with a specified error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
public UnexpectedEndOfStreamException(string message) : base(message) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the UnexpectedEndOfStreamException class with a specified
|
||||||
|
/// error message and a reference to the inner exception that is the cause of this exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
/// <param name="innerException">The inner exception</param>
|
||||||
|
public UnexpectedEndOfStreamException(string message, Exception innerException) : base(message, innerException) { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a value was outside of the expected range when decoding an input stream
|
||||||
|
/// </summary>
|
||||||
|
public class ValueOutOfRangeException : StreamDecodingException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the ValueOutOfRangeException class naming the the causing variable
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nameOfValue">Name of the variable, use: nameof()</param>
|
||||||
|
public ValueOutOfRangeException(string nameOfValue)
|
||||||
|
: base($"{nameOfValue} out of range") { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the ValueOutOfRangeException class naming the the causing variable,
|
||||||
|
/// it's current value and expected range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nameOfValue">Name of the variable, use: nameof()</param>
|
||||||
|
/// <param name="value">The invalid value</param>
|
||||||
|
/// <param name="maxValue">Expected maximum value</param>
|
||||||
|
/// <param name="minValue">Expected minimum value</param>
|
||||||
|
public ValueOutOfRangeException(string nameOfValue, long value, long maxValue, long minValue = 0)
|
||||||
|
: this(nameOfValue, value.ToString(), maxValue.ToString(), minValue.ToString()) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the ValueOutOfRangeException class naming the the causing variable,
|
||||||
|
/// it's current value and expected range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nameOfValue">Name of the variable, use: nameof()</param>
|
||||||
|
/// <param name="value">The invalid value</param>
|
||||||
|
/// <param name="maxValue">Expected maximum value</param>
|
||||||
|
/// <param name="minValue">Expected minimum value</param>
|
||||||
|
public ValueOutOfRangeException(string nameOfValue, string value, string maxValue, string minValue = "0") :
|
||||||
|
base($"{nameOfValue} out of range: {value}, should be {minValue}..{maxValue}")
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private ValueOutOfRangeException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueOutOfRangeException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,545 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Core
|
||||||
|
{
|
||||||
|
#region EventArgs
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event arguments for scanning.
|
||||||
|
/// </summary>
|
||||||
|
public class ScanEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ScanEventArgs"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The file or directory name.</param>
|
||||||
|
public ScanEventArgs(string name)
|
||||||
|
{
|
||||||
|
name_ = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file or directory name for this event.
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return name_; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get set a value indicating if scanning should continue or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool ContinueRunning
|
||||||
|
{
|
||||||
|
get { return continueRunning_; }
|
||||||
|
set { continueRunning_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private string name_;
|
||||||
|
private bool continueRunning_ = true;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event arguments during processing of a single file or directory.
|
||||||
|
/// </summary>
|
||||||
|
public class ProgressEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ScanEventArgs"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The file or directory name if known.</param>
|
||||||
|
/// <param name="processed">The number of bytes processed so far</param>
|
||||||
|
/// <param name="target">The total number of bytes to process, 0 if not known</param>
|
||||||
|
public ProgressEventArgs(string name, long processed, long target)
|
||||||
|
{
|
||||||
|
name_ = name;
|
||||||
|
processed_ = processed;
|
||||||
|
target_ = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name for this event if known.
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return name_; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get set a value indicating wether scanning should continue or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool ContinueRunning
|
||||||
|
{
|
||||||
|
get { return continueRunning_; }
|
||||||
|
set { continueRunning_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a percentage representing how much of the <see cref="Target"></see> has been processed
|
||||||
|
/// </summary>
|
||||||
|
/// <value>0.0 to 100.0 percent; 0 if target is not known.</value>
|
||||||
|
public float PercentComplete
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
float result;
|
||||||
|
if (target_ <= 0)
|
||||||
|
{
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = ((float)processed_ / (float)target_) * 100.0f;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of bytes processed so far
|
||||||
|
/// </summary>
|
||||||
|
public long Processed
|
||||||
|
{
|
||||||
|
get { return processed_; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of bytes to process.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Target may be 0 or negative if the value isnt known.</remarks>
|
||||||
|
public long Target
|
||||||
|
{
|
||||||
|
get { return target_; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private string name_;
|
||||||
|
private long processed_;
|
||||||
|
private long target_;
|
||||||
|
private bool continueRunning_ = true;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event arguments for directories.
|
||||||
|
/// </summary>
|
||||||
|
public class DirectoryEventArgs : ScanEventArgs
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize an instance of <see cref="DirectoryEventArgs"></see>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name for this directory.</param>
|
||||||
|
/// <param name="hasMatchingFiles">Flag value indicating if any matching files are contained in this directory.</param>
|
||||||
|
public DirectoryEventArgs(string name, bool hasMatchingFiles)
|
||||||
|
: base(name)
|
||||||
|
{
|
||||||
|
hasMatchingFiles_ = hasMatchingFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a value indicating if the directory contains any matching files or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasMatchingFiles
|
||||||
|
{
|
||||||
|
get { return hasMatchingFiles_; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
bool hasMatchingFiles_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Arguments passed when scan failures are detected.
|
||||||
|
/// </summary>
|
||||||
|
public class ScanFailureEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ScanFailureEventArgs"></see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to apply.</param>
|
||||||
|
/// <param name="e">The exception to use.</param>
|
||||||
|
public ScanFailureEventArgs(string name, Exception e)
|
||||||
|
{
|
||||||
|
name_ = name;
|
||||||
|
exception_ = e;
|
||||||
|
continueRunning_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The applicable name.
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return name_; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The applicable exception.
|
||||||
|
/// </summary>
|
||||||
|
public Exception Exception
|
||||||
|
{
|
||||||
|
get { return exception_; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set a value indicating wether scanning should continue.
|
||||||
|
/// </summary>
|
||||||
|
public bool ContinueRunning
|
||||||
|
{
|
||||||
|
get { return continueRunning_; }
|
||||||
|
set { continueRunning_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private string name_;
|
||||||
|
private Exception exception_;
|
||||||
|
private bool continueRunning_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion EventArgs
|
||||||
|
|
||||||
|
#region Delegates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate invoked before starting to process a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The source of the event</param>
|
||||||
|
/// <param name="e">The event arguments.</param>
|
||||||
|
public delegate void ProcessFileHandler(object sender, ScanEventArgs e);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate invoked during processing of a file or directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The source of the event</param>
|
||||||
|
/// <param name="e">The event arguments.</param>
|
||||||
|
public delegate void ProgressHandler(object sender, ProgressEventArgs e);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate invoked when a file has been completely processed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The source of the event</param>
|
||||||
|
/// <param name="e">The event arguments.</param>
|
||||||
|
public delegate void CompletedFileHandler(object sender, ScanEventArgs e);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate invoked when a directory failure is detected.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The source of the event</param>
|
||||||
|
/// <param name="e">The event arguments.</param>
|
||||||
|
public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate invoked when a file failure is detected.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The source of the event</param>
|
||||||
|
/// <param name="e">The event arguments.</param>
|
||||||
|
public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e);
|
||||||
|
|
||||||
|
#endregion Delegates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FileSystemScanner provides facilities scanning of files and directories.
|
||||||
|
/// </summary>
|
||||||
|
public class FileSystemScanner
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="FileSystemScanner"></see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filter">The <see cref="PathFilter">file filter</see> to apply when scanning.</param>
|
||||||
|
public FileSystemScanner(string filter)
|
||||||
|
{
|
||||||
|
fileFilter_ = new PathFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="FileSystemScanner"></see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param>
|
||||||
|
/// <param name="directoryFilter">The <see cref="PathFilter"> directory filter</see> to apply.</param>
|
||||||
|
public FileSystemScanner(string fileFilter, string directoryFilter)
|
||||||
|
{
|
||||||
|
fileFilter_ = new PathFilter(fileFilter);
|
||||||
|
directoryFilter_ = new PathFilter(directoryFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="FileSystemScanner"></see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param>
|
||||||
|
public FileSystemScanner(IScanFilter fileFilter)
|
||||||
|
{
|
||||||
|
fileFilter_ = fileFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="FileSystemScanner"></see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param>
|
||||||
|
/// <param name="directoryFilter">The directory <see cref="IScanFilter">filter</see> to apply.</param>
|
||||||
|
public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter)
|
||||||
|
{
|
||||||
|
fileFilter_ = fileFilter;
|
||||||
|
directoryFilter_ = directoryFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Delegates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when a directory is processed.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DirectoryEventArgs> ProcessDirectory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when a file is processed.
|
||||||
|
/// </summary>
|
||||||
|
public ProcessFileHandler ProcessFile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when processing for a file has finished.
|
||||||
|
/// </summary>
|
||||||
|
public CompletedFileHandler CompletedFile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when a directory failure is detected.
|
||||||
|
/// </summary>
|
||||||
|
public DirectoryFailureHandler DirectoryFailure;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when a file failure is detected.
|
||||||
|
/// </summary>
|
||||||
|
public FileFailureHandler FileFailure;
|
||||||
|
|
||||||
|
#endregion Delegates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raise the DirectoryFailure event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directory">The directory name.</param>
|
||||||
|
/// <param name="e">The exception detected.</param>
|
||||||
|
private bool OnDirectoryFailure(string directory, Exception e)
|
||||||
|
{
|
||||||
|
DirectoryFailureHandler handler = DirectoryFailure;
|
||||||
|
bool result = (handler != null);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
var args = new ScanFailureEventArgs(directory, e);
|
||||||
|
handler(this, args);
|
||||||
|
alive_ = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raise the FileFailure event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The file name.</param>
|
||||||
|
/// <param name="e">The exception detected.</param>
|
||||||
|
private bool OnFileFailure(string file, Exception e)
|
||||||
|
{
|
||||||
|
FileFailureHandler handler = FileFailure;
|
||||||
|
|
||||||
|
bool result = (handler != null);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
var args = new ScanFailureEventArgs(file, e);
|
||||||
|
FileFailure(this, args);
|
||||||
|
alive_ = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raise the ProcessFile event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The file name.</param>
|
||||||
|
private void OnProcessFile(string file)
|
||||||
|
{
|
||||||
|
ProcessFileHandler handler = ProcessFile;
|
||||||
|
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new ScanEventArgs(file);
|
||||||
|
handler(this, args);
|
||||||
|
alive_ = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raise the complete file event
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The file name</param>
|
||||||
|
private void OnCompleteFile(string file)
|
||||||
|
{
|
||||||
|
CompletedFileHandler handler = CompletedFile;
|
||||||
|
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new ScanEventArgs(file);
|
||||||
|
handler(this, args);
|
||||||
|
alive_ = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raise the ProcessDirectory event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directory">The directory name.</param>
|
||||||
|
/// <param name="hasMatchingFiles">Flag indicating if the directory has matching files.</param>
|
||||||
|
private void OnProcessDirectory(string directory, bool hasMatchingFiles)
|
||||||
|
{
|
||||||
|
EventHandler<DirectoryEventArgs> handler = ProcessDirectory;
|
||||||
|
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new DirectoryEventArgs(directory, hasMatchingFiles);
|
||||||
|
handler(this, args);
|
||||||
|
alive_ = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scan a directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directory">The base directory to scan.</param>
|
||||||
|
/// <param name="recurse">True to recurse subdirectories, false to scan a single directory.</param>
|
||||||
|
public void Scan(string directory, bool recurse)
|
||||||
|
{
|
||||||
|
alive_ = true;
|
||||||
|
ScanDir(directory, recurse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScanDir(string directory, bool recurse)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string[] names = System.IO.Directory.GetFiles(directory);
|
||||||
|
bool hasMatch = false;
|
||||||
|
for (int fileIndex = 0; fileIndex < names.Length; ++fileIndex)
|
||||||
|
{
|
||||||
|
if (!fileFilter_.IsMatch(names[fileIndex]))
|
||||||
|
{
|
||||||
|
names[fileIndex] = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasMatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnProcessDirectory(directory, hasMatch);
|
||||||
|
|
||||||
|
if (alive_ && hasMatch)
|
||||||
|
{
|
||||||
|
foreach (string fileName in names)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (fileName != null)
|
||||||
|
{
|
||||||
|
OnProcessFile(fileName);
|
||||||
|
if (!alive_)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (!OnFileFailure(fileName, e))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (!OnDirectoryFailure(directory, e))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alive_ && recurse)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string[] names = System.IO.Directory.GetDirectories(directory);
|
||||||
|
foreach (string fulldir in names)
|
||||||
|
{
|
||||||
|
if ((directoryFilter_ == null) || (directoryFilter_.IsMatch(fulldir)))
|
||||||
|
{
|
||||||
|
ScanDir(fulldir, true);
|
||||||
|
if (!alive_)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (!OnDirectoryFailure(directory, e))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file filter currently in use.
|
||||||
|
/// </summary>
|
||||||
|
private IScanFilter fileFilter_;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The directory filter currently in use.
|
||||||
|
/// </summary>
|
||||||
|
private IScanFilter directoryFilter_;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag indicating if scanning should continue running.
|
||||||
|
/// </summary>
|
||||||
|
private bool alive_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace ICSharpCode.SharpZipLib.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// INameTransform defines how file system names are transformed for use with archives, or vice versa.
|
||||||
|
/// </summary>
|
||||||
|
public interface INameTransform
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Given a file name determine the transformed value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to transform.</param>
|
||||||
|
/// <returns>The transformed file name.</returns>
|
||||||
|
string TransformFile(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a directory name determine the transformed value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to transform.</param>
|
||||||
|
/// <returns>The transformed directory name</returns>
|
||||||
|
string TransformDirectory(string name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace ICSharpCode.SharpZipLib.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Scanning filters support filtering of names.
|
||||||
|
/// </summary>
|
||||||
|
public interface IScanFilter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test a name to see if it 'matches' the filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to test.</param>
|
||||||
|
/// <returns>Returns true if the name matches the filter, false if it does not match.</returns>
|
||||||
|
bool IsMatch(string name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// InvalidNameException is thrown for invalid names such as directory traversal paths and names with invalid characters
|
||||||
|
/// </summary>
|
||||||
|
public class InvalidNameException : SharpZipBaseException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the InvalidNameException class with a default error message.
|
||||||
|
/// </summary>
|
||||||
|
public InvalidNameException() : base("An invalid name was specified")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the InvalidNameException class with a specified error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
public InvalidNameException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the InvalidNameException class with a specified
|
||||||
|
/// error message and a reference to the inner exception that is the cause of this exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A message describing the exception.</param>
|
||||||
|
/// <param name="innerException">The inner exception</param>
|
||||||
|
public InvalidNameException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,284 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// NameFilter is a string matching class which allows for both positive and negative
|
||||||
|
/// matching.
|
||||||
|
/// A filter is a sequence of independant <see cref="Regex">regular expressions</see> separated by semi-colons ';'.
|
||||||
|
/// To include a semi-colon it may be quoted as in \;. Each expression can be prefixed by a plus '+' sign or
|
||||||
|
/// a minus '-' sign to denote the expression is intended to include or exclude names.
|
||||||
|
/// If neither a plus or minus sign is found include is the default.
|
||||||
|
/// A given name is tested for inclusion before checking exclusions. Only names matching an include spec
|
||||||
|
/// and not matching an exclude spec are deemed to match the filter.
|
||||||
|
/// An empty filter matches any name.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>The following expression includes all name ending in '.dat' with the exception of 'dummy.dat'
|
||||||
|
/// "+\.dat$;-^dummy\.dat$"
|
||||||
|
/// </example>
|
||||||
|
public class NameFilter : IScanFilter
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct an instance based on the filter expression passed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filter">The filter expression.</param>
|
||||||
|
public NameFilter(string filter)
|
||||||
|
{
|
||||||
|
filter_ = filter;
|
||||||
|
inclusions_ = new List<Regex>();
|
||||||
|
exclusions_ = new List<Regex>();
|
||||||
|
Compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a string to see if it is a valid regular expression.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expression">The expression to test.</param>
|
||||||
|
/// <returns>True if expression is a valid <see cref="System.Text.RegularExpressions.Regex"/> false otherwise.</returns>
|
||||||
|
public static bool IsValidExpression(string expression)
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var exp = new Regex(expression, RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test an expression to see if it is valid as a filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toTest">The filter expression to test.</param>
|
||||||
|
/// <returns>True if the expression is valid, false otherwise.</returns>
|
||||||
|
public static bool IsValidFilterExpression(string toTest)
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (toTest != null)
|
||||||
|
{
|
||||||
|
string[] items = SplitQuoted(toTest);
|
||||||
|
for (int i = 0; i < items.Length; ++i)
|
||||||
|
{
|
||||||
|
if ((items[i] != null) && (items[i].Length > 0))
|
||||||
|
{
|
||||||
|
string toCompile;
|
||||||
|
|
||||||
|
if (items[i][0] == '+')
|
||||||
|
{
|
||||||
|
toCompile = items[i].Substring(1, items[i].Length - 1);
|
||||||
|
}
|
||||||
|
else if (items[i][0] == '-')
|
||||||
|
{
|
||||||
|
toCompile = items[i].Substring(1, items[i].Length - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toCompile = items[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
var testRegex = new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Split a string into its component pieces
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="original">The original string</param>
|
||||||
|
/// <returns>Returns an array of <see cref="T:System.String"/> values containing the individual filter elements.</returns>
|
||||||
|
public static string[] SplitQuoted(string original)
|
||||||
|
{
|
||||||
|
char escape = '\\';
|
||||||
|
char[] separators = { ';' };
|
||||||
|
|
||||||
|
var result = new List<string>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(original))
|
||||||
|
{
|
||||||
|
int endIndex = -1;
|
||||||
|
var b = new StringBuilder();
|
||||||
|
|
||||||
|
while (endIndex < original.Length)
|
||||||
|
{
|
||||||
|
endIndex += 1;
|
||||||
|
if (endIndex >= original.Length)
|
||||||
|
{
|
||||||
|
result.Add(b.ToString());
|
||||||
|
}
|
||||||
|
else if (original[endIndex] == escape)
|
||||||
|
{
|
||||||
|
endIndex += 1;
|
||||||
|
if (endIndex >= original.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Missing terminating escape character", nameof(original));
|
||||||
|
}
|
||||||
|
// include escape if this is not an escaped separator
|
||||||
|
if (Array.IndexOf(separators, original[endIndex]) < 0)
|
||||||
|
b.Append(escape);
|
||||||
|
|
||||||
|
b.Append(original[endIndex]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Array.IndexOf(separators, original[endIndex]) >= 0)
|
||||||
|
{
|
||||||
|
result.Add(b.ToString());
|
||||||
|
b.Length = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
b.Append(original[endIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert this filter to its string equivalent.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The string equivalent for this filter.</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return filter_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a value to see if it is included by the filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The value to test.</param>
|
||||||
|
/// <returns>True if the value is included, false otherwise.</returns>
|
||||||
|
public bool IsIncluded(string name)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (inclusions_.Count == 0)
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (Regex r in inclusions_)
|
||||||
|
{
|
||||||
|
if (r.IsMatch(name))
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a value to see if it is excluded by the filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The value to test.</param>
|
||||||
|
/// <returns>True if the value is excluded, false otherwise.</returns>
|
||||||
|
public bool IsExcluded(string name)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
foreach (Regex r in exclusions_)
|
||||||
|
{
|
||||||
|
if (r.IsMatch(name))
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IScanFilter Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a value to see if it matches the filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The value to test.</param>
|
||||||
|
/// <returns>True if the value matches, false otherwise.</returns>
|
||||||
|
public bool IsMatch(string name)
|
||||||
|
{
|
||||||
|
return (IsIncluded(name) && !IsExcluded(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion IScanFilter Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compile this filter.
|
||||||
|
/// </summary>
|
||||||
|
private void Compile()
|
||||||
|
{
|
||||||
|
// TODO: Check to see if combining RE's makes it faster/smaller.
|
||||||
|
// simple scheme would be to have one RE for inclusion and one for exclusion.
|
||||||
|
if (filter_ == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] items = SplitQuoted(filter_);
|
||||||
|
for (int i = 0; i < items.Length; ++i)
|
||||||
|
{
|
||||||
|
if ((items[i] != null) && (items[i].Length > 0))
|
||||||
|
{
|
||||||
|
bool include = (items[i][0] != '-');
|
||||||
|
string toCompile;
|
||||||
|
|
||||||
|
if (items[i][0] == '+')
|
||||||
|
{
|
||||||
|
toCompile = items[i].Substring(1, items[i].Length - 1);
|
||||||
|
}
|
||||||
|
else if (items[i][0] == '-')
|
||||||
|
{
|
||||||
|
toCompile = items[i].Substring(1, items[i].Length - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toCompile = items[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Regular expressions can fail to compile here for a number of reasons that cause an exception
|
||||||
|
// these are left unhandled here as the caller is responsible for ensuring all is valid.
|
||||||
|
// several functions IsValidFilterExpression and IsValidExpression are provided for such checking
|
||||||
|
if (include)
|
||||||
|
{
|
||||||
|
inclusions_.Add(new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exclusions_.Add(new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private string filter_;
|
||||||
|
private List<Regex> inclusions_;
|
||||||
|
private List<Regex> exclusions_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,318 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// PathFilter filters directories and files using a form of <see cref="System.Text.RegularExpressions.Regex">regular expressions</see>
|
||||||
|
/// by full path name.
|
||||||
|
/// See <see cref="NameFilter">NameFilter</see> for more detail on filtering.
|
||||||
|
/// </summary>
|
||||||
|
public class PathFilter : IScanFilter
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="PathFilter"></see>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filter">The <see cref="NameFilter">filter</see> expression to apply.</param>
|
||||||
|
public PathFilter(string filter)
|
||||||
|
{
|
||||||
|
nameFilter_ = new NameFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region IScanFilter Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a name to see if it matches the filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to test.</param>
|
||||||
|
/// <returns>True if the name matches, false otherwise.</returns>
|
||||||
|
/// <remarks><see cref="Path.GetFullPath(string)"/> is used to get the full path before matching.</remarks>
|
||||||
|
public virtual bool IsMatch(string name)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
if (name != null)
|
||||||
|
{
|
||||||
|
string cooked = (name.Length > 0) ? Path.GetFullPath(name) : "";
|
||||||
|
result = nameFilter_.IsMatch(cooked);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly
|
||||||
|
|
||||||
|
#endregion IScanFilter Members
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
NameFilter nameFilter_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ExtendedPathFilter filters based on name, file size, and the last write time of the file.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Provides an example of how to customise filtering.</remarks>
|
||||||
|
public class ExtendedPathFilter : PathFilter
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of ExtendedPathFilter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filter">The filter to apply.</param>
|
||||||
|
/// <param name="minSize">The minimum file size to include.</param>
|
||||||
|
/// <param name="maxSize">The maximum file size to include.</param>
|
||||||
|
public ExtendedPathFilter(string filter,
|
||||||
|
long minSize, long maxSize)
|
||||||
|
: base(filter)
|
||||||
|
{
|
||||||
|
MinSize = minSize;
|
||||||
|
MaxSize = maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of ExtendedPathFilter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filter">The filter to apply.</param>
|
||||||
|
/// <param name="minDate">The minimum <see cref="DateTime"/> to include.</param>
|
||||||
|
/// <param name="maxDate">The maximum <see cref="DateTime"/> to include.</param>
|
||||||
|
public ExtendedPathFilter(string filter,
|
||||||
|
DateTime minDate, DateTime maxDate)
|
||||||
|
: base(filter)
|
||||||
|
{
|
||||||
|
MinDate = minDate;
|
||||||
|
MaxDate = maxDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of ExtendedPathFilter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filter">The filter to apply.</param>
|
||||||
|
/// <param name="minSize">The minimum file size to include.</param>
|
||||||
|
/// <param name="maxSize">The maximum file size to include.</param>
|
||||||
|
/// <param name="minDate">The minimum <see cref="DateTime"/> to include.</param>
|
||||||
|
/// <param name="maxDate">The maximum <see cref="DateTime"/> to include.</param>
|
||||||
|
public ExtendedPathFilter(string filter,
|
||||||
|
long minSize, long maxSize,
|
||||||
|
DateTime minDate, DateTime maxDate)
|
||||||
|
: base(filter)
|
||||||
|
{
|
||||||
|
MinSize = minSize;
|
||||||
|
MaxSize = maxSize;
|
||||||
|
MinDate = minDate;
|
||||||
|
MaxDate = maxDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region IScanFilter Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a filename to see if it matches the filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The filename to test.</param>
|
||||||
|
/// <returns>True if the filter matches, false otherwise.</returns>
|
||||||
|
/// <exception cref="System.IO.FileNotFoundException">The <see paramref="fileName"/> doesnt exist</exception>
|
||||||
|
public override bool IsMatch(string name)
|
||||||
|
{
|
||||||
|
bool result = base.IsMatch(name);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(name);
|
||||||
|
result =
|
||||||
|
(MinSize <= fileInfo.Length) &&
|
||||||
|
(MaxSize >= fileInfo.Length) &&
|
||||||
|
(MinDate <= fileInfo.LastWriteTime) &&
|
||||||
|
(MaxDate >= fileInfo.LastWriteTime)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion IScanFilter Members
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the minimum size/length for a file that will match this filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is zero.</remarks>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">value is less than zero; greater than <see cref="MaxSize"/></exception>
|
||||||
|
public long MinSize
|
||||||
|
{
|
||||||
|
get { return minSize_; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if ((value < 0) || (maxSize_ < value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
minSize_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the maximum size/length for a file that will match this filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is <see cref="System.Int64.MaxValue"/></remarks>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">value is less than zero or less than <see cref="MinSize"/></exception>
|
||||||
|
public long MaxSize
|
||||||
|
{
|
||||||
|
get { return maxSize_; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if ((value < 0) || (minSize_ > value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
maxSize_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the minimum <see cref="DateTime"/> value that will match for this filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Files with a LastWrite time less than this value are excluded by the filter.</remarks>
|
||||||
|
public DateTime MinDate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return minDate_;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value > maxDate_)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MaxDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
minDate_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the maximum <see cref="DateTime"/> value that will match for this filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Files with a LastWrite time greater than this value are excluded by the filter.</remarks>
|
||||||
|
public DateTime MaxDate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return maxDate_;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (minDate_ > value)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MinDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
maxDate_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Properties
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private long minSize_;
|
||||||
|
private long maxSize_ = long.MaxValue;
|
||||||
|
private DateTime minDate_ = DateTime.MinValue;
|
||||||
|
private DateTime maxDate_ = DateTime.MaxValue;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// NameAndSizeFilter filters based on name and file size.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>A sample showing how filters might be extended.</remarks>
|
||||||
|
[Obsolete("Use ExtendedPathFilter instead")]
|
||||||
|
public class NameAndSizeFilter : PathFilter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of NameAndSizeFilter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filter">The filter to apply.</param>
|
||||||
|
/// <param name="minSize">The minimum file size to include.</param>
|
||||||
|
/// <param name="maxSize">The maximum file size to include.</param>
|
||||||
|
public NameAndSizeFilter(string filter, long minSize, long maxSize)
|
||||||
|
: base(filter)
|
||||||
|
{
|
||||||
|
MinSize = minSize;
|
||||||
|
MaxSize = maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a filename to see if it matches the filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The filename to test.</param>
|
||||||
|
/// <returns>True if the filter matches, false otherwise.</returns>
|
||||||
|
public override bool IsMatch(string name)
|
||||||
|
{
|
||||||
|
bool result = base.IsMatch(name);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(name);
|
||||||
|
long length = fileInfo.Length;
|
||||||
|
result =
|
||||||
|
(MinSize <= length) &&
|
||||||
|
(MaxSize >= length);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the minimum size for a file that will match this filter.
|
||||||
|
/// </summary>
|
||||||
|
public long MinSize
|
||||||
|
{
|
||||||
|
get { return minSize_; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if ((value < 0) || (maxSize_ < value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
minSize_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the maximum size for a file that will match this filter.
|
||||||
|
/// </summary>
|
||||||
|
public long MaxSize
|
||||||
|
{
|
||||||
|
get { return maxSize_; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if ((value < 0) || (minSize_ > value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
maxSize_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private long minSize_;
|
||||||
|
private long maxSize_ = long.MaxValue;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,284 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides simple <see cref="Stream"/>" utilities.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class StreamUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Read from a <see cref="Stream"/> ensuring all the required data is read.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to read.</param>
|
||||||
|
/// <param name="buffer">The buffer to fill.</param>
|
||||||
|
/// <seealso cref="ReadFully(Stream,byte[],int,int)"/>
|
||||||
|
static public void ReadFully(Stream stream, byte[] buffer)
|
||||||
|
{
|
||||||
|
ReadFully(stream, buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read from a <see cref="Stream"/>" ensuring all the required data is read.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to read data from.</param>
|
||||||
|
/// <param name="buffer">The buffer to store data in.</param>
|
||||||
|
/// <param name="offset">The offset at which to begin storing data.</param>
|
||||||
|
/// <param name="count">The number of bytes of data to store.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Required parameter is null</exception>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> and or <paramref name="count"/> are invalid.</exception>
|
||||||
|
/// <exception cref="EndOfStreamException">End of stream is encountered before all the data has been read.</exception>
|
||||||
|
static public void ReadFully(Stream stream, byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset can equal length when buffer and count are 0.
|
||||||
|
if ((offset < 0) || (offset > buffer.Length))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((count < 0) || (offset + count > buffer.Length))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (count > 0)
|
||||||
|
{
|
||||||
|
int readCount = stream.Read(buffer, offset, count);
|
||||||
|
if (readCount <= 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException();
|
||||||
|
}
|
||||||
|
offset += readCount;
|
||||||
|
count -= readCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read as much data as possible from a <see cref="Stream"/>", up to the requested number of bytes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to read data from.</param>
|
||||||
|
/// <param name="buffer">The buffer to store data in.</param>
|
||||||
|
/// <param name="offset">The offset at which to begin storing data.</param>
|
||||||
|
/// <param name="count">The number of bytes of data to store.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Required parameter is null</exception>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> and or <paramref name="count"/> are invalid.</exception>
|
||||||
|
static public int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset can equal length when buffer and count are 0.
|
||||||
|
if ((offset < 0) || (offset > buffer.Length))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((count < 0) || (offset + count > buffer.Length))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalReadCount = 0;
|
||||||
|
while (count > 0)
|
||||||
|
{
|
||||||
|
int readCount = stream.Read(buffer, offset, count);
|
||||||
|
if (readCount <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset += readCount;
|
||||||
|
count -= readCount;
|
||||||
|
totalReadCount += readCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalReadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy the contents of one <see cref="Stream"/> to another.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The stream to source data from.</param>
|
||||||
|
/// <param name="destination">The stream to write data to.</param>
|
||||||
|
/// <param name="buffer">The buffer to use during copying.</param>
|
||||||
|
static public void Copy(Stream source, Stream destination, byte[] buffer)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destination == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure a reasonable size of buffer is used without being prohibitive.
|
||||||
|
if (buffer.Length < 128)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Buffer is too small", nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copying = true;
|
||||||
|
|
||||||
|
while (copying)
|
||||||
|
{
|
||||||
|
int bytesRead = source.Read(buffer, 0, buffer.Length);
|
||||||
|
if (bytesRead > 0)
|
||||||
|
{
|
||||||
|
destination.Write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
destination.Flush();
|
||||||
|
copying = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy the contents of one <see cref="Stream"/> to another.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The stream to source data from.</param>
|
||||||
|
/// <param name="destination">The stream to write data to.</param>
|
||||||
|
/// <param name="buffer">The buffer to use during copying.</param>
|
||||||
|
/// <param name="progressHandler">The <see cref="ProgressHandler">progress handler delegate</see> to use.</param>
|
||||||
|
/// <param name="updateInterval">The minimum <see cref="TimeSpan"/> between progress updates.</param>
|
||||||
|
/// <param name="sender">The source for this event.</param>
|
||||||
|
/// <param name="name">The name to use with the event.</param>
|
||||||
|
/// <remarks>This form is specialised for use within #Zip to support events during archive operations.</remarks>
|
||||||
|
static public void Copy(Stream source, Stream destination,
|
||||||
|
byte[] buffer, ProgressHandler progressHandler, TimeSpan updateInterval, object sender, string name)
|
||||||
|
{
|
||||||
|
Copy(source, destination, buffer, progressHandler, updateInterval, sender, name, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy the contents of one <see cref="Stream"/> to another.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The stream to source data from.</param>
|
||||||
|
/// <param name="destination">The stream to write data to.</param>
|
||||||
|
/// <param name="buffer">The buffer to use during copying.</param>
|
||||||
|
/// <param name="progressHandler">The <see cref="ProgressHandler">progress handler delegate</see> to use.</param>
|
||||||
|
/// <param name="updateInterval">The minimum <see cref="TimeSpan"/> between progress updates.</param>
|
||||||
|
/// <param name="sender">The source for this event.</param>
|
||||||
|
/// <param name="name">The name to use with the event.</param>
|
||||||
|
/// <param name="fixedTarget">A predetermined fixed target value to use with progress updates.
|
||||||
|
/// If the value is negative the target is calculated by looking at the stream.</param>
|
||||||
|
/// <remarks>This form is specialised for use within #Zip to support events during archive operations.</remarks>
|
||||||
|
static public void Copy(Stream source, Stream destination,
|
||||||
|
byte[] buffer,
|
||||||
|
ProgressHandler progressHandler, TimeSpan updateInterval,
|
||||||
|
object sender, string name, long fixedTarget)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destination == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure a reasonable size of buffer is used without being prohibitive.
|
||||||
|
if (buffer.Length < 128)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Buffer is too small", nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressHandler == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(progressHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copying = true;
|
||||||
|
|
||||||
|
DateTime marker = DateTime.Now;
|
||||||
|
long processed = 0;
|
||||||
|
long target = 0;
|
||||||
|
|
||||||
|
if (fixedTarget >= 0)
|
||||||
|
{
|
||||||
|
target = fixedTarget;
|
||||||
|
}
|
||||||
|
else if (source.CanSeek)
|
||||||
|
{
|
||||||
|
target = source.Length - source.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always fire 0% progress..
|
||||||
|
var args = new ProgressEventArgs(name, processed, target);
|
||||||
|
progressHandler(sender, args);
|
||||||
|
|
||||||
|
bool progressFired = true;
|
||||||
|
|
||||||
|
while (copying)
|
||||||
|
{
|
||||||
|
int bytesRead = source.Read(buffer, 0, buffer.Length);
|
||||||
|
if (bytesRead > 0)
|
||||||
|
{
|
||||||
|
processed += bytesRead;
|
||||||
|
progressFired = false;
|
||||||
|
destination.Write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
destination.Flush();
|
||||||
|
copying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.Now - marker > updateInterval)
|
||||||
|
{
|
||||||
|
progressFired = true;
|
||||||
|
marker = DateTime.Now;
|
||||||
|
args = new ProgressEventArgs(name, processed, target);
|
||||||
|
progressHandler(sender, args);
|
||||||
|
|
||||||
|
copying = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!progressFired)
|
||||||
|
{
|
||||||
|
args = new ProgressEventArgs(name, processed, target);
|
||||||
|
progressHandler(sender, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise an instance of <see cref="StreamUtils"></see>
|
||||||
|
/// </summary>
|
||||||
|
private StreamUtils()
|
||||||
|
{
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
namespace ICSharpCode.SharpZipLib.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// WindowsPathUtils provides simple utilities for handling windows paths.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class WindowsPathUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="WindowsPathUtils"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal WindowsPathUtils()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove any path root present in the path
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">A <see cref="string"/> containing path information.</param>
|
||||||
|
/// <returns>The path with the root removed if it was present; path otherwise.</returns>
|
||||||
|
/// <remarks>Unlike the <see cref="System.IO.Path"/> class the path isnt otherwise checked for validity.</remarks>
|
||||||
|
public static string DropPathRoot(string path)
|
||||||
|
{
|
||||||
|
string result = path;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
if ((path[0] == '\\') || (path[0] == '/'))
|
||||||
|
{
|
||||||
|
// UNC name ?
|
||||||
|
if ((path.Length > 1) && ((path[1] == '\\') || (path[1] == '/')))
|
||||||
|
{
|
||||||
|
int index = 2;
|
||||||
|
int elements = 2;
|
||||||
|
|
||||||
|
// Scan for two separate elements \\machine\share\restofpath
|
||||||
|
while ((index <= path.Length) &&
|
||||||
|
(((path[index] != '\\') && (path[index] != '/')) || (--elements > 0)))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
|
||||||
|
if (index < path.Length)
|
||||||
|
{
|
||||||
|
result = path.Substring(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((path.Length > 1) && (path[1] == ':'))
|
||||||
|
{
|
||||||
|
int dropCount = 2;
|
||||||
|
if ((path.Length > 2) && ((path[2] == '\\') || (path[2] == '/')))
|
||||||
|
{
|
||||||
|
dropCount = 3;
|
||||||
|
}
|
||||||
|
result = result.Remove(0, dropCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,485 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Encryption
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives.
|
||||||
|
/// While it has been superceded by more recent and more powerful algorithms, its still in use and
|
||||||
|
/// is viable for preventing casual snooping
|
||||||
|
/// </summary>
|
||||||
|
public abstract class PkzipClassic : SymmetricAlgorithm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates new encryption keys based on given seed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">The seed value to initialise keys with.</param>
|
||||||
|
/// <returns>A new key value.</returns>
|
||||||
|
static public byte[] GenerateKeys(byte[] seed)
|
||||||
|
{
|
||||||
|
if (seed == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seed.Length == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Length is zero", nameof(seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint[] newKeys = {
|
||||||
|
0x12345678,
|
||||||
|
0x23456789,
|
||||||
|
0x34567890
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < seed.Length; ++i)
|
||||||
|
{
|
||||||
|
newKeys[0] = Crc32.ComputeCrc32(newKeys[0], seed[i]);
|
||||||
|
newKeys[1] = newKeys[1] + (byte)newKeys[0];
|
||||||
|
newKeys[1] = newKeys[1] * 134775813 + 1;
|
||||||
|
newKeys[2] = Crc32.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] result = new byte[12];
|
||||||
|
result[0] = (byte)(newKeys[0] & 0xff);
|
||||||
|
result[1] = (byte)((newKeys[0] >> 8) & 0xff);
|
||||||
|
result[2] = (byte)((newKeys[0] >> 16) & 0xff);
|
||||||
|
result[3] = (byte)((newKeys[0] >> 24) & 0xff);
|
||||||
|
result[4] = (byte)(newKeys[1] & 0xff);
|
||||||
|
result[5] = (byte)((newKeys[1] >> 8) & 0xff);
|
||||||
|
result[6] = (byte)((newKeys[1] >> 16) & 0xff);
|
||||||
|
result[7] = (byte)((newKeys[1] >> 24) & 0xff);
|
||||||
|
result[8] = (byte)(newKeys[2] & 0xff);
|
||||||
|
result[9] = (byte)((newKeys[2] >> 8) & 0xff);
|
||||||
|
result[10] = (byte)((newKeys[2] >> 16) & 0xff);
|
||||||
|
result[11] = (byte)((newKeys[2] >> 24) & 0xff);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PkzipClassicCryptoBase provides the low level facilities for encryption
|
||||||
|
/// and decryption using the PkzipClassic algorithm.
|
||||||
|
/// </summary>
|
||||||
|
internal class PkzipClassicCryptoBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Transform a single byte
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The transformed value
|
||||||
|
/// </returns>
|
||||||
|
protected byte TransformByte()
|
||||||
|
{
|
||||||
|
uint temp = ((keys[2] & 0xFFFF) | 2);
|
||||||
|
return (byte)((temp * (temp ^ 1)) >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the key schedule for encryption/decryption.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyData">The data use to set the keys from.</param>
|
||||||
|
protected void SetKeys(byte[] keyData)
|
||||||
|
{
|
||||||
|
if (keyData == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(keyData));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyData.Length != 12)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Key length is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = new uint[3];
|
||||||
|
keys[0] = (uint)((keyData[3] << 24) | (keyData[2] << 16) | (keyData[1] << 8) | keyData[0]);
|
||||||
|
keys[1] = (uint)((keyData[7] << 24) | (keyData[6] << 16) | (keyData[5] << 8) | keyData[4]);
|
||||||
|
keys[2] = (uint)((keyData[11] << 24) | (keyData[10] << 16) | (keyData[9] << 8) | keyData[8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update encryption keys
|
||||||
|
/// </summary>
|
||||||
|
protected void UpdateKeys(byte ch)
|
||||||
|
{
|
||||||
|
keys[0] = Crc32.ComputeCrc32(keys[0], ch);
|
||||||
|
keys[1] = keys[1] + (byte)keys[0];
|
||||||
|
keys[1] = keys[1] * 134775813 + 1;
|
||||||
|
keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset the internal state.
|
||||||
|
/// </summary>
|
||||||
|
protected void Reset()
|
||||||
|
{
|
||||||
|
keys[0] = 0;
|
||||||
|
keys[1] = 0;
|
||||||
|
keys[2] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private uint[] keys;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PkzipClassic CryptoTransform for encryption.
|
||||||
|
/// </summary>
|
||||||
|
internal class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="PkzipClassicEncryptCryptoTransform"></see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyBlock">The key block to use.</param>
|
||||||
|
internal PkzipClassicEncryptCryptoTransform(byte[] keyBlock)
|
||||||
|
{
|
||||||
|
SetKeys(keyBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ICryptoTransform Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms the specified region of the specified byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBuffer">The input for which to compute the transform.</param>
|
||||||
|
/// <param name="inputOffset">The offset into the byte array from which to begin using data.</param>
|
||||||
|
/// <param name="inputCount">The number of bytes in the byte array to use as data.</param>
|
||||||
|
/// <returns>The computed transform.</returns>
|
||||||
|
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
|
||||||
|
{
|
||||||
|
byte[] result = new byte[inputCount];
|
||||||
|
TransformBlock(inputBuffer, inputOffset, inputCount, result, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms the specified region of the input byte array and copies
|
||||||
|
/// the resulting transform to the specified region of the output byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBuffer">The input for which to compute the transform.</param>
|
||||||
|
/// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
|
||||||
|
/// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
|
||||||
|
/// <param name="outputBuffer">The output to which to write the transform.</param>
|
||||||
|
/// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
|
||||||
|
/// <returns>The number of bytes written.</returns>
|
||||||
|
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
|
||||||
|
{
|
||||||
|
for (int i = inputOffset; i < inputOffset + inputCount; ++i)
|
||||||
|
{
|
||||||
|
byte oldbyte = inputBuffer[i];
|
||||||
|
outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte());
|
||||||
|
UpdateKeys(oldbyte);
|
||||||
|
}
|
||||||
|
return inputCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current transform can be reused.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanReuseTransform
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the input data blocks in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int InputBlockSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the output data blocks in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int OutputBlockSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether multiple blocks can be transformed.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanTransformMultipleBlocks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion ICryptoTransform Members
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleanup internal state.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion IDisposable Members
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PkzipClassic CryptoTransform for decryption.
|
||||||
|
/// </summary>
|
||||||
|
internal class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="PkzipClassicDecryptCryptoTransform"></see>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyBlock">The key block to decrypt with.</param>
|
||||||
|
internal PkzipClassicDecryptCryptoTransform(byte[] keyBlock)
|
||||||
|
{
|
||||||
|
SetKeys(keyBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ICryptoTransform Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms the specified region of the specified byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBuffer">The input for which to compute the transform.</param>
|
||||||
|
/// <param name="inputOffset">The offset into the byte array from which to begin using data.</param>
|
||||||
|
/// <param name="inputCount">The number of bytes in the byte array to use as data.</param>
|
||||||
|
/// <returns>The computed transform.</returns>
|
||||||
|
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
|
||||||
|
{
|
||||||
|
byte[] result = new byte[inputCount];
|
||||||
|
TransformBlock(inputBuffer, inputOffset, inputCount, result, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms the specified region of the input byte array and copies
|
||||||
|
/// the resulting transform to the specified region of the output byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputBuffer">The input for which to compute the transform.</param>
|
||||||
|
/// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param>
|
||||||
|
/// <param name="inputCount">The number of bytes in the input byte array to use as data.</param>
|
||||||
|
/// <param name="outputBuffer">The output to which to write the transform.</param>
|
||||||
|
/// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param>
|
||||||
|
/// <returns>The number of bytes written.</returns>
|
||||||
|
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
|
||||||
|
{
|
||||||
|
for (int i = inputOffset; i < inputOffset + inputCount; ++i)
|
||||||
|
{
|
||||||
|
var newByte = (byte)(inputBuffer[i] ^ TransformByte());
|
||||||
|
outputBuffer[outputOffset++] = newByte;
|
||||||
|
UpdateKeys(newByte);
|
||||||
|
}
|
||||||
|
return inputCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current transform can be reused.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanReuseTransform
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the input data blocks in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int InputBlockSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the output data blocks in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int OutputBlockSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether multiple blocks can be transformed.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanTransformMultipleBlocks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion ICryptoTransform Members
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleanup internal state.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion IDisposable Members
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a wrapper object to access the Pkzip algorithm.
|
||||||
|
/// This class cannot be inherited.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PkzipClassicManaged : PkzipClassic
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set the applicable block size in bits.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The only valid block size is 8.</remarks>
|
||||||
|
public override int BlockSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != 8)
|
||||||
|
{
|
||||||
|
throw new CryptographicException("Block size is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an array of legal <see cref="KeySizes">key sizes.</see>
|
||||||
|
/// </summary>
|
||||||
|
public override KeySizes[] LegalKeySizes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
KeySizes[] keySizes = new KeySizes[1];
|
||||||
|
keySizes[0] = new KeySizes(12 * 8, 12 * 8, 0);
|
||||||
|
return keySizes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate an initial vector.
|
||||||
|
/// </summary>
|
||||||
|
public override void GenerateIV()
|
||||||
|
{
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an array of legal <see cref="KeySizes">block sizes</see>.
|
||||||
|
/// </summary>
|
||||||
|
public override KeySizes[] LegalBlockSizes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
KeySizes[] keySizes = new KeySizes[1];
|
||||||
|
keySizes[0] = new KeySizes(1 * 8, 1 * 8, 0);
|
||||||
|
return keySizes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set the key value applicable.
|
||||||
|
/// </summary>
|
||||||
|
public override byte[] Key
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (key_ == null)
|
||||||
|
{
|
||||||
|
GenerateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (byte[])key_.Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length != 12)
|
||||||
|
{
|
||||||
|
throw new CryptographicException("Key size is illegal");
|
||||||
|
}
|
||||||
|
|
||||||
|
key_ = (byte[])value.Clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a new random key.
|
||||||
|
/// </summary>
|
||||||
|
public override void GenerateKey()
|
||||||
|
{
|
||||||
|
key_ = new byte[12];
|
||||||
|
var rnd = new Random();
|
||||||
|
rnd.NextBytes(key_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an encryptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rgbKey">The key to use for this encryptor.</param>
|
||||||
|
/// <param name="rgbIV">Initialisation vector for the new encryptor.</param>
|
||||||
|
/// <returns>Returns a new PkzipClassic encryptor</returns>
|
||||||
|
public override ICryptoTransform CreateEncryptor(
|
||||||
|
byte[] rgbKey,
|
||||||
|
byte[] rgbIV)
|
||||||
|
{
|
||||||
|
key_ = rgbKey;
|
||||||
|
return new PkzipClassicEncryptCryptoTransform(Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a decryptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rgbKey">Keys to use for this new decryptor.</param>
|
||||||
|
/// <param name="rgbIV">Initialisation vector for the new decryptor.</param>
|
||||||
|
/// <returns>Returns a new decryptor.</returns>
|
||||||
|
public override ICryptoTransform CreateDecryptor(
|
||||||
|
byte[] rgbKey,
|
||||||
|
byte[] rgbIV)
|
||||||
|
{
|
||||||
|
key_ = rgbKey;
|
||||||
|
return new PkzipClassicDecryptCryptoTransform(Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private byte[] key_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using ICSharpCode.SharpZipLib.Core;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Encryption
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Encrypts and decrypts AES ZIP
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Based on information from http://www.winzip.com/aes_info.htm
|
||||||
|
/// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/
|
||||||
|
/// </remarks>
|
||||||
|
internal class ZipAESStream : CryptoStream
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream on which to perform the cryptographic transformation.</param>
|
||||||
|
/// <param name="transform">Instance of ZipAESTransform</param>
|
||||||
|
/// <param name="mode">Read or Write</param>
|
||||||
|
public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode)
|
||||||
|
: base(stream, transform, mode)
|
||||||
|
{
|
||||||
|
_stream = stream;
|
||||||
|
_transform = transform;
|
||||||
|
_slideBuffer = new byte[1024];
|
||||||
|
|
||||||
|
_blockAndAuth = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH;
|
||||||
|
|
||||||
|
// mode:
|
||||||
|
// CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method.
|
||||||
|
// Write bypasses this stream and uses the Transform directly.
|
||||||
|
if (mode != CryptoStreamMode.Read)
|
||||||
|
{
|
||||||
|
throw new Exception("ZipAESStream only for read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The final n bytes of the AES stream contain the Auth Code.
|
||||||
|
private const int AUTH_CODE_LENGTH = 10;
|
||||||
|
|
||||||
|
private Stream _stream;
|
||||||
|
private ZipAESTransform _transform;
|
||||||
|
private byte[] _slideBuffer;
|
||||||
|
private int _slideBufStartPos;
|
||||||
|
private int _slideBufFreePos;
|
||||||
|
|
||||||
|
// Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
|
||||||
|
private const int CRYPTO_BLOCK_SIZE = 16;
|
||||||
|
|
||||||
|
private int _blockAndAuth;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a sequence of bytes from the current CryptoStream into buffer,
|
||||||
|
/// and advances the position within the stream by the number of bytes read.
|
||||||
|
/// </summary>
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
int nBytes = 0;
|
||||||
|
while (nBytes < count)
|
||||||
|
{
|
||||||
|
// Calculate buffer quantities vs read-ahead size, and check for sufficient free space
|
||||||
|
int byteCount = _slideBufFreePos - _slideBufStartPos;
|
||||||
|
|
||||||
|
// Need to handle final block and Auth Code specially, but don't know total data length.
|
||||||
|
// Maintain a read-ahead equal to the length of (crypto block + Auth Code).
|
||||||
|
// When that runs out we can detect these final sections.
|
||||||
|
int lengthToRead = _blockAndAuth - byteCount;
|
||||||
|
if (_slideBuffer.Length - _slideBufFreePos < lengthToRead)
|
||||||
|
{
|
||||||
|
// Shift the data to the beginning of the buffer
|
||||||
|
int iTo = 0;
|
||||||
|
for (int iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++)
|
||||||
|
{
|
||||||
|
_slideBuffer[iTo] = _slideBuffer[iFrom];
|
||||||
|
}
|
||||||
|
_slideBufFreePos -= _slideBufStartPos; // Note the -=
|
||||||
|
_slideBufStartPos = 0;
|
||||||
|
}
|
||||||
|
int obtained = StreamUtils.ReadRequestedBytes(_stream, _slideBuffer, _slideBufFreePos, lengthToRead);
|
||||||
|
_slideBufFreePos += obtained;
|
||||||
|
|
||||||
|
// Recalculate how much data we now have
|
||||||
|
byteCount = _slideBufFreePos - _slideBufStartPos;
|
||||||
|
if (byteCount >= _blockAndAuth)
|
||||||
|
{
|
||||||
|
// At least a 16 byte block and an auth code remains.
|
||||||
|
_transform.TransformBlock(_slideBuffer,
|
||||||
|
_slideBufStartPos,
|
||||||
|
CRYPTO_BLOCK_SIZE,
|
||||||
|
buffer,
|
||||||
|
offset);
|
||||||
|
nBytes += CRYPTO_BLOCK_SIZE;
|
||||||
|
offset += CRYPTO_BLOCK_SIZE;
|
||||||
|
_slideBufStartPos += CRYPTO_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Last round.
|
||||||
|
if (byteCount > AUTH_CODE_LENGTH)
|
||||||
|
{
|
||||||
|
// At least one byte of data plus auth code
|
||||||
|
int finalBlock = byteCount - AUTH_CODE_LENGTH;
|
||||||
|
_transform.TransformBlock(_slideBuffer,
|
||||||
|
_slideBufStartPos,
|
||||||
|
finalBlock,
|
||||||
|
buffer,
|
||||||
|
offset);
|
||||||
|
|
||||||
|
nBytes += finalBlock;
|
||||||
|
_slideBufStartPos += finalBlock;
|
||||||
|
}
|
||||||
|
else if (byteCount < AUTH_CODE_LENGTH)
|
||||||
|
throw new Exception("Internal error missed auth code"); // Coding bug
|
||||||
|
// Final block done. Check Auth code.
|
||||||
|
byte[] calcAuthCode = _transform.GetAuthCode();
|
||||||
|
for (int i = 0; i < AUTH_CODE_LENGTH; i++)
|
||||||
|
{
|
||||||
|
if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i])
|
||||||
|
{
|
||||||
|
throw new Exception("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n"
|
||||||
|
+ "The file may be damaged.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break; // Reached the auth code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream. </param>
|
||||||
|
/// <param name="offset">The byte offset in buffer at which to begin copying bytes to the current stream. </param>
|
||||||
|
/// <param name="count">The number of bytes to be written to the current stream. </param>
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
// ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly.
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Encryption
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms stream using AES in CTR mode
|
||||||
|
/// </summary>
|
||||||
|
internal class ZipAESTransform : ICryptoTransform
|
||||||
|
{
|
||||||
|
#if NET45
|
||||||
|
class IncrementalHash : HMACSHA1
|
||||||
|
{
|
||||||
|
bool _finalised;
|
||||||
|
public IncrementalHash(byte[] key) : base(key) { }
|
||||||
|
public static IncrementalHash CreateHMAC(string n, byte[] key) => new IncrementalHash(key);
|
||||||
|
public void AppendData(byte[] buffer, int offset, int count) => TransformBlock(buffer, offset, count, buffer, offset);
|
||||||
|
public byte[] GetHashAndReset()
|
||||||
|
{
|
||||||
|
if (!_finalised)
|
||||||
|
{
|
||||||
|
byte[] dummy = new byte[0];
|
||||||
|
TransformFinalBlock(dummy, 0, 0);
|
||||||
|
_finalised = true;
|
||||||
|
}
|
||||||
|
return Hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class HashAlgorithmName
|
||||||
|
{
|
||||||
|
public static string SHA1 = null;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private const int PWD_VER_LENGTH = 2;
|
||||||
|
|
||||||
|
// WinZip use iteration count of 1000 for PBKDF2 key generation
|
||||||
|
private const int KEY_ROUNDS = 1000;
|
||||||
|
|
||||||
|
// For 128-bit AES (16 bytes) the encryption is implemented as expected.
|
||||||
|
// For 256-bit AES (32 bytes) WinZip do full 256 bit AES of the nonce to create the encryption
|
||||||
|
// block but use only the first 16 bytes of it, and discard the second half.
|
||||||
|
private const int ENCRYPT_BLOCK = 16;
|
||||||
|
|
||||||
|
private int _blockSize;
|
||||||
|
private readonly ICryptoTransform _encryptor;
|
||||||
|
private readonly byte[] _counterNonce;
|
||||||
|
private byte[] _encryptBuffer;
|
||||||
|
private int _encrPos;
|
||||||
|
private byte[] _pwdVerifier;
|
||||||
|
private IncrementalHash _hmacsha1;
|
||||||
|
private byte[] _authCode = null;
|
||||||
|
|
||||||
|
private bool _writeMode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Password string</param>
|
||||||
|
/// <param name="saltBytes">Random bytes, length depends on encryption strength.
|
||||||
|
/// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.</param>
|
||||||
|
/// <param name="blockSize">The encryption strength, in bytes eg 16 for 128 bits.</param>
|
||||||
|
/// <param name="writeMode">True when creating a zip, false when reading. For the AuthCode.</param>
|
||||||
|
///
|
||||||
|
public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode)
|
||||||
|
{
|
||||||
|
if (blockSize != 16 && blockSize != 32) // 24 valid for AES but not supported by Winzip
|
||||||
|
throw new Exception("Invalid blocksize " + blockSize + ". Must be 16 or 32.");
|
||||||
|
if (saltBytes.Length != blockSize / 2)
|
||||||
|
throw new Exception("Invalid salt len. Must be " + blockSize / 2 + " for blocksize " + blockSize);
|
||||||
|
// initialise the encryption buffer and buffer pos
|
||||||
|
_blockSize = blockSize;
|
||||||
|
_encryptBuffer = new byte[_blockSize];
|
||||||
|
_encrPos = ENCRYPT_BLOCK;
|
||||||
|
|
||||||
|
// Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c
|
||||||
|
var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS);
|
||||||
|
var rm = Aes.Create();
|
||||||
|
rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode
|
||||||
|
_counterNonce = new byte[_blockSize];
|
||||||
|
byte[] key1bytes = pdb.GetBytes(_blockSize);
|
||||||
|
byte[] key2bytes = pdb.GetBytes(_blockSize);
|
||||||
|
|
||||||
|
// Use empty IV for AES
|
||||||
|
_encryptor = rm.CreateEncryptor(key1bytes, new byte[16]);
|
||||||
|
_pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH);
|
||||||
|
//
|
||||||
|
_hmacsha1 = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, key2bytes);
|
||||||
|
_writeMode = writeMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement the ICryptoTransform method.
|
||||||
|
/// </summary>
|
||||||
|
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
|
||||||
|
{
|
||||||
|
// Pass the data stream to the hash algorithm for generating the Auth Code.
|
||||||
|
// This does not change the inputBuffer. Do this before decryption for read mode.
|
||||||
|
if (!_writeMode)
|
||||||
|
{
|
||||||
|
_hmacsha1.AppendData(inputBuffer, inputOffset, inputCount);
|
||||||
|
}
|
||||||
|
// Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this.
|
||||||
|
int ix = 0;
|
||||||
|
while (ix < inputCount)
|
||||||
|
{
|
||||||
|
if (_encrPos == ENCRYPT_BLOCK)
|
||||||
|
{
|
||||||
|
/* increment encryption nonce */
|
||||||
|
int j = 0;
|
||||||
|
while (++_counterNonce[j] == 0)
|
||||||
|
{
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
/* encrypt the nonce to form next xor buffer */
|
||||||
|
_encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0);
|
||||||
|
_encrPos = 0;
|
||||||
|
}
|
||||||
|
outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]);
|
||||||
|
//
|
||||||
|
ix++;
|
||||||
|
}
|
||||||
|
if (_writeMode)
|
||||||
|
{
|
||||||
|
// This does not change the buffer.
|
||||||
|
_hmacsha1.AppendData(outputBuffer, outputOffset, inputCount);
|
||||||
|
}
|
||||||
|
return inputCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the 2 byte password verifier
|
||||||
|
/// </summary>
|
||||||
|
public byte[] PwdVerifier
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _pwdVerifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] GetAuthCode()
|
||||||
|
{
|
||||||
|
if (_authCode == null)
|
||||||
|
{
|
||||||
|
_authCode = _hmacsha1.GetHashAndReset();
|
||||||
|
}
|
||||||
|
return _authCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ICryptoTransform Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not implemented.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
|
||||||
|
{
|
||||||
|
if(inputCount > 0)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("TransformFinalBlock is not implemented and inputCount is greater than 0");
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the input data blocks in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int InputBlockSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _blockSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the output data blocks in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int OutputBlockSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _blockSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether multiple blocks can be transformed.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanTransformMultipleBlocks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current transform can be reused.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanReuseTransform
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleanup internal state.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_encryptor.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion ICryptoTransform Members
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.GZip
|
||||||
|
{
|
||||||
|
using static Zip.Compression.Deflater;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An example class to demonstrate compression and decompression of GZip streams.
|
||||||
|
/// </summary>
|
||||||
|
public static class GZip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Decompress the <paramref name="inStream">input</paramref> writing
|
||||||
|
/// uncompressed data to the <paramref name="outStream">output stream</paramref>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inStream">The readable stream containing data to decompress.</param>
|
||||||
|
/// <param name="outStream">The output stream to receive the decompressed data.</param>
|
||||||
|
/// <param name="isStreamOwner">Both streams are closed on completion if true.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Input or output stream is null</exception>
|
||||||
|
public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner)
|
||||||
|
{
|
||||||
|
if (inStream == null)
|
||||||
|
throw new ArgumentNullException(nameof(inStream), "Input stream is null");
|
||||||
|
|
||||||
|
if (outStream == null)
|
||||||
|
throw new ArgumentNullException(nameof(outStream), "Output stream is null");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (GZipInputStream gzipInput = new GZipInputStream(inStream))
|
||||||
|
{
|
||||||
|
gzipInput.IsStreamOwner = isStreamOwner;
|
||||||
|
Core.StreamUtils.Copy(gzipInput, outStream, new byte[4096]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (isStreamOwner)
|
||||||
|
{
|
||||||
|
// inStream is closed by the GZipInputStream if stream owner
|
||||||
|
outStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compress the <paramref name="inStream">input stream</paramref> sending
|
||||||
|
/// result data to <paramref name="outStream">output stream</paramref>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inStream">The readable stream to compress.</param>
|
||||||
|
/// <param name="outStream">The output stream to receive the compressed data.</param>
|
||||||
|
/// <param name="isStreamOwner">Both streams are closed on completion if true.</param>
|
||||||
|
/// <param name="bufferSize">Deflate buffer size, minimum 512</param>
|
||||||
|
/// <param name="level">Deflate compression level, 0-9</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Input or output stream is null</exception>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Buffer Size is smaller than 512</exception>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Compression level outside 0-9</exception>
|
||||||
|
public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int bufferSize = 512, int level = 6)
|
||||||
|
{
|
||||||
|
if (inStream == null)
|
||||||
|
throw new ArgumentNullException(nameof(inStream), "Input stream is null");
|
||||||
|
|
||||||
|
if (outStream == null)
|
||||||
|
throw new ArgumentNullException(nameof(outStream), "Output stream is null");
|
||||||
|
|
||||||
|
if (bufferSize < 512)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bufferSize), "Deflate buffer size must be >= 512");
|
||||||
|
|
||||||
|
if (level < NO_COMPRESSION || level > BEST_COMPRESSION)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(level), "Compression level must be 0-9");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (GZipOutputStream gzipOutput = new GZipOutputStream(outStream, bufferSize))
|
||||||
|
{
|
||||||
|
gzipOutput.SetLevel(level);
|
||||||
|
gzipOutput.IsStreamOwner = isStreamOwner;
|
||||||
|
Core.StreamUtils.Copy(inStream, gzipOutput, new byte[bufferSize]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (isStreamOwner)
|
||||||
|
{
|
||||||
|
// outStream is closed by the GZipOutputStream if stream owner
|
||||||
|
inStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
namespace ICSharpCode.SharpZipLib.GZip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class contains constants used for gzip.
|
||||||
|
/// </summary>
|
||||||
|
sealed public class GZipConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Magic number found at start of GZIP header
|
||||||
|
/// </summary>
|
||||||
|
public const int GZIP_MAGIC = 0x1F8B;
|
||||||
|
|
||||||
|
/* The flag byte is divided into individual bits as follows:
|
||||||
|
|
||||||
|
bit 0 FTEXT
|
||||||
|
bit 1 FHCRC
|
||||||
|
bit 2 FEXTRA
|
||||||
|
bit 3 FNAME
|
||||||
|
bit 4 FCOMMENT
|
||||||
|
bit 5 reserved
|
||||||
|
bit 6 reserved
|
||||||
|
bit 7 reserved
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag bit mask for text
|
||||||
|
/// </summary>
|
||||||
|
public const int FTEXT = 0x1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag bitmask for Crc
|
||||||
|
/// </summary>
|
||||||
|
public const int FHCRC = 0x2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag bit mask for extra
|
||||||
|
/// </summary>
|
||||||
|
public const int FEXTRA = 0x4;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// flag bitmask for name
|
||||||
|
/// </summary>
|
||||||
|
public const int FNAME = 0x8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// flag bit mask indicating comment is present
|
||||||
|
/// </summary>
|
||||||
|
public const int FCOMMENT = 0x10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise default instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Constructor is private to prevent instances being created.</remarks>
|
||||||
|
private GZipConstants()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.GZip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// GZipException represents exceptions specific to GZip classes and code.
|
||||||
|
/// </summary>
|
||||||
|
public class GZipException : SharpZipBaseException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="GZipException" />.
|
||||||
|
/// </summary>
|
||||||
|
public GZipException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="GZipException" /> with its message string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
public GZipException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="GZipException" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
/// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param>
|
||||||
|
public GZipException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,389 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.GZip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This filter stream is used to decompress a "GZIP" format stream.
|
||||||
|
/// The "GZIP" format is described baseInputStream RFC 1952.
|
||||||
|
///
|
||||||
|
/// author of the original java version : John Leuner
|
||||||
|
/// </summary>
|
||||||
|
/// <example> This sample shows how to unzip a gzipped file
|
||||||
|
/// <code>
|
||||||
|
/// using System;
|
||||||
|
/// using System.IO;
|
||||||
|
///
|
||||||
|
/// using ICSharpCode.SharpZipLib.Core;
|
||||||
|
/// using ICSharpCode.SharpZipLib.GZip;
|
||||||
|
///
|
||||||
|
/// class MainClass
|
||||||
|
/// {
|
||||||
|
/// public static void Main(string[] args)
|
||||||
|
/// {
|
||||||
|
/// using (Stream inStream = new GZipInputStream(File.OpenRead(args[0])))
|
||||||
|
/// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) {
|
||||||
|
/// byte[] buffer = new byte[4096];
|
||||||
|
/// StreamUtils.Copy(inStream, outStream, buffer);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public class GZipInputStream : InflaterInputStream
|
||||||
|
{
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRC-32 value for uncompressed data
|
||||||
|
/// </summary>
|
||||||
|
protected Crc32 crc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag to indicate if we've read the GZIP header yet for the current member (block of compressed data).
|
||||||
|
/// This is tracked per-block as the file is parsed.
|
||||||
|
/// </summary>
|
||||||
|
private bool readGZIPHeader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag to indicate if at least one block in a stream with concatenated blocks was read successfully.
|
||||||
|
/// This allows us to exit gracefully if downstream data is not in gzip format.
|
||||||
|
/// </summary>
|
||||||
|
private bool completedLastBlock;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a GZipInputStream with the default buffer size
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseInputStream">
|
||||||
|
/// The stream to read compressed data from (baseInputStream GZIP format)
|
||||||
|
/// </param>
|
||||||
|
public GZipInputStream(Stream baseInputStream)
|
||||||
|
: this(baseInputStream, 4096)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a GZIPInputStream with the specified buffer size
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseInputStream">
|
||||||
|
/// The stream to read compressed data from (baseInputStream GZIP format)
|
||||||
|
/// </param>
|
||||||
|
/// <param name="size">
|
||||||
|
/// Size of the buffer to use
|
||||||
|
/// </param>
|
||||||
|
public GZipInputStream(Stream baseInputStream, int size)
|
||||||
|
: base(baseInputStream, new Inflater(true), size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Stream overrides
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads uncompressed data into an array of bytes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// The buffer to read uncompressed data into
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// The offset indicating where the data should be placed
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// The number of uncompressed bytes to be read
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Returns the number of bytes actually read.</returns>
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
// A GZIP file can contain multiple blocks of compressed data, although this is quite rare.
|
||||||
|
// A compressed block could potentially be empty, so we need to loop until we reach EOF or
|
||||||
|
// we find data.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// If we haven't read the header for this block, read it
|
||||||
|
if (!readGZIPHeader)
|
||||||
|
{
|
||||||
|
// Try to read header. If there is no header (0 bytes available), this is EOF. If there is
|
||||||
|
// an incomplete header, this will throw an exception.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!ReadHeader())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (completedLastBlock && (ex is GZipException || ex is EndOfStreamException))
|
||||||
|
{
|
||||||
|
// if we completed the last block (i.e. we're in a stream that has multiple blocks concatenated
|
||||||
|
// we want to return gracefully from any header parsing exceptions since sometimes there may
|
||||||
|
// be trailing garbage on a stream
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read compressed data
|
||||||
|
int bytesRead = base.Read(buffer, offset, count);
|
||||||
|
if (bytesRead > 0)
|
||||||
|
{
|
||||||
|
crc.Update(new ArraySegment<byte>(buffer, offset, bytesRead));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the end of stream, read the footer
|
||||||
|
if (inf.IsFinished)
|
||||||
|
{
|
||||||
|
ReadFooter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesRead > 0)
|
||||||
|
{
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Stream overrides
|
||||||
|
|
||||||
|
#region Support routines
|
||||||
|
|
||||||
|
private bool ReadHeader()
|
||||||
|
{
|
||||||
|
// Initialize CRC for this block
|
||||||
|
crc = new Crc32();
|
||||||
|
|
||||||
|
// Make sure there is data in file. We can't rely on ReadLeByte() to fill the buffer, as this could be EOF,
|
||||||
|
// which is fine, but ReadLeByte() throws an exception if it doesn't find data, so we do this part ourselves.
|
||||||
|
if (inputBuffer.Available <= 0)
|
||||||
|
{
|
||||||
|
inputBuffer.Fill();
|
||||||
|
if (inputBuffer.Available <= 0)
|
||||||
|
{
|
||||||
|
// No header, EOF.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Check the two magic bytes
|
||||||
|
var headCRC = new Crc32();
|
||||||
|
int magic = inputBuffer.ReadLeByte();
|
||||||
|
|
||||||
|
if (magic < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
|
||||||
|
headCRC.Update(magic);
|
||||||
|
if (magic != (GZipConstants.GZIP_MAGIC >> 8))
|
||||||
|
{
|
||||||
|
throw new GZipException("Error GZIP header, first magic byte doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
//magic = baseInputStream.ReadByte();
|
||||||
|
magic = inputBuffer.ReadLeByte();
|
||||||
|
|
||||||
|
if (magic < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magic != (GZipConstants.GZIP_MAGIC & 0xFF))
|
||||||
|
{
|
||||||
|
throw new GZipException("Error GZIP header, second magic byte doesn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
headCRC.Update(magic);
|
||||||
|
|
||||||
|
// 2. Check the compression type (must be 8)
|
||||||
|
int compressionType = inputBuffer.ReadLeByte();
|
||||||
|
|
||||||
|
if (compressionType < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compressionType != 8)
|
||||||
|
{
|
||||||
|
throw new GZipException("Error GZIP header, data not in deflate format");
|
||||||
|
}
|
||||||
|
headCRC.Update(compressionType);
|
||||||
|
|
||||||
|
// 3. Check the flags
|
||||||
|
int flags = inputBuffer.ReadLeByte();
|
||||||
|
if (flags < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
headCRC.Update(flags);
|
||||||
|
|
||||||
|
/* This flag byte is divided into individual bits as follows:
|
||||||
|
|
||||||
|
bit 0 FTEXT
|
||||||
|
bit 1 FHCRC
|
||||||
|
bit 2 FEXTRA
|
||||||
|
bit 3 FNAME
|
||||||
|
bit 4 FCOMMENT
|
||||||
|
bit 5 reserved
|
||||||
|
bit 6 reserved
|
||||||
|
bit 7 reserved
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 3.1 Check the reserved bits are zero
|
||||||
|
|
||||||
|
if ((flags & 0xE0) != 0)
|
||||||
|
{
|
||||||
|
throw new GZipException("Reserved flag bits in GZIP header != 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.-6. Skip the modification time, extra flags, and OS type
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
int readByte = inputBuffer.ReadLeByte();
|
||||||
|
if (readByte < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
headCRC.Update(readByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Read extra field
|
||||||
|
if ((flags & GZipConstants.FEXTRA) != 0)
|
||||||
|
{
|
||||||
|
// XLEN is total length of extra subfields, we will skip them all
|
||||||
|
int len1, len2;
|
||||||
|
len1 = inputBuffer.ReadLeByte();
|
||||||
|
len2 = inputBuffer.ReadLeByte();
|
||||||
|
if ((len1 < 0) || (len2 < 0))
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
headCRC.Update(len1);
|
||||||
|
headCRC.Update(len2);
|
||||||
|
|
||||||
|
int extraLen = (len2 << 8) | len1; // gzip is LSB first
|
||||||
|
for (int i = 0; i < extraLen; i++)
|
||||||
|
{
|
||||||
|
int readByte = inputBuffer.ReadLeByte();
|
||||||
|
if (readByte < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
headCRC.Update(readByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Read file name
|
||||||
|
if ((flags & GZipConstants.FNAME) != 0)
|
||||||
|
{
|
||||||
|
int readByte;
|
||||||
|
while ((readByte = inputBuffer.ReadLeByte()) > 0)
|
||||||
|
{
|
||||||
|
headCRC.Update(readByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readByte < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
headCRC.Update(readByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. Read comment
|
||||||
|
if ((flags & GZipConstants.FCOMMENT) != 0)
|
||||||
|
{
|
||||||
|
int readByte;
|
||||||
|
while ((readByte = inputBuffer.ReadLeByte()) > 0)
|
||||||
|
{
|
||||||
|
headCRC.Update(readByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readByte < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
|
||||||
|
headCRC.Update(readByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. Read header CRC
|
||||||
|
if ((flags & GZipConstants.FHCRC) != 0)
|
||||||
|
{
|
||||||
|
int tempByte;
|
||||||
|
int crcval = inputBuffer.ReadLeByte();
|
||||||
|
if (crcval < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
|
||||||
|
tempByte = inputBuffer.ReadLeByte();
|
||||||
|
if (tempByte < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP header");
|
||||||
|
}
|
||||||
|
|
||||||
|
crcval = (crcval << 8) | tempByte;
|
||||||
|
if (crcval != ((int)headCRC.Value & 0xffff))
|
||||||
|
{
|
||||||
|
throw new GZipException("Header CRC value mismatch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readGZIPHeader = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadFooter()
|
||||||
|
{
|
||||||
|
byte[] footer = new byte[8];
|
||||||
|
|
||||||
|
// End of stream; reclaim all bytes from inf, read the final byte count, and reset the inflator
|
||||||
|
long bytesRead = inf.TotalOut & 0xffffffff;
|
||||||
|
inputBuffer.Available += inf.RemainingInput;
|
||||||
|
inf.Reset();
|
||||||
|
|
||||||
|
// Read footer from inputBuffer
|
||||||
|
int needed = 8;
|
||||||
|
while (needed > 0)
|
||||||
|
{
|
||||||
|
int count = inputBuffer.ReadClearTextBuffer(footer, 8 - needed, needed);
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("EOS reading GZIP footer");
|
||||||
|
}
|
||||||
|
needed -= count; // Jewel Jan 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate CRC
|
||||||
|
int crcval = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8) | ((footer[2] & 0xff) << 16) | (footer[3] << 24);
|
||||||
|
if (crcval != (int)crc.Value)
|
||||||
|
{
|
||||||
|
throw new GZipException("GZIP crc sum mismatch, theirs \"" + crcval + "\" and ours \"" + (int)crc.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE The total here is the original total modulo 2 ^ 32.
|
||||||
|
uint total =
|
||||||
|
(uint)((uint)footer[4] & 0xff) |
|
||||||
|
(uint)(((uint)footer[5] & 0xff) << 8) |
|
||||||
|
(uint)(((uint)footer[6] & 0xff) << 16) |
|
||||||
|
(uint)((uint)footer[7] << 24);
|
||||||
|
|
||||||
|
if (bytesRead != total)
|
||||||
|
{
|
||||||
|
throw new GZipException("Number of bytes mismatch in footer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark header read as false so if another header exists, we'll continue reading through the file
|
||||||
|
readGZIPHeader = false;
|
||||||
|
|
||||||
|
// Indicate that we succeeded on at least one block so we can exit gracefully if there is trailing garbage downstream
|
||||||
|
completedLastBlock = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Support routines
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.GZip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This filter stream is used to compress a stream into a "GZIP" stream.
|
||||||
|
/// The "GZIP" format is described in RFC 1952.
|
||||||
|
///
|
||||||
|
/// author of the original java version : John Leuner
|
||||||
|
/// </summary>
|
||||||
|
/// <example> This sample shows how to gzip a file
|
||||||
|
/// <code>
|
||||||
|
/// using System;
|
||||||
|
/// using System.IO;
|
||||||
|
///
|
||||||
|
/// using ICSharpCode.SharpZipLib.GZip;
|
||||||
|
/// using ICSharpCode.SharpZipLib.Core;
|
||||||
|
///
|
||||||
|
/// class MainClass
|
||||||
|
/// {
|
||||||
|
/// public static void Main(string[] args)
|
||||||
|
/// {
|
||||||
|
/// using (Stream s = new GZipOutputStream(File.Create(args[0] + ".gz")))
|
||||||
|
/// using (FileStream fs = File.OpenRead(args[0])) {
|
||||||
|
/// byte[] writeData = new byte[4096];
|
||||||
|
/// Streamutils.Copy(s, fs, writeData);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public class GZipOutputStream : DeflaterOutputStream
|
||||||
|
{
|
||||||
|
private enum OutputState
|
||||||
|
{
|
||||||
|
Header,
|
||||||
|
Footer,
|
||||||
|
Finished,
|
||||||
|
Closed,
|
||||||
|
};
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRC-32 value for uncompressed data
|
||||||
|
/// </summary>
|
||||||
|
protected Crc32 crc = new Crc32();
|
||||||
|
|
||||||
|
private OutputState state_ = OutputState.Header;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a GzipOutputStream with the default buffer size
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseOutputStream">
|
||||||
|
/// The stream to read data (to be compressed) from
|
||||||
|
/// </param>
|
||||||
|
public GZipOutputStream(Stream baseOutputStream)
|
||||||
|
: this(baseOutputStream, 4096)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a GZipOutputStream with the specified buffer size
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseOutputStream">
|
||||||
|
/// The stream to read data (to be compressed) from
|
||||||
|
/// </param>
|
||||||
|
/// <param name="size">
|
||||||
|
/// Size of the buffer to use
|
||||||
|
/// </param>
|
||||||
|
public GZipOutputStream(Stream baseOutputStream, int size) : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Public API
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the active compression level (0-9). The new level will be activated
|
||||||
|
/// immediately.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The compression level to set.</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Level specified is not supported.
|
||||||
|
/// </exception>
|
||||||
|
/// <see cref="Deflater"/>
|
||||||
|
public void SetLevel(int level)
|
||||||
|
{
|
||||||
|
if (level < Deflater.NO_COMPRESSION || level > Deflater.BEST_COMPRESSION)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(level), "Compression level must be 0-9");
|
||||||
|
|
||||||
|
deflater_.SetLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current compression level.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current compression level.</returns>
|
||||||
|
public int GetLevel()
|
||||||
|
{
|
||||||
|
return deflater_.GetLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public API
|
||||||
|
|
||||||
|
#region Stream overrides
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write given buffer to output updating crc
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Buffer to write</param>
|
||||||
|
/// <param name="offset">Offset of first byte in buf to write</param>
|
||||||
|
/// <param name="count">Number of bytes to write</param>
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (state_ == OutputState.Header)
|
||||||
|
{
|
||||||
|
WriteHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ != OutputState.Footer)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Write not permitted in current state");
|
||||||
|
}
|
||||||
|
|
||||||
|
crc.Update(new ArraySegment<byte>(buffer, offset, count));
|
||||||
|
base.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes remaining compressed output data to the output stream
|
||||||
|
/// and closes it.
|
||||||
|
/// </summary>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Finish();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (state_ != OutputState.Closed)
|
||||||
|
{
|
||||||
|
state_ = OutputState.Closed;
|
||||||
|
if (IsStreamOwner)
|
||||||
|
{
|
||||||
|
baseOutputStream_.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Stream overrides
|
||||||
|
|
||||||
|
#region DeflaterOutputStream overrides
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finish compression and write any footer information required to stream
|
||||||
|
/// </summary>
|
||||||
|
public override void Finish()
|
||||||
|
{
|
||||||
|
// If no data has been written a header should be added.
|
||||||
|
if (state_ == OutputState.Header)
|
||||||
|
{
|
||||||
|
WriteHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ == OutputState.Footer)
|
||||||
|
{
|
||||||
|
state_ = OutputState.Finished;
|
||||||
|
base.Finish();
|
||||||
|
|
||||||
|
var totalin = (uint)(deflater_.TotalIn & 0xffffffff);
|
||||||
|
var crcval = (uint)(crc.Value & 0xffffffff);
|
||||||
|
|
||||||
|
byte[] gzipFooter;
|
||||||
|
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
gzipFooter = new byte[] {
|
||||||
|
(byte) crcval, (byte) (crcval >> 8),
|
||||||
|
(byte) (crcval >> 16), (byte) (crcval >> 24),
|
||||||
|
|
||||||
|
(byte) totalin, (byte) (totalin >> 8),
|
||||||
|
(byte) (totalin >> 16), (byte) (totalin >> 24)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
baseOutputStream_.Write(gzipFooter, 0, gzipFooter.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion DeflaterOutputStream overrides
|
||||||
|
|
||||||
|
#region Support Routines
|
||||||
|
|
||||||
|
private void WriteHeader()
|
||||||
|
{
|
||||||
|
if (state_ == OutputState.Header)
|
||||||
|
{
|
||||||
|
state_ = OutputState.Footer;
|
||||||
|
|
||||||
|
var mod_time = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100ns intervals
|
||||||
|
byte[] gzipHeader = {
|
||||||
|
// The two magic bytes
|
||||||
|
(byte) (GZipConstants.GZIP_MAGIC >> 8), (byte) (GZipConstants.GZIP_MAGIC & 0xff),
|
||||||
|
|
||||||
|
// The compression type
|
||||||
|
(byte) Deflater.DEFLATED,
|
||||||
|
|
||||||
|
// The flags (not set)
|
||||||
|
0,
|
||||||
|
|
||||||
|
// The modification time
|
||||||
|
(byte) mod_time, (byte) (mod_time >> 8),
|
||||||
|
(byte) (mod_time >> 16), (byte) (mod_time >> 24),
|
||||||
|
|
||||||
|
// The extra flags
|
||||||
|
0,
|
||||||
|
|
||||||
|
// The OS type (unknown)
|
||||||
|
(byte) 255
|
||||||
|
};
|
||||||
|
baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Support Routines
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netstandard2;net45</TargetFrameworks>
|
||||||
|
<SignAssembly>True</SignAssembly>
|
||||||
|
<AssemblyOriginatorKeyFile>ICSharpCode.SharpZipLib.snk</AssemblyOriginatorKeyFile>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Nuget specific tags -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyVersion>1.1.0.6</AssemblyVersion>
|
||||||
|
<FileVersion>1.1.0.6</FileVersion>
|
||||||
|
<Version>1.1.0</Version>
|
||||||
|
<PackageId>SharpZipLib</PackageId>
|
||||||
|
<Company>ICSharpCode</Company>
|
||||||
|
<Authors>ICSharpCode</Authors>
|
||||||
|
<Description>SharpZipLib (#ziplib, formerly NZipLib) is a compression library for Zip, GZip, BZip2, and Tar written entirely in C# for .NET. It is implemented as an assembly (installable in the GAC), and thus can easily be incorporated into other projects (in any .NET language)</Description>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
<PackageProjectUrl>http://icsharpcode.github.io/SharpZipLib/</PackageProjectUrl>
|
||||||
|
<PackageIconUrl>http://icsharpcode.github.io/SharpZipLib/assets/sharpziplib-nuget-256x256.png</PackageIconUrl>
|
||||||
|
<RepositoryUrl>https://github.com/icsharpcode/SharpZipLib</RepositoryUrl>
|
||||||
|
<Copyright>Copyright © 2000-2018 SharpZipLib Contributors</Copyright>
|
||||||
|
<PackageTags>Compression Library Zip GZip BZip2 LZW Tar</PackageTags>
|
||||||
|
<NeutralLanguage>en-US</NeutralLanguage>
|
||||||
|
<PackageReleaseNotes>
|
||||||
|
Please see https://github.com/icsharpcode/SharpZipLib/wiki/Release-1.1 for more information.</PackageReleaseNotes>
|
||||||
|
<PackageProjectUrl>https://github.com/icsharpcode/SharpZipLib</PackageProjectUrl>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2|AnyCPU'">
|
||||||
|
<DocumentationFile>bin\Debug\netstandard2\ICSharpCode.SharpZipLib.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2|AnyCPU'">
|
||||||
|
<DocumentationFile>bin\Release\netstandard2\ICSharpCode.SharpZipLib.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net45|AnyCPU'">
|
||||||
|
<DocumentationFile>bin\Debug\net45\ICSharpCode.SharpZipLib.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net45|AnyCPU'">
|
||||||
|
<DocumentationFile>bin\Release\net45\ICSharpCode.SharpZipLib.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-63127-02" PrivateAssets="All"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
Binary file not shown.
|
@ -0,0 +1,62 @@
|
||||||
|
namespace ICSharpCode.SharpZipLib.Lzw
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class contains constants used for LZW
|
||||||
|
/// </summary>
|
||||||
|
sealed public class LzwConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Magic number found at start of LZW header: 0x1f 0x9d
|
||||||
|
/// </summary>
|
||||||
|
public const int MAGIC = 0x1f9d;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of bits per code
|
||||||
|
/// </summary>
|
||||||
|
public const int MAX_BITS = 16;
|
||||||
|
|
||||||
|
/* 3rd header byte:
|
||||||
|
* bit 0..4 Number of compression bits
|
||||||
|
* bit 5 Extended header
|
||||||
|
* bit 6 Free
|
||||||
|
* bit 7 Block mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mask for 'number of compression bits'
|
||||||
|
/// </summary>
|
||||||
|
public const int BIT_MASK = 0x1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates the presence of a fourth header byte
|
||||||
|
/// </summary>
|
||||||
|
public const int EXTENDED_MASK = 0x20;
|
||||||
|
|
||||||
|
//public const int FREE_MASK = 0x40;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved bits
|
||||||
|
/// </summary>
|
||||||
|
public const int RESERVED_MASK = 0x60;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Block compression: if table is full and compression rate is dropping,
|
||||||
|
/// clear the dictionary.
|
||||||
|
/// </summary>
|
||||||
|
public const int BLOCK_MODE_MASK = 0x80;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LZW file header size (in bytes)
|
||||||
|
/// </summary>
|
||||||
|
public const int HDR_SIZE = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initial number of bits per code
|
||||||
|
/// </summary>
|
||||||
|
public const int INIT_BITS = 9;
|
||||||
|
|
||||||
|
private LzwConstants()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Lzw
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// LzwException represents exceptions specific to LZW classes and code.
|
||||||
|
/// </summary>
|
||||||
|
public class LzwException : SharpZipBaseException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="LzwException" />.
|
||||||
|
/// </summary>
|
||||||
|
public LzwException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="LzwException" /> with its message string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
public LzwException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="LzwException" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
/// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param>
|
||||||
|
public LzwException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,572 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Lzw
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This filter stream is used to decompress a LZW format stream.
|
||||||
|
/// Specifically, a stream that uses the LZC compression method.
|
||||||
|
/// This file format is usually associated with the .Z file extension.
|
||||||
|
///
|
||||||
|
/// See http://en.wikipedia.org/wiki/Compress
|
||||||
|
/// See http://wiki.wxwidgets.org/Development:_Z_File_Format
|
||||||
|
///
|
||||||
|
/// The file header consists of 3 (or optionally 4) bytes. The first two bytes
|
||||||
|
/// contain the magic marker "0x1f 0x9d", followed by a byte of flags.
|
||||||
|
///
|
||||||
|
/// Based on Java code by Ronald Tschalar, which in turn was based on the unlzw.c
|
||||||
|
/// code in the gzip package.
|
||||||
|
/// </summary>
|
||||||
|
/// <example> This sample shows how to unzip a compressed file
|
||||||
|
/// <code>
|
||||||
|
/// using System;
|
||||||
|
/// using System.IO;
|
||||||
|
///
|
||||||
|
/// using ICSharpCode.SharpZipLib.Core;
|
||||||
|
/// using ICSharpCode.SharpZipLib.LZW;
|
||||||
|
///
|
||||||
|
/// class MainClass
|
||||||
|
/// {
|
||||||
|
/// public static void Main(string[] args)
|
||||||
|
/// {
|
||||||
|
/// using (Stream inStream = new LzwInputStream(File.OpenRead(args[0])))
|
||||||
|
/// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) {
|
||||||
|
/// byte[] buffer = new byte[4096];
|
||||||
|
/// StreamUtils.Copy(inStream, outStream, buffer);
|
||||||
|
/// // OR
|
||||||
|
/// inStream.Read(buffer, 0, buffer.Length);
|
||||||
|
/// // now do something with the buffer
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public class LzwInputStream : Stream
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating ownership of underlying stream.
|
||||||
|
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is true.</remarks>
|
||||||
|
public bool IsStreamOwner { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a LzwInputStream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseInputStream">
|
||||||
|
/// The stream to read compressed data from (baseInputStream LZW format)
|
||||||
|
/// </param>
|
||||||
|
public LzwInputStream(Stream baseInputStream)
|
||||||
|
{
|
||||||
|
this.baseInputStream = baseInputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See <see cref="System.IO.Stream.ReadByte"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override int ReadByte()
|
||||||
|
{
|
||||||
|
int b = Read(one, 0, 1);
|
||||||
|
if (b == 1)
|
||||||
|
return (one[0] & 0xff);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads decompressed data into the provided buffer byte array
|
||||||
|
/// </summary>
|
||||||
|
/// <param name ="buffer">
|
||||||
|
/// The array to read and decompress data into
|
||||||
|
/// </param>
|
||||||
|
/// <param name ="offset">
|
||||||
|
/// The offset indicating where the data should be placed
|
||||||
|
/// </param>
|
||||||
|
/// <param name ="count">
|
||||||
|
/// The number of bytes to decompress
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The number of bytes read. Zero signals the end of stream</returns>
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (!headerParsed)
|
||||||
|
ParseHeader();
|
||||||
|
|
||||||
|
if (eof)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int start = offset;
|
||||||
|
|
||||||
|
/* Using local copies of various variables speeds things up by as
|
||||||
|
* much as 30% in Java! Performance not tested in C#.
|
||||||
|
*/
|
||||||
|
int[] lTabPrefix = tabPrefix;
|
||||||
|
byte[] lTabSuffix = tabSuffix;
|
||||||
|
byte[] lStack = stack;
|
||||||
|
int lNBits = nBits;
|
||||||
|
int lMaxCode = maxCode;
|
||||||
|
int lMaxMaxCode = maxMaxCode;
|
||||||
|
int lBitMask = bitMask;
|
||||||
|
int lOldCode = oldCode;
|
||||||
|
byte lFinChar = finChar;
|
||||||
|
int lStackP = stackP;
|
||||||
|
int lFreeEnt = freeEnt;
|
||||||
|
byte[] lData = data;
|
||||||
|
int lBitPos = bitPos;
|
||||||
|
|
||||||
|
// empty stack if stuff still left
|
||||||
|
int sSize = lStack.Length - lStackP;
|
||||||
|
if (sSize > 0)
|
||||||
|
{
|
||||||
|
int num = (sSize >= count) ? count : sSize;
|
||||||
|
Array.Copy(lStack, lStackP, buffer, offset, num);
|
||||||
|
offset += num;
|
||||||
|
count -= num;
|
||||||
|
lStackP += num;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
stackP = lStackP;
|
||||||
|
return offset - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop, filling local buffer until enough data has been decompressed
|
||||||
|
MainLoop:
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (end < EXTRA)
|
||||||
|
{
|
||||||
|
Fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitIn = (got > 0) ? (end - end % lNBits) << 3 :
|
||||||
|
(end << 3) - (lNBits - 1);
|
||||||
|
|
||||||
|
while (lBitPos < bitIn)
|
||||||
|
{
|
||||||
|
#region A
|
||||||
|
|
||||||
|
// handle 1-byte reads correctly
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
nBits = lNBits;
|
||||||
|
maxCode = lMaxCode;
|
||||||
|
maxMaxCode = lMaxMaxCode;
|
||||||
|
bitMask = lBitMask;
|
||||||
|
oldCode = lOldCode;
|
||||||
|
finChar = lFinChar;
|
||||||
|
stackP = lStackP;
|
||||||
|
freeEnt = lFreeEnt;
|
||||||
|
bitPos = lBitPos;
|
||||||
|
|
||||||
|
return offset - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for code-width expansion
|
||||||
|
if (lFreeEnt > lMaxCode)
|
||||||
|
{
|
||||||
|
int nBytes = lNBits << 3;
|
||||||
|
lBitPos = (lBitPos - 1) +
|
||||||
|
nBytes - (lBitPos - 1 + nBytes) % nBytes;
|
||||||
|
|
||||||
|
lNBits++;
|
||||||
|
lMaxCode = (lNBits == maxBits) ? lMaxMaxCode :
|
||||||
|
(1 << lNBits) - 1;
|
||||||
|
|
||||||
|
lBitMask = (1 << lNBits) - 1;
|
||||||
|
lBitPos = ResetBuf(lBitPos);
|
||||||
|
goto MainLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion A
|
||||||
|
|
||||||
|
#region B
|
||||||
|
|
||||||
|
// read next code
|
||||||
|
int pos = lBitPos >> 3;
|
||||||
|
int code = (((lData[pos] & 0xFF) |
|
||||||
|
((lData[pos + 1] & 0xFF) << 8) |
|
||||||
|
((lData[pos + 2] & 0xFF) << 16)) >>
|
||||||
|
(lBitPos & 0x7)) & lBitMask;
|
||||||
|
|
||||||
|
lBitPos += lNBits;
|
||||||
|
|
||||||
|
// handle first iteration
|
||||||
|
if (lOldCode == -1)
|
||||||
|
{
|
||||||
|
if (code >= 256)
|
||||||
|
throw new LzwException("corrupt input: " + code + " > 255");
|
||||||
|
|
||||||
|
lFinChar = (byte)(lOldCode = code);
|
||||||
|
buffer[offset++] = lFinChar;
|
||||||
|
count--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle CLEAR code
|
||||||
|
if (code == TBL_CLEAR && blockMode)
|
||||||
|
{
|
||||||
|
Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length);
|
||||||
|
lFreeEnt = TBL_FIRST - 1;
|
||||||
|
|
||||||
|
int nBytes = lNBits << 3;
|
||||||
|
lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes;
|
||||||
|
lNBits = LzwConstants.INIT_BITS;
|
||||||
|
lMaxCode = (1 << lNBits) - 1;
|
||||||
|
lBitMask = lMaxCode;
|
||||||
|
|
||||||
|
// Code tables reset
|
||||||
|
|
||||||
|
lBitPos = ResetBuf(lBitPos);
|
||||||
|
goto MainLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion B
|
||||||
|
|
||||||
|
#region C
|
||||||
|
|
||||||
|
// setup
|
||||||
|
int inCode = code;
|
||||||
|
lStackP = lStack.Length;
|
||||||
|
|
||||||
|
// Handle KwK case
|
||||||
|
if (code >= lFreeEnt)
|
||||||
|
{
|
||||||
|
if (code > lFreeEnt)
|
||||||
|
{
|
||||||
|
throw new LzwException("corrupt input: code=" + code +
|
||||||
|
", freeEnt=" + lFreeEnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
lStack[--lStackP] = lFinChar;
|
||||||
|
code = lOldCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate output characters in reverse order
|
||||||
|
while (code >= 256)
|
||||||
|
{
|
||||||
|
lStack[--lStackP] = lTabSuffix[code];
|
||||||
|
code = lTabPrefix[code];
|
||||||
|
}
|
||||||
|
|
||||||
|
lFinChar = lTabSuffix[code];
|
||||||
|
buffer[offset++] = lFinChar;
|
||||||
|
count--;
|
||||||
|
|
||||||
|
// And put them out in forward order
|
||||||
|
sSize = lStack.Length - lStackP;
|
||||||
|
int num = (sSize >= count) ? count : sSize;
|
||||||
|
Array.Copy(lStack, lStackP, buffer, offset, num);
|
||||||
|
offset += num;
|
||||||
|
count -= num;
|
||||||
|
lStackP += num;
|
||||||
|
|
||||||
|
#endregion C
|
||||||
|
|
||||||
|
#region D
|
||||||
|
|
||||||
|
// generate new entry in table
|
||||||
|
if (lFreeEnt < lMaxMaxCode)
|
||||||
|
{
|
||||||
|
lTabPrefix[lFreeEnt] = lOldCode;
|
||||||
|
lTabSuffix[lFreeEnt] = lFinChar;
|
||||||
|
lFreeEnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember previous code
|
||||||
|
lOldCode = inCode;
|
||||||
|
|
||||||
|
// if output buffer full, then return
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
nBits = lNBits;
|
||||||
|
maxCode = lMaxCode;
|
||||||
|
bitMask = lBitMask;
|
||||||
|
oldCode = lOldCode;
|
||||||
|
finChar = lFinChar;
|
||||||
|
stackP = lStackP;
|
||||||
|
freeEnt = lFreeEnt;
|
||||||
|
bitPos = lBitPos;
|
||||||
|
|
||||||
|
return offset - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion D
|
||||||
|
} // while
|
||||||
|
|
||||||
|
lBitPos = ResetBuf(lBitPos);
|
||||||
|
} while (got > 0); // do..while
|
||||||
|
|
||||||
|
nBits = lNBits;
|
||||||
|
maxCode = lMaxCode;
|
||||||
|
bitMask = lBitMask;
|
||||||
|
oldCode = lOldCode;
|
||||||
|
finChar = lFinChar;
|
||||||
|
stackP = lStackP;
|
||||||
|
freeEnt = lFreeEnt;
|
||||||
|
bitPos = lBitPos;
|
||||||
|
|
||||||
|
eof = true;
|
||||||
|
return offset - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves the unread data in the buffer to the beginning and resets
|
||||||
|
/// the pointers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bitPosition"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private int ResetBuf(int bitPosition)
|
||||||
|
{
|
||||||
|
int pos = bitPosition >> 3;
|
||||||
|
Array.Copy(data, pos, data, 0, end - pos);
|
||||||
|
end -= pos;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Fill()
|
||||||
|
{
|
||||||
|
got = baseInputStream.Read(data, end, data.Length - 1 - end);
|
||||||
|
if (got > 0)
|
||||||
|
{
|
||||||
|
end += got;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseHeader()
|
||||||
|
{
|
||||||
|
headerParsed = true;
|
||||||
|
|
||||||
|
byte[] hdr = new byte[LzwConstants.HDR_SIZE];
|
||||||
|
|
||||||
|
int result = baseInputStream.Read(hdr, 0, hdr.Length);
|
||||||
|
|
||||||
|
// Check the magic marker
|
||||||
|
if (result < 0)
|
||||||
|
throw new LzwException("Failed to read LZW header");
|
||||||
|
|
||||||
|
if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff))
|
||||||
|
{
|
||||||
|
throw new LzwException(String.Format(
|
||||||
|
"Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}",
|
||||||
|
hdr[0], hdr[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the 3rd header byte
|
||||||
|
blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0;
|
||||||
|
maxBits = hdr[2] & LzwConstants.BIT_MASK;
|
||||||
|
|
||||||
|
if (maxBits > LzwConstants.MAX_BITS)
|
||||||
|
{
|
||||||
|
throw new LzwException("Stream compressed with " + maxBits +
|
||||||
|
" bits, but decompression can only handle " +
|
||||||
|
LzwConstants.MAX_BITS + " bits.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0)
|
||||||
|
{
|
||||||
|
throw new LzwException("Unsupported bits set in the header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize variables
|
||||||
|
maxMaxCode = 1 << maxBits;
|
||||||
|
nBits = LzwConstants.INIT_BITS;
|
||||||
|
maxCode = (1 << nBits) - 1;
|
||||||
|
bitMask = maxCode;
|
||||||
|
oldCode = -1;
|
||||||
|
finChar = 0;
|
||||||
|
freeEnt = blockMode ? TBL_FIRST : 256;
|
||||||
|
|
||||||
|
tabPrefix = new int[1 << maxBits];
|
||||||
|
tabSuffix = new byte[1 << maxBits];
|
||||||
|
stack = new byte[1 << maxBits];
|
||||||
|
stackP = stack.Length;
|
||||||
|
|
||||||
|
for (int idx = 255; idx >= 0; idx--)
|
||||||
|
tabSuffix[idx] = (byte)idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Stream Overrides
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current stream supports reading
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return baseInputStream.CanRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value of false indicating seeking is not supported for this stream.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value of false indicating that this stream is not writeable.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value representing the length of the stream in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current position within the stream.
|
||||||
|
/// Throws a NotSupportedException when attempting to set the position
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="NotSupportedException">Attempting to set the position</exception>
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return baseInputStream.Position;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("InflaterInputStream Position not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes the baseInputStream
|
||||||
|
/// </summary>
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
baseInputStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the position within the current stream
|
||||||
|
/// Always throws a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The relative offset to seek to.</param>
|
||||||
|
/// <param name="origin">The <see cref="SeekOrigin"/> defining where to seek from.</param>
|
||||||
|
/// <returns>The new position in the stream.</returns>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Seek not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the length of the current stream
|
||||||
|
/// Always throws a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The new length value for the stream.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("InflaterInputStream SetLength not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a sequence of bytes to stream and advances the current position
|
||||||
|
/// This method always throws a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Thew buffer containing data to write.</param>
|
||||||
|
/// <param name="offset">The offset of the first byte to write.</param>
|
||||||
|
/// <param name="count">The number of bytes to write.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("InflaterInputStream Write not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes one byte to the current stream and advances the current position
|
||||||
|
/// Always throws a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The byte to write.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("InflaterInputStream WriteByte not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the input stream. When <see cref="IsStreamOwner"></see>
|
||||||
|
/// is true the underlying stream is also closed.
|
||||||
|
/// </summary>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!isClosed)
|
||||||
|
{
|
||||||
|
isClosed = true;
|
||||||
|
if (IsStreamOwner)
|
||||||
|
{
|
||||||
|
baseInputStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Stream Overrides
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private Stream baseInputStream;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag indicating wether this instance has been closed or not.
|
||||||
|
/// </summary>
|
||||||
|
private bool isClosed;
|
||||||
|
|
||||||
|
private readonly byte[] one = new byte[1];
|
||||||
|
private bool headerParsed;
|
||||||
|
|
||||||
|
// string table stuff
|
||||||
|
private const int TBL_CLEAR = 0x100;
|
||||||
|
|
||||||
|
private const int TBL_FIRST = TBL_CLEAR + 1;
|
||||||
|
|
||||||
|
private int[] tabPrefix;
|
||||||
|
private byte[] tabSuffix;
|
||||||
|
private readonly int[] zeros = new int[256];
|
||||||
|
private byte[] stack;
|
||||||
|
|
||||||
|
// various state
|
||||||
|
private bool blockMode;
|
||||||
|
|
||||||
|
private int nBits;
|
||||||
|
private int maxBits;
|
||||||
|
private int maxMaxCode;
|
||||||
|
private int maxCode;
|
||||||
|
private int bitMask;
|
||||||
|
private int oldCode;
|
||||||
|
private byte finChar;
|
||||||
|
private int stackP;
|
||||||
|
private int freeEnt;
|
||||||
|
|
||||||
|
// input buffer
|
||||||
|
private readonly byte[] data = new byte[1024 * 8];
|
||||||
|
|
||||||
|
private int bitPos;
|
||||||
|
private int end;
|
||||||
|
private int got;
|
||||||
|
private bool eof;
|
||||||
|
private const int EXTRA = 64;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Tar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This exception is used to indicate that there is a problem
|
||||||
|
/// with a TAR archive header.
|
||||||
|
/// </summary>
|
||||||
|
public class InvalidHeaderException : TarException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of the InvalidHeaderException class.
|
||||||
|
/// </summary>
|
||||||
|
public InvalidHeaderException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises a new instance of the InvalidHeaderException class with a specified message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">Message describing the exception cause.</param>
|
||||||
|
public InvalidHeaderException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of InvalidHeaderException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">Message describing the problem.</param>
|
||||||
|
/// <param name="exception">The exception that is the cause of the current exception.</param>
|
||||||
|
public InvalidHeaderException(string message, Exception exception)
|
||||||
|
: base(message, exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,958 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Tar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to advise clients of 'events' while processing archives
|
||||||
|
/// </summary>
|
||||||
|
public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The TarArchive class implements the concept of a
|
||||||
|
/// 'Tape Archive'. A tar archive is a series of entries, each of
|
||||||
|
/// which represents a file system object. Each entry in
|
||||||
|
/// the archive consists of a header block followed by 0 or more data blocks.
|
||||||
|
/// Directory entries consist only of the header block, and are followed by entries
|
||||||
|
/// for the directory's contents. File entries consist of a
|
||||||
|
/// header followed by the number of blocks needed to
|
||||||
|
/// contain the file's contents. All entries are written on
|
||||||
|
/// block boundaries. Blocks are 512 bytes long.
|
||||||
|
///
|
||||||
|
/// TarArchives are instantiated in either read or write mode,
|
||||||
|
/// based upon whether they are instantiated with an InputStream
|
||||||
|
/// or an OutputStream. Once instantiated TarArchives read/write
|
||||||
|
/// mode can not be changed.
|
||||||
|
///
|
||||||
|
/// There is currently no support for random access to tar archives.
|
||||||
|
/// However, it seems that subclassing TarArchive, and using the
|
||||||
|
/// TarBuffer.CurrentRecord and TarBuffer.CurrentBlock
|
||||||
|
/// properties, this would be rather trivial.
|
||||||
|
/// </summary>
|
||||||
|
public class TarArchive : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client hook allowing detailed information to be reported during processing
|
||||||
|
/// </summary>
|
||||||
|
public event ProgressMessageHandler ProgressMessageEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the ProgressMessage event
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry">The <see cref="TarEntry">TarEntry</see> for this event</param>
|
||||||
|
/// <param name="message">message for this event. Null is no message</param>
|
||||||
|
protected virtual void OnProgressMessageEvent(TarEntry entry, string message)
|
||||||
|
{
|
||||||
|
ProgressMessageHandler handler = ProgressMessageEvent;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, entry, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for a default <see cref="TarArchive"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected TarArchive()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initalise a TarArchive for input.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The <see cref="TarInputStream"/> to use for input.</param>
|
||||||
|
protected TarArchive(TarInputStream stream)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
tarIn = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a TarArchive for output.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The <see cref="TarOutputStream"/> to use for output.</param>
|
||||||
|
protected TarArchive(TarOutputStream stream)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
tarOut = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Static factory methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The InputStream based constructors create a TarArchive for the
|
||||||
|
/// purposes of extracting or listing a tar archive. Thus, use
|
||||||
|
/// these constructors when you wish to extract files from or list
|
||||||
|
/// the contents of an existing tar archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">The stream to retrieve archive data from.</param>
|
||||||
|
/// <returns>Returns a new <see cref="TarArchive"/> suitable for reading from.</returns>
|
||||||
|
public static TarArchive CreateInputTarArchive(Stream inputStream)
|
||||||
|
{
|
||||||
|
if (inputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(inputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
var tarStream = inputStream as TarInputStream;
|
||||||
|
|
||||||
|
TarArchive result;
|
||||||
|
if (tarStream != null)
|
||||||
|
{
|
||||||
|
result = new TarArchive(tarStream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create TarArchive for reading setting block factor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">A stream containing the tar archive contents</param>
|
||||||
|
/// <param name="blockFactor">The blocking factor to apply</param>
|
||||||
|
/// <returns>Returns a <see cref="TarArchive"/> suitable for reading.</returns>
|
||||||
|
public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor)
|
||||||
|
{
|
||||||
|
if (inputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(inputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputStream is TarInputStream)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("TarInputStream not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TarArchive(new TarInputStream(inputStream, blockFactor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a TarArchive for writing to, using the default blocking factor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputStream">The <see cref="Stream"/> to write to</param>
|
||||||
|
/// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns>
|
||||||
|
public static TarArchive CreateOutputTarArchive(Stream outputStream)
|
||||||
|
{
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(outputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
var tarStream = outputStream as TarOutputStream;
|
||||||
|
|
||||||
|
TarArchive result;
|
||||||
|
if (tarStream != null)
|
||||||
|
{
|
||||||
|
result = new TarArchive(tarStream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="TarArchive">tar archive</see> for writing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputStream">The stream to write to</param>
|
||||||
|
/// <param name="blockFactor">The blocking factor to use for buffering.</param>
|
||||||
|
/// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns>
|
||||||
|
public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor)
|
||||||
|
{
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(outputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputStream is TarOutputStream)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("TarOutputStream is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TarArchive(new TarOutputStream(outputStream, blockFactor));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Static factory methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the flag that determines whether existing files are
|
||||||
|
/// kept, or overwritten during extraction.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keepExistingFiles">
|
||||||
|
/// If true, do not overwrite existing files.
|
||||||
|
/// </param>
|
||||||
|
public void SetKeepOldFiles(bool keepExistingFiles)
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
keepOldFiles = keepExistingFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the ascii file translation flag. If ascii file translation
|
||||||
|
/// is true, then the file is checked to see if it a binary file or not.
|
||||||
|
/// If the flag is true and the test indicates it is ascii text
|
||||||
|
/// file, it will be translated. The translation converts the local
|
||||||
|
/// operating system's concept of line ends into the UNIX line end,
|
||||||
|
/// '\n', which is the defacto standard for a TAR archive. This makes
|
||||||
|
/// text files compatible with UNIX.
|
||||||
|
/// </summary>
|
||||||
|
public bool AsciiTranslate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
return asciiTranslate;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
asciiTranslate = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the ascii file translation flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name= "translateAsciiFiles">
|
||||||
|
/// If true, translate ascii text files.
|
||||||
|
/// </param>
|
||||||
|
[Obsolete("Use the AsciiTranslate property")]
|
||||||
|
public void SetAsciiTranslation(bool translateAsciiFiles)
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
asciiTranslate = translateAsciiFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PathPrefix is added to entry names as they are written if the value is not null.
|
||||||
|
/// A slash character is appended after PathPrefix
|
||||||
|
/// </summary>
|
||||||
|
public string PathPrefix
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
pathPrefix = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RootPath is removed from entry names if it is found at the
|
||||||
|
/// beginning of the name.
|
||||||
|
/// </summary>
|
||||||
|
public string RootPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
// Convert to forward slashes for matching. Trim trailing / for correct final path
|
||||||
|
rootPath = value.Replace('\\', '/').TrimEnd('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set user and group information that will be used to fill in the
|
||||||
|
/// tar archive's entry headers. This information is based on that available
|
||||||
|
/// for the linux operating system, which is not always available on other
|
||||||
|
/// operating systems. TarArchive allows the programmer to specify values
|
||||||
|
/// to be used in their place.
|
||||||
|
/// <see cref="ApplyUserInfoOverrides"/> is set to true by this call.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">
|
||||||
|
/// The user id to use in the headers.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="userName">
|
||||||
|
/// The user name to use in the headers.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="groupId">
|
||||||
|
/// The group id to use in the headers.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="groupName">
|
||||||
|
/// The group name to use in the headers.
|
||||||
|
/// </param>
|
||||||
|
public void SetUserInfo(int userId, string userName, int groupId, string groupName)
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userId = userId;
|
||||||
|
this.userName = userName;
|
||||||
|
this.groupId = groupId;
|
||||||
|
this.groupName = groupName;
|
||||||
|
applyUserInfoOverrides = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get or set a value indicating if overrides defined by <see cref="SetUserInfo">SetUserInfo</see> should be applied.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>If overrides are not applied then the values as set in each header will be used.</remarks>
|
||||||
|
public bool ApplyUserInfoOverrides
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyUserInfoOverrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
applyUserInfoOverrides = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the archive user id.
|
||||||
|
/// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
|
||||||
|
/// on how to allow setting values on a per entry basis.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current user id.
|
||||||
|
/// </returns>
|
||||||
|
public int UserId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the archive user name.
|
||||||
|
/// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
|
||||||
|
/// on how to allow setting values on a per entry basis.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current user name.
|
||||||
|
/// </returns>
|
||||||
|
public string UserName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the archive group id.
|
||||||
|
/// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
|
||||||
|
/// on how to allow setting values on a per entry basis.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current group id.
|
||||||
|
/// </returns>
|
||||||
|
public int GroupId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the archive group name.
|
||||||
|
/// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
|
||||||
|
/// on how to allow setting values on a per entry basis.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current group name.
|
||||||
|
/// </returns>
|
||||||
|
public string GroupName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the archive's record size. Tar archives are composed of
|
||||||
|
/// a series of RECORDS each containing a number of BLOCKS.
|
||||||
|
/// This allowed tar archives to match the IO characteristics of
|
||||||
|
/// the physical device being used. Archives are expected
|
||||||
|
/// to be properly "blocked".
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The record size this archive is using.
|
||||||
|
/// </returns>
|
||||||
|
public int RecordSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tarIn != null)
|
||||||
|
{
|
||||||
|
return tarIn.RecordSize;
|
||||||
|
}
|
||||||
|
else if (tarOut != null)
|
||||||
|
{
|
||||||
|
return tarOut.RecordSize;
|
||||||
|
}
|
||||||
|
return TarBuffer.DefaultRecordSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the IsStreamOwner property on the underlying stream.
|
||||||
|
/// Set this to false to prevent the Close of the TarArchive from closing the stream.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsStreamOwner
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (tarIn != null)
|
||||||
|
{
|
||||||
|
tarIn.IsStreamOwner = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tarOut.IsStreamOwner = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close the archive.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use Close instead")]
|
||||||
|
public void CloseArchive()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform the "list" command for the archive contents.
|
||||||
|
///
|
||||||
|
/// NOTE That this method uses the <see cref="ProgressMessageEvent"> progress event</see> to actually list
|
||||||
|
/// the contents. If the progress display event is not set, nothing will be listed!
|
||||||
|
/// </summary>
|
||||||
|
public void ListContents()
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
TarEntry entry = tarIn.GetNextEntry();
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
OnProgressMessageEvent(entry, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform the "extract" command and extract the contents of the archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destinationDirectory">
|
||||||
|
/// The destination directory into which to extract.
|
||||||
|
/// </param>
|
||||||
|
public void ExtractContents(string destinationDirectory)
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
TarEntry entry = tarIn.GetNextEntry();
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.TarHeader.TypeFlag == TarHeader.LF_LINK || entry.TarHeader.TypeFlag == TarHeader.LF_SYMLINK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ExtractEntry(destinationDirectory, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract an entry from the archive. This method assumes that the
|
||||||
|
/// tarIn stream has been properly set with a call to GetNextEntry().
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destDir">
|
||||||
|
/// The destination directory into which to extract.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="entry">
|
||||||
|
/// The TarEntry returned by tarIn.GetNextEntry().
|
||||||
|
/// </param>
|
||||||
|
private void ExtractEntry(string destDir, TarEntry entry)
|
||||||
|
{
|
||||||
|
OnProgressMessageEvent(entry, null);
|
||||||
|
|
||||||
|
string name = entry.Name;
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(name))
|
||||||
|
{
|
||||||
|
// NOTE:
|
||||||
|
// for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt
|
||||||
|
name = name.Substring(Path.GetPathRoot(name).Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
name = name.Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
string destFile = Path.Combine(destDir, name);
|
||||||
|
|
||||||
|
if (entry.IsDirectory)
|
||||||
|
{
|
||||||
|
EnsureDirectoryExists(destFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string parentDirectory = Path.GetDirectoryName(destFile);
|
||||||
|
EnsureDirectoryExists(parentDirectory);
|
||||||
|
|
||||||
|
bool process = true;
|
||||||
|
var fileInfo = new FileInfo(destFile);
|
||||||
|
if (fileInfo.Exists)
|
||||||
|
{
|
||||||
|
if (keepOldFiles)
|
||||||
|
{
|
||||||
|
OnProgressMessageEvent(entry, "Destination file already exists");
|
||||||
|
process = false;
|
||||||
|
}
|
||||||
|
else if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0)
|
||||||
|
{
|
||||||
|
OnProgressMessageEvent(entry, "Destination file already exists, and is read-only");
|
||||||
|
process = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process)
|
||||||
|
{
|
||||||
|
bool asciiTrans = false;
|
||||||
|
|
||||||
|
Stream outputStream = File.Create(destFile);
|
||||||
|
if (this.asciiTranslate)
|
||||||
|
{
|
||||||
|
asciiTrans = !IsBinary(destFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamWriter outw = null;
|
||||||
|
if (asciiTrans)
|
||||||
|
{
|
||||||
|
outw = new StreamWriter(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] rdbuf = new byte[32 * 1024];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int numRead = tarIn.Read(rdbuf, 0, rdbuf.Length);
|
||||||
|
|
||||||
|
if (numRead <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asciiTrans)
|
||||||
|
{
|
||||||
|
for (int off = 0, b = 0; b < numRead; ++b)
|
||||||
|
{
|
||||||
|
if (rdbuf[b] == 10)
|
||||||
|
{
|
||||||
|
string s = Encoding.ASCII.GetString(rdbuf, off, (b - off));
|
||||||
|
outw.WriteLine(s);
|
||||||
|
off = b + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outputStream.Write(rdbuf, 0, numRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asciiTrans)
|
||||||
|
{
|
||||||
|
outw.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outputStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an entry to the archive. This method will call the putNextEntry
|
||||||
|
/// and then write the contents of the entry, and finally call closeEntry()
|
||||||
|
/// for entries that are files. For directories, it will call putNextEntry(),
|
||||||
|
/// and then, if the recurse flag is true, process each entry that is a
|
||||||
|
/// child of the directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceEntry">
|
||||||
|
/// The TarEntry representing the entry to write to the archive.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="recurse">
|
||||||
|
/// If true, process the children of directory entries.
|
||||||
|
/// </param>
|
||||||
|
public void WriteEntry(TarEntry sourceEntry, bool recurse)
|
||||||
|
{
|
||||||
|
if (sourceEntry == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(sourceEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("TarArchive");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (recurse)
|
||||||
|
{
|
||||||
|
TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName,
|
||||||
|
sourceEntry.GroupId, sourceEntry.GroupName);
|
||||||
|
}
|
||||||
|
WriteEntryCore(sourceEntry, recurse);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (recurse)
|
||||||
|
{
|
||||||
|
TarHeader.RestoreSetValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an entry to the archive. This method will call the putNextEntry
|
||||||
|
/// and then write the contents of the entry, and finally call closeEntry()
|
||||||
|
/// for entries that are files. For directories, it will call putNextEntry(),
|
||||||
|
/// and then, if the recurse flag is true, process each entry that is a
|
||||||
|
/// child of the directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceEntry">
|
||||||
|
/// The TarEntry representing the entry to write to the archive.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="recurse">
|
||||||
|
/// If true, process the children of directory entries.
|
||||||
|
/// </param>
|
||||||
|
private void WriteEntryCore(TarEntry sourceEntry, bool recurse)
|
||||||
|
{
|
||||||
|
string tempFileName = null;
|
||||||
|
string entryFilename = sourceEntry.File;
|
||||||
|
|
||||||
|
var entry = (TarEntry)sourceEntry.Clone();
|
||||||
|
|
||||||
|
if (applyUserInfoOverrides)
|
||||||
|
{
|
||||||
|
entry.GroupId = groupId;
|
||||||
|
entry.GroupName = groupName;
|
||||||
|
entry.UserId = userId;
|
||||||
|
entry.UserName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnProgressMessageEvent(entry, null);
|
||||||
|
|
||||||
|
if (asciiTranslate && !entry.IsDirectory)
|
||||||
|
{
|
||||||
|
if (!IsBinary(entryFilename))
|
||||||
|
{
|
||||||
|
tempFileName = Path.GetTempFileName();
|
||||||
|
|
||||||
|
using (StreamReader inStream = File.OpenText(entryFilename))
|
||||||
|
{
|
||||||
|
using (Stream outStream = File.Create(tempFileName))
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
string line = inStream.ReadLine();
|
||||||
|
if (line == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
byte[] data = Encoding.ASCII.GetBytes(line);
|
||||||
|
outStream.Write(data, 0, data.Length);
|
||||||
|
outStream.WriteByte((byte)'\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
outStream.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Size = new FileInfo(tempFileName).Length;
|
||||||
|
entryFilename = tempFileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string newName = null;
|
||||||
|
|
||||||
|
if (rootPath != null)
|
||||||
|
{
|
||||||
|
if (entry.Name.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
newName = entry.Name.Substring(rootPath.Length + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathPrefix != null)
|
||||||
|
{
|
||||||
|
newName = (newName == null) ? pathPrefix + "/" + entry.Name : pathPrefix + "/" + newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newName != null)
|
||||||
|
{
|
||||||
|
entry.Name = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
tarOut.PutNextEntry(entry);
|
||||||
|
|
||||||
|
if (entry.IsDirectory)
|
||||||
|
{
|
||||||
|
if (recurse)
|
||||||
|
{
|
||||||
|
TarEntry[] list = entry.GetDirectoryEntries();
|
||||||
|
for (int i = 0; i < list.Length; ++i)
|
||||||
|
{
|
||||||
|
WriteEntryCore(list[i], recurse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (Stream inputStream = File.OpenRead(entryFilename))
|
||||||
|
{
|
||||||
|
byte[] localBuffer = new byte[32 * 1024];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int numRead = inputStream.Read(localBuffer, 0, localBuffer.Length);
|
||||||
|
|
||||||
|
if (numRead <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tarOut.Write(localBuffer, 0, numRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(tempFileName))
|
||||||
|
{
|
||||||
|
File.Delete(tempFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
tarOut.CloseEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases the unmanaged resources used by the FileStream and optionally releases the managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true to release both managed and unmanaged resources;
|
||||||
|
/// false to release only unmanaged resources.</param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!isDisposed)
|
||||||
|
{
|
||||||
|
isDisposed = true;
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (tarOut != null)
|
||||||
|
{
|
||||||
|
tarOut.Flush();
|
||||||
|
tarOut.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tarIn != null)
|
||||||
|
{
|
||||||
|
tarIn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the archive and releases any associated resources.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Close()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that resources are freed and other cleanup operations are performed
|
||||||
|
/// when the garbage collector reclaims the <see cref="TarArchive"/>.
|
||||||
|
/// </summary>
|
||||||
|
~TarArchive()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureDirectoryExists(string directoryName)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(directoryName))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directoryName);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new TarException("Exception creating directory '" + directoryName + "', " + e.Message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: TarArchive - Is there a better way to test for a text file?
|
||||||
|
// It no longer reads entire files into memory but is still a weak test!
|
||||||
|
// This assumes that byte values 0-7, 14-31 or 255 are binary
|
||||||
|
// and that all non text files contain one of these values
|
||||||
|
private static bool IsBinary(string filename)
|
||||||
|
{
|
||||||
|
using (FileStream fs = File.OpenRead(filename))
|
||||||
|
{
|
||||||
|
int sampleSize = Math.Min(4096, (int)fs.Length);
|
||||||
|
byte[] content = new byte[sampleSize];
|
||||||
|
|
||||||
|
int bytesRead = fs.Read(content, 0, sampleSize);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytesRead; ++i)
|
||||||
|
{
|
||||||
|
byte b = content[i];
|
||||||
|
if ((b < 8) || ((b > 13) && (b < 32)) || (b == 255))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private bool keepOldFiles;
|
||||||
|
private bool asciiTranslate;
|
||||||
|
|
||||||
|
private int userId;
|
||||||
|
private string userName = string.Empty;
|
||||||
|
private int groupId;
|
||||||
|
private string groupName = string.Empty;
|
||||||
|
|
||||||
|
private string rootPath;
|
||||||
|
private string pathPrefix;
|
||||||
|
|
||||||
|
private bool applyUserInfoOverrides;
|
||||||
|
|
||||||
|
private TarInputStream tarIn;
|
||||||
|
private TarOutputStream tarOut;
|
||||||
|
private bool isDisposed;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,599 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Tar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The TarBuffer class implements the tar archive concept
|
||||||
|
/// of a buffered input stream. This concept goes back to the
|
||||||
|
/// days of blocked tape drives and special io devices. In the
|
||||||
|
/// C# universe, the only real function that this class
|
||||||
|
/// performs is to ensure that files have the correct "record"
|
||||||
|
/// size, or other tars will complain.
|
||||||
|
/// <p>
|
||||||
|
/// You should never have a need to access this class directly.
|
||||||
|
/// TarBuffers are created by Tar IO Streams.
|
||||||
|
/// </p>
|
||||||
|
/// </summary>
|
||||||
|
public class TarBuffer
|
||||||
|
{
|
||||||
|
/* A quote from GNU tar man file on blocking and records
|
||||||
|
A `tar' archive file contains a series of blocks. Each block
|
||||||
|
contains `BLOCKSIZE' bytes. Although this format may be thought of as
|
||||||
|
being on magnetic tape, other media are often used.
|
||||||
|
|
||||||
|
Each file archived is represented by a header block which describes
|
||||||
|
the file, followed by zero or more blocks which give the contents of
|
||||||
|
the file. At the end of the archive file there may be a block filled
|
||||||
|
with binary zeros as an end-of-file marker. A reasonable system should
|
||||||
|
write a block of zeros at the end, but must not assume that such a
|
||||||
|
block exists when reading an archive.
|
||||||
|
|
||||||
|
The blocks may be "blocked" for physical I/O operations. Each
|
||||||
|
record of N blocks is written with a single 'write ()'
|
||||||
|
operation. On magnetic tapes, the result of such a write is a single
|
||||||
|
record. When writing an archive, the last record of blocks should be
|
||||||
|
written at the full size, with blocks after the zero block containing
|
||||||
|
all zeros. When reading an archive, a reasonable system should
|
||||||
|
properly handle an archive whose last record is shorter than the rest,
|
||||||
|
or which contains garbage records after a zero block.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of a block in a tar archive in bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is 512 bytes.</remarks>
|
||||||
|
public const int BlockSize = 512;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of blocks in a default record.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The default value is 20 blocks per record.
|
||||||
|
/// </remarks>
|
||||||
|
public const int DefaultBlockFactor = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size in bytes of a default record.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The default size is 10KB.
|
||||||
|
/// </remarks>
|
||||||
|
public const int DefaultRecordSize = BlockSize * DefaultBlockFactor;
|
||||||
|
|
||||||
|
#endregion Constants
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the record size for this buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The record size in bytes.
|
||||||
|
/// This is equal to the <see cref="BlockFactor"/> multiplied by the <see cref="BlockSize"/></value>
|
||||||
|
public int RecordSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return recordSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the TAR Buffer's record size.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The record size in bytes.
|
||||||
|
/// This is equal to the <see cref="BlockFactor"/> multiplied by the <see cref="BlockSize"/></returns>
|
||||||
|
[Obsolete("Use RecordSize property instead")]
|
||||||
|
public int GetRecordSize()
|
||||||
|
{
|
||||||
|
return recordSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the Blocking factor for the buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <value>This is the number of blocks in each record.</value>
|
||||||
|
public int BlockFactor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return blockFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the TAR Buffer's block factor
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The block factor; the number of blocks per record.</returns>
|
||||||
|
[Obsolete("Use BlockFactor property instead")]
|
||||||
|
public int GetBlockFactor()
|
||||||
|
{
|
||||||
|
return blockFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct a default TarBuffer
|
||||||
|
/// </summary>
|
||||||
|
protected TarBuffer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create TarBuffer for reading with default BlockFactor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">Stream to buffer</param>
|
||||||
|
/// <returns>A new <see cref="TarBuffer"/> suitable for input.</returns>
|
||||||
|
public static TarBuffer CreateInputTarBuffer(Stream inputStream)
|
||||||
|
{
|
||||||
|
if (inputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(inputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateInputTarBuffer(inputStream, DefaultBlockFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct TarBuffer for reading inputStream setting BlockFactor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">Stream to buffer</param>
|
||||||
|
/// <param name="blockFactor">Blocking factor to apply</param>
|
||||||
|
/// <returns>A new <see cref="TarBuffer"/> suitable for input.</returns>
|
||||||
|
public static TarBuffer CreateInputTarBuffer(Stream inputStream, int blockFactor)
|
||||||
|
{
|
||||||
|
if (inputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(inputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockFactor <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
var tarBuffer = new TarBuffer();
|
||||||
|
tarBuffer.inputStream = inputStream;
|
||||||
|
tarBuffer.outputStream = null;
|
||||||
|
tarBuffer.Initialize(blockFactor);
|
||||||
|
|
||||||
|
return tarBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct TarBuffer for writing with default BlockFactor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputStream">output stream for buffer</param>
|
||||||
|
/// <returns>A new <see cref="TarBuffer"/> suitable for output.</returns>
|
||||||
|
public static TarBuffer CreateOutputTarBuffer(Stream outputStream)
|
||||||
|
{
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(outputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateOutputTarBuffer(outputStream, DefaultBlockFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct TarBuffer for writing Tar output to streams.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputStream">Output stream to write to.</param>
|
||||||
|
/// <param name="blockFactor">Blocking factor to apply</param>
|
||||||
|
/// <returns>A new <see cref="TarBuffer"/> suitable for output.</returns>
|
||||||
|
public static TarBuffer CreateOutputTarBuffer(Stream outputStream, int blockFactor)
|
||||||
|
{
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(outputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockFactor <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
var tarBuffer = new TarBuffer();
|
||||||
|
tarBuffer.inputStream = null;
|
||||||
|
tarBuffer.outputStream = outputStream;
|
||||||
|
tarBuffer.Initialize(blockFactor);
|
||||||
|
|
||||||
|
return tarBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialization common to all constructors.
|
||||||
|
/// </summary>
|
||||||
|
private void Initialize(int archiveBlockFactor)
|
||||||
|
{
|
||||||
|
blockFactor = archiveBlockFactor;
|
||||||
|
recordSize = archiveBlockFactor * BlockSize;
|
||||||
|
recordBuffer = new byte[RecordSize];
|
||||||
|
|
||||||
|
if (inputStream != null)
|
||||||
|
{
|
||||||
|
currentRecordIndex = -1;
|
||||||
|
currentBlockIndex = BlockFactor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentRecordIndex = 0;
|
||||||
|
currentBlockIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if an archive block indicates End of Archive. End of
|
||||||
|
/// archive is indicated by a block that consists entirely of null bytes.
|
||||||
|
/// All remaining blocks for the record should also be null's
|
||||||
|
/// However some older tars only do a couple of null blocks (Old GNU tar for one)
|
||||||
|
/// and also partial records
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "block">The data block to check.</param>
|
||||||
|
/// <returns>Returns true if the block is an EOF block; false otherwise.</returns>
|
||||||
|
[Obsolete("Use IsEndOfArchiveBlock instead")]
|
||||||
|
public bool IsEOFBlock(byte[] block)
|
||||||
|
{
|
||||||
|
if (block == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.Length != BlockSize)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("block length is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < BlockSize; ++i)
|
||||||
|
{
|
||||||
|
if (block[i] != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if an archive block indicates the End of an Archive has been reached.
|
||||||
|
/// End of archive is indicated by a block that consists entirely of null bytes.
|
||||||
|
/// All remaining blocks for the record should also be null's
|
||||||
|
/// However some older tars only do a couple of null blocks (Old GNU tar for one)
|
||||||
|
/// and also partial records
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "block">The data block to check.</param>
|
||||||
|
/// <returns>Returns true if the block is an EOF block; false otherwise.</returns>
|
||||||
|
public static bool IsEndOfArchiveBlock(byte[] block)
|
||||||
|
{
|
||||||
|
if (block == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.Length != BlockSize)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("block length is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < BlockSize; ++i)
|
||||||
|
{
|
||||||
|
if (block[i] != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip over a block on the input stream.
|
||||||
|
/// </summary>
|
||||||
|
public void SkipBlock()
|
||||||
|
{
|
||||||
|
if (inputStream == null)
|
||||||
|
{
|
||||||
|
throw new TarException("no input stream defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBlockIndex >= BlockFactor)
|
||||||
|
{
|
||||||
|
if (!ReadRecord())
|
||||||
|
{
|
||||||
|
throw new TarException("Failed to read a record");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBlockIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a block from the input stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The block of data read.
|
||||||
|
/// </returns>
|
||||||
|
public byte[] ReadBlock()
|
||||||
|
{
|
||||||
|
if (inputStream == null)
|
||||||
|
{
|
||||||
|
throw new TarException("TarBuffer.ReadBlock - no input stream defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBlockIndex >= BlockFactor)
|
||||||
|
{
|
||||||
|
if (!ReadRecord())
|
||||||
|
{
|
||||||
|
throw new TarException("Failed to read a record");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] result = new byte[BlockSize];
|
||||||
|
|
||||||
|
Array.Copy(recordBuffer, (currentBlockIndex * BlockSize), result, 0, BlockSize);
|
||||||
|
currentBlockIndex++;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a record from data stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// false if End-Of-File, else true.
|
||||||
|
/// </returns>
|
||||||
|
private bool ReadRecord()
|
||||||
|
{
|
||||||
|
if (inputStream == null)
|
||||||
|
{
|
||||||
|
throw new TarException("no input stream stream defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBlockIndex = 0;
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
int bytesNeeded = RecordSize;
|
||||||
|
|
||||||
|
while (bytesNeeded > 0)
|
||||||
|
{
|
||||||
|
long numBytes = inputStream.Read(recordBuffer, offset, bytesNeeded);
|
||||||
|
|
||||||
|
//
|
||||||
|
// NOTE
|
||||||
|
// We have found EOF, and the record is not full!
|
||||||
|
//
|
||||||
|
// This is a broken archive. It does not follow the standard
|
||||||
|
// blocking algorithm. However, because we are generous, and
|
||||||
|
// it requires little effort, we will simply ignore the error
|
||||||
|
// and continue as if the entire record were read. This does
|
||||||
|
// not appear to break anything upstream. We used to return
|
||||||
|
// false in this case.
|
||||||
|
//
|
||||||
|
// Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
|
||||||
|
//
|
||||||
|
if (numBytes <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += (int)numBytes;
|
||||||
|
bytesNeeded -= (int)numBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRecordIndex++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current block number, within the current record, zero based.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Block numbers are zero based values</remarks>
|
||||||
|
/// <seealso cref="RecordSize"/>
|
||||||
|
public int CurrentBlock
|
||||||
|
{
|
||||||
|
get { return currentBlockIndex; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating ownership of underlying stream.
|
||||||
|
/// When the flag is true <see cref="Close" /> will close the underlying stream also.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is true.</remarks>
|
||||||
|
public bool IsStreamOwner { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current block number, within the current record, zero based.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current zero based block number.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// The absolute block number = (<see cref="GetCurrentRecordNum">record number</see> * <see cref="BlockFactor">block factor</see>) + <see cref="GetCurrentBlockNum">block number</see>.
|
||||||
|
/// </remarks>
|
||||||
|
[Obsolete("Use CurrentBlock property instead")]
|
||||||
|
public int GetCurrentBlockNum()
|
||||||
|
{
|
||||||
|
return currentBlockIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current record number.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current zero based record number.
|
||||||
|
/// </returns>
|
||||||
|
public int CurrentRecord
|
||||||
|
{
|
||||||
|
get { return currentRecordIndex; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current record number.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current zero based record number.
|
||||||
|
/// </returns>
|
||||||
|
[Obsolete("Use CurrentRecord property instead")]
|
||||||
|
public int GetCurrentRecordNum()
|
||||||
|
{
|
||||||
|
return currentRecordIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a block of data to the archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="block">
|
||||||
|
/// The data to write to the archive.
|
||||||
|
/// </param>
|
||||||
|
public void WriteBlock(byte[] block)
|
||||||
|
{
|
||||||
|
if (block == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new TarException("TarBuffer.WriteBlock - no output stream defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.Length != BlockSize)
|
||||||
|
{
|
||||||
|
string errorText = string.Format("TarBuffer.WriteBlock - block to write has length '{0}' which is not the block size of '{1}'",
|
||||||
|
block.Length, BlockSize);
|
||||||
|
throw new TarException(errorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBlockIndex >= BlockFactor)
|
||||||
|
{
|
||||||
|
WriteRecord();
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Copy(block, 0, recordBuffer, (currentBlockIndex * BlockSize), BlockSize);
|
||||||
|
currentBlockIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an archive record to the archive, where the record may be
|
||||||
|
/// inside of a larger array buffer. The buffer must be "offset plus
|
||||||
|
/// record size" long.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// The buffer containing the record data to write.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// The offset of the record data within buffer.
|
||||||
|
/// </param>
|
||||||
|
public void WriteBlock(byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new TarException("TarBuffer.WriteBlock - no output stream stream defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((offset < 0) || (offset >= buffer.Length))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((offset + BlockSize) > buffer.Length)
|
||||||
|
{
|
||||||
|
string errorText = string.Format("TarBuffer.WriteBlock - record has length '{0}' with offset '{1}' which is less than the record size of '{2}'",
|
||||||
|
buffer.Length, offset, recordSize);
|
||||||
|
throw new TarException(errorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBlockIndex >= BlockFactor)
|
||||||
|
{
|
||||||
|
WriteRecord();
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Copy(buffer, offset, recordBuffer, (currentBlockIndex * BlockSize), BlockSize);
|
||||||
|
|
||||||
|
currentBlockIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a TarBuffer record to the archive.
|
||||||
|
/// </summary>
|
||||||
|
private void WriteRecord()
|
||||||
|
{
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new TarException("TarBuffer.WriteRecord no output stream defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream.Write(recordBuffer, 0, RecordSize);
|
||||||
|
outputStream.Flush();
|
||||||
|
|
||||||
|
currentBlockIndex = 0;
|
||||||
|
currentRecordIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WriteFinalRecord writes the current record buffer to output any unwritten data is present.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Any trailing bytes are set to zero which is by definition correct behaviour
|
||||||
|
/// for the end of a tar stream.</remarks>
|
||||||
|
private void WriteFinalRecord()
|
||||||
|
{
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new TarException("TarBuffer.WriteFinalRecord no output stream defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBlockIndex > 0)
|
||||||
|
{
|
||||||
|
int dataBytes = currentBlockIndex * BlockSize;
|
||||||
|
Array.Clear(recordBuffer, dataBytes, RecordSize - dataBytes);
|
||||||
|
WriteRecord();
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close the TarBuffer. If this is an output buffer, also flush the
|
||||||
|
/// current block before closing.
|
||||||
|
/// </summary>
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (outputStream != null)
|
||||||
|
{
|
||||||
|
WriteFinalRecord();
|
||||||
|
|
||||||
|
if (IsStreamOwner)
|
||||||
|
{
|
||||||
|
outputStream.Dispose();
|
||||||
|
}
|
||||||
|
outputStream = null;
|
||||||
|
}
|
||||||
|
else if (inputStream != null)
|
||||||
|
{
|
||||||
|
if (IsStreamOwner)
|
||||||
|
{
|
||||||
|
inputStream.Dispose();
|
||||||
|
}
|
||||||
|
inputStream = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private Stream inputStream;
|
||||||
|
private Stream outputStream;
|
||||||
|
|
||||||
|
private byte[] recordBuffer;
|
||||||
|
private int currentBlockIndex;
|
||||||
|
private int currentRecordIndex;
|
||||||
|
|
||||||
|
private int recordSize = DefaultRecordSize;
|
||||||
|
private int blockFactor = DefaultBlockFactor;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,547 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Tar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents an entry in a Tar archive. It consists
|
||||||
|
/// of the entry's header, as well as the entry's File. Entries
|
||||||
|
/// can be instantiated in one of three ways, depending on how
|
||||||
|
/// they are to be used.
|
||||||
|
/// <p>
|
||||||
|
/// TarEntries that are created from the header bytes read from
|
||||||
|
/// an archive are instantiated with the TarEntry( byte[] )
|
||||||
|
/// constructor. These entries will be used when extracting from
|
||||||
|
/// or listing the contents of an archive. These entries have their
|
||||||
|
/// header filled in using the header bytes. They also set the File
|
||||||
|
/// to null, since they reference an archive entry not a file.</p>
|
||||||
|
/// <p>
|
||||||
|
/// TarEntries that are created from files that are to be written
|
||||||
|
/// into an archive are instantiated with the CreateEntryFromFile(string)
|
||||||
|
/// pseudo constructor. These entries have their header filled in using
|
||||||
|
/// the File's information. They also keep a reference to the File
|
||||||
|
/// for convenience when writing entries.</p>
|
||||||
|
/// <p>
|
||||||
|
/// Finally, TarEntries can be constructed from nothing but a name.
|
||||||
|
/// This allows the programmer to construct the entry by hand, for
|
||||||
|
/// instance when only an InputStream is available for writing to
|
||||||
|
/// the archive, and the header information is constructed from
|
||||||
|
/// other information. In this case the header fields are set to
|
||||||
|
/// defaults and the File is set to null.</p>
|
||||||
|
/// <see cref="TarHeader"/>
|
||||||
|
/// </summary>
|
||||||
|
public class TarEntry
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a default instance of <see cref="TarEntry"/>.
|
||||||
|
/// </summary>
|
||||||
|
private TarEntry()
|
||||||
|
{
|
||||||
|
header = new TarHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct an entry from an archive's header bytes. File is set
|
||||||
|
/// to null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "headerBuffer">
|
||||||
|
/// The header bytes from a tar archive entry.
|
||||||
|
/// </param>
|
||||||
|
public TarEntry(byte[] headerBuffer)
|
||||||
|
{
|
||||||
|
header = new TarHeader();
|
||||||
|
header.ParseBuffer(headerBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct a TarEntry using the <paramref name="header">header</paramref> provided
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="header">Header details for entry</param>
|
||||||
|
public TarEntry(TarHeader header)
|
||||||
|
{
|
||||||
|
if (header == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.header = (TarHeader)header.Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region ICloneable Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clone this tar entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns a clone of this entry.</returns>
|
||||||
|
public object Clone()
|
||||||
|
{
|
||||||
|
var entry = new TarEntry();
|
||||||
|
entry.file = file;
|
||||||
|
entry.header = (TarHeader)header.Clone();
|
||||||
|
entry.Name = Name;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion ICloneable Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct an entry with only a <paramref name="name">name</paramref>.
|
||||||
|
/// This allows the programmer to construct the entry's header "by hand".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to use for the entry</param>
|
||||||
|
/// <returns>Returns the newly created <see cref="TarEntry"/></returns>
|
||||||
|
public static TarEntry CreateTarEntry(string name)
|
||||||
|
{
|
||||||
|
var entry = new TarEntry();
|
||||||
|
TarEntry.NameTarHeader(entry.header, name);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct an entry for a file. File is set to file, and the
|
||||||
|
/// header is constructed from information from the file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "fileName">The file name that the entry represents.</param>
|
||||||
|
/// <returns>Returns the newly created <see cref="TarEntry"/></returns>
|
||||||
|
public static TarEntry CreateEntryFromFile(string fileName)
|
||||||
|
{
|
||||||
|
var entry = new TarEntry();
|
||||||
|
entry.GetFileTarHeader(entry.header, fileName);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if the two entries are equal. Equality is determined
|
||||||
|
/// by the header names being equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The <see cref="Object"/> to compare with the current Object.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the entries are equal; false if not.
|
||||||
|
/// </returns>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var localEntry = obj as TarEntry;
|
||||||
|
|
||||||
|
if (localEntry != null)
|
||||||
|
{
|
||||||
|
return Name.Equals(localEntry.Name);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Derive a Hash value for the current <see cref="Object"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A Hash code for the current <see cref="Object"/></returns>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Name.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if the given entry is a descendant of this entry.
|
||||||
|
/// Descendancy is determined by the name of the descendant
|
||||||
|
/// starting with this entry's name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "toTest">
|
||||||
|
/// Entry to be checked as a descendent of this.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if entry is a descendant of this.
|
||||||
|
/// </returns>
|
||||||
|
public bool IsDescendent(TarEntry toTest)
|
||||||
|
{
|
||||||
|
if (toTest == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(toTest));
|
||||||
|
}
|
||||||
|
|
||||||
|
return toTest.Name.StartsWith(Name, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get this entry's header.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// This entry's TarHeader.
|
||||||
|
/// </returns>
|
||||||
|
public TarHeader TarHeader
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/Set this entry's name.
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return header.Name;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
header.Name = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set this entry's user id.
|
||||||
|
/// </summary>
|
||||||
|
public int UserId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return header.UserId;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
header.UserId = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set this entry's group id.
|
||||||
|
/// </summary>
|
||||||
|
public int GroupId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return header.GroupId;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
header.GroupId = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set this entry's user name.
|
||||||
|
/// </summary>
|
||||||
|
public string UserName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return header.UserName;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
header.UserName = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set this entry's group name.
|
||||||
|
/// </summary>
|
||||||
|
public string GroupName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return header.GroupName;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
header.GroupName = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenience method to set this entry's group and user ids.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">
|
||||||
|
/// This entry's new user id.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="groupId">
|
||||||
|
/// This entry's new group id.
|
||||||
|
/// </param>
|
||||||
|
public void SetIds(int userId, int groupId)
|
||||||
|
{
|
||||||
|
UserId = userId;
|
||||||
|
GroupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenience method to set this entry's group and user names.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userName">
|
||||||
|
/// This entry's new user name.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="groupName">
|
||||||
|
/// This entry's new group name.
|
||||||
|
/// </param>
|
||||||
|
public void SetNames(string userName, string groupName)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
GroupName = groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/Set the modification time for this entry
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ModTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return header.ModTime;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
header.ModTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get this entry's file.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// This entry's file.
|
||||||
|
/// </returns>
|
||||||
|
public string File
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set this entry's recorded file size.
|
||||||
|
/// </summary>
|
||||||
|
public long Size
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return header.Size;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
header.Size = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return true if this entry represents a directory, false otherwise
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// True if this entry is a directory.
|
||||||
|
/// </returns>
|
||||||
|
public bool IsDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
return Directory.Exists(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header != null)
|
||||||
|
{
|
||||||
|
if ((header.TypeFlag == TarHeader.LF_DIR) || Name.EndsWith("/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fill in a TarHeader with information from a File.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="header">
|
||||||
|
/// The TarHeader to fill in.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="file">
|
||||||
|
/// The file from which to get the header information.
|
||||||
|
/// </param>
|
||||||
|
public void GetFileTarHeader(TarHeader header, string file)
|
||||||
|
{
|
||||||
|
if (header == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.file = file;
|
||||||
|
|
||||||
|
// bugfix from torhovl from #D forum:
|
||||||
|
string name = file;
|
||||||
|
|
||||||
|
// 23-Jan-2004 GnuTar allows device names in path where the name is not local to the current directory
|
||||||
|
if (name.IndexOf(Directory.GetCurrentDirectory(), StringComparison.Ordinal) == 0)
|
||||||
|
{
|
||||||
|
name = name.Substring(Directory.GetCurrentDirectory().Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (Path.DirectorySeparatorChar == '\\')
|
||||||
|
{
|
||||||
|
// check if the OS is Windows
|
||||||
|
// Strip off drive letters!
|
||||||
|
if (name.Length > 2)
|
||||||
|
{
|
||||||
|
char ch1 = name[0];
|
||||||
|
char ch2 = name[1];
|
||||||
|
|
||||||
|
if (ch2 == ':' && Char.IsLetter(ch1))
|
||||||
|
{
|
||||||
|
name = name.Substring(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
name = name.Replace(Path.DirectorySeparatorChar, '/');
|
||||||
|
|
||||||
|
// No absolute pathnames
|
||||||
|
// Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\",
|
||||||
|
// so we loop on starting /'s.
|
||||||
|
while (name.StartsWith("/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
name = name.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
header.LinkName = String.Empty;
|
||||||
|
header.Name = name;
|
||||||
|
|
||||||
|
if (Directory.Exists(file))
|
||||||
|
{
|
||||||
|
header.Mode = 1003; // Magic number for security access for a UNIX filesystem
|
||||||
|
header.TypeFlag = TarHeader.LF_DIR;
|
||||||
|
if ((header.Name.Length == 0) || header.Name[header.Name.Length - 1] != '/')
|
||||||
|
{
|
||||||
|
header.Name = header.Name + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
header.Size = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
header.Mode = 33216; // Magic number for security access for a UNIX filesystem
|
||||||
|
header.TypeFlag = TarHeader.LF_NORMAL;
|
||||||
|
header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime();
|
||||||
|
header.DevMajor = 0;
|
||||||
|
header.DevMinor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get entries for all files present in this entries directory.
|
||||||
|
/// If this entry doesnt represent a directory zero entries are returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// An array of TarEntry's for this entry's children.
|
||||||
|
/// </returns>
|
||||||
|
public TarEntry[] GetDirectoryEntries()
|
||||||
|
{
|
||||||
|
if ((file == null) || !Directory.Exists(file))
|
||||||
|
{
|
||||||
|
return new TarEntry[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] list = Directory.GetFileSystemEntries(file);
|
||||||
|
TarEntry[] result = new TarEntry[list.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < list.Length; ++i)
|
||||||
|
{
|
||||||
|
result[i] = TarEntry.CreateEntryFromFile(list[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an entry's header information to a header buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "outBuffer">
|
||||||
|
/// The tar entry header buffer to fill in.
|
||||||
|
/// </param>
|
||||||
|
public void WriteEntryHeader(byte[] outBuffer)
|
||||||
|
{
|
||||||
|
header.WriteHeader(outBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenience method that will modify an entry's name directly
|
||||||
|
/// in place in an entry header buffer byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// The buffer containing the entry header to modify.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="newName">
|
||||||
|
/// The new name to place into the header buffer.
|
||||||
|
/// </param>
|
||||||
|
static public void AdjustEntryName(byte[] buffer, string newName)
|
||||||
|
{
|
||||||
|
TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fill in a TarHeader given only the entry's name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="header">
|
||||||
|
/// The TarHeader to fill in.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="name">
|
||||||
|
/// The tar entry name.
|
||||||
|
/// </param>
|
||||||
|
static public void NameTarHeader(TarHeader header, string name)
|
||||||
|
{
|
||||||
|
if (header == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDir = name.EndsWith("/", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
header.Name = name;
|
||||||
|
header.Mode = isDir ? 1003 : 33216;
|
||||||
|
header.UserId = 0;
|
||||||
|
header.GroupId = 0;
|
||||||
|
header.Size = 0;
|
||||||
|
|
||||||
|
header.ModTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
header.TypeFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
|
||||||
|
|
||||||
|
header.LinkName = String.Empty;
|
||||||
|
header.UserName = String.Empty;
|
||||||
|
header.GroupName = String.Empty;
|
||||||
|
|
||||||
|
header.DevMajor = 0;
|
||||||
|
header.DevMinor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the file this entry represents or null if the entry is not based on a file.
|
||||||
|
/// </summary>
|
||||||
|
private string file;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entry's header information.
|
||||||
|
/// </summary>
|
||||||
|
private TarHeader header;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Tar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// TarException represents exceptions specific to Tar classes and code.
|
||||||
|
/// </summary>
|
||||||
|
public class TarException : SharpZipBaseException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="TarException" />.
|
||||||
|
/// </summary>
|
||||||
|
public TarException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="TarException" /> with its message string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
public TarException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="TarException" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
/// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param>
|
||||||
|
public TarException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Tar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the extended header of a Tar stream
|
||||||
|
/// </summary>
|
||||||
|
public class TarExtendedHeaderReader
|
||||||
|
{
|
||||||
|
private const byte LENGTH = 0;
|
||||||
|
private const byte KEY = 1;
|
||||||
|
private const byte VALUE = 2;
|
||||||
|
private const byte END = 3;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
private string[] headerParts = new string[3];
|
||||||
|
|
||||||
|
private int bbIndex;
|
||||||
|
private byte[] byteBuffer;
|
||||||
|
private char[] charBuffer;
|
||||||
|
|
||||||
|
private readonly StringBuilder sb = new StringBuilder();
|
||||||
|
private readonly Decoder decoder = Encoding.UTF8.GetDecoder();
|
||||||
|
|
||||||
|
private int state = LENGTH;
|
||||||
|
|
||||||
|
private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="TarExtendedHeaderReader"/>.
|
||||||
|
/// </summary>
|
||||||
|
public TarExtendedHeaderReader()
|
||||||
|
{
|
||||||
|
ResetBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read <paramref name="length"/> bytes from <paramref name="buffer"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer"></param>
|
||||||
|
/// <param name="length"></param>
|
||||||
|
public void Read(byte[] buffer, int length)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
byte next = buffer[i];
|
||||||
|
|
||||||
|
if (next == StateNext[state])
|
||||||
|
{
|
||||||
|
Flush();
|
||||||
|
headerParts[state] = sb.ToString();
|
||||||
|
sb.Clear();
|
||||||
|
|
||||||
|
if (++state == END)
|
||||||
|
{
|
||||||
|
headers.Add(headerParts[KEY], headerParts[VALUE]);
|
||||||
|
headerParts = new string[3];
|
||||||
|
state = LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byteBuffer[bbIndex++] = next;
|
||||||
|
if (bbIndex == 4)
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Flush()
|
||||||
|
{
|
||||||
|
decoder.Convert(byteBuffer, 0, bbIndex, charBuffer, 0, 4, false, out int bytesUsed, out int charsUsed, out bool completed);
|
||||||
|
|
||||||
|
sb.Append(charBuffer, 0, charsUsed);
|
||||||
|
ResetBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetBuffers()
|
||||||
|
{
|
||||||
|
charBuffer = new char[4];
|
||||||
|
byteBuffer = new byte[4];
|
||||||
|
bbIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the parsed headers as key-value strings
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Headers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// TODO: Check for invalid state? -NM 2018-07-01
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,726 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Tar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The TarInputStream reads a UNIX tar archive as an InputStream.
|
||||||
|
/// methods are provided to position at each successive entry in
|
||||||
|
/// the archive, and the read each entry as a normal input stream
|
||||||
|
/// using read().
|
||||||
|
/// </summary>
|
||||||
|
public class TarInputStream : Stream
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct a TarInputStream with default block factor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">stream to source data from</param>
|
||||||
|
public TarInputStream(Stream inputStream)
|
||||||
|
: this(inputStream, TarBuffer.DefaultBlockFactor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct a TarInputStream with user specified block factor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">stream to source data from</param>
|
||||||
|
/// <param name="blockFactor">block factor to apply to archive</param>
|
||||||
|
public TarInputStream(Stream inputStream, int blockFactor)
|
||||||
|
{
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating ownership of underlying stream.
|
||||||
|
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is true.</remarks>
|
||||||
|
public bool IsStreamOwner
|
||||||
|
{
|
||||||
|
get { return tarBuffer.IsStreamOwner; }
|
||||||
|
set { tarBuffer.IsStreamOwner = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Stream Overrides
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current stream supports reading
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return inputStream.CanRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current stream supports seeking
|
||||||
|
/// This property always returns false.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating if the stream supports writing.
|
||||||
|
/// This property always returns false.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The length in bytes of the stream
|
||||||
|
/// </summary>
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return inputStream.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the position within the stream.
|
||||||
|
/// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="NotSupportedException">Any attempt to set position</exception>
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return inputStream.Position;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("TarInputStream Seek not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes the baseInputStream
|
||||||
|
/// </summary>
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
inputStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the streams position. This operation is not supported and will throw a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset relative to the origin to seek to.</param>
|
||||||
|
/// <param name="origin">The <see cref="SeekOrigin"/> to start seeking from.</param>
|
||||||
|
/// <returns>The new position in the stream.</returns>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("TarInputStream Seek not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the length of the stream
|
||||||
|
/// This operation is not supported and will throw a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The new stream length.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("TarInputStream SetLength not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a block of bytes to this stream using data from a buffer.
|
||||||
|
/// This operation is not supported and will throw a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer containing bytes to write.</param>
|
||||||
|
/// <param name="offset">The offset in the buffer of the frist byte to write.</param>
|
||||||
|
/// <param name="count">The number of bytes to write.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("TarInputStream Write not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a byte to the current position in the file stream.
|
||||||
|
/// This operation is not supported and will throw a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The byte value to write.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("TarInputStream WriteByte not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a byte from the current tar archive entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A byte cast to an int; -1 if the at the end of the stream.</returns>
|
||||||
|
public override int ReadByte()
|
||||||
|
{
|
||||||
|
byte[] oneByteBuffer = new byte[1];
|
||||||
|
int num = Read(oneByteBuffer, 0, 1);
|
||||||
|
if (num <= 0)
|
||||||
|
{
|
||||||
|
// return -1 to indicate that no byte was read.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return oneByteBuffer[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads bytes from the current tar archive entry.
|
||||||
|
///
|
||||||
|
/// This method is aware of the boundaries of the current
|
||||||
|
/// entry in the archive and will deal with them appropriately
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// The buffer into which to place bytes read.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// The offset at which to place bytes read.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// The number of bytes to read.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of bytes read, or 0 at end of stream/EOF.
|
||||||
|
/// </returns>
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalRead = 0;
|
||||||
|
|
||||||
|
if (entryOffset >= entrySize)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long numToRead = count;
|
||||||
|
|
||||||
|
if ((numToRead + entryOffset) > entrySize)
|
||||||
|
{
|
||||||
|
numToRead = entrySize - entryOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readBuffer != null)
|
||||||
|
{
|
||||||
|
int sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int)numToRead;
|
||||||
|
|
||||||
|
Array.Copy(readBuffer, 0, buffer, offset, sz);
|
||||||
|
|
||||||
|
if (sz >= readBuffer.Length)
|
||||||
|
{
|
||||||
|
readBuffer = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int newLen = readBuffer.Length - sz;
|
||||||
|
byte[] newBuf = new byte[newLen];
|
||||||
|
Array.Copy(readBuffer, sz, newBuf, 0, newLen);
|
||||||
|
readBuffer = newBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalRead += sz;
|
||||||
|
numToRead -= sz;
|
||||||
|
offset += sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (numToRead > 0)
|
||||||
|
{
|
||||||
|
byte[] rec = tarBuffer.ReadBlock();
|
||||||
|
if (rec == null)
|
||||||
|
{
|
||||||
|
// Unexpected EOF!
|
||||||
|
throw new TarException("unexpected EOF with " + numToRead + " bytes unread");
|
||||||
|
}
|
||||||
|
|
||||||
|
var sz = (int)numToRead;
|
||||||
|
int recLen = rec.Length;
|
||||||
|
|
||||||
|
if (recLen > sz)
|
||||||
|
{
|
||||||
|
Array.Copy(rec, 0, buffer, offset, sz);
|
||||||
|
readBuffer = new byte[recLen - sz];
|
||||||
|
Array.Copy(rec, sz, readBuffer, 0, recLen - sz);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sz = recLen;
|
||||||
|
Array.Copy(rec, 0, buffer, offset, recLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalRead += sz;
|
||||||
|
numToRead -= sz;
|
||||||
|
offset += sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
entryOffset += totalRead;
|
||||||
|
|
||||||
|
return totalRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes this stream. Calls the TarBuffer's close() method.
|
||||||
|
/// The underlying stream is closed by the TarBuffer.
|
||||||
|
/// </summary>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
tarBuffer.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Stream Overrides
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the entry factory for this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factory">The factory for creating new entries</param>
|
||||||
|
public void SetEntryFactory(IEntryFactory factory)
|
||||||
|
{
|
||||||
|
entryFactory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the record size being used by this stream's TarBuffer.
|
||||||
|
/// </summary>
|
||||||
|
public int RecordSize
|
||||||
|
{
|
||||||
|
get { return tarBuffer.RecordSize; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the record size being used by this stream's TarBuffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// TarBuffer record size.
|
||||||
|
/// </returns>
|
||||||
|
[Obsolete("Use RecordSize property instead")]
|
||||||
|
public int GetRecordSize()
|
||||||
|
{
|
||||||
|
return tarBuffer.RecordSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the available data that can be read from the current
|
||||||
|
/// entry in the archive. This does not indicate how much data
|
||||||
|
/// is left in the entire archive, only in the current entry.
|
||||||
|
/// This value is determined from the entry's size header field
|
||||||
|
/// and the amount of data already read from the current entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of available bytes for the current entry.
|
||||||
|
/// </returns>
|
||||||
|
public long Available
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return entrySize - entryOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip bytes in the input buffer. This skips bytes in the
|
||||||
|
/// current entry's data, not the entire archive, and will
|
||||||
|
/// stop at the end of the current entry's data if the number
|
||||||
|
/// to skip extends beyond that point.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skipCount">
|
||||||
|
/// The number of bytes to skip.
|
||||||
|
/// </param>
|
||||||
|
public void Skip(long skipCount)
|
||||||
|
{
|
||||||
|
// TODO: REVIEW efficiency of TarInputStream.Skip
|
||||||
|
// This is horribly inefficient, but it ensures that we
|
||||||
|
// properly skip over bytes via the TarBuffer...
|
||||||
|
//
|
||||||
|
byte[] skipBuf = new byte[8 * 1024];
|
||||||
|
|
||||||
|
for (long num = skipCount; num > 0;)
|
||||||
|
{
|
||||||
|
int toRead = num > skipBuf.Length ? skipBuf.Length : (int)num;
|
||||||
|
int numRead = Read(skipBuf, 0, toRead);
|
||||||
|
|
||||||
|
if (numRead == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
num -= numRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return a value of true if marking is supported; false otherwise.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Currently marking is not supported, the return value is always false.</remarks>
|
||||||
|
public bool IsMarkSupported
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Since we do not support marking just yet, we do nothing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name ="markLimit">
|
||||||
|
/// The limit to mark.
|
||||||
|
/// </param>
|
||||||
|
public void Mark(int markLimit)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Since we do not support marking just yet, we do nothing.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the next entry in this tar archive. This will skip
|
||||||
|
/// over any remaining data in the current entry, if there
|
||||||
|
/// is one, and place the input stream at the header of the
|
||||||
|
/// next entry, and read the header and instantiate a new
|
||||||
|
/// TarEntry from the header bytes and return that entry.
|
||||||
|
/// If there are no more entries in the archive, null will
|
||||||
|
/// be returned to indicate that the end of the archive has
|
||||||
|
/// been reached.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The next TarEntry in the archive, or null.
|
||||||
|
/// </returns>
|
||||||
|
public TarEntry GetNextEntry()
|
||||||
|
{
|
||||||
|
if (hasHitEOF)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentEntry != null)
|
||||||
|
{
|
||||||
|
SkipToNextEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] headerBuf = tarBuffer.ReadBlock();
|
||||||
|
|
||||||
|
if (headerBuf == null)
|
||||||
|
{
|
||||||
|
hasHitEOF = true;
|
||||||
|
}
|
||||||
|
else if (TarBuffer.IsEndOfArchiveBlock(headerBuf))
|
||||||
|
{
|
||||||
|
hasHitEOF = true;
|
||||||
|
|
||||||
|
// Read the second zero-filled block
|
||||||
|
tarBuffer.ReadBlock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasHitEOF = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasHitEOF)
|
||||||
|
{
|
||||||
|
currentEntry = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var header = new TarHeader();
|
||||||
|
header.ParseBuffer(headerBuf);
|
||||||
|
if (!header.IsChecksumValid)
|
||||||
|
{
|
||||||
|
throw new TarException("Header checksum is invalid");
|
||||||
|
}
|
||||||
|
this.entryOffset = 0;
|
||||||
|
this.entrySize = header.Size;
|
||||||
|
|
||||||
|
StringBuilder longName = null;
|
||||||
|
|
||||||
|
if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME)
|
||||||
|
{
|
||||||
|
byte[] nameBuffer = new byte[TarBuffer.BlockSize];
|
||||||
|
long numToRead = this.entrySize;
|
||||||
|
|
||||||
|
longName = new StringBuilder();
|
||||||
|
|
||||||
|
while (numToRead > 0)
|
||||||
|
{
|
||||||
|
int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead));
|
||||||
|
|
||||||
|
if (numRead == -1)
|
||||||
|
{
|
||||||
|
throw new InvalidHeaderException("Failed to read long name entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString());
|
||||||
|
numToRead -= numRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkipToNextEntry();
|
||||||
|
headerBuf = this.tarBuffer.ReadBlock();
|
||||||
|
}
|
||||||
|
else if (header.TypeFlag == TarHeader.LF_GHDR)
|
||||||
|
{ // POSIX global extended header
|
||||||
|
// Ignore things we dont understand completely for now
|
||||||
|
SkipToNextEntry();
|
||||||
|
headerBuf = this.tarBuffer.ReadBlock();
|
||||||
|
}
|
||||||
|
else if (header.TypeFlag == TarHeader.LF_XHDR)
|
||||||
|
{ // POSIX extended header
|
||||||
|
byte[] nameBuffer = new byte[TarBuffer.BlockSize];
|
||||||
|
long numToRead = this.entrySize;
|
||||||
|
|
||||||
|
var xhr = new TarExtendedHeaderReader();
|
||||||
|
|
||||||
|
while (numToRead > 0)
|
||||||
|
{
|
||||||
|
int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead));
|
||||||
|
|
||||||
|
if (numRead == -1)
|
||||||
|
{
|
||||||
|
throw new InvalidHeaderException("Failed to read long name entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.Read(nameBuffer, numRead);
|
||||||
|
numToRead -= numRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xhr.Headers.TryGetValue("path", out string name))
|
||||||
|
{
|
||||||
|
longName = new StringBuilder(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
SkipToNextEntry();
|
||||||
|
headerBuf = this.tarBuffer.ReadBlock();
|
||||||
|
}
|
||||||
|
else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR)
|
||||||
|
{
|
||||||
|
// TODO: could show volume name when verbose
|
||||||
|
SkipToNextEntry();
|
||||||
|
headerBuf = this.tarBuffer.ReadBlock();
|
||||||
|
}
|
||||||
|
else if (header.TypeFlag != TarHeader.LF_NORMAL &&
|
||||||
|
header.TypeFlag != TarHeader.LF_OLDNORM &&
|
||||||
|
header.TypeFlag != TarHeader.LF_LINK &&
|
||||||
|
header.TypeFlag != TarHeader.LF_SYMLINK &&
|
||||||
|
header.TypeFlag != TarHeader.LF_DIR)
|
||||||
|
{
|
||||||
|
// Ignore things we dont understand completely for now
|
||||||
|
SkipToNextEntry();
|
||||||
|
headerBuf = tarBuffer.ReadBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entryFactory == null)
|
||||||
|
{
|
||||||
|
currentEntry = new TarEntry(headerBuf);
|
||||||
|
if (longName != null)
|
||||||
|
{
|
||||||
|
currentEntry.Name = longName.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentEntry = entryFactory.CreateEntry(headerBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Magic was checked here for 'ustar' but there are multiple valid possibilities
|
||||||
|
// so this is not done anymore.
|
||||||
|
|
||||||
|
entryOffset = 0;
|
||||||
|
|
||||||
|
// TODO: Review How do we resolve this discrepancy?!
|
||||||
|
entrySize = this.currentEntry.Size;
|
||||||
|
}
|
||||||
|
catch (InvalidHeaderException ex)
|
||||||
|
{
|
||||||
|
entrySize = 0;
|
||||||
|
entryOffset = 0;
|
||||||
|
currentEntry = null;
|
||||||
|
string errorText = string.Format("Bad header in record {0} block {1} {2}",
|
||||||
|
tarBuffer.CurrentRecord, tarBuffer.CurrentBlock, ex.Message);
|
||||||
|
throw new InvalidHeaderException(errorText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the contents of the current tar archive entry directly into
|
||||||
|
/// an output stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputStream">
|
||||||
|
/// The OutputStream into which to write the entry's data.
|
||||||
|
/// </param>
|
||||||
|
public void CopyEntryContents(Stream outputStream)
|
||||||
|
{
|
||||||
|
byte[] tempBuffer = new byte[32 * 1024];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int numRead = Read(tempBuffer, 0, tempBuffer.Length);
|
||||||
|
if (numRead <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
outputStream.Write(tempBuffer, 0, numRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SkipToNextEntry()
|
||||||
|
{
|
||||||
|
long numToSkip = entrySize - entryOffset;
|
||||||
|
|
||||||
|
if (numToSkip > 0)
|
||||||
|
{
|
||||||
|
Skip(numToSkip);
|
||||||
|
}
|
||||||
|
|
||||||
|
readBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This interface is provided, along with the method <see cref="SetEntryFactory"/>, to allow
|
||||||
|
/// the programmer to have their own <see cref="TarEntry"/> subclass instantiated for the
|
||||||
|
/// entries return from <see cref="GetNextEntry"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEntryFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create an entry based on name alone
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">
|
||||||
|
/// Name of the new EntryPointNotFoundException to create
|
||||||
|
/// </param>
|
||||||
|
/// <returns>created TarEntry or descendant class</returns>
|
||||||
|
TarEntry CreateEntry(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an instance based on an actual file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">
|
||||||
|
/// Name of file to represent in the entry
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Created TarEntry or descendant class
|
||||||
|
/// </returns>
|
||||||
|
TarEntry CreateEntryFromFile(string fileName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a tar entry based on the header information passed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerBuffer">
|
||||||
|
/// Buffer containing header information to create an an entry from.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Created TarEntry or descendant class
|
||||||
|
/// </returns>
|
||||||
|
TarEntry CreateEntry(byte[] headerBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Standard entry factory class creating instances of the class TarEntry
|
||||||
|
/// </summary>
|
||||||
|
public class EntryFactoryAdapter : IEntryFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="TarEntry"/> based on named
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to use for the entry</param>
|
||||||
|
/// <returns>A new <see cref="TarEntry"/></returns>
|
||||||
|
public TarEntry CreateEntry(string name)
|
||||||
|
{
|
||||||
|
return TarEntry.CreateTarEntry(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a tar entry with details obtained from <paramref name="fileName">file</paramref>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the file to retrieve details from.</param>
|
||||||
|
/// <returns>A new <see cref="TarEntry"/></returns>
|
||||||
|
public TarEntry CreateEntryFromFile(string fileName)
|
||||||
|
{
|
||||||
|
return TarEntry.CreateEntryFromFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an entry based on details in <paramref name="headerBuffer">header</paramref>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerBuffer">The buffer containing entry details.</param>
|
||||||
|
/// <returns>A new <see cref="TarEntry"/></returns>
|
||||||
|
public TarEntry CreateEntry(byte[] headerBuffer)
|
||||||
|
{
|
||||||
|
return new TarEntry(headerBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag set when last block has been read
|
||||||
|
/// </summary>
|
||||||
|
protected bool hasHitEOF;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of this entry as recorded in header
|
||||||
|
/// </summary>
|
||||||
|
protected long entrySize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of bytes read for this entry so far
|
||||||
|
/// </summary>
|
||||||
|
protected long entryOffset;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer used with calls to <code>Read()</code>
|
||||||
|
/// </summary>
|
||||||
|
protected byte[] readBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Working buffer
|
||||||
|
/// </summary>
|
||||||
|
protected TarBuffer tarBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current entry being read
|
||||||
|
/// </summary>
|
||||||
|
private TarEntry currentEntry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory used to create TarEntry or descendant class instance
|
||||||
|
/// </summary>
|
||||||
|
protected IEntryFactory entryFactory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stream used as the source of input data.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Stream inputStream;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,480 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Tar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The TarOutputStream writes a UNIX tar archive as an OutputStream.
|
||||||
|
/// Methods are provided to put entries, and then write their contents
|
||||||
|
/// by writing to this stream using write().
|
||||||
|
/// </summary>
|
||||||
|
/// public
|
||||||
|
public class TarOutputStream : Stream
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct TarOutputStream using default block factor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputStream">stream to write to</param>
|
||||||
|
public TarOutputStream(Stream outputStream)
|
||||||
|
: this(outputStream, TarBuffer.DefaultBlockFactor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct TarOutputStream with user specified block factor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputStream">stream to write to</param>
|
||||||
|
/// <param name="blockFactor">blocking factor</param>
|
||||||
|
public TarOutputStream(Stream outputStream, int blockFactor)
|
||||||
|
{
|
||||||
|
if (outputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(outputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outputStream = outputStream;
|
||||||
|
buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor);
|
||||||
|
|
||||||
|
assemblyBuffer = new byte[TarBuffer.BlockSize];
|
||||||
|
blockBuffer = new byte[TarBuffer.BlockSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating ownership of underlying stream.
|
||||||
|
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is true.</remarks>
|
||||||
|
public bool IsStreamOwner
|
||||||
|
{
|
||||||
|
get { return buffer.IsStreamOwner; }
|
||||||
|
set { buffer.IsStreamOwner = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// true if the stream supports reading; otherwise, false.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return outputStream.CanRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// true if the stream supports seeking; otherwise, false.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return outputStream.CanSeek;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// true if stream supports writing; otherwise, false.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return outputStream.CanWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// length of stream in bytes
|
||||||
|
/// </summary>
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return outputStream.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// gets or sets the position within the current stream.
|
||||||
|
/// </summary>
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return outputStream.Position;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
outputStream.Position = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// set the position within the current stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset relative to the <paramref name="origin"/> to seek to</param>
|
||||||
|
/// <param name="origin">The <see cref="SeekOrigin"/> to seek from.</param>
|
||||||
|
/// <returns>The new position in the stream.</returns>
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
return outputStream.Seek(offset, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the length of the current stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The new stream length.</param>
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
outputStream.SetLength(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a byte from the stream and advance the position within the stream
|
||||||
|
/// by one byte or returns -1 if at the end of the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The byte value or -1 if at end of stream</returns>
|
||||||
|
public override int ReadByte()
|
||||||
|
{
|
||||||
|
return outputStream.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// read bytes from the current stream and advance the position within the
|
||||||
|
/// stream by the number of bytes read.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer to store read bytes in.</param>
|
||||||
|
/// <param name="offset">The index into the buffer to being storing bytes at.</param>
|
||||||
|
/// <param name="count">The desired number of bytes to read.</param>
|
||||||
|
/// <returns>The total number of bytes read, or zero if at the end of the stream.
|
||||||
|
/// The number of bytes may be less than the <paramref name="count">count</paramref>
|
||||||
|
/// requested if data is not avialable.</returns>
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
return outputStream.Read(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All buffered data is written to destination
|
||||||
|
/// </summary>
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
outputStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends the TAR archive without closing the underlying OutputStream.
|
||||||
|
/// The result is that the EOF block of nulls is written.
|
||||||
|
/// </summary>
|
||||||
|
public void Finish()
|
||||||
|
{
|
||||||
|
if (IsEntryOpen)
|
||||||
|
{
|
||||||
|
CloseEntry();
|
||||||
|
}
|
||||||
|
WriteEofBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends the TAR archive and closes the underlying OutputStream.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This means that Finish() is called followed by calling the
|
||||||
|
/// TarBuffer's Close().</remarks>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!isClosed)
|
||||||
|
{
|
||||||
|
isClosed = true;
|
||||||
|
Finish();
|
||||||
|
buffer.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the record size being used by this stream's TarBuffer.
|
||||||
|
/// </summary>
|
||||||
|
public int RecordSize
|
||||||
|
{
|
||||||
|
get { return buffer.RecordSize; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the record size being used by this stream's TarBuffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The TarBuffer record size.
|
||||||
|
/// </returns>
|
||||||
|
[Obsolete("Use RecordSize property instead")]
|
||||||
|
public int GetRecordSize()
|
||||||
|
{
|
||||||
|
return buffer.RecordSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a value indicating wether an entry is open, requiring more data to be written.
|
||||||
|
/// </summary>
|
||||||
|
private bool IsEntryOpen
|
||||||
|
{
|
||||||
|
get { return (currBytes < currSize); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Put an entry on the output stream. This writes the entry's
|
||||||
|
/// header and positions the output stream for writing
|
||||||
|
/// the contents of the entry. Once this method is called, the
|
||||||
|
/// stream is ready for calls to write() to write the entry's
|
||||||
|
/// contents. Once the contents are written, closeEntry()
|
||||||
|
/// <B>MUST</B> be called to ensure that all buffered data
|
||||||
|
/// is completely written to the output stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry">
|
||||||
|
/// The TarEntry to be written to the archive.
|
||||||
|
/// </param>
|
||||||
|
public void PutNextEntry(TarEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.TarHeader.Name.Length > TarHeader.NAMELEN)
|
||||||
|
{
|
||||||
|
var longHeader = new TarHeader();
|
||||||
|
longHeader.TypeFlag = TarHeader.LF_GNU_LONGNAME;
|
||||||
|
longHeader.Name = longHeader.Name + "././@LongLink";
|
||||||
|
longHeader.Mode = 420;//644 by default
|
||||||
|
longHeader.UserId = entry.UserId;
|
||||||
|
longHeader.GroupId = entry.GroupId;
|
||||||
|
longHeader.GroupName = entry.GroupName;
|
||||||
|
longHeader.UserName = entry.UserName;
|
||||||
|
longHeader.LinkName = "";
|
||||||
|
longHeader.Size = entry.TarHeader.Name.Length + 1; // Plus one to avoid dropping last char
|
||||||
|
|
||||||
|
longHeader.WriteHeader(blockBuffer);
|
||||||
|
buffer.WriteBlock(blockBuffer); // Add special long filename header block
|
||||||
|
|
||||||
|
int nameCharIndex = 0;
|
||||||
|
|
||||||
|
while (nameCharIndex < entry.TarHeader.Name.Length + 1 /* we've allocated one for the null char, now we must make sure it gets written out */)
|
||||||
|
{
|
||||||
|
Array.Clear(blockBuffer, 0, blockBuffer.Length);
|
||||||
|
TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0, TarBuffer.BlockSize); // This func handles OK the extra char out of string length
|
||||||
|
nameCharIndex += TarBuffer.BlockSize;
|
||||||
|
buffer.WriteBlock(blockBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.WriteEntryHeader(blockBuffer);
|
||||||
|
buffer.WriteBlock(blockBuffer);
|
||||||
|
|
||||||
|
currBytes = 0;
|
||||||
|
|
||||||
|
currSize = entry.IsDirectory ? 0 : entry.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close an entry. This method MUST be called for all file
|
||||||
|
/// entries that contain data. The reason is that we must
|
||||||
|
/// buffer data written to the stream in order to satisfy
|
||||||
|
/// the buffer's block based writes. Thus, there may be
|
||||||
|
/// data fragments still being assembled that must be written
|
||||||
|
/// to the output stream before this entry is closed and the
|
||||||
|
/// next entry written.
|
||||||
|
/// </summary>
|
||||||
|
public void CloseEntry()
|
||||||
|
{
|
||||||
|
if (assemblyBufferLength > 0)
|
||||||
|
{
|
||||||
|
Array.Clear(assemblyBuffer, assemblyBufferLength, assemblyBuffer.Length - assemblyBufferLength);
|
||||||
|
|
||||||
|
buffer.WriteBlock(assemblyBuffer);
|
||||||
|
|
||||||
|
currBytes += assemblyBufferLength;
|
||||||
|
assemblyBufferLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currBytes < currSize)
|
||||||
|
{
|
||||||
|
string errorText = string.Format(
|
||||||
|
"Entry closed at '{0}' before the '{1}' bytes specified in the header were written",
|
||||||
|
currBytes, currSize);
|
||||||
|
throw new TarException(errorText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a byte to the current tar archive entry.
|
||||||
|
/// This method simply calls Write(byte[], int, int).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">
|
||||||
|
/// The byte to be written.
|
||||||
|
/// </param>
|
||||||
|
public override void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
Write(new byte[] { value }, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes bytes to the current tar archive entry. This method
|
||||||
|
/// is aware of the current entry and will throw an exception if
|
||||||
|
/// you attempt to write bytes past the length specified for the
|
||||||
|
/// current entry. The method is also (painfully) aware of the
|
||||||
|
/// record buffering required by TarBuffer, and manages buffers
|
||||||
|
/// that are not a multiple of recordsize in length, including
|
||||||
|
/// assembling records from small buffers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "buffer">
|
||||||
|
/// The buffer to write to the archive.
|
||||||
|
/// </param>
|
||||||
|
/// <param name = "offset">
|
||||||
|
/// The offset in the buffer from which to get bytes.
|
||||||
|
/// </param>
|
||||||
|
/// <param name = "count">
|
||||||
|
/// The number of bytes to write.
|
||||||
|
/// </param>
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.Length - offset < count)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("offset and count combination is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((currBytes + count) > currSize)
|
||||||
|
{
|
||||||
|
string errorText = string.Format("request to write '{0}' bytes exceeds size in header of '{1}' bytes",
|
||||||
|
count, this.currSize);
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count), errorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// We have to deal with assembly!!!
|
||||||
|
// The programmer can be writing little 32 byte chunks for all
|
||||||
|
// we know, and we must assemble complete blocks for writing.
|
||||||
|
// TODO REVIEW Maybe this should be in TarBuffer? Could that help to
|
||||||
|
// eliminate some of the buffer copying.
|
||||||
|
//
|
||||||
|
if (assemblyBufferLength > 0)
|
||||||
|
{
|
||||||
|
if ((assemblyBufferLength + count) >= blockBuffer.Length)
|
||||||
|
{
|
||||||
|
int aLen = blockBuffer.Length - assemblyBufferLength;
|
||||||
|
|
||||||
|
Array.Copy(assemblyBuffer, 0, blockBuffer, 0, assemblyBufferLength);
|
||||||
|
Array.Copy(buffer, offset, blockBuffer, assemblyBufferLength, aLen);
|
||||||
|
|
||||||
|
this.buffer.WriteBlock(blockBuffer);
|
||||||
|
|
||||||
|
currBytes += blockBuffer.Length;
|
||||||
|
|
||||||
|
offset += aLen;
|
||||||
|
count -= aLen;
|
||||||
|
|
||||||
|
assemblyBufferLength = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count);
|
||||||
|
offset += count;
|
||||||
|
assemblyBufferLength += count;
|
||||||
|
count -= count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// When we get here we have EITHER:
|
||||||
|
// o An empty "assembly" buffer.
|
||||||
|
// o No bytes to write (count == 0)
|
||||||
|
//
|
||||||
|
while (count > 0)
|
||||||
|
{
|
||||||
|
if (count < blockBuffer.Length)
|
||||||
|
{
|
||||||
|
Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count);
|
||||||
|
assemblyBufferLength += count;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer.WriteBlock(buffer, offset);
|
||||||
|
|
||||||
|
int bufferLength = blockBuffer.Length;
|
||||||
|
currBytes += bufferLength;
|
||||||
|
count -= bufferLength;
|
||||||
|
offset += bufferLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an EOF (end of archive) block to the tar archive.
|
||||||
|
/// The end of the archive is indicated by two blocks consisting entirely of zero bytes.
|
||||||
|
/// </summary>
|
||||||
|
private void WriteEofBlock()
|
||||||
|
{
|
||||||
|
Array.Clear(blockBuffer, 0, blockBuffer.Length);
|
||||||
|
buffer.WriteBlock(blockBuffer);
|
||||||
|
buffer.WriteBlock(blockBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// bytes written for this entry so far
|
||||||
|
/// </summary>
|
||||||
|
private long currBytes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// current 'Assembly' buffer length
|
||||||
|
/// </summary>
|
||||||
|
private int assemblyBufferLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag indicating wether this instance has been closed or not.
|
||||||
|
/// </summary>
|
||||||
|
private bool isClosed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size for the current entry
|
||||||
|
/// </summary>
|
||||||
|
protected long currSize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// single block working buffer
|
||||||
|
/// </summary>
|
||||||
|
protected byte[] blockBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 'Assembly' buffer used to assemble data before writing
|
||||||
|
/// </summary>
|
||||||
|
protected byte[] assemblyBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TarBuffer used to provide correct blocking factor
|
||||||
|
/// </summary>
|
||||||
|
protected TarBuffer buffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the destination stream for the archive contents
|
||||||
|
/// </summary>
|
||||||
|
protected Stream outputStream;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,604 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is the Deflater class. The deflater class compresses input
|
||||||
|
/// with the deflate algorithm described in RFC 1951. It has several
|
||||||
|
/// compression levels and three different strategies described below.
|
||||||
|
///
|
||||||
|
/// This class is <i>not</i> thread safe. This is inherent in the API, due
|
||||||
|
/// to the split of deflate and setInput.
|
||||||
|
///
|
||||||
|
/// author of the original java version : Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
public class Deflater
|
||||||
|
{
|
||||||
|
#region Deflater Documentation
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Deflater can do the following state transitions:
|
||||||
|
*
|
||||||
|
* (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---.
|
||||||
|
* / | (2) (5) |
|
||||||
|
* / v (5) |
|
||||||
|
* (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3)
|
||||||
|
* \ | (3) | ,--------'
|
||||||
|
* | | | (3) /
|
||||||
|
* v v (5) v v
|
||||||
|
* (1) -> BUSY_STATE ----> FINISHING_STATE
|
||||||
|
* | (6)
|
||||||
|
* v
|
||||||
|
* FINISHED_STATE
|
||||||
|
* \_____________________________________/
|
||||||
|
* | (7)
|
||||||
|
* v
|
||||||
|
* CLOSED_STATE
|
||||||
|
*
|
||||||
|
* (1) If we should produce a header we start in INIT_STATE, otherwise
|
||||||
|
* we start in BUSY_STATE.
|
||||||
|
* (2) A dictionary may be set only when we are in INIT_STATE, then
|
||||||
|
* we change the state as indicated.
|
||||||
|
* (3) Whether a dictionary is set or not, on the first call of deflate
|
||||||
|
* we change to BUSY_STATE.
|
||||||
|
* (4) -- intentionally left blank -- :)
|
||||||
|
* (5) FINISHING_STATE is entered, when flush() is called to indicate that
|
||||||
|
* there is no more INPUT. There are also states indicating, that
|
||||||
|
* the header wasn't written yet.
|
||||||
|
* (6) FINISHED_STATE is entered, when everything has been flushed to the
|
||||||
|
* internal pending output buffer.
|
||||||
|
* (7) At any time (7)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endregion Deflater Documentation
|
||||||
|
|
||||||
|
#region Public Constants
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The best and slowest compression level. This tries to find very
|
||||||
|
/// long and distant string repetitions.
|
||||||
|
/// </summary>
|
||||||
|
public const int BEST_COMPRESSION = 9;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The worst but fastest compression level.
|
||||||
|
/// </summary>
|
||||||
|
public const int BEST_SPEED = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default compression level.
|
||||||
|
/// </summary>
|
||||||
|
public const int DEFAULT_COMPRESSION = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This level won't compress at all but output uncompressed blocks.
|
||||||
|
/// </summary>
|
||||||
|
public const int NO_COMPRESSION = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The compression method. This is the only method supported so far.
|
||||||
|
/// There is no need to use this constant at all.
|
||||||
|
/// </summary>
|
||||||
|
public const int DEFLATED = 8;
|
||||||
|
|
||||||
|
#endregion Public Constants
|
||||||
|
|
||||||
|
#region Public Enum
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compression Level as an enum for safer use
|
||||||
|
/// </summary>
|
||||||
|
public enum CompressionLevel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The best and slowest compression level. This tries to find very
|
||||||
|
/// long and distant string repetitions.
|
||||||
|
/// </summary>
|
||||||
|
BEST_COMPRESSION = Deflater.BEST_COMPRESSION,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The worst but fastest compression level.
|
||||||
|
/// </summary>
|
||||||
|
BEST_SPEED = Deflater.BEST_SPEED,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default compression level.
|
||||||
|
/// </summary>
|
||||||
|
DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This level won't compress at all but output uncompressed blocks.
|
||||||
|
/// </summary>
|
||||||
|
NO_COMPRESSION = Deflater.NO_COMPRESSION,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The compression method. This is the only method supported so far.
|
||||||
|
/// There is no need to use this constant at all.
|
||||||
|
/// </summary>
|
||||||
|
DEFLATED = Deflater.DEFLATED
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public Enum
|
||||||
|
|
||||||
|
#region Local Constants
|
||||||
|
|
||||||
|
private const int IS_SETDICT = 0x01;
|
||||||
|
private const int IS_FLUSHING = 0x04;
|
||||||
|
private const int IS_FINISHING = 0x08;
|
||||||
|
|
||||||
|
private const int INIT_STATE = 0x00;
|
||||||
|
private const int SETDICT_STATE = 0x01;
|
||||||
|
|
||||||
|
// private static int INIT_FINISHING_STATE = 0x08;
|
||||||
|
// private static int SETDICT_FINISHING_STATE = 0x09;
|
||||||
|
private const int BUSY_STATE = 0x10;
|
||||||
|
|
||||||
|
private const int FLUSHING_STATE = 0x14;
|
||||||
|
private const int FINISHING_STATE = 0x1c;
|
||||||
|
private const int FINISHED_STATE = 0x1e;
|
||||||
|
private const int CLOSED_STATE = 0x7f;
|
||||||
|
|
||||||
|
#endregion Local Constants
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new deflater with default compression level.
|
||||||
|
/// </summary>
|
||||||
|
public Deflater() : this(DEFAULT_COMPRESSION, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new deflater with given compression level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">
|
||||||
|
/// the compression level, a value between NO_COMPRESSION
|
||||||
|
/// and BEST_COMPRESSION, or DEFAULT_COMPRESSION.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception>
|
||||||
|
public Deflater(int level) : this(level, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new deflater with given compression level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">
|
||||||
|
/// the compression level, a value between NO_COMPRESSION
|
||||||
|
/// and BEST_COMPRESSION.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="noZlibHeaderOrFooter">
|
||||||
|
/// true, if we should suppress the Zlib/RFC1950 header at the
|
||||||
|
/// beginning and the adler checksum at the end of the output. This is
|
||||||
|
/// useful for the GZIP/PKZIP formats.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception>
|
||||||
|
public Deflater(int level, bool noZlibHeaderOrFooter)
|
||||||
|
{
|
||||||
|
if (level == DEFAULT_COMPRESSION)
|
||||||
|
{
|
||||||
|
level = 6;
|
||||||
|
}
|
||||||
|
else if (level < NO_COMPRESSION || level > BEST_COMPRESSION)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(level));
|
||||||
|
}
|
||||||
|
|
||||||
|
pending = new DeflaterPending();
|
||||||
|
engine = new DeflaterEngine(pending, noZlibHeaderOrFooter);
|
||||||
|
this.noZlibHeaderOrFooter = noZlibHeaderOrFooter;
|
||||||
|
SetStrategy(DeflateStrategy.Default);
|
||||||
|
SetLevel(level);
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the deflater. The deflater acts afterwards as if it was
|
||||||
|
/// just created with the same compression level and strategy as it
|
||||||
|
/// had before.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
state = (noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE);
|
||||||
|
totalOut = 0;
|
||||||
|
pending.Reset();
|
||||||
|
engine.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current adler checksum of the data that was processed so far.
|
||||||
|
/// </summary>
|
||||||
|
public int Adler
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return engine.Adler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of input bytes processed so far.
|
||||||
|
/// </summary>
|
||||||
|
public long TotalIn
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return engine.TotalIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of output bytes so far.
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOut
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return totalOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes the current input block. Further calls to deflate() will
|
||||||
|
/// produce enough output to inflate everything in the current input
|
||||||
|
/// block. This is not part of Sun's JDK so I have made it package
|
||||||
|
/// private. It is used by DeflaterOutputStream to implement
|
||||||
|
/// flush().
|
||||||
|
/// </summary>
|
||||||
|
public void Flush()
|
||||||
|
{
|
||||||
|
state |= IS_FLUSHING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finishes the deflater with the current input block. It is an error
|
||||||
|
/// to give more input after this method was called. This method must
|
||||||
|
/// be called to force all bytes to be flushed.
|
||||||
|
/// </summary>
|
||||||
|
public void Finish()
|
||||||
|
{
|
||||||
|
state |= (IS_FLUSHING | IS_FINISHING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the stream was finished and no more output bytes
|
||||||
|
/// are available.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFinished
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (state == FINISHED_STATE) && pending.IsFlushed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true, if the input buffer is empty.
|
||||||
|
/// You should then call setInput().
|
||||||
|
/// NOTE: This method can also return true when the stream
|
||||||
|
/// was finished.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNeedingInput
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return engine.NeedsInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the data which should be compressed next. This should be only
|
||||||
|
/// called when needsInput indicates that more input is needed.
|
||||||
|
/// If you call setInput when needsInput() returns false, the
|
||||||
|
/// previous input that is still pending will be thrown away.
|
||||||
|
/// The given byte array should not be changed, before needsInput() returns
|
||||||
|
/// true again.
|
||||||
|
/// This call is equivalent to <code>setInput(input, 0, input.length)</code>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">
|
||||||
|
/// the buffer containing the input data.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// if the buffer was finished() or ended().
|
||||||
|
/// </exception>
|
||||||
|
public void SetInput(byte[] input)
|
||||||
|
{
|
||||||
|
SetInput(input, 0, input.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the data which should be compressed next. This should be
|
||||||
|
/// only called when needsInput indicates that more input is needed.
|
||||||
|
/// The given byte array should not be changed, before needsInput() returns
|
||||||
|
/// true again.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">
|
||||||
|
/// the buffer containing the input data.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// the start of the data.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// the number of data bytes of input.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// if the buffer was Finish()ed or if previous input is still pending.
|
||||||
|
/// </exception>
|
||||||
|
public void SetInput(byte[] input, int offset, int count)
|
||||||
|
{
|
||||||
|
if ((state & IS_FINISHING) != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Finish() already called");
|
||||||
|
}
|
||||||
|
engine.SetInput(input, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the compression level. There is no guarantee of the exact
|
||||||
|
/// position of the change, but if you call this when needsInput is
|
||||||
|
/// true the change of compression level will occur somewhere near
|
||||||
|
/// before the end of the so far given input.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">
|
||||||
|
/// the new compression level.
|
||||||
|
/// </param>
|
||||||
|
public void SetLevel(int level)
|
||||||
|
{
|
||||||
|
if (level == DEFAULT_COMPRESSION)
|
||||||
|
{
|
||||||
|
level = 6;
|
||||||
|
}
|
||||||
|
else if (level < NO_COMPRESSION || level > BEST_COMPRESSION)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(level));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.level != level)
|
||||||
|
{
|
||||||
|
this.level = level;
|
||||||
|
engine.SetLevel(level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get current compression level
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the current compression level</returns>
|
||||||
|
public int GetLevel()
|
||||||
|
{
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the compression strategy. Strategy is one of
|
||||||
|
/// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact
|
||||||
|
/// position where the strategy is changed, the same as for
|
||||||
|
/// SetLevel() applies.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strategy">
|
||||||
|
/// The new compression strategy.
|
||||||
|
/// </param>
|
||||||
|
public void SetStrategy(DeflateStrategy strategy)
|
||||||
|
{
|
||||||
|
engine.Strategy = strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deflates the current input block with to the given array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="output">
|
||||||
|
/// The buffer where compressed data is stored
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of compressed bytes added to the output, or 0 if either
|
||||||
|
/// IsNeedingInput() or IsFinished returns true or length is zero.
|
||||||
|
/// </returns>
|
||||||
|
public int Deflate(byte[] output)
|
||||||
|
{
|
||||||
|
return Deflate(output, 0, output.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deflates the current input block to the given array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="output">
|
||||||
|
/// Buffer to store the compressed data.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// Offset into the output array.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="length">
|
||||||
|
/// The maximum number of bytes that may be stored.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of compressed bytes added to the output, or 0 if either
|
||||||
|
/// needsInput() or finished() returns true or length is zero.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// If Finish() was previously called.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
|
/// If offset or length don't match the array length.
|
||||||
|
/// </exception>
|
||||||
|
public int Deflate(byte[] output, int offset, int length)
|
||||||
|
{
|
||||||
|
int origLength = length;
|
||||||
|
|
||||||
|
if (state == CLOSED_STATE)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Deflater closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state < BUSY_STATE)
|
||||||
|
{
|
||||||
|
// output header
|
||||||
|
int header = (DEFLATED +
|
||||||
|
((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8;
|
||||||
|
int level_flags = (level - 1) >> 1;
|
||||||
|
if (level_flags < 0 || level_flags > 3)
|
||||||
|
{
|
||||||
|
level_flags = 3;
|
||||||
|
}
|
||||||
|
header |= level_flags << 6;
|
||||||
|
if ((state & IS_SETDICT) != 0)
|
||||||
|
{
|
||||||
|
// Dictionary was set
|
||||||
|
header |= DeflaterConstants.PRESET_DICT;
|
||||||
|
}
|
||||||
|
header += 31 - (header % 31);
|
||||||
|
|
||||||
|
pending.WriteShortMSB(header);
|
||||||
|
if ((state & IS_SETDICT) != 0)
|
||||||
|
{
|
||||||
|
int chksum = engine.Adler;
|
||||||
|
engine.ResetAdler();
|
||||||
|
pending.WriteShortMSB(chksum >> 16);
|
||||||
|
pending.WriteShortMSB(chksum & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; ; )
|
||||||
|
{
|
||||||
|
int count = pending.Flush(output, offset, length);
|
||||||
|
offset += count;
|
||||||
|
totalOut += count;
|
||||||
|
length -= count;
|
||||||
|
|
||||||
|
if (length == 0 || state == FINISHED_STATE)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case BUSY_STATE:
|
||||||
|
// We need more input now
|
||||||
|
return origLength - length;
|
||||||
|
|
||||||
|
case FLUSHING_STATE:
|
||||||
|
if (level != NO_COMPRESSION)
|
||||||
|
{
|
||||||
|
/* We have to supply some lookahead. 8 bit lookahead
|
||||||
|
* is needed by the zlib inflater, and we must fill
|
||||||
|
* the next byte, so that all bits are flushed.
|
||||||
|
*/
|
||||||
|
int neededbits = 8 + ((-pending.BitCount) & 7);
|
||||||
|
while (neededbits > 0)
|
||||||
|
{
|
||||||
|
/* write a static tree block consisting solely of
|
||||||
|
* an EOF:
|
||||||
|
*/
|
||||||
|
pending.WriteBits(2, 10);
|
||||||
|
neededbits -= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = BUSY_STATE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FINISHING_STATE:
|
||||||
|
pending.AlignToByte();
|
||||||
|
|
||||||
|
// Compressed data is complete. Write footer information if required.
|
||||||
|
if (!noZlibHeaderOrFooter)
|
||||||
|
{
|
||||||
|
int adler = engine.Adler;
|
||||||
|
pending.WriteShortMSB(adler >> 16);
|
||||||
|
pending.WriteShortMSB(adler & 0xffff);
|
||||||
|
}
|
||||||
|
state = FINISHED_STATE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return origLength - length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the dictionary which should be used in the deflate process.
|
||||||
|
/// This call is equivalent to <code>setDictionary(dict, 0, dict.Length)</code>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dictionary">
|
||||||
|
/// the dictionary.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// if SetInput () or Deflate () were already called or another dictionary was already set.
|
||||||
|
/// </exception>
|
||||||
|
public void SetDictionary(byte[] dictionary)
|
||||||
|
{
|
||||||
|
SetDictionary(dictionary, 0, dictionary.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the dictionary which should be used in the deflate process.
|
||||||
|
/// The dictionary is a byte array containing strings that are
|
||||||
|
/// likely to occur in the data which should be compressed. The
|
||||||
|
/// dictionary is not stored in the compressed output, only a
|
||||||
|
/// checksum. To decompress the output you need to supply the same
|
||||||
|
/// dictionary again.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dictionary">
|
||||||
|
/// The dictionary data
|
||||||
|
/// </param>
|
||||||
|
/// <param name="index">
|
||||||
|
/// The index where dictionary information commences.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// The number of bytes in the dictionary.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// If SetInput () or Deflate() were already called or another dictionary was already set.
|
||||||
|
/// </exception>
|
||||||
|
public void SetDictionary(byte[] dictionary, int index, int count)
|
||||||
|
{
|
||||||
|
if (state != INIT_STATE)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
state = SETDICT_STATE;
|
||||||
|
engine.SetDictionary(dictionary, index, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compression level.
|
||||||
|
/// </summary>
|
||||||
|
private int level;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true no Zlib/RFC1950 headers or footers are generated
|
||||||
|
/// </summary>
|
||||||
|
private bool noZlibHeaderOrFooter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current state.
|
||||||
|
/// </summary>
|
||||||
|
private int state;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total bytes of output written.
|
||||||
|
/// </summary>
|
||||||
|
private long totalOut;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The pending output.
|
||||||
|
/// </summary>
|
||||||
|
private DeflaterPending pending;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The deflater engine.
|
||||||
|
/// </summary>
|
||||||
|
private DeflaterEngine engine;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class contains constants used for deflation.
|
||||||
|
/// </summary>
|
||||||
|
public static class DeflaterConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true to enable debugging
|
||||||
|
/// </summary>
|
||||||
|
public const bool DEBUGGING = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Written to Zip file to identify a stored block
|
||||||
|
/// </summary>
|
||||||
|
public const int STORED_BLOCK = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies static tree in Zip file
|
||||||
|
/// </summary>
|
||||||
|
public const int STATIC_TREES = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies dynamic tree in Zip file
|
||||||
|
/// </summary>
|
||||||
|
public const int DYN_TREES = 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Header flag indicating a preset dictionary for deflation
|
||||||
|
/// </summary>
|
||||||
|
public const int PRESET_DICT = 0x20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets internal buffer sizes for Huffman encoding
|
||||||
|
/// </summary>
|
||||||
|
public const int DEFAULT_MEM_LEVEL = 8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int MAX_MATCH = 258;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int MIN_MATCH = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int MAX_WBITS = 15;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int WSIZE = 1 << MAX_WBITS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int WMASK = WSIZE - 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int HASH_SIZE = 1 << HASH_BITS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int HASH_MASK = HASH_SIZE - 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int DEFLATE_STORED = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int DEFLATE_FAST = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public const int DEFLATE_SLOW = 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compression engine constant
|
||||||
|
/// </summary>
|
||||||
|
public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 };
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,946 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Strategies for deflater
|
||||||
|
/// </summary>
|
||||||
|
public enum DeflateStrategy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default strategy
|
||||||
|
/// </summary>
|
||||||
|
Default = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This strategy will only allow longer string repetitions. It is
|
||||||
|
/// useful for random data with a small character set.
|
||||||
|
/// </summary>
|
||||||
|
Filtered = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This strategy will not look for string repetitions at all. It
|
||||||
|
/// only encodes with Huffman trees (which means, that more common
|
||||||
|
/// characters get a smaller encoding.
|
||||||
|
/// </summary>
|
||||||
|
HuffmanOnly = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEFLATE ALGORITHM:
|
||||||
|
//
|
||||||
|
// The uncompressed stream is inserted into the window array. When
|
||||||
|
// the window array is full the first half is thrown away and the
|
||||||
|
// second half is copied to the beginning.
|
||||||
|
//
|
||||||
|
// The head array is a hash table. Three characters build a hash value
|
||||||
|
// and they the value points to the corresponding index in window of
|
||||||
|
// the last string with this hash. The prev array implements a
|
||||||
|
// linked list of matches with the same hash: prev[index & WMASK] points
|
||||||
|
// to the previous index with the same hash.
|
||||||
|
//
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Low level compression engine for deflate algorithm which uses a 32K sliding window
|
||||||
|
/// with secondary compression from Huffman/Shannon-Fano codes.
|
||||||
|
/// </summary>
|
||||||
|
public class DeflaterEngine
|
||||||
|
{
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
private const int TooFar = 4096;
|
||||||
|
|
||||||
|
#endregion Constants
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct instance with pending buffer
|
||||||
|
/// Adler calculation will be peformed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pending">
|
||||||
|
/// Pending buffer to use
|
||||||
|
/// </param>
|
||||||
|
public DeflaterEngine(DeflaterPending pending)
|
||||||
|
: this (pending, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct instance with pending buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pending">
|
||||||
|
/// Pending buffer to use
|
||||||
|
/// </param>
|
||||||
|
/// <param name="noAdlerCalculation">
|
||||||
|
/// If no adler calculation should be performed
|
||||||
|
/// </param>
|
||||||
|
public DeflaterEngine(DeflaterPending pending, bool noAdlerCalculation)
|
||||||
|
{
|
||||||
|
this.pending = pending;
|
||||||
|
huffman = new DeflaterHuffman(pending);
|
||||||
|
if (!noAdlerCalculation)
|
||||||
|
adler = new Adler32();
|
||||||
|
|
||||||
|
window = new byte[2 * DeflaterConstants.WSIZE];
|
||||||
|
head = new short[DeflaterConstants.HASH_SIZE];
|
||||||
|
prev = new short[DeflaterConstants.WSIZE];
|
||||||
|
|
||||||
|
// We start at index 1, to avoid an implementation deficiency, that
|
||||||
|
// we cannot build a repeat pattern at index 0.
|
||||||
|
blockStart = strstart = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deflate drives actual compression of data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flush">True to flush input buffers</param>
|
||||||
|
/// <param name="finish">Finish deflation with the current input.</param>
|
||||||
|
/// <returns>Returns true if progress has been made.</returns>
|
||||||
|
public bool Deflate(bool flush, bool finish)
|
||||||
|
{
|
||||||
|
bool progress;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
FillWindow();
|
||||||
|
bool canFlush = flush && (inputOff == inputEnd);
|
||||||
|
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING) {
|
||||||
|
Console.WriteLine("window: [" + blockStart + "," + strstart + ","
|
||||||
|
+ lookahead + "], " + compressionFunction + "," + canFlush);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
switch (compressionFunction)
|
||||||
|
{
|
||||||
|
case DeflaterConstants.DEFLATE_STORED:
|
||||||
|
progress = DeflateStored(canFlush, finish);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DeflaterConstants.DEFLATE_FAST:
|
||||||
|
progress = DeflateFast(canFlush, finish);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DeflaterConstants.DEFLATE_SLOW:
|
||||||
|
progress = DeflateSlow(canFlush, finish);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException("unknown compressionFunction");
|
||||||
|
}
|
||||||
|
} while (pending.IsFlushed && progress); // repeat while we have no pending output and progress was made
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets input data to be deflated. Should only be called when <code>NeedsInput()</code>
|
||||||
|
/// returns true
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer containing input data.</param>
|
||||||
|
/// <param name="offset">The offset of the first byte of data.</param>
|
||||||
|
/// <param name="count">The number of bytes of data to use as input.</param>
|
||||||
|
public void SetInput(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputOff < inputEnd)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Old input was not completely processed");
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = offset + count;
|
||||||
|
|
||||||
|
/* We want to throw an ArrayIndexOutOfBoundsException early. The
|
||||||
|
* check is very tricky: it also handles integer wrap around.
|
||||||
|
*/
|
||||||
|
if ((offset > end) || (end > buffer.Length))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
inputBuf = buffer;
|
||||||
|
inputOff = offset;
|
||||||
|
inputEnd = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if more <see cref="SetInput">input</see> is needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Return true if input is needed via <see cref="SetInput">SetInput</see></returns>
|
||||||
|
public bool NeedsInput()
|
||||||
|
{
|
||||||
|
return (inputEnd == inputOff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set compression dictionary
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer containing the dictionary data</param>
|
||||||
|
/// <param name="offset">The offset in the buffer for the first byte of data</param>
|
||||||
|
/// <param name="length">The length of the dictionary data.</param>
|
||||||
|
public void SetDictionary(byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (strstart != 1) )
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("strstart not 1");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
adler?.Update(new ArraySegment<byte>(buffer, offset, length));
|
||||||
|
if (length < DeflaterConstants.MIN_MATCH)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > DeflaterConstants.MAX_DIST)
|
||||||
|
{
|
||||||
|
offset += length - DeflaterConstants.MAX_DIST;
|
||||||
|
length = DeflaterConstants.MAX_DIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Array.Copy(buffer, offset, window, strstart, length);
|
||||||
|
|
||||||
|
UpdateHash();
|
||||||
|
--length;
|
||||||
|
while (--length > 0)
|
||||||
|
{
|
||||||
|
InsertString();
|
||||||
|
strstart++;
|
||||||
|
}
|
||||||
|
strstart += 2;
|
||||||
|
blockStart = strstart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset internal state
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
huffman.Reset();
|
||||||
|
adler?.Reset();
|
||||||
|
blockStart = strstart = 1;
|
||||||
|
lookahead = 0;
|
||||||
|
totalIn = 0;
|
||||||
|
prevAvailable = false;
|
||||||
|
matchLen = DeflaterConstants.MIN_MATCH - 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < DeflaterConstants.HASH_SIZE; i++)
|
||||||
|
{
|
||||||
|
head[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < DeflaterConstants.WSIZE; i++)
|
||||||
|
{
|
||||||
|
prev[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset Adler checksum
|
||||||
|
/// </summary>
|
||||||
|
public void ResetAdler()
|
||||||
|
{
|
||||||
|
adler?.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get current value of Adler checksum
|
||||||
|
/// </summary>
|
||||||
|
public int Adler
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (adler != null) ? unchecked((int)adler.Value) : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total data processed
|
||||||
|
/// </summary>
|
||||||
|
public long TotalIn
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return totalIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the <see cref="DeflateStrategy">deflate strategy</see>
|
||||||
|
/// </summary>
|
||||||
|
public DeflateStrategy Strategy
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
strategy = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the deflate level (0-9)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The value to set the level to.</param>
|
||||||
|
public void SetLevel(int level)
|
||||||
|
{
|
||||||
|
if ((level < 0) || (level > 9))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(level));
|
||||||
|
}
|
||||||
|
|
||||||
|
goodLength = DeflaterConstants.GOOD_LENGTH[level];
|
||||||
|
max_lazy = DeflaterConstants.MAX_LAZY[level];
|
||||||
|
niceLength = DeflaterConstants.NICE_LENGTH[level];
|
||||||
|
max_chain = DeflaterConstants.MAX_CHAIN[level];
|
||||||
|
|
||||||
|
if (DeflaterConstants.COMPR_FUNC[level] != compressionFunction)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING) {
|
||||||
|
Console.WriteLine("Change from " + compressionFunction + " to "
|
||||||
|
+ DeflaterConstants.COMPR_FUNC[level]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
switch (compressionFunction)
|
||||||
|
{
|
||||||
|
case DeflaterConstants.DEFLATE_STORED:
|
||||||
|
if (strstart > blockStart)
|
||||||
|
{
|
||||||
|
huffman.FlushStoredBlock(window, blockStart,
|
||||||
|
strstart - blockStart, false);
|
||||||
|
blockStart = strstart;
|
||||||
|
}
|
||||||
|
UpdateHash();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DeflaterConstants.DEFLATE_FAST:
|
||||||
|
if (strstart > blockStart)
|
||||||
|
{
|
||||||
|
huffman.FlushBlock(window, blockStart, strstart - blockStart,
|
||||||
|
false);
|
||||||
|
blockStart = strstart;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DeflaterConstants.DEFLATE_SLOW:
|
||||||
|
if (prevAvailable)
|
||||||
|
{
|
||||||
|
huffman.TallyLit(window[strstart - 1] & 0xff);
|
||||||
|
}
|
||||||
|
if (strstart > blockStart)
|
||||||
|
{
|
||||||
|
huffman.FlushBlock(window, blockStart, strstart - blockStart, false);
|
||||||
|
blockStart = strstart;
|
||||||
|
}
|
||||||
|
prevAvailable = false;
|
||||||
|
matchLen = DeflaterConstants.MIN_MATCH - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
compressionFunction = DeflaterConstants.COMPR_FUNC[level];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fill the window
|
||||||
|
/// </summary>
|
||||||
|
public void FillWindow()
|
||||||
|
{
|
||||||
|
/* If the window is almost full and there is insufficient lookahead,
|
||||||
|
* move the upper half to the lower one to make room in the upper half.
|
||||||
|
*/
|
||||||
|
if (strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST)
|
||||||
|
{
|
||||||
|
SlideWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there is not enough lookahead, but still some input left,
|
||||||
|
* read in the input
|
||||||
|
*/
|
||||||
|
if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd)
|
||||||
|
{
|
||||||
|
int more = 2 * DeflaterConstants.WSIZE - lookahead - strstart;
|
||||||
|
|
||||||
|
if (more > inputEnd - inputOff)
|
||||||
|
{
|
||||||
|
more = inputEnd - inputOff;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more);
|
||||||
|
adler?.Update(new ArraySegment<byte>(inputBuf, inputOff, more));
|
||||||
|
|
||||||
|
inputOff += more;
|
||||||
|
totalIn += more;
|
||||||
|
lookahead += more;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lookahead >= DeflaterConstants.MIN_MATCH)
|
||||||
|
{
|
||||||
|
UpdateHash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHash()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
if (DEBUGGING) {
|
||||||
|
Console.WriteLine("updateHash: "+strstart);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
ins_h = (window[strstart] << DeflaterConstants.HASH_SHIFT) ^ window[strstart + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts the current string in the head hash and returns the previous
|
||||||
|
/// value for this hash.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The previous hash value</returns>
|
||||||
|
private int InsertString()
|
||||||
|
{
|
||||||
|
short match;
|
||||||
|
int hash = ((ins_h << DeflaterConstants.HASH_SHIFT) ^ window[strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK;
|
||||||
|
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING)
|
||||||
|
{
|
||||||
|
if (hash != (((window[strstart] << (2*HASH_SHIFT)) ^
|
||||||
|
(window[strstart + 1] << HASH_SHIFT) ^
|
||||||
|
(window[strstart + 2])) & HASH_MASK)) {
|
||||||
|
throw new SharpZipBaseException("hash inconsistent: " + hash + "/"
|
||||||
|
+window[strstart] + ","
|
||||||
|
+window[strstart + 1] + ","
|
||||||
|
+window[strstart + 2] + "," + HASH_SHIFT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
prev[strstart & DeflaterConstants.WMASK] = match = head[hash];
|
||||||
|
head[hash] = unchecked((short)strstart);
|
||||||
|
ins_h = hash;
|
||||||
|
return match & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SlideWindow()
|
||||||
|
{
|
||||||
|
Array.Copy(window, DeflaterConstants.WSIZE, window, 0, DeflaterConstants.WSIZE);
|
||||||
|
matchStart -= DeflaterConstants.WSIZE;
|
||||||
|
strstart -= DeflaterConstants.WSIZE;
|
||||||
|
blockStart -= DeflaterConstants.WSIZE;
|
||||||
|
|
||||||
|
// Slide the hash table (could be avoided with 32 bit values
|
||||||
|
// at the expense of memory usage).
|
||||||
|
for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i)
|
||||||
|
{
|
||||||
|
int m = head[i] & 0xffff;
|
||||||
|
head[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slide the prev table.
|
||||||
|
for (int i = 0; i < DeflaterConstants.WSIZE; i++)
|
||||||
|
{
|
||||||
|
int m = prev[i] & 0xffff;
|
||||||
|
prev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the best (longest) string in the window matching the
|
||||||
|
/// string starting at strstart.
|
||||||
|
///
|
||||||
|
/// Preconditions:
|
||||||
|
/// <code>
|
||||||
|
/// strstart + DeflaterConstants.MAX_MATCH <= window.length.</code>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="curMatch"></param>
|
||||||
|
/// <returns>True if a match greater than the minimum length is found</returns>
|
||||||
|
private bool FindLongestMatch(int curMatch)
|
||||||
|
{
|
||||||
|
int match;
|
||||||
|
int scan = strstart;
|
||||||
|
// scanMax is the highest position that we can look at
|
||||||
|
int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, lookahead) - 1;
|
||||||
|
int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0);
|
||||||
|
|
||||||
|
byte[] window = this.window;
|
||||||
|
short[] prev = this.prev;
|
||||||
|
int chainLength = this.max_chain;
|
||||||
|
int niceLength = Math.Min(this.niceLength, lookahead);
|
||||||
|
|
||||||
|
matchLen = Math.Max(matchLen, DeflaterConstants.MIN_MATCH - 1);
|
||||||
|
|
||||||
|
if (scan + matchLen > scanMax) return false;
|
||||||
|
|
||||||
|
byte scan_end1 = window[scan + matchLen - 1];
|
||||||
|
byte scan_end = window[scan + matchLen];
|
||||||
|
|
||||||
|
// Do not waste too much time if we already have a good match:
|
||||||
|
if (matchLen >= this.goodLength) chainLength >>= 2;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
match = curMatch;
|
||||||
|
scan = strstart;
|
||||||
|
|
||||||
|
if (window[match + matchLen] != scan_end
|
||||||
|
|| window[match + matchLen - 1] != scan_end1
|
||||||
|
|| window[match] != window[scan]
|
||||||
|
|| window[++match] != window[++scan])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan is set to strstart+1 and the comparison passed, so
|
||||||
|
// scanMax - scan is the maximum number of bytes we can compare.
|
||||||
|
// below we compare 8 bytes at a time, so first we compare
|
||||||
|
// (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8
|
||||||
|
|
||||||
|
switch ((scanMax - scan) % 8)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
if (window[++scan] == window[++match]) break;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]) break;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
if (window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]) break;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
if (window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]) break;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
if (window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]) break;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
if (window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]) break;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
if (window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]) break;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window[scan] == window[match])
|
||||||
|
{
|
||||||
|
/* We check for insufficient lookahead only every 8th comparison;
|
||||||
|
* the 256th check will be made at strstart + 258 unless lookahead is
|
||||||
|
* exhausted first.
|
||||||
|
*/
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (scan == scanMax)
|
||||||
|
{
|
||||||
|
++scan; // advance to first position not matched
|
||||||
|
++match;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]
|
||||||
|
&& window[++scan] == window[++match]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scan - strstart > matchLen)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (ins_h == 0) )
|
||||||
|
Console.Error.WriteLine("Found match: " + curMatch + "-" + (scan - strstart));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
matchStart = curMatch;
|
||||||
|
matchLen = scan - strstart;
|
||||||
|
|
||||||
|
if (matchLen >= niceLength)
|
||||||
|
break;
|
||||||
|
|
||||||
|
scan_end1 = window[scan - 1];
|
||||||
|
scan_end = window[scan];
|
||||||
|
}
|
||||||
|
} while ((curMatch = (prev[curMatch & DeflaterConstants.WMASK] & 0xffff)) > limit && 0 != --chainLength);
|
||||||
|
|
||||||
|
return matchLen >= DeflaterConstants.MIN_MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DeflateStored(bool flush, bool finish)
|
||||||
|
{
|
||||||
|
if (!flush && (lookahead == 0))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
strstart += lookahead;
|
||||||
|
lookahead = 0;
|
||||||
|
|
||||||
|
int storedLength = strstart - blockStart;
|
||||||
|
|
||||||
|
if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full
|
||||||
|
(blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window
|
||||||
|
flush)
|
||||||
|
{
|
||||||
|
bool lastBlock = finish;
|
||||||
|
if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE)
|
||||||
|
{
|
||||||
|
storedLength = DeflaterConstants.MAX_BLOCK_SIZE;
|
||||||
|
lastBlock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING)
|
||||||
|
{
|
||||||
|
Console.WriteLine("storedBlock[" + storedLength + "," + lastBlock + "]");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
huffman.FlushStoredBlock(window, blockStart, storedLength, lastBlock);
|
||||||
|
blockStart += storedLength;
|
||||||
|
return !lastBlock;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DeflateFast(bool flush, bool finish)
|
||||||
|
{
|
||||||
|
if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
|
||||||
|
{
|
||||||
|
if (lookahead == 0)
|
||||||
|
{
|
||||||
|
// We are flushing everything
|
||||||
|
huffman.FlushBlock(window, blockStart, strstart - blockStart, finish);
|
||||||
|
blockStart = strstart;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstart > 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD)
|
||||||
|
{
|
||||||
|
/* slide window, as FindLongestMatch needs this.
|
||||||
|
* This should only happen when flushing and the window
|
||||||
|
* is almost full.
|
||||||
|
*/
|
||||||
|
SlideWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
int hashHead;
|
||||||
|
if (lookahead >= DeflaterConstants.MIN_MATCH &&
|
||||||
|
(hashHead = InsertString()) != 0 &&
|
||||||
|
strategy != DeflateStrategy.HuffmanOnly &&
|
||||||
|
strstart - hashHead <= DeflaterConstants.MAX_DIST &&
|
||||||
|
FindLongestMatch(hashHead))
|
||||||
|
{
|
||||||
|
// longestMatch sets matchStart and matchLen
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING)
|
||||||
|
{
|
||||||
|
for (int i = 0 ; i < matchLen; i++) {
|
||||||
|
if (window[strstart + i] != window[matchStart + i]) {
|
||||||
|
throw new SharpZipBaseException("Match failure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool full = huffman.TallyDist(strstart - matchStart, matchLen);
|
||||||
|
|
||||||
|
lookahead -= matchLen;
|
||||||
|
if (matchLen <= max_lazy && lookahead >= DeflaterConstants.MIN_MATCH)
|
||||||
|
{
|
||||||
|
while (--matchLen > 0)
|
||||||
|
{
|
||||||
|
++strstart;
|
||||||
|
InsertString();
|
||||||
|
}
|
||||||
|
++strstart;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strstart += matchLen;
|
||||||
|
if (lookahead >= DeflaterConstants.MIN_MATCH - 1)
|
||||||
|
{
|
||||||
|
UpdateHash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matchLen = DeflaterConstants.MIN_MATCH - 1;
|
||||||
|
if (!full)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No match found
|
||||||
|
huffman.TallyLit(window[strstart] & 0xff);
|
||||||
|
++strstart;
|
||||||
|
--lookahead;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (huffman.IsFull())
|
||||||
|
{
|
||||||
|
bool lastBlock = finish && (lookahead == 0);
|
||||||
|
huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock);
|
||||||
|
blockStart = strstart;
|
||||||
|
return !lastBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DeflateSlow(bool flush, bool finish)
|
||||||
|
{
|
||||||
|
if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
|
||||||
|
{
|
||||||
|
if (lookahead == 0)
|
||||||
|
{
|
||||||
|
if (prevAvailable)
|
||||||
|
{
|
||||||
|
huffman.TallyLit(window[strstart - 1] & 0xff);
|
||||||
|
}
|
||||||
|
prevAvailable = false;
|
||||||
|
|
||||||
|
// We are flushing everything
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && !flush)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Not flushing, but no lookahead");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
huffman.FlushBlock(window, blockStart, strstart - blockStart,
|
||||||
|
finish);
|
||||||
|
blockStart = strstart;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstart >= 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD)
|
||||||
|
{
|
||||||
|
/* slide window, as FindLongestMatch needs this.
|
||||||
|
* This should only happen when flushing and the window
|
||||||
|
* is almost full.
|
||||||
|
*/
|
||||||
|
SlideWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
int prevMatch = matchStart;
|
||||||
|
int prevLen = matchLen;
|
||||||
|
if (lookahead >= DeflaterConstants.MIN_MATCH)
|
||||||
|
{
|
||||||
|
int hashHead = InsertString();
|
||||||
|
|
||||||
|
if (strategy != DeflateStrategy.HuffmanOnly &&
|
||||||
|
hashHead != 0 &&
|
||||||
|
strstart - hashHead <= DeflaterConstants.MAX_DIST &&
|
||||||
|
FindLongestMatch(hashHead))
|
||||||
|
{
|
||||||
|
// longestMatch sets matchStart and matchLen
|
||||||
|
|
||||||
|
// Discard match if too small and too far away
|
||||||
|
if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == DeflaterConstants.MIN_MATCH && strstart - matchStart > TooFar)))
|
||||||
|
{
|
||||||
|
matchLen = DeflaterConstants.MIN_MATCH - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// previous match was better
|
||||||
|
if ((prevLen >= DeflaterConstants.MIN_MATCH) && (matchLen <= prevLen))
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING)
|
||||||
|
{
|
||||||
|
for (int i = 0 ; i < matchLen; i++) {
|
||||||
|
if (window[strstart-1+i] != window[prevMatch + i])
|
||||||
|
throw new SharpZipBaseException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
huffman.TallyDist(strstart - 1 - prevMatch, prevLen);
|
||||||
|
prevLen -= 2;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
strstart++;
|
||||||
|
lookahead--;
|
||||||
|
if (lookahead >= DeflaterConstants.MIN_MATCH)
|
||||||
|
{
|
||||||
|
InsertString();
|
||||||
|
}
|
||||||
|
} while (--prevLen > 0);
|
||||||
|
|
||||||
|
strstart++;
|
||||||
|
lookahead--;
|
||||||
|
prevAvailable = false;
|
||||||
|
matchLen = DeflaterConstants.MIN_MATCH - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (prevAvailable)
|
||||||
|
{
|
||||||
|
huffman.TallyLit(window[strstart - 1] & 0xff);
|
||||||
|
}
|
||||||
|
prevAvailable = true;
|
||||||
|
strstart++;
|
||||||
|
lookahead--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (huffman.IsFull())
|
||||||
|
{
|
||||||
|
int len = strstart - blockStart;
|
||||||
|
if (prevAvailable)
|
||||||
|
{
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
bool lastBlock = (finish && (lookahead == 0) && !prevAvailable);
|
||||||
|
huffman.FlushBlock(window, blockStart, len, lastBlock);
|
||||||
|
blockStart += len;
|
||||||
|
return !lastBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
// Hash index of string to be inserted
|
||||||
|
private int ins_h;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hashtable, hashing three characters to an index for window, so
|
||||||
|
/// that window[index]..window[index+2] have this hash code.
|
||||||
|
/// Note that the array should really be unsigned short, so you need
|
||||||
|
/// to and the values with 0xffff.
|
||||||
|
/// </summary>
|
||||||
|
private short[] head;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <code>prev[index & WMASK]</code> points to the previous index that has the
|
||||||
|
/// same hash code as the string starting at index. This way
|
||||||
|
/// entries with the same hash code are in a linked list.
|
||||||
|
/// Note that the array should really be unsigned short, so you need
|
||||||
|
/// to and the values with 0xffff.
|
||||||
|
/// </summary>
|
||||||
|
private short[] prev;
|
||||||
|
|
||||||
|
private int matchStart;
|
||||||
|
|
||||||
|
// Length of best match
|
||||||
|
private int matchLen;
|
||||||
|
|
||||||
|
// Set if previous match exists
|
||||||
|
private bool prevAvailable;
|
||||||
|
|
||||||
|
private int blockStart;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Points to the current character in the window.
|
||||||
|
/// </summary>
|
||||||
|
private int strstart;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// lookahead is the number of characters starting at strstart in
|
||||||
|
/// window that are valid.
|
||||||
|
/// So window[strstart] until window[strstart+lookahead-1] are valid
|
||||||
|
/// characters.
|
||||||
|
/// </summary>
|
||||||
|
private int lookahead;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This array contains the part of the uncompressed stream that
|
||||||
|
/// is of relevance. The current character is indexed by strstart.
|
||||||
|
/// </summary>
|
||||||
|
private byte[] window;
|
||||||
|
|
||||||
|
private DeflateStrategy strategy;
|
||||||
|
private int max_chain, max_lazy, niceLength, goodLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current compression function.
|
||||||
|
/// </summary>
|
||||||
|
private int compressionFunction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The input data for compression.
|
||||||
|
/// </summary>
|
||||||
|
private byte[] inputBuf;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total bytes of input read.
|
||||||
|
/// </summary>
|
||||||
|
private long totalIn;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The offset into inputBuf, where input data starts.
|
||||||
|
/// </summary>
|
||||||
|
private int inputOff;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The end offset of the input data.
|
||||||
|
/// </summary>
|
||||||
|
private int inputEnd;
|
||||||
|
|
||||||
|
private DeflaterPending pending;
|
||||||
|
private DeflaterHuffman huffman;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The adler checksum
|
||||||
|
/// </summary>
|
||||||
|
private Adler32 adler;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,959 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is the DeflaterHuffman class.
|
||||||
|
///
|
||||||
|
/// This class is <i>not</i> thread safe. This is inherent in the API, due
|
||||||
|
/// to the split of Deflate and SetInput.
|
||||||
|
///
|
||||||
|
/// author of the original java version : Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
public class DeflaterHuffman
|
||||||
|
{
|
||||||
|
private const int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6);
|
||||||
|
private const int LITERAL_NUM = 286;
|
||||||
|
|
||||||
|
// Number of distance codes
|
||||||
|
private const int DIST_NUM = 30;
|
||||||
|
|
||||||
|
// Number of codes used to transfer bit lengths
|
||||||
|
private const int BITLEN_NUM = 19;
|
||||||
|
|
||||||
|
// repeat previous bit length 3-6 times (2 bits of repeat count)
|
||||||
|
private const int REP_3_6 = 16;
|
||||||
|
|
||||||
|
// repeat a zero length 3-10 times (3 bits of repeat count)
|
||||||
|
private const int REP_3_10 = 17;
|
||||||
|
|
||||||
|
// repeat a zero length 11-138 times (7 bits of repeat count)
|
||||||
|
private const int REP_11_138 = 18;
|
||||||
|
|
||||||
|
private const int EOF_SYMBOL = 256;
|
||||||
|
|
||||||
|
// The lengths of the bit length codes are sent in order of decreasing
|
||||||
|
// probability, to avoid transmitting the lengths for unused bit length codes.
|
||||||
|
private static readonly int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
|
||||||
|
|
||||||
|
private static readonly byte[] bit4Reverse = {
|
||||||
|
0,
|
||||||
|
8,
|
||||||
|
4,
|
||||||
|
12,
|
||||||
|
2,
|
||||||
|
10,
|
||||||
|
6,
|
||||||
|
14,
|
||||||
|
1,
|
||||||
|
9,
|
||||||
|
5,
|
||||||
|
13,
|
||||||
|
3,
|
||||||
|
11,
|
||||||
|
7,
|
||||||
|
15
|
||||||
|
};
|
||||||
|
|
||||||
|
private static short[] staticLCodes;
|
||||||
|
private static byte[] staticLLength;
|
||||||
|
private static short[] staticDCodes;
|
||||||
|
private static byte[] staticDLength;
|
||||||
|
|
||||||
|
private class Tree
|
||||||
|
{
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
public short[] freqs;
|
||||||
|
|
||||||
|
public byte[] length;
|
||||||
|
|
||||||
|
public int minNumCodes;
|
||||||
|
|
||||||
|
public int numCodes;
|
||||||
|
|
||||||
|
private short[] codes;
|
||||||
|
private readonly int[] bl_counts;
|
||||||
|
private readonly int maxLength;
|
||||||
|
private DeflaterHuffman dh;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength)
|
||||||
|
{
|
||||||
|
this.dh = dh;
|
||||||
|
this.minNumCodes = minCodes;
|
||||||
|
this.maxLength = maxLength;
|
||||||
|
freqs = new short[elems];
|
||||||
|
bl_counts = new int[maxLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the internal state of the tree
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < freqs.Length; i++)
|
||||||
|
{
|
||||||
|
freqs[i] = 0;
|
||||||
|
}
|
||||||
|
codes = null;
|
||||||
|
length = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteSymbol(int code)
|
||||||
|
{
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// freqs[code]--;
|
||||||
|
// // Console.Write("writeSymbol("+freqs.length+","+code+"): ");
|
||||||
|
// }
|
||||||
|
dh.pending.WriteBits(codes[code] & 0xffff, length[code]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check that all frequencies are zero
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// At least one frequency is non-zero
|
||||||
|
/// </exception>
|
||||||
|
public void CheckEmpty()
|
||||||
|
{
|
||||||
|
bool empty = true;
|
||||||
|
for (int i = 0; i < freqs.Length; i++)
|
||||||
|
{
|
||||||
|
empty &= freqs[i] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("!Empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set static codes and length
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="staticCodes">new codes</param>
|
||||||
|
/// <param name="staticLengths">length for new codes</param>
|
||||||
|
public void SetStaticCodes(short[] staticCodes, byte[] staticLengths)
|
||||||
|
{
|
||||||
|
codes = staticCodes;
|
||||||
|
length = staticLengths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build dynamic codes and lengths
|
||||||
|
/// </summary>
|
||||||
|
public void BuildCodes()
|
||||||
|
{
|
||||||
|
int numSymbols = freqs.Length;
|
||||||
|
int[] nextCode = new int[maxLength];
|
||||||
|
int code = 0;
|
||||||
|
|
||||||
|
codes = new short[freqs.Length];
|
||||||
|
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("buildCodes: "+freqs.Length);
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (int bits = 0; bits < maxLength; bits++)
|
||||||
|
{
|
||||||
|
nextCode[bits] = code;
|
||||||
|
code += bl_counts[bits] << (15 - bits);
|
||||||
|
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("bits: " + ( bits + 1) + " count: " + bl_counts[bits]
|
||||||
|
// +" nextCode: "+code);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DebugDeflation
|
||||||
|
if ( DeflaterConstants.DEBUGGING && (code != 65536) )
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Inconsistent bl_counts!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (int i = 0; i < numCodes; i++)
|
||||||
|
{
|
||||||
|
int bits = length[i];
|
||||||
|
if (bits > 0)
|
||||||
|
{
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("codes["+i+"] = rev(" + nextCode[bits-1]+"),
|
||||||
|
// +bits);
|
||||||
|
// }
|
||||||
|
|
||||||
|
codes[i] = BitReverse(nextCode[bits - 1]);
|
||||||
|
nextCode[bits - 1] += 1 << (16 - bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BuildTree()
|
||||||
|
{
|
||||||
|
int numSymbols = freqs.Length;
|
||||||
|
|
||||||
|
/* heap is a priority queue, sorted by frequency, least frequent
|
||||||
|
* nodes first. The heap is a binary tree, with the property, that
|
||||||
|
* the parent node is smaller than both child nodes. This assures
|
||||||
|
* that the smallest node is the first parent.
|
||||||
|
*
|
||||||
|
* The binary tree is encoded in an array: 0 is root node and
|
||||||
|
* the nodes 2*n+1, 2*n+2 are the child nodes of node n.
|
||||||
|
*/
|
||||||
|
int[] heap = new int[numSymbols];
|
||||||
|
int heapLen = 0;
|
||||||
|
int maxCode = 0;
|
||||||
|
for (int n = 0; n < numSymbols; n++)
|
||||||
|
{
|
||||||
|
int freq = freqs[n];
|
||||||
|
if (freq != 0)
|
||||||
|
{
|
||||||
|
// Insert n into heap
|
||||||
|
int pos = heapLen++;
|
||||||
|
int ppos;
|
||||||
|
while (pos > 0 && freqs[heap[ppos = (pos - 1) / 2]] > freq)
|
||||||
|
{
|
||||||
|
heap[pos] = heap[ppos];
|
||||||
|
pos = ppos;
|
||||||
|
}
|
||||||
|
heap[pos] = n;
|
||||||
|
|
||||||
|
maxCode = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We could encode a single literal with 0 bits but then we
|
||||||
|
* don't see the literals. Therefore we force at least two
|
||||||
|
* literals to avoid this case. We don't care about order in
|
||||||
|
* this case, both literals get a 1 bit code.
|
||||||
|
*/
|
||||||
|
while (heapLen < 2)
|
||||||
|
{
|
||||||
|
int node = maxCode < 2 ? ++maxCode : 0;
|
||||||
|
heap[heapLen++] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
numCodes = Math.Max(maxCode + 1, minNumCodes);
|
||||||
|
|
||||||
|
int numLeafs = heapLen;
|
||||||
|
int[] childs = new int[4 * heapLen - 2];
|
||||||
|
int[] values = new int[2 * heapLen - 1];
|
||||||
|
int numNodes = numLeafs;
|
||||||
|
for (int i = 0; i < heapLen; i++)
|
||||||
|
{
|
||||||
|
int node = heap[i];
|
||||||
|
childs[2 * i] = node;
|
||||||
|
childs[2 * i + 1] = -1;
|
||||||
|
values[i] = freqs[node] << 8;
|
||||||
|
heap[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Construct the Huffman tree by repeatedly combining the least two
|
||||||
|
* frequent nodes.
|
||||||
|
*/
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int first = heap[0];
|
||||||
|
int last = heap[--heapLen];
|
||||||
|
|
||||||
|
// Propagate the hole to the leafs of the heap
|
||||||
|
int ppos = 0;
|
||||||
|
int path = 1;
|
||||||
|
|
||||||
|
while (path < heapLen)
|
||||||
|
{
|
||||||
|
if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]])
|
||||||
|
{
|
||||||
|
path++;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap[ppos] = heap[path];
|
||||||
|
ppos = path;
|
||||||
|
path = path * 2 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now propagate the last element down along path. Normally
|
||||||
|
* it shouldn't go too deep.
|
||||||
|
*/
|
||||||
|
int lastVal = values[last];
|
||||||
|
while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal)
|
||||||
|
{
|
||||||
|
heap[path] = heap[ppos];
|
||||||
|
}
|
||||||
|
heap[path] = last;
|
||||||
|
|
||||||
|
int second = heap[0];
|
||||||
|
|
||||||
|
// Create a new node father of first and second
|
||||||
|
last = numNodes++;
|
||||||
|
childs[2 * last] = first;
|
||||||
|
childs[2 * last + 1] = second;
|
||||||
|
int mindepth = Math.Min(values[first] & 0xff, values[second] & 0xff);
|
||||||
|
values[last] = lastVal = values[first] + values[second] - mindepth + 1;
|
||||||
|
|
||||||
|
// Again, propagate the hole to the leafs
|
||||||
|
ppos = 0;
|
||||||
|
path = 1;
|
||||||
|
|
||||||
|
while (path < heapLen)
|
||||||
|
{
|
||||||
|
if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]])
|
||||||
|
{
|
||||||
|
path++;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap[ppos] = heap[path];
|
||||||
|
ppos = path;
|
||||||
|
path = ppos * 2 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now propagate the new element down along path
|
||||||
|
while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal)
|
||||||
|
{
|
||||||
|
heap[path] = heap[ppos];
|
||||||
|
}
|
||||||
|
heap[path] = last;
|
||||||
|
} while (heapLen > 1);
|
||||||
|
|
||||||
|
if (heap[0] != childs.Length / 2 - 1)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Heap invariant violated");
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildLength(childs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get encoded length
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Encoded length, the sum of frequencies * lengths</returns>
|
||||||
|
public int GetEncodedLength()
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
for (int i = 0; i < freqs.Length; i++)
|
||||||
|
{
|
||||||
|
len += freqs[i] * length[i];
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scan a literal or distance tree to determine the frequencies of the codes
|
||||||
|
/// in the bit length tree.
|
||||||
|
/// </summary>
|
||||||
|
public void CalcBLFreq(Tree blTree)
|
||||||
|
{
|
||||||
|
int max_count; /* max repeat count */
|
||||||
|
int min_count; /* min repeat count */
|
||||||
|
int count; /* repeat count of the current code */
|
||||||
|
int curlen = -1; /* length of current code */
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < numCodes)
|
||||||
|
{
|
||||||
|
count = 1;
|
||||||
|
int nextlen = length[i];
|
||||||
|
if (nextlen == 0)
|
||||||
|
{
|
||||||
|
max_count = 138;
|
||||||
|
min_count = 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
max_count = 6;
|
||||||
|
min_count = 3;
|
||||||
|
if (curlen != nextlen)
|
||||||
|
{
|
||||||
|
blTree.freqs[nextlen]++;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curlen = nextlen;
|
||||||
|
i++;
|
||||||
|
|
||||||
|
while (i < numCodes && curlen == length[i])
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
if (++count >= max_count)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < min_count)
|
||||||
|
{
|
||||||
|
blTree.freqs[curlen] += (short)count;
|
||||||
|
}
|
||||||
|
else if (curlen != 0)
|
||||||
|
{
|
||||||
|
blTree.freqs[REP_3_6]++;
|
||||||
|
}
|
||||||
|
else if (count <= 10)
|
||||||
|
{
|
||||||
|
blTree.freqs[REP_3_10]++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
blTree.freqs[REP_11_138]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write tree values
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blTree">Tree to write</param>
|
||||||
|
public void WriteTree(Tree blTree)
|
||||||
|
{
|
||||||
|
int max_count; // max repeat count
|
||||||
|
int min_count; // min repeat count
|
||||||
|
int count; // repeat count of the current code
|
||||||
|
int curlen = -1; // length of current code
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < numCodes)
|
||||||
|
{
|
||||||
|
count = 1;
|
||||||
|
int nextlen = length[i];
|
||||||
|
if (nextlen == 0)
|
||||||
|
{
|
||||||
|
max_count = 138;
|
||||||
|
min_count = 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
max_count = 6;
|
||||||
|
min_count = 3;
|
||||||
|
if (curlen != nextlen)
|
||||||
|
{
|
||||||
|
blTree.WriteSymbol(nextlen);
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curlen = nextlen;
|
||||||
|
i++;
|
||||||
|
|
||||||
|
while (i < numCodes && curlen == length[i])
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
if (++count >= max_count)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < min_count)
|
||||||
|
{
|
||||||
|
while (count-- > 0)
|
||||||
|
{
|
||||||
|
blTree.WriteSymbol(curlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (curlen != 0)
|
||||||
|
{
|
||||||
|
blTree.WriteSymbol(REP_3_6);
|
||||||
|
dh.pending.WriteBits(count - 3, 2);
|
||||||
|
}
|
||||||
|
else if (count <= 10)
|
||||||
|
{
|
||||||
|
blTree.WriteSymbol(REP_3_10);
|
||||||
|
dh.pending.WriteBits(count - 3, 3);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
blTree.WriteSymbol(REP_11_138);
|
||||||
|
dh.pending.WriteBits(count - 11, 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildLength(int[] childs)
|
||||||
|
{
|
||||||
|
this.length = new byte[freqs.Length];
|
||||||
|
int numNodes = childs.Length / 2;
|
||||||
|
int numLeafs = (numNodes + 1) / 2;
|
||||||
|
int overflow = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < maxLength; i++)
|
||||||
|
{
|
||||||
|
bl_counts[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First calculate optimal bit lengths
|
||||||
|
int[] lengths = new int[numNodes];
|
||||||
|
lengths[numNodes - 1] = 0;
|
||||||
|
|
||||||
|
for (int i = numNodes - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (childs[2 * i + 1] != -1)
|
||||||
|
{
|
||||||
|
int bitLength = lengths[i] + 1;
|
||||||
|
if (bitLength > maxLength)
|
||||||
|
{
|
||||||
|
bitLength = maxLength;
|
||||||
|
overflow++;
|
||||||
|
}
|
||||||
|
lengths[childs[2 * i]] = lengths[childs[2 * i + 1]] = bitLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// A leaf node
|
||||||
|
int bitLength = lengths[i];
|
||||||
|
bl_counts[bitLength - 1]++;
|
||||||
|
this.length[childs[2 * i]] = (byte)lengths[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("Tree "+freqs.Length+" lengths:");
|
||||||
|
// for (int i=0; i < numLeafs; i++) {
|
||||||
|
// //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]]
|
||||||
|
// + " len: "+length[childs[2*i]]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (overflow == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int incrBitLen = maxLength - 1;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Find the first bit length which could increase:
|
||||||
|
while (bl_counts[--incrBitLen] == 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move this node one down and remove a corresponding
|
||||||
|
// number of overflow nodes.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bl_counts[incrBitLen]--;
|
||||||
|
bl_counts[++incrBitLen]++;
|
||||||
|
overflow -= 1 << (maxLength - 1 - incrBitLen);
|
||||||
|
} while (overflow > 0 && incrBitLen < maxLength - 1);
|
||||||
|
} while (overflow > 0);
|
||||||
|
|
||||||
|
/* We may have overshot above. Move some nodes from maxLength to
|
||||||
|
* maxLength-1 in that case.
|
||||||
|
*/
|
||||||
|
bl_counts[maxLength - 1] += overflow;
|
||||||
|
bl_counts[maxLength - 2] -= overflow;
|
||||||
|
|
||||||
|
/* Now recompute all bit lengths, scanning in increasing
|
||||||
|
* frequency. It is simpler to reconstruct all lengths instead of
|
||||||
|
* fixing only the wrong ones. This idea is taken from 'ar'
|
||||||
|
* written by Haruhiko Okumura.
|
||||||
|
*
|
||||||
|
* The nodes were inserted with decreasing frequency into the childs
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
int nodePtr = 2 * numLeafs;
|
||||||
|
for (int bits = maxLength; bits != 0; bits--)
|
||||||
|
{
|
||||||
|
int n = bl_counts[bits - 1];
|
||||||
|
while (n > 0)
|
||||||
|
{
|
||||||
|
int childPtr = 2 * childs[nodePtr++];
|
||||||
|
if (childs[childPtr + 1] == -1)
|
||||||
|
{
|
||||||
|
// We found another leaf
|
||||||
|
length[childs[childPtr]] = (byte)bits;
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("*** After overflow elimination. ***");
|
||||||
|
// for (int i=0; i < numLeafs; i++) {
|
||||||
|
// //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]]
|
||||||
|
// + " len: "+length[childs[2*i]]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pending buffer to use
|
||||||
|
/// </summary>
|
||||||
|
public DeflaterPending pending;
|
||||||
|
|
||||||
|
private Tree literalTree;
|
||||||
|
private Tree distTree;
|
||||||
|
private Tree blTree;
|
||||||
|
|
||||||
|
// Buffer for distances
|
||||||
|
private short[] d_buf;
|
||||||
|
|
||||||
|
private byte[] l_buf;
|
||||||
|
private int last_lit;
|
||||||
|
private int extra_bits;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
static DeflaterHuffman()
|
||||||
|
{
|
||||||
|
// See RFC 1951 3.2.6
|
||||||
|
// Literal codes
|
||||||
|
staticLCodes = new short[LITERAL_NUM];
|
||||||
|
staticLLength = new byte[LITERAL_NUM];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < 144)
|
||||||
|
{
|
||||||
|
staticLCodes[i] = BitReverse((0x030 + i) << 8);
|
||||||
|
staticLLength[i++] = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < 256)
|
||||||
|
{
|
||||||
|
staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7);
|
||||||
|
staticLLength[i++] = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < 280)
|
||||||
|
{
|
||||||
|
staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9);
|
||||||
|
staticLLength[i++] = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < LITERAL_NUM)
|
||||||
|
{
|
||||||
|
staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8);
|
||||||
|
staticLLength[i++] = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance codes
|
||||||
|
staticDCodes = new short[DIST_NUM];
|
||||||
|
staticDLength = new byte[DIST_NUM];
|
||||||
|
for (i = 0; i < DIST_NUM; i++)
|
||||||
|
{
|
||||||
|
staticDCodes[i] = BitReverse(i << 11);
|
||||||
|
staticDLength[i] = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct instance with pending buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pending">Pending buffer to use</param>
|
||||||
|
public DeflaterHuffman(DeflaterPending pending)
|
||||||
|
{
|
||||||
|
this.pending = pending;
|
||||||
|
|
||||||
|
literalTree = new Tree(this, LITERAL_NUM, 257, 15);
|
||||||
|
distTree = new Tree(this, DIST_NUM, 1, 15);
|
||||||
|
blTree = new Tree(this, BITLEN_NUM, 4, 7);
|
||||||
|
|
||||||
|
d_buf = new short[BUFSIZE];
|
||||||
|
l_buf = new byte[BUFSIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset internal state
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
last_lit = 0;
|
||||||
|
extra_bits = 0;
|
||||||
|
literalTree.Reset();
|
||||||
|
distTree.Reset();
|
||||||
|
blTree.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write all trees to pending buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blTreeCodes">The number/rank of treecodes to send.</param>
|
||||||
|
public void SendAllTrees(int blTreeCodes)
|
||||||
|
{
|
||||||
|
blTree.BuildCodes();
|
||||||
|
literalTree.BuildCodes();
|
||||||
|
distTree.BuildCodes();
|
||||||
|
pending.WriteBits(literalTree.numCodes - 257, 5);
|
||||||
|
pending.WriteBits(distTree.numCodes - 1, 5);
|
||||||
|
pending.WriteBits(blTreeCodes - 4, 4);
|
||||||
|
for (int rank = 0; rank < blTreeCodes; rank++)
|
||||||
|
{
|
||||||
|
pending.WriteBits(blTree.length[BL_ORDER[rank]], 3);
|
||||||
|
}
|
||||||
|
literalTree.WriteTree(blTree);
|
||||||
|
distTree.WriteTree(blTree);
|
||||||
|
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING) {
|
||||||
|
blTree.CheckEmpty();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compress current buffer writing data to pending buffer
|
||||||
|
/// </summary>
|
||||||
|
public void CompressBlock()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < last_lit; i++)
|
||||||
|
{
|
||||||
|
int litlen = l_buf[i] & 0xff;
|
||||||
|
int dist = d_buf[i];
|
||||||
|
if (dist-- != 0)
|
||||||
|
{
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// Console.Write("["+(dist+1)+","+(litlen+3)+"]: ");
|
||||||
|
// }
|
||||||
|
|
||||||
|
int lc = Lcode(litlen);
|
||||||
|
literalTree.WriteSymbol(lc);
|
||||||
|
|
||||||
|
int bits = (lc - 261) / 4;
|
||||||
|
if (bits > 0 && bits <= 5)
|
||||||
|
{
|
||||||
|
pending.WriteBits(litlen & ((1 << bits) - 1), bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dc = Dcode(dist);
|
||||||
|
distTree.WriteSymbol(dc);
|
||||||
|
|
||||||
|
bits = dc / 2 - 1;
|
||||||
|
if (bits > 0)
|
||||||
|
{
|
||||||
|
pending.WriteBits(dist & ((1 << bits) - 1), bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// if (litlen > 32 && litlen < 127) {
|
||||||
|
// Console.Write("("+(char)litlen+"): ");
|
||||||
|
// } else {
|
||||||
|
// Console.Write("{"+litlen+"}: ");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
literalTree.WriteSymbol(litlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING) {
|
||||||
|
Console.Write("EOF: ");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
literalTree.WriteSymbol(EOF_SYMBOL);
|
||||||
|
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING) {
|
||||||
|
literalTree.CheckEmpty();
|
||||||
|
distTree.CheckEmpty();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flush block to output with no compression
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stored">Data to write</param>
|
||||||
|
/// <param name="storedOffset">Index of first byte to write</param>
|
||||||
|
/// <param name="storedLength">Count of bytes to write</param>
|
||||||
|
/// <param name="lastBlock">True if this is the last block</param>
|
||||||
|
public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("Flushing stored block "+ storedLength);
|
||||||
|
// }
|
||||||
|
#endif
|
||||||
|
pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3);
|
||||||
|
pending.AlignToByte();
|
||||||
|
pending.WriteShort(storedLength);
|
||||||
|
pending.WriteShort(~storedLength);
|
||||||
|
pending.WriteBlock(stored, storedOffset, storedLength);
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flush block to output with compression
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stored">Data to flush</param>
|
||||||
|
/// <param name="storedOffset">Index of first byte to flush</param>
|
||||||
|
/// <param name="storedLength">Count of bytes to flush</param>
|
||||||
|
/// <param name="lastBlock">True if this is the last block</param>
|
||||||
|
public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
|
||||||
|
{
|
||||||
|
literalTree.freqs[EOF_SYMBOL]++;
|
||||||
|
|
||||||
|
// Build trees
|
||||||
|
literalTree.BuildTree();
|
||||||
|
distTree.BuildTree();
|
||||||
|
|
||||||
|
// Calculate bitlen frequency
|
||||||
|
literalTree.CalcBLFreq(blTree);
|
||||||
|
distTree.CalcBLFreq(blTree);
|
||||||
|
|
||||||
|
// Build bitlen tree
|
||||||
|
blTree.BuildTree();
|
||||||
|
|
||||||
|
int blTreeCodes = 4;
|
||||||
|
for (int i = 18; i > blTreeCodes; i--)
|
||||||
|
{
|
||||||
|
if (blTree.length[BL_ORDER[i]] > 0)
|
||||||
|
{
|
||||||
|
blTreeCodes = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int opt_len = 14 + blTreeCodes * 3 + blTree.GetEncodedLength() +
|
||||||
|
literalTree.GetEncodedLength() + distTree.GetEncodedLength() +
|
||||||
|
extra_bits;
|
||||||
|
|
||||||
|
int static_len = extra_bits;
|
||||||
|
for (int i = 0; i < LITERAL_NUM; i++)
|
||||||
|
{
|
||||||
|
static_len += literalTree.freqs[i] * staticLLength[i];
|
||||||
|
}
|
||||||
|
for (int i = 0; i < DIST_NUM; i++)
|
||||||
|
{
|
||||||
|
static_len += distTree.freqs[i] * staticDLength[i];
|
||||||
|
}
|
||||||
|
if (opt_len >= static_len)
|
||||||
|
{
|
||||||
|
// Force static trees
|
||||||
|
opt_len = static_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3)
|
||||||
|
{
|
||||||
|
// Store Block
|
||||||
|
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("Storing, since " + storedLength + " < " + opt_len
|
||||||
|
// + " <= " + static_len);
|
||||||
|
// }
|
||||||
|
FlushStoredBlock(stored, storedOffset, storedLength, lastBlock);
|
||||||
|
}
|
||||||
|
else if (opt_len == static_len)
|
||||||
|
{
|
||||||
|
// Encode with static tree
|
||||||
|
pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3);
|
||||||
|
literalTree.SetStaticCodes(staticLCodes, staticLLength);
|
||||||
|
distTree.SetStaticCodes(staticDCodes, staticDLength);
|
||||||
|
CompressBlock();
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Encode with dynamic tree
|
||||||
|
pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3);
|
||||||
|
SendAllTrees(blTreeCodes);
|
||||||
|
CompressBlock();
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get value indicating if internal buffer is full
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if buffer is full</returns>
|
||||||
|
public bool IsFull()
|
||||||
|
{
|
||||||
|
return last_lit >= BUFSIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add literal to buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="literal">Literal value to add to buffer.</param>
|
||||||
|
/// <returns>Value indicating internal buffer is full</returns>
|
||||||
|
public bool TallyLit(int literal)
|
||||||
|
{
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// if (lit > 32 && lit < 127) {
|
||||||
|
// //Console.WriteLine("("+(char)lit+")");
|
||||||
|
// } else {
|
||||||
|
// //Console.WriteLine("{"+lit+"}");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
d_buf[last_lit] = 0;
|
||||||
|
l_buf[last_lit++] = (byte)literal;
|
||||||
|
literalTree.freqs[literal]++;
|
||||||
|
return IsFull();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add distance code and length to literal and distance trees
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="distance">Distance code</param>
|
||||||
|
/// <param name="length">Length</param>
|
||||||
|
/// <returns>Value indicating if internal buffer is full</returns>
|
||||||
|
public bool TallyDist(int distance, int length)
|
||||||
|
{
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("[" + distance + "," + length + "]");
|
||||||
|
// }
|
||||||
|
|
||||||
|
d_buf[last_lit] = (short)distance;
|
||||||
|
l_buf[last_lit++] = (byte)(length - 3);
|
||||||
|
|
||||||
|
int lc = Lcode(length - 3);
|
||||||
|
literalTree.freqs[lc]++;
|
||||||
|
if (lc >= 265 && lc < 285)
|
||||||
|
{
|
||||||
|
extra_bits += (lc - 261) / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dc = Dcode(distance - 1);
|
||||||
|
distTree.freqs[dc]++;
|
||||||
|
if (dc >= 4)
|
||||||
|
{
|
||||||
|
extra_bits += dc / 2 - 1;
|
||||||
|
}
|
||||||
|
return IsFull();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reverse the bits of a 16 bit value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toReverse">Value to reverse bits</param>
|
||||||
|
/// <returns>Value with bits reversed</returns>
|
||||||
|
public static short BitReverse(int toReverse)
|
||||||
|
{
|
||||||
|
return (short)(bit4Reverse[toReverse & 0xF] << 12 |
|
||||||
|
bit4Reverse[(toReverse >> 4) & 0xF] << 8 |
|
||||||
|
bit4Reverse[(toReverse >> 8) & 0xF] << 4 |
|
||||||
|
bit4Reverse[toReverse >> 12]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Lcode(int length)
|
||||||
|
{
|
||||||
|
if (length == 255)
|
||||||
|
{
|
||||||
|
return 285;
|
||||||
|
}
|
||||||
|
|
||||||
|
int code = 257;
|
||||||
|
while (length >= 8)
|
||||||
|
{
|
||||||
|
code += 4;
|
||||||
|
length >>= 1;
|
||||||
|
}
|
||||||
|
return code + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Dcode(int distance)
|
||||||
|
{
|
||||||
|
int code = 0;
|
||||||
|
while (distance >= 4)
|
||||||
|
{
|
||||||
|
code += 2;
|
||||||
|
distance >>= 1;
|
||||||
|
}
|
||||||
|
return code + distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class stores the pending output of the Deflater.
|
||||||
|
///
|
||||||
|
/// author of the original java version : Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
public class DeflaterPending : PendingBuffer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Construct instance with default buffer size
|
||||||
|
/// </summary>
|
||||||
|
public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,887 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Inflater is used to decompress data that has been compressed according
|
||||||
|
/// to the "deflate" standard described in rfc1951.
|
||||||
|
///
|
||||||
|
/// By default Zlib (rfc1950) headers and footers are expected in the input.
|
||||||
|
/// You can use constructor <code> public Inflater(bool noHeader)</code> passing true
|
||||||
|
/// if there is no Zlib header information
|
||||||
|
///
|
||||||
|
/// The usage is as following. First you have to set some input with
|
||||||
|
/// <code>SetInput()</code>, then Inflate() it. If inflate doesn't
|
||||||
|
/// inflate any bytes there may be three reasons:
|
||||||
|
/// <ul>
|
||||||
|
/// <li>IsNeedingInput() returns true because the input buffer is empty.
|
||||||
|
/// You have to provide more input with <code>SetInput()</code>.
|
||||||
|
/// NOTE: IsNeedingInput() also returns true when, the stream is finished.
|
||||||
|
/// </li>
|
||||||
|
/// <li>IsNeedingDictionary() returns true, you have to provide a preset
|
||||||
|
/// dictionary with <code>SetDictionary()</code>.</li>
|
||||||
|
/// <li>IsFinished returns true, the inflater has finished.</li>
|
||||||
|
/// </ul>
|
||||||
|
/// Once the first output byte is produced, a dictionary will not be
|
||||||
|
/// needed at a later stage.
|
||||||
|
///
|
||||||
|
/// author of the original java version : John Leuner, Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
public class Inflater
|
||||||
|
{
|
||||||
|
#region Constants/Readonly
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy lengths for literal codes 257..285
|
||||||
|
/// </summary>
|
||||||
|
private static readonly int[] CPLENS = {
|
||||||
|
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
||||||
|
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extra bits for literal codes 257..285
|
||||||
|
/// </summary>
|
||||||
|
private static readonly int[] CPLEXT = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||||
|
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy offsets for distance codes 0..29
|
||||||
|
/// </summary>
|
||||||
|
private static readonly int[] CPDIST = {
|
||||||
|
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
||||||
|
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
||||||
|
8193, 12289, 16385, 24577
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extra bits for distance codes
|
||||||
|
/// </summary>
|
||||||
|
private static readonly int[] CPDEXT = {
|
||||||
|
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||||
|
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
|
||||||
|
12, 12, 13, 13
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// These are the possible states for an inflater
|
||||||
|
/// </summary>
|
||||||
|
private const int DECODE_HEADER = 0;
|
||||||
|
|
||||||
|
private const int DECODE_DICT = 1;
|
||||||
|
private const int DECODE_BLOCKS = 2;
|
||||||
|
private const int DECODE_STORED_LEN1 = 3;
|
||||||
|
private const int DECODE_STORED_LEN2 = 4;
|
||||||
|
private const int DECODE_STORED = 5;
|
||||||
|
private const int DECODE_DYN_HEADER = 6;
|
||||||
|
private const int DECODE_HUFFMAN = 7;
|
||||||
|
private const int DECODE_HUFFMAN_LENBITS = 8;
|
||||||
|
private const int DECODE_HUFFMAN_DIST = 9;
|
||||||
|
private const int DECODE_HUFFMAN_DISTBITS = 10;
|
||||||
|
private const int DECODE_CHKSUM = 11;
|
||||||
|
private const int FINISHED = 12;
|
||||||
|
|
||||||
|
#endregion Constants/Readonly
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This variable contains the current state.
|
||||||
|
/// </summary>
|
||||||
|
private int mode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The adler checksum of the dictionary or of the decompressed
|
||||||
|
/// stream, as it is written in the header resp. footer of the
|
||||||
|
/// compressed stream.
|
||||||
|
/// Only valid if mode is DECODE_DICT or DECODE_CHKSUM.
|
||||||
|
/// </summary>
|
||||||
|
private int readAdler;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of bits needed to complete the current state. This
|
||||||
|
/// is valid, if mode is DECODE_DICT, DECODE_CHKSUM,
|
||||||
|
/// DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS.
|
||||||
|
/// </summary>
|
||||||
|
private int neededBits;
|
||||||
|
|
||||||
|
private int repLength;
|
||||||
|
private int repDist;
|
||||||
|
private int uncomprLen;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True, if the last block flag was set in the last block of the
|
||||||
|
/// inflated stream. This means that the stream ends after the
|
||||||
|
/// current block.
|
||||||
|
/// </summary>
|
||||||
|
private bool isLastBlock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total number of inflated bytes.
|
||||||
|
/// </summary>
|
||||||
|
private long totalOut;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total number of bytes set with setInput(). This is not the
|
||||||
|
/// value returned by the TotalIn property, since this also includes the
|
||||||
|
/// unprocessed input.
|
||||||
|
/// </summary>
|
||||||
|
private long totalIn;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This variable stores the noHeader flag that was given to the constructor.
|
||||||
|
/// True means, that the inflated stream doesn't contain a Zlib header or
|
||||||
|
/// footer.
|
||||||
|
/// </summary>
|
||||||
|
private bool noHeader;
|
||||||
|
|
||||||
|
private readonly StreamManipulator input;
|
||||||
|
private OutputWindow outputWindow;
|
||||||
|
private InflaterDynHeader dynHeader;
|
||||||
|
private InflaterHuffmanTree litlenTree, distTree;
|
||||||
|
private Adler32 adler;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new inflater or RFC1951 decompressor
|
||||||
|
/// RFC1950/Zlib headers and footers will be expected in the input data
|
||||||
|
/// </summary>
|
||||||
|
public Inflater() : this(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new inflater.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="noHeader">
|
||||||
|
/// True if no RFC1950/Zlib header and footer fields are expected in the input data
|
||||||
|
///
|
||||||
|
/// This is used for GZIPed/Zipped input.
|
||||||
|
///
|
||||||
|
/// For compatibility with
|
||||||
|
/// Sun JDK you should provide one byte of input more than needed in
|
||||||
|
/// this case.
|
||||||
|
/// </param>
|
||||||
|
public Inflater(bool noHeader)
|
||||||
|
{
|
||||||
|
this.noHeader = noHeader;
|
||||||
|
if (!noHeader)
|
||||||
|
this.adler = new Adler32();
|
||||||
|
input = new StreamManipulator();
|
||||||
|
outputWindow = new OutputWindow();
|
||||||
|
mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the inflater so that a new stream can be decompressed. All
|
||||||
|
/// pending input and output will be discarded.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER;
|
||||||
|
totalIn = 0;
|
||||||
|
totalOut = 0;
|
||||||
|
input.Reset();
|
||||||
|
outputWindow.Reset();
|
||||||
|
dynHeader = null;
|
||||||
|
litlenTree = null;
|
||||||
|
distTree = null;
|
||||||
|
isLastBlock = false;
|
||||||
|
adler?.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes a zlib/RFC1950 header.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// False if more input is needed.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// The header is invalid.
|
||||||
|
/// </exception>
|
||||||
|
private bool DecodeHeader()
|
||||||
|
{
|
||||||
|
int header = input.PeekBits(16);
|
||||||
|
if (header < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.DropBits(16);
|
||||||
|
|
||||||
|
// The header is written in "wrong" byte order
|
||||||
|
header = ((header << 8) | (header >> 8)) & 0xffff;
|
||||||
|
if (header % 31 != 0)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Header checksum illegal");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((header & 0x0f00) != (Deflater.DEFLATED << 8))
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Compression Method unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Maximum size of the backwards window in bits.
|
||||||
|
* We currently ignore this, but we could use it to make the
|
||||||
|
* inflater window more space efficient. On the other hand the
|
||||||
|
* full window (15 bits) is needed most times, anyway.
|
||||||
|
int max_wbits = ((header & 0x7000) >> 12) + 8;
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((header & 0x0020) == 0)
|
||||||
|
{ // Dictionary flag?
|
||||||
|
mode = DECODE_BLOCKS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mode = DECODE_DICT;
|
||||||
|
neededBits = 32;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes the dictionary checksum after the deflate header.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// False if more input is needed.
|
||||||
|
/// </returns>
|
||||||
|
private bool DecodeDict()
|
||||||
|
{
|
||||||
|
while (neededBits > 0)
|
||||||
|
{
|
||||||
|
int dictByte = input.PeekBits(8);
|
||||||
|
if (dictByte < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.DropBits(8);
|
||||||
|
readAdler = (readAdler << 8) | dictByte;
|
||||||
|
neededBits -= 8;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes the huffman encoded symbols in the input stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// false if more input is needed, true if output window is
|
||||||
|
/// full or the current block ends.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// if deflated stream is invalid.
|
||||||
|
/// </exception>
|
||||||
|
private bool DecodeHuffman()
|
||||||
|
{
|
||||||
|
int free = outputWindow.GetFreeSpace();
|
||||||
|
while (free >= 258)
|
||||||
|
{
|
||||||
|
int symbol;
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case DECODE_HUFFMAN:
|
||||||
|
// This is the inner loop so it is optimized a bit
|
||||||
|
while (((symbol = litlenTree.GetSymbol(input)) & ~0xff) == 0)
|
||||||
|
{
|
||||||
|
outputWindow.Write(symbol);
|
||||||
|
if (--free < 258)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol < 257)
|
||||||
|
{
|
||||||
|
if (symbol < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// symbol == 256: end of block
|
||||||
|
distTree = null;
|
||||||
|
litlenTree = null;
|
||||||
|
mode = DECODE_BLOCKS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
repLength = CPLENS[symbol - 257];
|
||||||
|
neededBits = CPLEXT[symbol - 257];
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Illegal rep length code");
|
||||||
|
}
|
||||||
|
goto case DECODE_HUFFMAN_LENBITS; // fall through
|
||||||
|
|
||||||
|
case DECODE_HUFFMAN_LENBITS:
|
||||||
|
if (neededBits > 0)
|
||||||
|
{
|
||||||
|
mode = DECODE_HUFFMAN_LENBITS;
|
||||||
|
int i = input.PeekBits(neededBits);
|
||||||
|
if (i < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.DropBits(neededBits);
|
||||||
|
repLength += i;
|
||||||
|
}
|
||||||
|
mode = DECODE_HUFFMAN_DIST;
|
||||||
|
goto case DECODE_HUFFMAN_DIST; // fall through
|
||||||
|
|
||||||
|
case DECODE_HUFFMAN_DIST:
|
||||||
|
symbol = distTree.GetSymbol(input);
|
||||||
|
if (symbol < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
repDist = CPDIST[symbol];
|
||||||
|
neededBits = CPDEXT[symbol];
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Illegal rep dist code");
|
||||||
|
}
|
||||||
|
|
||||||
|
goto case DECODE_HUFFMAN_DISTBITS; // fall through
|
||||||
|
|
||||||
|
case DECODE_HUFFMAN_DISTBITS:
|
||||||
|
if (neededBits > 0)
|
||||||
|
{
|
||||||
|
mode = DECODE_HUFFMAN_DISTBITS;
|
||||||
|
int i = input.PeekBits(neededBits);
|
||||||
|
if (i < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.DropBits(neededBits);
|
||||||
|
repDist += i;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputWindow.Repeat(repLength, repDist);
|
||||||
|
free -= repLength;
|
||||||
|
mode = DECODE_HUFFMAN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new SharpZipBaseException("Inflater unknown mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes the adler checksum after the deflate stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// false if more input is needed.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// If checksum doesn't match.
|
||||||
|
/// </exception>
|
||||||
|
private bool DecodeChksum()
|
||||||
|
{
|
||||||
|
while (neededBits > 0)
|
||||||
|
{
|
||||||
|
int chkByte = input.PeekBits(8);
|
||||||
|
if (chkByte < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.DropBits(8);
|
||||||
|
readAdler = (readAdler << 8) | chkByte;
|
||||||
|
neededBits -= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int)adler?.Value != readAdler)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Adler chksum doesn't match: " + (int)adler?.Value + " vs. " + readAdler);
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = FINISHED;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes the deflated stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// false if more input is needed, or if finished.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// if deflated stream is invalid.
|
||||||
|
/// </exception>
|
||||||
|
private bool Decode()
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case DECODE_HEADER:
|
||||||
|
return DecodeHeader();
|
||||||
|
|
||||||
|
case DECODE_DICT:
|
||||||
|
return DecodeDict();
|
||||||
|
|
||||||
|
case DECODE_CHKSUM:
|
||||||
|
return DecodeChksum();
|
||||||
|
|
||||||
|
case DECODE_BLOCKS:
|
||||||
|
if (isLastBlock)
|
||||||
|
{
|
||||||
|
if (noHeader)
|
||||||
|
{
|
||||||
|
mode = FINISHED;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
input.SkipToByteBoundary();
|
||||||
|
neededBits = 32;
|
||||||
|
mode = DECODE_CHKSUM;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int type = input.PeekBits(3);
|
||||||
|
if (type < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.DropBits(3);
|
||||||
|
|
||||||
|
isLastBlock |= (type & 1) != 0;
|
||||||
|
switch (type >> 1)
|
||||||
|
{
|
||||||
|
case DeflaterConstants.STORED_BLOCK:
|
||||||
|
input.SkipToByteBoundary();
|
||||||
|
mode = DECODE_STORED_LEN1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DeflaterConstants.STATIC_TREES:
|
||||||
|
litlenTree = InflaterHuffmanTree.defLitLenTree;
|
||||||
|
distTree = InflaterHuffmanTree.defDistTree;
|
||||||
|
mode = DECODE_HUFFMAN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DeflaterConstants.DYN_TREES:
|
||||||
|
dynHeader = new InflaterDynHeader(input);
|
||||||
|
mode = DECODE_DYN_HEADER;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new SharpZipBaseException("Unknown block type " + type);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case DECODE_STORED_LEN1:
|
||||||
|
{
|
||||||
|
if ((uncomprLen = input.PeekBits(16)) < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.DropBits(16);
|
||||||
|
mode = DECODE_STORED_LEN2;
|
||||||
|
}
|
||||||
|
goto case DECODE_STORED_LEN2; // fall through
|
||||||
|
|
||||||
|
case DECODE_STORED_LEN2:
|
||||||
|
{
|
||||||
|
int nlen = input.PeekBits(16);
|
||||||
|
if (nlen < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.DropBits(16);
|
||||||
|
if (nlen != (uncomprLen ^ 0xffff))
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("broken uncompressed block");
|
||||||
|
}
|
||||||
|
mode = DECODE_STORED;
|
||||||
|
}
|
||||||
|
goto case DECODE_STORED; // fall through
|
||||||
|
|
||||||
|
case DECODE_STORED:
|
||||||
|
{
|
||||||
|
int more = outputWindow.CopyStored(input, uncomprLen);
|
||||||
|
uncomprLen -= more;
|
||||||
|
if (uncomprLen == 0)
|
||||||
|
{
|
||||||
|
mode = DECODE_BLOCKS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !input.IsNeedingInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DECODE_DYN_HEADER:
|
||||||
|
if (!dynHeader.AttemptRead())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
litlenTree = dynHeader.LiteralLengthTree;
|
||||||
|
distTree = dynHeader.DistanceTree;
|
||||||
|
mode = DECODE_HUFFMAN;
|
||||||
|
goto case DECODE_HUFFMAN; // fall through
|
||||||
|
|
||||||
|
case DECODE_HUFFMAN:
|
||||||
|
case DECODE_HUFFMAN_LENBITS:
|
||||||
|
case DECODE_HUFFMAN_DIST:
|
||||||
|
case DECODE_HUFFMAN_DISTBITS:
|
||||||
|
return DecodeHuffman();
|
||||||
|
|
||||||
|
case FINISHED:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new SharpZipBaseException("Inflater.Decode unknown mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the preset dictionary. This should only be called, if
|
||||||
|
/// needsDictionary() returns true and it should set the same
|
||||||
|
/// dictionary, that was used for deflating. The getAdler()
|
||||||
|
/// function returns the checksum of the dictionary needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// The dictionary.
|
||||||
|
/// </param>
|
||||||
|
public void SetDictionary(byte[] buffer)
|
||||||
|
{
|
||||||
|
SetDictionary(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the preset dictionary. This should only be called, if
|
||||||
|
/// needsDictionary() returns true and it should set the same
|
||||||
|
/// dictionary, that was used for deflating. The getAdler()
|
||||||
|
/// function returns the checksum of the dictionary needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// The dictionary.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="index">
|
||||||
|
/// The index into buffer where the dictionary starts.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// The number of bytes in the dictionary.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// No dictionary is needed.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// The adler checksum for the buffer is invalid
|
||||||
|
/// </exception>
|
||||||
|
public void SetDictionary(byte[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsNeedingDictionary)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Dictionary is not needed");
|
||||||
|
}
|
||||||
|
|
||||||
|
adler?.Update(new ArraySegment<byte>(buffer, index, count));
|
||||||
|
|
||||||
|
if (adler != null && (int)adler.Value != readAdler)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Wrong adler checksum");
|
||||||
|
}
|
||||||
|
adler?.Reset();
|
||||||
|
outputWindow.CopyDict(buffer, index, count);
|
||||||
|
mode = DECODE_BLOCKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the input. This should only be called, if needsInput()
|
||||||
|
/// returns true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// the input.
|
||||||
|
/// </param>
|
||||||
|
public void SetInput(byte[] buffer)
|
||||||
|
{
|
||||||
|
SetInput(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the input. This should only be called, if needsInput()
|
||||||
|
/// returns true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// The source of input data
|
||||||
|
/// </param>
|
||||||
|
/// <param name="index">
|
||||||
|
/// The index into buffer where the input starts.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// The number of bytes of input to use.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// No input is needed.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
|
/// The index and/or count are wrong.
|
||||||
|
/// </exception>
|
||||||
|
public void SetInput(byte[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
input.SetInput(buffer, index, count);
|
||||||
|
totalIn += (long)count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inflates the compressed stream to the output buffer. If this
|
||||||
|
/// returns 0, you should check, whether IsNeedingDictionary(),
|
||||||
|
/// IsNeedingInput() or IsFinished() returns true, to determine why no
|
||||||
|
/// further output is produced.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// the output buffer.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of bytes written to the buffer, 0 if no further
|
||||||
|
/// output can be produced.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
|
/// if buffer has length 0.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.FormatException">
|
||||||
|
/// if deflated stream is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public int Inflate(byte[] buffer)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Inflate(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inflates the compressed stream to the output buffer. If this
|
||||||
|
/// returns 0, you should check, whether needsDictionary(),
|
||||||
|
/// needsInput() or finished() returns true, to determine why no
|
||||||
|
/// further output is produced.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// the output buffer.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// the offset in buffer where storing starts.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// the maximum number of bytes to output.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// the number of bytes written to the buffer, 0 if no further output can be produced.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
|
/// if count is less than 0.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
|
/// if the index and / or count are wrong.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.FormatException">
|
||||||
|
/// if deflated stream is invalid.
|
||||||
|
/// </exception>
|
||||||
|
public int Inflate(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + count > buffer.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("count exceeds buffer bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: count may be zero
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
if (!IsFinished)
|
||||||
|
{ // -jr- 08-Nov-2003 INFLATE_BUG fix..
|
||||||
|
Decode();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytesCopied = 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (mode != DECODE_CHKSUM)
|
||||||
|
{
|
||||||
|
/* Don't give away any output, if we are waiting for the
|
||||||
|
* checksum in the input stream.
|
||||||
|
*
|
||||||
|
* With this trick we have always:
|
||||||
|
* IsNeedingInput() and not IsFinished()
|
||||||
|
* implies more output can be produced.
|
||||||
|
*/
|
||||||
|
int more = outputWindow.CopyOutput(buffer, offset, count);
|
||||||
|
if (more > 0)
|
||||||
|
{
|
||||||
|
adler?.Update(new ArraySegment<byte>(buffer, offset, more));
|
||||||
|
offset += more;
|
||||||
|
bytesCopied += more;
|
||||||
|
totalOut += (long)more;
|
||||||
|
count -= more;
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return bytesCopied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (Decode() || ((outputWindow.GetAvailable() > 0) && (mode != DECODE_CHKSUM)));
|
||||||
|
return bytesCopied;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true, if the input buffer is empty.
|
||||||
|
/// You should then call setInput().
|
||||||
|
/// NOTE: This method also returns true when the stream is finished.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNeedingInput
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return input.IsNeedingInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true, if a preset dictionary is needed to inflate the input.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNeedingDictionary
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mode == DECODE_DICT && neededBits == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true, if the inflater has finished. This means, that no
|
||||||
|
/// input is needed and no output can be produced.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFinished
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mode == FINISHED && outputWindow.GetAvailable() == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the adler checksum. This is either the checksum of all
|
||||||
|
/// uncompressed bytes returned by inflate(), or if needsDictionary()
|
||||||
|
/// returns true (and thus no output was yet produced) this is the
|
||||||
|
/// adler checksum of the expected dictionary.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// the adler checksum.
|
||||||
|
/// </returns>
|
||||||
|
public int Adler
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsNeedingDictionary)
|
||||||
|
{
|
||||||
|
return readAdler;
|
||||||
|
}
|
||||||
|
else if (adler != null)
|
||||||
|
{
|
||||||
|
return (int)adler.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the total number of output bytes returned by Inflate().
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// the total number of output bytes.
|
||||||
|
/// </returns>
|
||||||
|
public long TotalOut
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return totalOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the total number of processed compressed input bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The total number of bytes of processed input bytes.
|
||||||
|
/// </returns>
|
||||||
|
public long TotalIn
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return totalIn - (long)RemainingInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of unprocessed input bytes. Useful, if the end of the
|
||||||
|
/// stream is reached and you want to further process the bytes after
|
||||||
|
/// the deflate stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of bytes of the input which have not been processed.
|
||||||
|
/// </returns>
|
||||||
|
public int RemainingInput
|
||||||
|
{
|
||||||
|
// TODO: This should be a long?
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return input.AvailableBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
internal class InflaterDynHeader
|
||||||
|
{
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
// maximum number of literal/length codes
|
||||||
|
private const int LITLEN_MAX = 286;
|
||||||
|
|
||||||
|
// maximum number of distance codes
|
||||||
|
private const int DIST_MAX = 30;
|
||||||
|
|
||||||
|
// maximum data code lengths to read
|
||||||
|
private const int CODELEN_MAX = LITLEN_MAX + DIST_MAX;
|
||||||
|
|
||||||
|
// maximum meta code length codes to read
|
||||||
|
private const int META_MAX = 19;
|
||||||
|
|
||||||
|
private static readonly int[] MetaCodeLengthIndex =
|
||||||
|
{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
|
||||||
|
|
||||||
|
#endregion Constants
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Continue decoding header from <see cref="input"/> until more bits are needed or decoding has been completed
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns whether decoding could be completed</returns>
|
||||||
|
public bool AttemptRead()
|
||||||
|
=> !state.MoveNext() || state.Current;
|
||||||
|
|
||||||
|
public InflaterDynHeader(StreamManipulator input)
|
||||||
|
{
|
||||||
|
this.input = input;
|
||||||
|
stateMachine = CreateStateMachine();
|
||||||
|
state = stateMachine.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<bool> CreateStateMachine()
|
||||||
|
{
|
||||||
|
// Read initial code length counts from header
|
||||||
|
while (!input.TryGetBits(5, ref litLenCodeCount, 257)) yield return false;
|
||||||
|
while (!input.TryGetBits(5, ref distanceCodeCount, 1)) yield return false;
|
||||||
|
while (!input.TryGetBits(4, ref metaCodeCount, 4)) yield return false;
|
||||||
|
var dataCodeCount = litLenCodeCount + distanceCodeCount;
|
||||||
|
|
||||||
|
if (litLenCodeCount > LITLEN_MAX) throw new ValueOutOfRangeException(nameof(litLenCodeCount));
|
||||||
|
if (distanceCodeCount > DIST_MAX) throw new ValueOutOfRangeException(nameof(distanceCodeCount));
|
||||||
|
if (metaCodeCount > META_MAX) throw new ValueOutOfRangeException(nameof(metaCodeCount));
|
||||||
|
|
||||||
|
// Load code lengths for the meta tree from the header bits
|
||||||
|
for (int i = 0; i < metaCodeCount; i++)
|
||||||
|
{
|
||||||
|
while (!input.TryGetBits(3, ref codeLengths, MetaCodeLengthIndex[i])) yield return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var metaCodeTree = new InflaterHuffmanTree(codeLengths);
|
||||||
|
|
||||||
|
// Decompress the meta tree symbols into the data table code lengths
|
||||||
|
int index = 0;
|
||||||
|
while (index < dataCodeCount)
|
||||||
|
{
|
||||||
|
byte codeLength;
|
||||||
|
int symbol;
|
||||||
|
|
||||||
|
while ((symbol = metaCodeTree.GetSymbol(input)) < 0) yield return false;
|
||||||
|
|
||||||
|
if (symbol < 16)
|
||||||
|
{
|
||||||
|
// append literal code length
|
||||||
|
codeLengths[index++] = (byte)symbol;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int repeatCount = 0;
|
||||||
|
|
||||||
|
if (symbol == 16) // Repeat last code length 3..6 times
|
||||||
|
{
|
||||||
|
if (index == 0)
|
||||||
|
throw new StreamDecodingException("Cannot repeat previous code length when no other code length has been read");
|
||||||
|
|
||||||
|
codeLength = codeLengths[index - 1];
|
||||||
|
|
||||||
|
// 2 bits + 3, [3..6]
|
||||||
|
while (!input.TryGetBits(2, ref repeatCount, 3)) yield return false;
|
||||||
|
}
|
||||||
|
else if (symbol == 17) // Repeat zero 3..10 times
|
||||||
|
{
|
||||||
|
codeLength = 0;
|
||||||
|
|
||||||
|
// 3 bits + 3, [3..10]
|
||||||
|
while (!input.TryGetBits(3, ref repeatCount, 3)) yield return false;
|
||||||
|
}
|
||||||
|
else // (symbol == 18), Repeat zero 11..138 times
|
||||||
|
{
|
||||||
|
codeLength = 0;
|
||||||
|
|
||||||
|
// 7 bits + 11, [11..138]
|
||||||
|
while (!input.TryGetBits(7, ref repeatCount, 11)) yield return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index + repeatCount > dataCodeCount)
|
||||||
|
throw new StreamDecodingException("Cannot repeat code lengths past total number of data code lengths");
|
||||||
|
|
||||||
|
while (repeatCount-- > 0)
|
||||||
|
codeLengths[index++] = codeLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeLengths[256] == 0)
|
||||||
|
throw new StreamDecodingException("Inflater dynamic header end-of-block code missing");
|
||||||
|
|
||||||
|
litLenTree = new InflaterHuffmanTree(new ArraySegment<byte>(codeLengths, 0, litLenCodeCount));
|
||||||
|
distTree = new InflaterHuffmanTree(new ArraySegment<byte>(codeLengths, litLenCodeCount, distanceCodeCount));
|
||||||
|
|
||||||
|
yield return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get literal/length huffman tree, must not be used before <see cref="AttemptRead"/> has returned true
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="StreamDecodingException">If hader has not been successfully read by the state machine</exception>
|
||||||
|
public InflaterHuffmanTree LiteralLengthTree
|
||||||
|
=> litLenTree ?? throw new StreamDecodingException("Header properties were accessed before header had been successfully read");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get distance huffman tree, must not be used before <see cref="AttemptRead"/> has returned true
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="StreamDecodingException">If hader has not been successfully read by the state machine</exception>
|
||||||
|
public InflaterHuffmanTree DistanceTree
|
||||||
|
=> distTree ?? throw new StreamDecodingException("Header properties were accessed before header had been successfully read");
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private readonly StreamManipulator input;
|
||||||
|
private readonly IEnumerator<bool> state;
|
||||||
|
private readonly IEnumerable<bool> stateMachine;
|
||||||
|
|
||||||
|
private byte[] codeLengths = new byte[CODELEN_MAX];
|
||||||
|
|
||||||
|
private InflaterHuffmanTree litLenTree;
|
||||||
|
private InflaterHuffmanTree distTree;
|
||||||
|
|
||||||
|
private int litLenCodeCount, distanceCodeCount, metaCodeCount;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Huffman tree used for inflation
|
||||||
|
/// </summary>
|
||||||
|
public class InflaterHuffmanTree
|
||||||
|
{
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
private const int MAX_BITLEN = 15;
|
||||||
|
|
||||||
|
#endregion Constants
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private short[] tree;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Literal length tree
|
||||||
|
/// </summary>
|
||||||
|
public static InflaterHuffmanTree defLitLenTree;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Distance tree
|
||||||
|
/// </summary>
|
||||||
|
public static InflaterHuffmanTree defDistTree;
|
||||||
|
|
||||||
|
static InflaterHuffmanTree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] codeLengths = new byte[288];
|
||||||
|
int i = 0;
|
||||||
|
while (i < 144)
|
||||||
|
{
|
||||||
|
codeLengths[i++] = 8;
|
||||||
|
}
|
||||||
|
while (i < 256)
|
||||||
|
{
|
||||||
|
codeLengths[i++] = 9;
|
||||||
|
}
|
||||||
|
while (i < 280)
|
||||||
|
{
|
||||||
|
codeLengths[i++] = 7;
|
||||||
|
}
|
||||||
|
while (i < 288)
|
||||||
|
{
|
||||||
|
codeLengths[i++] = 8;
|
||||||
|
}
|
||||||
|
defLitLenTree = new InflaterHuffmanTree(codeLengths);
|
||||||
|
|
||||||
|
codeLengths = new byte[32];
|
||||||
|
i = 0;
|
||||||
|
while (i < 32)
|
||||||
|
{
|
||||||
|
codeLengths[i++] = 5;
|
||||||
|
}
|
||||||
|
defDistTree = new InflaterHuffmanTree(codeLengths);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("InflaterHuffmanTree: static tree length illegal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a Huffman tree from the array of code lengths.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "codeLengths">
|
||||||
|
/// the array of code lengths
|
||||||
|
/// </param>
|
||||||
|
public InflaterHuffmanTree(IList<byte> codeLengths)
|
||||||
|
{
|
||||||
|
BuildTree(codeLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
private void BuildTree(IList<byte> codeLengths)
|
||||||
|
{
|
||||||
|
int[] blCount = new int[MAX_BITLEN + 1];
|
||||||
|
int[] nextCode = new int[MAX_BITLEN + 1];
|
||||||
|
|
||||||
|
for (int i = 0; i < codeLengths.Count; i++)
|
||||||
|
{
|
||||||
|
int bits = codeLengths[i];
|
||||||
|
if (bits > 0)
|
||||||
|
{
|
||||||
|
blCount[bits]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int code = 0;
|
||||||
|
int treeSize = 512;
|
||||||
|
for (int bits = 1; bits <= MAX_BITLEN; bits++)
|
||||||
|
{
|
||||||
|
nextCode[bits] = code;
|
||||||
|
code += blCount[bits] << (16 - bits);
|
||||||
|
if (bits >= 10)
|
||||||
|
{
|
||||||
|
/* We need an extra table for bit lengths >= 10. */
|
||||||
|
int start = nextCode[bits] & 0x1ff80;
|
||||||
|
int end = code & 0x1ff80;
|
||||||
|
treeSize += (end - start) >> (16 - bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -jr comment this out! doesnt work for dynamic trees and pkzip 2.04g
|
||||||
|
if (code != 65536)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Code lengths don't add up properly.");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/* Now create and fill the extra tables from longest to shortest
|
||||||
|
* bit len. This way the sub trees will be aligned.
|
||||||
|
*/
|
||||||
|
tree = new short[treeSize];
|
||||||
|
int treePtr = 512;
|
||||||
|
for (int bits = MAX_BITLEN; bits >= 10; bits--)
|
||||||
|
{
|
||||||
|
int end = code & 0x1ff80;
|
||||||
|
code -= blCount[bits] << (16 - bits);
|
||||||
|
int start = code & 0x1ff80;
|
||||||
|
for (int i = start; i < end; i += 1 << 7)
|
||||||
|
{
|
||||||
|
tree[DeflaterHuffman.BitReverse(i)] = (short)((-treePtr << 4) | bits);
|
||||||
|
treePtr += 1 << (bits - 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < codeLengths.Count; i++)
|
||||||
|
{
|
||||||
|
int bits = codeLengths[i];
|
||||||
|
if (bits == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
code = nextCode[bits];
|
||||||
|
int revcode = DeflaterHuffman.BitReverse(code);
|
||||||
|
if (bits <= 9)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
tree[revcode] = (short)((i << 4) | bits);
|
||||||
|
revcode += 1 << bits;
|
||||||
|
} while (revcode < 512);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int subTree = tree[revcode & 511];
|
||||||
|
int treeLen = 1 << (subTree & 15);
|
||||||
|
subTree = -(subTree >> 4);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
tree[subTree | (revcode >> 9)] = (short)((i << 4) | bits);
|
||||||
|
revcode += 1 << bits;
|
||||||
|
} while (revcode < treeLen);
|
||||||
|
}
|
||||||
|
nextCode[bits] = code + (1 << (16 - bits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the next symbol from input. The symbol is encoded using the
|
||||||
|
/// huffman tree.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">
|
||||||
|
/// input the input source.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// the next symbol, or -1 if not enough input is available.
|
||||||
|
/// </returns>
|
||||||
|
public int GetSymbol(StreamManipulator input)
|
||||||
|
{
|
||||||
|
int lookahead, symbol;
|
||||||
|
if ((lookahead = input.PeekBits(9)) >= 0)
|
||||||
|
{
|
||||||
|
symbol = tree[lookahead];
|
||||||
|
int bitlen = symbol & 15;
|
||||||
|
|
||||||
|
if (symbol >= 0)
|
||||||
|
{
|
||||||
|
if(bitlen == 0){
|
||||||
|
throw new SharpZipBaseException("Encountered invalid codelength 0");
|
||||||
|
}
|
||||||
|
input.DropBits(bitlen);
|
||||||
|
return symbol >> 4;
|
||||||
|
}
|
||||||
|
int subtree = -(symbol >> 4);
|
||||||
|
if ((lookahead = input.PeekBits(bitlen)) >= 0)
|
||||||
|
{
|
||||||
|
symbol = tree[subtree | (lookahead >> 9)];
|
||||||
|
input.DropBits(symbol & 15);
|
||||||
|
return symbol >> 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int bits = input.AvailableBits;
|
||||||
|
lookahead = input.PeekBits(bits);
|
||||||
|
symbol = tree[subtree | (lookahead >> 9)];
|
||||||
|
if ((symbol & 15) <= bits)
|
||||||
|
{
|
||||||
|
input.DropBits(symbol & 15);
|
||||||
|
return symbol >> 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Less than 9 bits
|
||||||
|
{
|
||||||
|
int bits = input.AvailableBits;
|
||||||
|
lookahead = input.PeekBits(bits);
|
||||||
|
symbol = tree[lookahead];
|
||||||
|
if (symbol >= 0 && (symbol & 15) <= bits)
|
||||||
|
{
|
||||||
|
input.DropBits(symbol & 15);
|
||||||
|
return symbol >> 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is general purpose class for writing data to a buffer.
|
||||||
|
///
|
||||||
|
/// It allows you to write bits as well as bytes
|
||||||
|
/// Based on DeflaterPending.java
|
||||||
|
///
|
||||||
|
/// author of the original java version : Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
public class PendingBuffer
|
||||||
|
{
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal work buffer
|
||||||
|
/// </summary>
|
||||||
|
private readonly byte[] buffer;
|
||||||
|
|
||||||
|
private int start;
|
||||||
|
private int end;
|
||||||
|
|
||||||
|
private uint bits;
|
||||||
|
private int bitCount;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// construct instance using default buffer size of 4096
|
||||||
|
/// </summary>
|
||||||
|
public PendingBuffer() : this(4096)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// construct instance using specified buffer size
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bufferSize">
|
||||||
|
/// size to use for internal buffer
|
||||||
|
/// </param>
|
||||||
|
public PendingBuffer(int bufferSize)
|
||||||
|
{
|
||||||
|
buffer = new byte[bufferSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear internal state/buffers
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
start = end = bitCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a byte to buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">
|
||||||
|
/// The value to write
|
||||||
|
/// </param>
|
||||||
|
public void WriteByte(int value)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (start != 0) )
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Debug check: start != 0");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
buffer[end++] = unchecked((byte)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a short value to buffer LSB first
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">
|
||||||
|
/// The value to write.
|
||||||
|
/// </param>
|
||||||
|
public void WriteShort(int value)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (start != 0) )
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Debug check: start != 0");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
buffer[end++] = unchecked((byte)value);
|
||||||
|
buffer[end++] = unchecked((byte)(value >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// write an integer LSB first
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void WriteInt(int value)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (start != 0) )
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Debug check: start != 0");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
buffer[end++] = unchecked((byte)value);
|
||||||
|
buffer[end++] = unchecked((byte)(value >> 8));
|
||||||
|
buffer[end++] = unchecked((byte)(value >> 16));
|
||||||
|
buffer[end++] = unchecked((byte)(value >> 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a block of data to buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="block">data to write</param>
|
||||||
|
/// <param name="offset">offset of first byte to write</param>
|
||||||
|
/// <param name="length">number of bytes to write</param>
|
||||||
|
public void WriteBlock(byte[] block, int offset, int length)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (start != 0) )
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Debug check: start != 0");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
System.Array.Copy(block, offset, buffer, end, length);
|
||||||
|
end += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of bits written to the buffer
|
||||||
|
/// </summary>
|
||||||
|
public int BitCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return bitCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Align internal buffer on a byte boundary
|
||||||
|
/// </summary>
|
||||||
|
public void AlignToByte()
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (start != 0) )
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Debug check: start != 0");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (bitCount > 0)
|
||||||
|
{
|
||||||
|
buffer[end++] = unchecked((byte)bits);
|
||||||
|
if (bitCount > 8)
|
||||||
|
{
|
||||||
|
buffer[end++] = unchecked((byte)(bits >> 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bits = 0;
|
||||||
|
bitCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write bits to internal buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="b">source of bits</param>
|
||||||
|
/// <param name="count">number of bits to write</param>
|
||||||
|
public void WriteBits(int b, int count)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (start != 0) )
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Debug check: start != 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (DeflaterConstants.DEBUGGING) {
|
||||||
|
// //Console.WriteLine("writeBits("+b+","+count+")");
|
||||||
|
// }
|
||||||
|
#endif
|
||||||
|
bits |= (uint)(b << bitCount);
|
||||||
|
bitCount += count;
|
||||||
|
if (bitCount >= 16)
|
||||||
|
{
|
||||||
|
buffer[end++] = unchecked((byte)bits);
|
||||||
|
buffer[end++] = unchecked((byte)(bits >> 8));
|
||||||
|
bits >>= 16;
|
||||||
|
bitCount -= 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a short value to internal buffer most significant byte first
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="s">value to write</param>
|
||||||
|
public void WriteShortMSB(int s)
|
||||||
|
{
|
||||||
|
#if DebugDeflation
|
||||||
|
if (DeflaterConstants.DEBUGGING && (start != 0) )
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Debug check: start != 0");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
buffer[end++] = unchecked((byte)(s >> 8));
|
||||||
|
buffer[end++] = unchecked((byte)s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if buffer has been flushed
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFlushed
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return end == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes the pending buffer into the given output array. If the
|
||||||
|
/// output array is to small, only a partial flush is done.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="output">The output array.</param>
|
||||||
|
/// <param name="offset">The offset into output array.</param>
|
||||||
|
/// <param name="length">The maximum number of bytes to store.</param>
|
||||||
|
/// <returns>The number of bytes flushed.</returns>
|
||||||
|
public int Flush(byte[] output, int offset, int length)
|
||||||
|
{
|
||||||
|
if (bitCount >= 8)
|
||||||
|
{
|
||||||
|
buffer[end++] = unchecked((byte)bits);
|
||||||
|
bits >>= 8;
|
||||||
|
bitCount -= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > end - start)
|
||||||
|
{
|
||||||
|
length = end - start;
|
||||||
|
System.Array.Copy(buffer, start, output, offset, length);
|
||||||
|
start = 0;
|
||||||
|
end = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.Array.Copy(buffer, start, output, offset, length);
|
||||||
|
start += length;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert internal buffer to byte array.
|
||||||
|
/// Buffer is empty on completion
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The internal buffer contents converted to a byte array.
|
||||||
|
/// </returns>
|
||||||
|
public byte[] ToByteArray()
|
||||||
|
{
|
||||||
|
AlignToByte();
|
||||||
|
|
||||||
|
byte[] result = new byte[end - start];
|
||||||
|
System.Array.Copy(buffer, start, result, 0, result.Length);
|
||||||
|
start = 0;
|
||||||
|
end = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,490 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Encryption;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A special stream deflating or compressing the bytes that are
|
||||||
|
/// written to it. It uses a Deflater to perform actual deflating.<br/>
|
||||||
|
/// Authors of the original java version : Tom Tromey, Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
public class DeflaterOutputStream : Stream
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new DeflaterOutputStream with a default Deflater and default buffer size.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseOutputStream">
|
||||||
|
/// the output stream where deflated output should be written.
|
||||||
|
/// </param>
|
||||||
|
public DeflaterOutputStream(Stream baseOutputStream)
|
||||||
|
: this(baseOutputStream, new Deflater(), 512)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new DeflaterOutputStream with the given Deflater and
|
||||||
|
/// default buffer size.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseOutputStream">
|
||||||
|
/// the output stream where deflated output should be written.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="deflater">
|
||||||
|
/// the underlying deflater.
|
||||||
|
/// </param>
|
||||||
|
public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater)
|
||||||
|
: this(baseOutputStream, deflater, 512)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new DeflaterOutputStream with the given Deflater and
|
||||||
|
/// buffer size.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseOutputStream">
|
||||||
|
/// The output stream where deflated output is written.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="deflater">
|
||||||
|
/// The underlying deflater to use
|
||||||
|
/// </param>
|
||||||
|
/// <param name="bufferSize">
|
||||||
|
/// The buffer size in bytes to use when deflating (minimum value 512)
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// bufsize is less than or equal to zero.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// baseOutputStream does not support writing
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// deflater instance is null
|
||||||
|
/// </exception>
|
||||||
|
public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufferSize)
|
||||||
|
{
|
||||||
|
if (baseOutputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(baseOutputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseOutputStream.CanWrite == false)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Must support writing", nameof(baseOutputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferSize < 512)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
baseOutputStream_ = baseOutputStream;
|
||||||
|
buffer_ = new byte[bufferSize];
|
||||||
|
deflater_ = deflater ?? throw new ArgumentNullException(nameof(deflater));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Public API
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finishes the stream by calling finish() on the deflater.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// Not all input is deflated
|
||||||
|
/// </exception>
|
||||||
|
public virtual void Finish()
|
||||||
|
{
|
||||||
|
deflater_.Finish();
|
||||||
|
while (!deflater_.IsFinished)
|
||||||
|
{
|
||||||
|
int len = deflater_.Deflate(buffer_, 0, buffer_.Length);
|
||||||
|
if (len <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cryptoTransform_ != null)
|
||||||
|
{
|
||||||
|
EncryptBlock(buffer_, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
baseOutputStream_.Write(buffer_, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deflater_.IsFinished)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Can't deflate all input?");
|
||||||
|
}
|
||||||
|
|
||||||
|
baseOutputStream_.Flush();
|
||||||
|
|
||||||
|
if (cryptoTransform_ != null)
|
||||||
|
{
|
||||||
|
if (cryptoTransform_ is ZipAESTransform)
|
||||||
|
{
|
||||||
|
AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
|
||||||
|
}
|
||||||
|
cryptoTransform_.Dispose();
|
||||||
|
cryptoTransform_ = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating ownership of underlying stream.
|
||||||
|
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is true.</remarks>
|
||||||
|
public bool IsStreamOwner { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows client to determine if an entry can be patched after its added
|
||||||
|
/// </summary>
|
||||||
|
public bool CanPatchEntries
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return baseOutputStream_.CanSeek;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public API
|
||||||
|
|
||||||
|
#region Encryption
|
||||||
|
|
||||||
|
private string password;
|
||||||
|
|
||||||
|
private ICryptoTransform cryptoTransform_;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream.
|
||||||
|
/// </summary>
|
||||||
|
protected byte[] AESAuthCode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the password used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>When set to null or if the password is empty no encryption is performed</remarks>
|
||||||
|
public string Password
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if ((value != null) && (value.Length == 0))
|
||||||
|
{
|
||||||
|
password = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
password = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encrypt a block of data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// Data to encrypt. NOTE the original contents of the buffer are lost
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// Offset of first byte in buffer to encrypt
|
||||||
|
/// </param>
|
||||||
|
/// <param name="length">
|
||||||
|
/// Number of bytes in buffer to encrypt
|
||||||
|
/// </param>
|
||||||
|
protected void EncryptBlock(byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes encryption keys based on given <paramref name="password"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="password">The password.</param>
|
||||||
|
protected void InitializePassword(string password)
|
||||||
|
{
|
||||||
|
var pkManaged = new PkzipClassicManaged();
|
||||||
|
byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));
|
||||||
|
cryptoTransform_ = pkManaged.CreateEncryptor(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes encryption keys based on given password.
|
||||||
|
/// </summary>
|
||||||
|
protected void InitializeAESPassword(ZipEntry entry, string rawPassword,
|
||||||
|
out byte[] salt, out byte[] pwdVerifier)
|
||||||
|
{
|
||||||
|
salt = new byte[entry.AESSaltLen];
|
||||||
|
// Salt needs to be cryptographically random, and unique per file
|
||||||
|
if (_aesRnd == null)
|
||||||
|
_aesRnd = RandomNumberGenerator.Create();
|
||||||
|
_aesRnd.GetBytes(salt);
|
||||||
|
int blockSize = entry.AESKeySize / 8; // bits to bytes
|
||||||
|
|
||||||
|
cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true);
|
||||||
|
pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Encryption
|
||||||
|
|
||||||
|
#region Deflation Support
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deflates everything in the input buffers. This will call
|
||||||
|
/// <code>def.deflate()</code> until all bytes from the input buffers
|
||||||
|
/// are processed.
|
||||||
|
/// </summary>
|
||||||
|
protected void Deflate()
|
||||||
|
{
|
||||||
|
while (!deflater_.IsNeedingInput)
|
||||||
|
{
|
||||||
|
int deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length);
|
||||||
|
|
||||||
|
if (deflateCount <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cryptoTransform_ != null)
|
||||||
|
{
|
||||||
|
EncryptBlock(buffer_, 0, deflateCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
baseOutputStream_.Write(buffer_, 0, deflateCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deflater_.IsNeedingInput)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("DeflaterOutputStream can't deflate all input?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Deflation Support
|
||||||
|
|
||||||
|
#region Stream Overrides
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets value indicating stream can be read from
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating if seeking is supported for this stream
|
||||||
|
/// This property always returns false
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get value indicating if this stream supports writing
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return baseOutputStream_.CanWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get current length of stream
|
||||||
|
/// </summary>
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return baseOutputStream_.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current position within the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="NotSupportedException">Any attempt to set position</exception>
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return baseOutputStream_.Position;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Position property not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the current position of this stream to the given value. Not supported by this class!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset relative to the <paramref name="origin"/> to seek.</param>
|
||||||
|
/// <param name="origin">The <see cref="SeekOrigin"/> to seek from.</param>
|
||||||
|
/// <returns>The new position in the stream.</returns>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("DeflaterOutputStream Seek not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the length of this stream to the given value. Not supported by this class!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The new stream length.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("DeflaterOutputStream SetLength not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a byte from stream advancing position by one
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The byte read cast to an int. THe value is -1 if at the end of the stream.</returns>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override int ReadByte()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("DeflaterOutputStream ReadByte not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a block of bytes from stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer to store read data in.</param>
|
||||||
|
/// <param name="offset">The offset to start storing at.</param>
|
||||||
|
/// <param name="count">The maximum number of bytes to read.</param>
|
||||||
|
/// <returns>The actual number of bytes read. Zero if end of stream is detected.</returns>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("DeflaterOutputStream Read not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes the stream by calling <see cref="DeflaterOutputStream.Flush">Flush</see> on the deflater and then
|
||||||
|
/// on the underlying stream. This ensures that all bytes are flushed.
|
||||||
|
/// </summary>
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
deflater_.Flush();
|
||||||
|
Deflate();
|
||||||
|
baseOutputStream_.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls <see cref="Finish"/> and closes the underlying
|
||||||
|
/// stream when <see cref="IsStreamOwner"></see> is true.
|
||||||
|
/// </summary>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!isClosed_)
|
||||||
|
{
|
||||||
|
isClosed_ = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Finish();
|
||||||
|
if (cryptoTransform_ != null)
|
||||||
|
{
|
||||||
|
GetAuthCodeIfAES();
|
||||||
|
cryptoTransform_.Dispose();
|
||||||
|
cryptoTransform_ = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (IsStreamOwner)
|
||||||
|
{
|
||||||
|
baseOutputStream_.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the Auth code for AES encrypted entries
|
||||||
|
/// </summary>
|
||||||
|
protected void GetAuthCodeIfAES()
|
||||||
|
{
|
||||||
|
if (cryptoTransform_ is ZipAESTransform)
|
||||||
|
{
|
||||||
|
AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a single byte to the compressed output stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">
|
||||||
|
/// The byte value.
|
||||||
|
/// </param>
|
||||||
|
public override void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
byte[] b = new byte[1];
|
||||||
|
b[0] = value;
|
||||||
|
Write(b, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes bytes from an array to the compressed stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">
|
||||||
|
/// The byte array
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// The offset into the byte array where to start.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// The number of bytes to write.
|
||||||
|
/// </param>
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
deflater_.SetInput(buffer, offset, count);
|
||||||
|
Deflate();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Stream Overrides
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This buffer is used temporarily to retrieve the bytes from the
|
||||||
|
/// deflater and write them to the underlying output stream.
|
||||||
|
/// </summary>
|
||||||
|
private byte[] buffer_;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The deflater which is used to deflate the stream.
|
||||||
|
/// </summary>
|
||||||
|
protected Deflater deflater_;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base stream the deflater depends on.
|
||||||
|
/// </summary>
|
||||||
|
protected Stream baseOutputStream_;
|
||||||
|
|
||||||
|
private bool isClosed_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
#region Static Fields
|
||||||
|
|
||||||
|
// Static to help ensure that multiple files within a zip will get different random salt
|
||||||
|
private static RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create();
|
||||||
|
|
||||||
|
#endregion Static Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,713 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An input buffer customised for use by <see cref="InflaterInputStream"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The buffer supports decryption of incoming data.
|
||||||
|
/// </remarks>
|
||||||
|
public class InflaterInputBuffer
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="InflaterInputBuffer"/> with a default buffer size
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to buffer.</param>
|
||||||
|
public InflaterInputBuffer(Stream stream) : this(stream, 4096)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="InflaterInputBuffer"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to buffer.</param>
|
||||||
|
/// <param name="bufferSize">The size to use for the buffer</param>
|
||||||
|
/// <remarks>A minimum buffer size of 1KB is permitted. Lower sizes are treated as 1KB.</remarks>
|
||||||
|
public InflaterInputBuffer(Stream stream, int bufferSize)
|
||||||
|
{
|
||||||
|
inputStream = stream;
|
||||||
|
if (bufferSize < 1024)
|
||||||
|
{
|
||||||
|
bufferSize = 1024;
|
||||||
|
}
|
||||||
|
rawData = new byte[bufferSize];
|
||||||
|
clearText = rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the length of bytes bytes in the <see cref="RawData"/>
|
||||||
|
/// </summary>
|
||||||
|
public int RawLength
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return rawLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the contents of the raw data buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This may contain encrypted data.</remarks>
|
||||||
|
public byte[] RawData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the number of useable bytes in <see cref="ClearText"/>
|
||||||
|
/// </summary>
|
||||||
|
public int ClearTextLength
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return clearTextLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the contents of the clear text buffer.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] ClearText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return clearText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the number of bytes available
|
||||||
|
/// </summary>
|
||||||
|
public int Available
|
||||||
|
{
|
||||||
|
get { return available; }
|
||||||
|
set { available = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call <see cref="Inflater.SetInput(byte[], int, int)"/> passing the current clear text buffer contents.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inflater">The inflater to set input for.</param>
|
||||||
|
public void SetInflaterInput(Inflater inflater)
|
||||||
|
{
|
||||||
|
if (available > 0)
|
||||||
|
{
|
||||||
|
inflater.SetInput(clearText, clearTextLength - available, available);
|
||||||
|
available = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fill the buffer from the underlying input stream.
|
||||||
|
/// </summary>
|
||||||
|
public void Fill()
|
||||||
|
{
|
||||||
|
rawLength = 0;
|
||||||
|
int toRead = rawData.Length;
|
||||||
|
|
||||||
|
while (toRead > 0)
|
||||||
|
{
|
||||||
|
int count = inputStream.Read(rawData, rawLength, toRead);
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rawLength += count;
|
||||||
|
toRead -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cryptoTransform != null)
|
||||||
|
{
|
||||||
|
clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clearTextLength = rawLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
available = clearTextLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a buffer directly from the input stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer to fill</param>
|
||||||
|
/// <returns>Returns the number of bytes read.</returns>
|
||||||
|
public int ReadRawBuffer(byte[] buffer)
|
||||||
|
{
|
||||||
|
return ReadRawBuffer(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a buffer directly from the input stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outBuffer">The buffer to read into</param>
|
||||||
|
/// <param name="offset">The offset to start reading data into.</param>
|
||||||
|
/// <param name="length">The number of bytes to read.</param>
|
||||||
|
/// <returns>Returns the number of bytes read.</returns>
|
||||||
|
public int ReadRawBuffer(byte[] outBuffer, int offset, int length)
|
||||||
|
{
|
||||||
|
if (length < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentOffset = offset;
|
||||||
|
int currentLength = length;
|
||||||
|
|
||||||
|
while (currentLength > 0)
|
||||||
|
{
|
||||||
|
if (available <= 0)
|
||||||
|
{
|
||||||
|
Fill();
|
||||||
|
if (available <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int toCopy = Math.Min(currentLength, available);
|
||||||
|
System.Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy);
|
||||||
|
currentOffset += toCopy;
|
||||||
|
currentLength -= toCopy;
|
||||||
|
available -= toCopy;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read clear text data from the input stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outBuffer">The buffer to add data to.</param>
|
||||||
|
/// <param name="offset">The offset to start adding data at.</param>
|
||||||
|
/// <param name="length">The number of bytes to read.</param>
|
||||||
|
/// <returns>Returns the number of bytes actually read.</returns>
|
||||||
|
public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length)
|
||||||
|
{
|
||||||
|
if (length < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentOffset = offset;
|
||||||
|
int currentLength = length;
|
||||||
|
|
||||||
|
while (currentLength > 0)
|
||||||
|
{
|
||||||
|
if (available <= 0)
|
||||||
|
{
|
||||||
|
Fill();
|
||||||
|
if (available <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int toCopy = Math.Min(currentLength, available);
|
||||||
|
Array.Copy(clearText, clearTextLength - (int)available, outBuffer, currentOffset, toCopy);
|
||||||
|
currentOffset += toCopy;
|
||||||
|
currentLength -= toCopy;
|
||||||
|
available -= toCopy;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a <see cref="byte"/> from the input stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the byte read.</returns>
|
||||||
|
public int ReadLeByte()
|
||||||
|
{
|
||||||
|
if (available <= 0)
|
||||||
|
{
|
||||||
|
Fill();
|
||||||
|
if (available <= 0)
|
||||||
|
{
|
||||||
|
throw new ZipException("EOF in header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte result = rawData[rawLength - available];
|
||||||
|
available -= 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read an <see cref="short"/> in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The short value read case to an int.</returns>
|
||||||
|
public int ReadLeShort()
|
||||||
|
{
|
||||||
|
return ReadLeByte() | (ReadLeByte() << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read an <see cref="int"/> in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The int value read.</returns>
|
||||||
|
public int ReadLeInt()
|
||||||
|
{
|
||||||
|
return ReadLeShort() | (ReadLeShort() << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a <see cref="long"/> in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The long value read.</returns>
|
||||||
|
public long ReadLeLong()
|
||||||
|
{
|
||||||
|
return (uint)ReadLeInt() | ((long)ReadLeInt() << 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the <see cref="ICryptoTransform"/> to apply to any data.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Set this value to null to have no transform applied.</remarks>
|
||||||
|
public ICryptoTransform CryptoTransform
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
cryptoTransform = value;
|
||||||
|
if (cryptoTransform != null)
|
||||||
|
{
|
||||||
|
if (rawData == clearText)
|
||||||
|
{
|
||||||
|
if (internalClearText == null)
|
||||||
|
{
|
||||||
|
internalClearText = new byte[rawData.Length];
|
||||||
|
}
|
||||||
|
clearText = internalClearText;
|
||||||
|
}
|
||||||
|
clearTextLength = rawLength;
|
||||||
|
if (available > 0)
|
||||||
|
{
|
||||||
|
cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clearText = rawData;
|
||||||
|
clearTextLength = rawLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private int rawLength;
|
||||||
|
private byte[] rawData;
|
||||||
|
|
||||||
|
private int clearTextLength;
|
||||||
|
private byte[] clearText;
|
||||||
|
private byte[] internalClearText;
|
||||||
|
|
||||||
|
private int available;
|
||||||
|
|
||||||
|
private ICryptoTransform cryptoTransform;
|
||||||
|
private Stream inputStream;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This filter stream is used to decompress data compressed using the "deflate"
|
||||||
|
/// format. The "deflate" format is described in RFC 1951.
|
||||||
|
///
|
||||||
|
/// This stream may form the basis for other decompression filters, such
|
||||||
|
/// as the <see cref="ICSharpCode.SharpZipLib.GZip.GZipInputStream">GZipInputStream</see>.
|
||||||
|
///
|
||||||
|
/// Author of the original java version : John Leuner.
|
||||||
|
/// </summary>
|
||||||
|
public class InflaterInputStream : Stream
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an InflaterInputStream with the default decompressor
|
||||||
|
/// and a default buffer size of 4KB.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "baseInputStream">
|
||||||
|
/// The InputStream to read bytes from
|
||||||
|
/// </param>
|
||||||
|
public InflaterInputStream(Stream baseInputStream)
|
||||||
|
: this(baseInputStream, new Inflater(), 4096)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an InflaterInputStream with the specified decompressor
|
||||||
|
/// and a default buffer size of 4KB.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "baseInputStream">
|
||||||
|
/// The source of input data
|
||||||
|
/// </param>
|
||||||
|
/// <param name = "inf">
|
||||||
|
/// The decompressor used to decompress data read from baseInputStream
|
||||||
|
/// </param>
|
||||||
|
public InflaterInputStream(Stream baseInputStream, Inflater inf)
|
||||||
|
: this(baseInputStream, inf, 4096)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an InflaterInputStream with the specified decompressor
|
||||||
|
/// and the specified buffer size.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name = "baseInputStream">
|
||||||
|
/// The InputStream to read bytes from
|
||||||
|
/// </param>
|
||||||
|
/// <param name = "inflater">
|
||||||
|
/// The decompressor to use
|
||||||
|
/// </param>
|
||||||
|
/// <param name = "bufferSize">
|
||||||
|
/// Size of the buffer to use
|
||||||
|
/// </param>
|
||||||
|
public InflaterInputStream(Stream baseInputStream, Inflater inflater, int bufferSize)
|
||||||
|
{
|
||||||
|
if (baseInputStream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(baseInputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inflater == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(inflater));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferSize <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.baseInputStream = baseInputStream;
|
||||||
|
this.inf = inflater;
|
||||||
|
|
||||||
|
inputBuffer = new InflaterInputBuffer(baseInputStream, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating ownership of underlying stream.
|
||||||
|
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is true.</remarks>
|
||||||
|
public bool IsStreamOwner { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip specified number of bytes of uncompressed data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name ="count">
|
||||||
|
/// Number of bytes to skip
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of bytes skipped, zero if the end of
|
||||||
|
/// stream has been reached
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// <paramref name="count">The number of bytes</paramref> to skip is less than or equal to zero.
|
||||||
|
/// </exception>
|
||||||
|
public long Skip(long count)
|
||||||
|
{
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
// v0.80 Skip by seeking if underlying stream supports it...
|
||||||
|
if (baseInputStream.CanSeek)
|
||||||
|
{
|
||||||
|
baseInputStream.Seek(count, SeekOrigin.Current);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int length = 2048;
|
||||||
|
if (count < length)
|
||||||
|
{
|
||||||
|
length = (int)count;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] tmp = new byte[length];
|
||||||
|
int readCount = 1;
|
||||||
|
long toSkip = count;
|
||||||
|
|
||||||
|
while ((toSkip > 0) && (readCount > 0))
|
||||||
|
{
|
||||||
|
if (toSkip < length)
|
||||||
|
{
|
||||||
|
length = (int)toSkip;
|
||||||
|
}
|
||||||
|
|
||||||
|
readCount = baseInputStream.Read(tmp, 0, length);
|
||||||
|
toSkip -= readCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count - toSkip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear any cryptographic state.
|
||||||
|
/// </summary>
|
||||||
|
protected void StopDecrypting()
|
||||||
|
{
|
||||||
|
inputBuffer.CryptoTransform = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns 0 once the end of the stream (EOF) has been reached.
|
||||||
|
/// Otherwise returns 1.
|
||||||
|
/// </summary>
|
||||||
|
public virtual int Available
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return inf.IsFinished ? 0 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fills the buffer with more data to decompress.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// Stream ends early
|
||||||
|
/// </exception>
|
||||||
|
protected void Fill()
|
||||||
|
{
|
||||||
|
// Protect against redundant calls
|
||||||
|
if (inputBuffer.Available <= 0)
|
||||||
|
{
|
||||||
|
inputBuffer.Fill();
|
||||||
|
if (inputBuffer.Available <= 0)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Unexpected EOF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputBuffer.SetInflaterInput(inf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Stream Overrides
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current stream supports reading
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return baseInputStream.CanRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value of false indicating seeking is not supported for this stream.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value of false indicating that this stream is not writeable.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value representing the length of the stream in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
//return inputBuffer.RawLength;
|
||||||
|
throw new NotSupportedException("InflaterInputStream Length is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current position within the stream.
|
||||||
|
/// Throws a NotSupportedException when attempting to set the position
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="NotSupportedException">Attempting to set the position</exception>
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return baseInputStream.Position;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("InflaterInputStream Position not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes the baseInputStream
|
||||||
|
/// </summary>
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
baseInputStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the position within the current stream
|
||||||
|
/// Always throws a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The relative offset to seek to.</param>
|
||||||
|
/// <param name="origin">The <see cref="SeekOrigin"/> defining where to seek from.</param>
|
||||||
|
/// <returns>The new position in the stream.</returns>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Seek not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the length of the current stream
|
||||||
|
/// Always throws a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The new length value for the stream.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("InflaterInputStream SetLength not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a sequence of bytes to stream and advances the current position
|
||||||
|
/// This method always throws a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Thew buffer containing data to write.</param>
|
||||||
|
/// <param name="offset">The offset of the first byte to write.</param>
|
||||||
|
/// <param name="count">The number of bytes to write.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("InflaterInputStream Write not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes one byte to the current stream and advances the current position
|
||||||
|
/// Always throws a NotSupportedException
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The byte to write.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Any access</exception>
|
||||||
|
public override void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("InflaterInputStream WriteByte not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the input stream. When <see cref="IsStreamOwner"></see>
|
||||||
|
/// is true the underlying stream is also closed.
|
||||||
|
/// </summary>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!isClosed)
|
||||||
|
{
|
||||||
|
isClosed = true;
|
||||||
|
if (IsStreamOwner)
|
||||||
|
{
|
||||||
|
baseInputStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads decompressed data into the provided buffer byte array
|
||||||
|
/// </summary>
|
||||||
|
/// <param name ="buffer">
|
||||||
|
/// The array to read and decompress data into
|
||||||
|
/// </param>
|
||||||
|
/// <param name ="offset">
|
||||||
|
/// The offset indicating where the data should be placed
|
||||||
|
/// </param>
|
||||||
|
/// <param name ="count">
|
||||||
|
/// The number of bytes to decompress
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The number of bytes read. Zero signals the end of stream</returns>
|
||||||
|
/// <exception cref="SharpZipBaseException">
|
||||||
|
/// Inflater needs a dictionary
|
||||||
|
/// </exception>
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (inf.IsNeedingDictionary)
|
||||||
|
{
|
||||||
|
throw new SharpZipBaseException("Need a dictionary");
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainingBytes = count;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int bytesRead = inf.Inflate(buffer, offset, remainingBytes);
|
||||||
|
offset += bytesRead;
|
||||||
|
remainingBytes -= bytesRead;
|
||||||
|
|
||||||
|
if (remainingBytes == 0 || inf.IsFinished)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inf.IsNeedingInput)
|
||||||
|
{
|
||||||
|
Fill();
|
||||||
|
}
|
||||||
|
else if (bytesRead == 0)
|
||||||
|
{
|
||||||
|
throw new ZipException("Invalid input data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count - remainingBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Stream Overrides
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decompressor for this stream
|
||||||
|
/// </summary>
|
||||||
|
protected Inflater inf;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="InflaterInputBuffer">Input buffer</see> for this stream.
|
||||||
|
/// </summary>
|
||||||
|
protected InflaterInputBuffer inputBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base stream the inflater reads from.
|
||||||
|
/// </summary>
|
||||||
|
private Stream baseInputStream;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The compressed size
|
||||||
|
/// </summary>
|
||||||
|
protected long csize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag indicating wether this instance has been closed or not.
|
||||||
|
/// </summary>
|
||||||
|
private bool isClosed;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the output from the Inflation process.
|
||||||
|
/// We need to have a window so that we can refer backwards into the output stream
|
||||||
|
/// to repeat stuff.<br/>
|
||||||
|
/// Author of the original java version : John Leuner
|
||||||
|
/// </summary>
|
||||||
|
public class OutputWindow
|
||||||
|
{
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
private const int WindowSize = 1 << 15;
|
||||||
|
private const int WindowMask = WindowSize - 1;
|
||||||
|
|
||||||
|
#endregion Constants
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private byte[] window = new byte[WindowSize]; //The window is 2^15 bytes
|
||||||
|
private int windowEnd;
|
||||||
|
private int windowFilled;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a byte to this output window
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">value to write</param>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// if window is full
|
||||||
|
/// </exception>
|
||||||
|
public void Write(int value)
|
||||||
|
{
|
||||||
|
if (windowFilled++ == WindowSize)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Window full");
|
||||||
|
}
|
||||||
|
window[windowEnd++] = (byte)value;
|
||||||
|
windowEnd &= WindowMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SlowRepeat(int repStart, int length, int distance)
|
||||||
|
{
|
||||||
|
while (length-- > 0)
|
||||||
|
{
|
||||||
|
window[windowEnd++] = window[repStart++];
|
||||||
|
windowEnd &= WindowMask;
|
||||||
|
repStart &= WindowMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Append a byte pattern already in the window itself
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">length of pattern to copy</param>
|
||||||
|
/// <param name="distance">distance from end of window pattern occurs</param>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// If the repeated data overflows the window
|
||||||
|
/// </exception>
|
||||||
|
public void Repeat(int length, int distance)
|
||||||
|
{
|
||||||
|
if ((windowFilled += length) > WindowSize)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Window full");
|
||||||
|
}
|
||||||
|
|
||||||
|
int repStart = (windowEnd - distance) & WindowMask;
|
||||||
|
int border = WindowSize - length;
|
||||||
|
if ((repStart <= border) && (windowEnd < border))
|
||||||
|
{
|
||||||
|
if (length <= distance)
|
||||||
|
{
|
||||||
|
System.Array.Copy(window, repStart, window, windowEnd, length);
|
||||||
|
windowEnd += length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We have to copy manually, since the repeat pattern overlaps.
|
||||||
|
while (length-- > 0)
|
||||||
|
{
|
||||||
|
window[windowEnd++] = window[repStart++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SlowRepeat(repStart, length, distance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy from input manipulator to internal window
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">source of data</param>
|
||||||
|
/// <param name="length">length of data to copy</param>
|
||||||
|
/// <returns>the number of bytes copied</returns>
|
||||||
|
public int CopyStored(StreamManipulator input, int length)
|
||||||
|
{
|
||||||
|
length = Math.Min(Math.Min(length, WindowSize - windowFilled), input.AvailableBytes);
|
||||||
|
int copied;
|
||||||
|
|
||||||
|
int tailLen = WindowSize - windowEnd;
|
||||||
|
if (length > tailLen)
|
||||||
|
{
|
||||||
|
copied = input.CopyBytes(window, windowEnd, tailLen);
|
||||||
|
if (copied == tailLen)
|
||||||
|
{
|
||||||
|
copied += input.CopyBytes(window, 0, length - tailLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
copied = input.CopyBytes(window, windowEnd, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
windowEnd = (windowEnd + copied) & WindowMask;
|
||||||
|
windowFilled += copied;
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy dictionary to window
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dictionary">source dictionary</param>
|
||||||
|
/// <param name="offset">offset of start in source dictionary</param>
|
||||||
|
/// <param name="length">length of dictionary</param>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// If window isnt empty
|
||||||
|
/// </exception>
|
||||||
|
public void CopyDict(byte[] dictionary, int offset, int length)
|
||||||
|
{
|
||||||
|
if (dictionary == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(dictionary));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (windowFilled > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > WindowSize)
|
||||||
|
{
|
||||||
|
offset += length - WindowSize;
|
||||||
|
length = WindowSize;
|
||||||
|
}
|
||||||
|
System.Array.Copy(dictionary, offset, window, 0, length);
|
||||||
|
windowEnd = length & WindowMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get remaining unfilled space in window
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Number of bytes left in window</returns>
|
||||||
|
public int GetFreeSpace()
|
||||||
|
{
|
||||||
|
return WindowSize - windowFilled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get bytes available for output in window
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Number of bytes filled</returns>
|
||||||
|
public int GetAvailable()
|
||||||
|
{
|
||||||
|
return windowFilled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy contents of window to output
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="output">buffer to copy to</param>
|
||||||
|
/// <param name="offset">offset to start at</param>
|
||||||
|
/// <param name="len">number of bytes to count</param>
|
||||||
|
/// <returns>The number of bytes copied</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// If a window underflow occurs
|
||||||
|
/// </exception>
|
||||||
|
public int CopyOutput(byte[] output, int offset, int len)
|
||||||
|
{
|
||||||
|
int copyEnd = windowEnd;
|
||||||
|
if (len > windowFilled)
|
||||||
|
{
|
||||||
|
len = windowFilled;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
copyEnd = (windowEnd - windowFilled + len) & WindowMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
int copied = len;
|
||||||
|
int tailLen = len - copyEnd;
|
||||||
|
|
||||||
|
if (tailLen > 0)
|
||||||
|
{
|
||||||
|
System.Array.Copy(window, WindowSize - tailLen, output, offset, tailLen);
|
||||||
|
offset += tailLen;
|
||||||
|
len = copyEnd;
|
||||||
|
}
|
||||||
|
System.Array.Copy(window, copyEnd - len, output, offset, len);
|
||||||
|
windowFilled -= copied;
|
||||||
|
if (windowFilled < 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset by clearing window so <see cref="GetAvailable">GetAvailable</see> returns 0
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
windowFilled = windowEnd = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,298 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class allows us to retrieve a specified number of bits from
|
||||||
|
/// the input buffer, as well as copy big byte blocks.
|
||||||
|
///
|
||||||
|
/// It uses an int buffer to store up to 31 bits for direct
|
||||||
|
/// manipulation. This guarantees that we can get at least 16 bits,
|
||||||
|
/// but we only need at most 15, so this is all safe.
|
||||||
|
///
|
||||||
|
/// There are some optimizations in this class, for example, you must
|
||||||
|
/// never peek more than 8 bits more than needed, and you must first
|
||||||
|
/// peek bits before you may drop them. This is not a general purpose
|
||||||
|
/// class but optimized for the behaviour of the Inflater.
|
||||||
|
///
|
||||||
|
/// authors of the original java version : John Leuner, Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
public class StreamManipulator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the next sequence of bits but don't increase input pointer. bitCount must be
|
||||||
|
/// less or equal 16 and if this call succeeds, you must drop
|
||||||
|
/// at least n - 8 bits in the next call.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bitCount">The number of bits to peek.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// the value of the bits, or -1 if not enough bits available. */
|
||||||
|
/// </returns>
|
||||||
|
public int PeekBits(int bitCount)
|
||||||
|
{
|
||||||
|
if (bitsInBuffer_ < bitCount)
|
||||||
|
{
|
||||||
|
if (windowStart_ == windowEnd_)
|
||||||
|
{
|
||||||
|
return -1; // ok
|
||||||
|
}
|
||||||
|
buffer_ |= (uint)((window_[windowStart_++] & 0xff |
|
||||||
|
(window_[windowStart_++] & 0xff) << 8) << bitsInBuffer_);
|
||||||
|
bitsInBuffer_ += 16;
|
||||||
|
}
|
||||||
|
return (int)(buffer_ & ((1 << bitCount) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to grab the next <paramref name="bitCount"/> bits from the input and
|
||||||
|
/// sets <paramref name="output"/> to the value, adding <paramref name="outputOffset"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if enough bits could be read, otherwise false</returns>
|
||||||
|
public bool TryGetBits(int bitCount, ref int output, int outputOffset = 0)
|
||||||
|
{
|
||||||
|
var bits = PeekBits(bitCount);
|
||||||
|
if (bits < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
output = bits + outputOffset;
|
||||||
|
DropBits(bitCount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to grab the next <paramref name="bitCount"/> bits from the input and
|
||||||
|
/// sets <paramref name="index"/> of <paramref name="array"/> to the value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if enough bits could be read, otherwise false</returns>
|
||||||
|
public bool TryGetBits(int bitCount, ref byte[] array, int index)
|
||||||
|
{
|
||||||
|
var bits = PeekBits(bitCount);
|
||||||
|
if (bits < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
array[index] = (byte)bits;
|
||||||
|
DropBits(bitCount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drops the next n bits from the input. You should have called PeekBits
|
||||||
|
/// with a bigger or equal n before, to make sure that enough bits are in
|
||||||
|
/// the bit buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bitCount">The number of bits to drop.</param>
|
||||||
|
public void DropBits(int bitCount)
|
||||||
|
{
|
||||||
|
buffer_ >>= bitCount;
|
||||||
|
bitsInBuffer_ -= bitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next n bits and increases input pointer. This is equivalent
|
||||||
|
/// to <see cref="PeekBits"/> followed by <see cref="DropBits"/>, except for correct error handling.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bitCount">The number of bits to retrieve.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// the value of the bits, or -1 if not enough bits available.
|
||||||
|
/// </returns>
|
||||||
|
public int GetBits(int bitCount)
|
||||||
|
{
|
||||||
|
int bits = PeekBits(bitCount);
|
||||||
|
if (bits >= 0)
|
||||||
|
{
|
||||||
|
DropBits(bitCount);
|
||||||
|
}
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of bits available in the bit buffer. This must be
|
||||||
|
/// only called when a previous PeekBits() returned -1.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// the number of bits available.
|
||||||
|
/// </returns>
|
||||||
|
public int AvailableBits
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return bitsInBuffer_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of bytes available.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of bytes available.
|
||||||
|
/// </returns>
|
||||||
|
public int AvailableBytes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return windowEnd_ - windowStart_ + (bitsInBuffer_ >> 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips to the next byte boundary.
|
||||||
|
/// </summary>
|
||||||
|
public void SkipToByteBoundary()
|
||||||
|
{
|
||||||
|
buffer_ >>= (bitsInBuffer_ & 7);
|
||||||
|
bitsInBuffer_ &= ~7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when SetInput can be called
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNeedingInput
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return windowStart_ == windowEnd_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies bytes from input buffer to output buffer starting
|
||||||
|
/// at output[offset]. You have to make sure, that the buffer is
|
||||||
|
/// byte aligned. If not enough bytes are available, copies fewer
|
||||||
|
/// bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="output">
|
||||||
|
/// The buffer to copy bytes to.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="offset">
|
||||||
|
/// The offset in the buffer at which copying starts
|
||||||
|
/// </param>
|
||||||
|
/// <param name="length">
|
||||||
|
/// The length to copy, 0 is allowed.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of bytes copied, 0 if no bytes were available.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Length is less than zero
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// Bit buffer isnt byte aligned
|
||||||
|
/// </exception>
|
||||||
|
public int CopyBytes(byte[] output, int offset, int length)
|
||||||
|
{
|
||||||
|
if (length < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((bitsInBuffer_ & 7) != 0)
|
||||||
|
{
|
||||||
|
// bits_in_buffer may only be 0 or a multiple of 8
|
||||||
|
throw new InvalidOperationException("Bit buffer is not byte aligned!");
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while ((bitsInBuffer_ > 0) && (length > 0))
|
||||||
|
{
|
||||||
|
output[offset++] = (byte)buffer_;
|
||||||
|
buffer_ >>= 8;
|
||||||
|
bitsInBuffer_ -= 8;
|
||||||
|
length--;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int avail = windowEnd_ - windowStart_;
|
||||||
|
if (length > avail)
|
||||||
|
{
|
||||||
|
length = avail;
|
||||||
|
}
|
||||||
|
System.Array.Copy(window_, windowStart_, output, offset, length);
|
||||||
|
windowStart_ += length;
|
||||||
|
|
||||||
|
if (((windowStart_ - windowEnd_) & 1) != 0)
|
||||||
|
{
|
||||||
|
// We always want an even number of bytes in input, see peekBits
|
||||||
|
buffer_ = (uint)(window_[windowStart_++] & 0xff);
|
||||||
|
bitsInBuffer_ = 8;
|
||||||
|
}
|
||||||
|
return count + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets state and empties internal buffers
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
buffer_ = 0;
|
||||||
|
windowStart_ = windowEnd_ = bitsInBuffer_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add more input for consumption.
|
||||||
|
/// Only call when IsNeedingInput returns true
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">data to be input</param>
|
||||||
|
/// <param name="offset">offset of first byte of input</param>
|
||||||
|
/// <param name="count">number of bytes of input to add.</param>
|
||||||
|
public void SetInput(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (windowStart_ < windowEnd_)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Old input was not completely processed");
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = offset + count;
|
||||||
|
|
||||||
|
// We want to throw an ArrayIndexOutOfBoundsException early.
|
||||||
|
// Note the check also handles integer wrap around.
|
||||||
|
if ((offset > end) || (end > buffer.Length))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((count & 1) != 0)
|
||||||
|
{
|
||||||
|
// We always want an even number of bytes in input, see PeekBits
|
||||||
|
buffer_ |= (uint)((buffer[offset++] & 0xff) << bitsInBuffer_);
|
||||||
|
bitsInBuffer_ += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = buffer;
|
||||||
|
windowStart_ = offset;
|
||||||
|
windowEnd_ = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private byte[] window_;
|
||||||
|
private int windowStart_;
|
||||||
|
private int windowEnd_;
|
||||||
|
|
||||||
|
private uint buffer_;
|
||||||
|
private int bitsInBuffer_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,779 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Core;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using static ICSharpCode.SharpZipLib.Zip.Compression.Deflater;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// FastZipEvents supports all events applicable to <see cref="FastZip">FastZip</see> operations.
|
||||||
|
/// </summary>
|
||||||
|
public class FastZipEvents
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when processing directories.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DirectoryEventArgs> ProcessDirectory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when processing files.
|
||||||
|
/// </summary>
|
||||||
|
public ProcessFileHandler ProcessFile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke during processing of files.
|
||||||
|
/// </summary>
|
||||||
|
public ProgressHandler Progress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when processing for a file has been completed.
|
||||||
|
/// </summary>
|
||||||
|
public CompletedFileHandler CompletedFile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when processing directory failures.
|
||||||
|
/// </summary>
|
||||||
|
public DirectoryFailureHandler DirectoryFailure;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to invoke when processing file failures.
|
||||||
|
/// </summary>
|
||||||
|
public FileFailureHandler FileFailure;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raise the <see cref="DirectoryFailure">directory failure</see> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directory">The directory causing the failure.</param>
|
||||||
|
/// <param name="e">The exception for this event.</param>
|
||||||
|
/// <returns>A boolean indicating if execution should continue or not.</returns>
|
||||||
|
public bool OnDirectoryFailure(string directory, Exception e)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
DirectoryFailureHandler handler = DirectoryFailure;
|
||||||
|
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new ScanFailureEventArgs(directory, e);
|
||||||
|
handler(this, args);
|
||||||
|
result = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires the <see cref="FileFailure"> file failure handler delegate</see>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The file causing the failure.</param>
|
||||||
|
/// <param name="e">The exception for this failure.</param>
|
||||||
|
/// <returns>A boolean indicating if execution should continue or not.</returns>
|
||||||
|
public bool OnFileFailure(string file, Exception e)
|
||||||
|
{
|
||||||
|
FileFailureHandler handler = FileFailure;
|
||||||
|
bool result = (handler != null);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
var args = new ScanFailureEventArgs(file, e);
|
||||||
|
handler(this, args);
|
||||||
|
result = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires the <see cref="ProcessFile">ProcessFile delegate</see>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The file being processed.</param>
|
||||||
|
/// <returns>A boolean indicating if execution should continue or not.</returns>
|
||||||
|
public bool OnProcessFile(string file)
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
ProcessFileHandler handler = ProcessFile;
|
||||||
|
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new ScanEventArgs(file);
|
||||||
|
handler(this, args);
|
||||||
|
result = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires the <see cref="CompletedFile"/> delegate
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The file whose processing has been completed.</param>
|
||||||
|
/// <returns>A boolean indicating if execution should continue or not.</returns>
|
||||||
|
public bool OnCompletedFile(string file)
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
CompletedFileHandler handler = CompletedFile;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new ScanEventArgs(file);
|
||||||
|
handler(this, args);
|
||||||
|
result = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires the <see cref="ProcessDirectory">process directory</see> delegate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directory">The directory being processed.</param>
|
||||||
|
/// <param name="hasMatchingFiles">Flag indicating if the directory has matching files as determined by the current filter.</param>
|
||||||
|
/// <returns>A <see cref="bool"/> of true if the operation should continue; false otherwise.</returns>
|
||||||
|
public bool OnProcessDirectory(string directory, bool hasMatchingFiles)
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
EventHandler<DirectoryEventArgs> handler = ProcessDirectory;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new DirectoryEventArgs(directory, hasMatchingFiles);
|
||||||
|
handler(this, args);
|
||||||
|
result = args.ContinueRunning;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum timespan between <see cref="Progress"/> events.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The minimum period of time between <see cref="Progress"/> events.</value>
|
||||||
|
/// <seealso cref="Progress"/>
|
||||||
|
/// <remarks>The default interval is three seconds.</remarks>
|
||||||
|
public TimeSpan ProgressInterval
|
||||||
|
{
|
||||||
|
get { return progressInterval_; }
|
||||||
|
set { progressInterval_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private TimeSpan progressInterval_ = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FastZip provides facilities for creating and extracting zip files.
|
||||||
|
/// </summary>
|
||||||
|
public class FastZip
|
||||||
|
{
|
||||||
|
#region Enumerations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the desired handling when overwriting files during extraction.
|
||||||
|
/// </summary>
|
||||||
|
public enum Overwrite
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Prompt the user to confirm overwriting
|
||||||
|
/// </summary>
|
||||||
|
Prompt,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Never overwrite files.
|
||||||
|
/// </summary>
|
||||||
|
Never,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Always overwrite files.
|
||||||
|
/// </summary>
|
||||||
|
Always
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Enumerations
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a default instance of <see cref="FastZip"/>.
|
||||||
|
/// </summary>
|
||||||
|
public FastZip()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="FastZip"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="events">The <see cref="FastZipEvents">events</see> to use during operations.</param>
|
||||||
|
public FastZip(FastZipEvents events)
|
||||||
|
{
|
||||||
|
events_ = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set a value indicating wether empty directories should be created.
|
||||||
|
/// </summary>
|
||||||
|
public bool CreateEmptyDirectories
|
||||||
|
{
|
||||||
|
get { return createEmptyDirectories_; }
|
||||||
|
set { createEmptyDirectories_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set the password value.
|
||||||
|
/// </summary>
|
||||||
|
public string Password
|
||||||
|
{
|
||||||
|
get { return password_; }
|
||||||
|
set { password_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get or set the <see cref="INameTransform"></see> active when creating Zip files.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="EntryFactory"></seealso>
|
||||||
|
public INameTransform NameTransform
|
||||||
|
{
|
||||||
|
get { return entryFactory_.NameTransform; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
entryFactory_.NameTransform = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get or set the <see cref="IEntryFactory"></see> active when creating Zip files.
|
||||||
|
/// </summary>
|
||||||
|
public IEntryFactory EntryFactory
|
||||||
|
{
|
||||||
|
get { return entryFactory_; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
entryFactory_ = new ZipEntryFactory();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entryFactory_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the setting for <see cref="UseZip64">Zip64 handling when writing.</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The default value is dynamic which is not backwards compatible with old
|
||||||
|
/// programs and can cause problems with XP's built in compression which cant
|
||||||
|
/// read Zip64 archives. However it does avoid the situation were a large file
|
||||||
|
/// is added and cannot be completed correctly.
|
||||||
|
/// NOTE: Setting the size for entries before they are added is the best solution!
|
||||||
|
/// By default the EntryFactory used by FastZip will set fhe file size.
|
||||||
|
/// </remarks>
|
||||||
|
public UseZip64 UseZip64
|
||||||
|
{
|
||||||
|
get { return useZip64_; }
|
||||||
|
set { useZip64_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set a value indicating wether file dates and times should
|
||||||
|
/// be restored when extracting files from an archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default value is false.</remarks>
|
||||||
|
public bool RestoreDateTimeOnExtract
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return restoreDateTimeOnExtract_;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
restoreDateTimeOnExtract_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set a value indicating whether file attributes should
|
||||||
|
/// be restored during extract operations
|
||||||
|
/// </summary>
|
||||||
|
public bool RestoreAttributesOnExtract
|
||||||
|
{
|
||||||
|
get { return restoreAttributesOnExtract_; }
|
||||||
|
set { restoreAttributesOnExtract_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the Compression Level that will be used
|
||||||
|
/// when creating the zip
|
||||||
|
/// </summary>
|
||||||
|
public Deflater.CompressionLevel CompressionLevel
|
||||||
|
{
|
||||||
|
get { return compressionLevel_; }
|
||||||
|
set { compressionLevel_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Properties
|
||||||
|
|
||||||
|
#region Delegates
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate called when confirming overwriting of files.
|
||||||
|
/// </summary>
|
||||||
|
public delegate bool ConfirmOverwriteDelegate(string fileName);
|
||||||
|
|
||||||
|
#endregion Delegates
|
||||||
|
|
||||||
|
#region CreateZip
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a zip file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="zipFileName">The name of the zip file to create.</param>
|
||||||
|
/// <param name="sourceDirectory">The directory to source files from.</param>
|
||||||
|
/// <param name="recurse">True to recurse directories, false for no recursion.</param>
|
||||||
|
/// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param>
|
||||||
|
/// <param name="directoryFilter">The <see cref="PathFilter">directory filter</see> to apply.</param>
|
||||||
|
public void CreateZip(string zipFileName, string sourceDirectory,
|
||||||
|
bool recurse, string fileFilter, string directoryFilter)
|
||||||
|
{
|
||||||
|
CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a zip file/archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="zipFileName">The name of the zip file to create.</param>
|
||||||
|
/// <param name="sourceDirectory">The directory to obtain files and directories from.</param>
|
||||||
|
/// <param name="recurse">True to recurse directories, false for no recursion.</param>
|
||||||
|
/// <param name="fileFilter">The file filter to apply.</param>
|
||||||
|
public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter)
|
||||||
|
{
|
||||||
|
CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a zip archive sending output to the <paramref name="outputStream"/> passed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputStream">The stream to write archive data to.</param>
|
||||||
|
/// <param name="sourceDirectory">The directory to source files from.</param>
|
||||||
|
/// <param name="recurse">True to recurse directories, false for no recursion.</param>
|
||||||
|
/// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param>
|
||||||
|
/// <param name="directoryFilter">The <see cref="PathFilter">directory filter</see> to apply.</param>
|
||||||
|
/// <remarks>The <paramref name="outputStream"/> is closed after creation.</remarks>
|
||||||
|
public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter)
|
||||||
|
{
|
||||||
|
NameTransform = new ZipNameTransform(sourceDirectory);
|
||||||
|
sourceDirectory_ = sourceDirectory;
|
||||||
|
|
||||||
|
using (outputStream_ = new ZipOutputStream(outputStream))
|
||||||
|
{
|
||||||
|
outputStream_.SetLevel((int)CompressionLevel);
|
||||||
|
|
||||||
|
if (password_ != null)
|
||||||
|
{
|
||||||
|
outputStream_.Password = password_;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream_.UseZip64 = UseZip64;
|
||||||
|
var scanner = new FileSystemScanner(fileFilter, directoryFilter);
|
||||||
|
scanner.ProcessFile += ProcessFile;
|
||||||
|
if (this.CreateEmptyDirectories)
|
||||||
|
{
|
||||||
|
scanner.ProcessDirectory += ProcessDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_ != null)
|
||||||
|
{
|
||||||
|
if (events_.FileFailure != null)
|
||||||
|
{
|
||||||
|
scanner.FileFailure += events_.FileFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_.DirectoryFailure != null)
|
||||||
|
{
|
||||||
|
scanner.DirectoryFailure += events_.DirectoryFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner.Scan(sourceDirectory, recurse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion CreateZip
|
||||||
|
|
||||||
|
#region ExtractZip
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract the contents of a zip file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="zipFileName">The zip file to extract from.</param>
|
||||||
|
/// <param name="targetDirectory">The directory to save extracted information in.</param>
|
||||||
|
/// <param name="fileFilter">A filter to apply to files.</param>
|
||||||
|
public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter)
|
||||||
|
{
|
||||||
|
ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract the contents of a zip file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="zipFileName">The zip file to extract from.</param>
|
||||||
|
/// <param name="targetDirectory">The directory to save extracted information in.</param>
|
||||||
|
/// <param name="overwrite">The style of <see cref="Overwrite">overwriting</see> to apply.</param>
|
||||||
|
/// <param name="confirmDelegate">A delegate to invoke when confirming overwriting.</param>
|
||||||
|
/// <param name="fileFilter">A filter to apply to files.</param>
|
||||||
|
/// <param name="directoryFilter">A filter to apply to directories.</param>
|
||||||
|
/// <param name="restoreDateTime">Flag indicating whether to restore the date and time for extracted files.</param>
|
||||||
|
/// <param name="allowParentTraversal">Allow parent directory traversal in file paths (e.g. ../file)</param>
|
||||||
|
public void ExtractZip(string zipFileName, string targetDirectory,
|
||||||
|
Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate,
|
||||||
|
string fileFilter, string directoryFilter, bool restoreDateTime, bool allowParentTraversal = false)
|
||||||
|
{
|
||||||
|
Stream inputStream = File.Open(zipFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
ExtractZip(inputStream, targetDirectory, overwrite, confirmDelegate, fileFilter, directoryFilter, restoreDateTime, true, allowParentTraversal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract the contents of a zip file held in a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">The seekable input stream containing the zip to extract from.</param>
|
||||||
|
/// <param name="targetDirectory">The directory to save extracted information in.</param>
|
||||||
|
/// <param name="overwrite">The style of <see cref="Overwrite">overwriting</see> to apply.</param>
|
||||||
|
/// <param name="confirmDelegate">A delegate to invoke when confirming overwriting.</param>
|
||||||
|
/// <param name="fileFilter">A filter to apply to files.</param>
|
||||||
|
/// <param name="directoryFilter">A filter to apply to directories.</param>
|
||||||
|
/// <param name="restoreDateTime">Flag indicating whether to restore the date and time for extracted files.</param>
|
||||||
|
/// <param name="isStreamOwner">Flag indicating whether the inputStream will be closed by this method.</param>
|
||||||
|
/// <param name="allowParentTraversal">Allow parent directory traversal in file paths (e.g. ../file)</param>
|
||||||
|
public void ExtractZip(Stream inputStream, string targetDirectory,
|
||||||
|
Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate,
|
||||||
|
string fileFilter, string directoryFilter, bool restoreDateTime,
|
||||||
|
bool isStreamOwner, bool allowParentTraversal = false)
|
||||||
|
{
|
||||||
|
if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(confirmDelegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
continueRunning_ = true;
|
||||||
|
overwrite_ = overwrite;
|
||||||
|
confirmDelegate_ = confirmDelegate;
|
||||||
|
extractNameTransform_ = new WindowsNameTransform(targetDirectory, allowParentTraversal);
|
||||||
|
|
||||||
|
fileFilter_ = new NameFilter(fileFilter);
|
||||||
|
directoryFilter_ = new NameFilter(directoryFilter);
|
||||||
|
restoreDateTimeOnExtract_ = restoreDateTime;
|
||||||
|
|
||||||
|
using (zipFile_ = new ZipFile(inputStream, !isStreamOwner))
|
||||||
|
{
|
||||||
|
if (password_ != null)
|
||||||
|
{
|
||||||
|
zipFile_.Password = password_;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator();
|
||||||
|
while (continueRunning_ && enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
var entry = (ZipEntry)enumerator.Current;
|
||||||
|
if (entry.IsFile)
|
||||||
|
{
|
||||||
|
// TODO Path.GetDirectory can fail here on invalid characters.
|
||||||
|
if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name))
|
||||||
|
{
|
||||||
|
ExtractEntry(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (entry.IsDirectory)
|
||||||
|
{
|
||||||
|
if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories)
|
||||||
|
{
|
||||||
|
ExtractEntry(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Do nothing for volume labels etc...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion ExtractZip
|
||||||
|
|
||||||
|
#region Internal Processing
|
||||||
|
|
||||||
|
private void ProcessDirectory(object sender, DirectoryEventArgs e)
|
||||||
|
{
|
||||||
|
if (!e.HasMatchingFiles && CreateEmptyDirectories)
|
||||||
|
{
|
||||||
|
if (events_ != null)
|
||||||
|
{
|
||||||
|
events_.OnProcessDirectory(e.Name, e.HasMatchingFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.ContinueRunning)
|
||||||
|
{
|
||||||
|
if (e.Name != sourceDirectory_)
|
||||||
|
{
|
||||||
|
ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name);
|
||||||
|
outputStream_.PutNextEntry(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessFile(object sender, ScanEventArgs e)
|
||||||
|
{
|
||||||
|
if ((events_ != null) && (events_.ProcessFile != null))
|
||||||
|
{
|
||||||
|
events_.ProcessFile(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.ContinueRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// The open below is equivalent to OpenRead which gaurantees that if opened the
|
||||||
|
// file will not be changed by subsequent openers, but precludes opening in some cases
|
||||||
|
// were it could succeed. ie the open may fail as its already open for writing and the share mode should reflect that.
|
||||||
|
using (FileStream stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||||
|
{
|
||||||
|
ZipEntry entry = entryFactory_.MakeFileEntry(e.Name);
|
||||||
|
outputStream_.PutNextEntry(entry);
|
||||||
|
AddFileContents(e.Name, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (events_ != null)
|
||||||
|
{
|
||||||
|
continueRunning_ = events_.OnFileFailure(e.Name, ex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continueRunning_ = false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFileContents(string name, Stream stream)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer_ == null)
|
||||||
|
{
|
||||||
|
buffer_ = new byte[4096];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((events_ != null) && (events_.Progress != null))
|
||||||
|
{
|
||||||
|
StreamUtils.Copy(stream, outputStream_, buffer_,
|
||||||
|
events_.Progress, events_.ProgressInterval, this, name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StreamUtils.Copy(stream, outputStream_, buffer_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_ != null)
|
||||||
|
{
|
||||||
|
continueRunning_ = events_.OnCompletedFile(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractFileEntry(ZipEntry entry, string targetName)
|
||||||
|
{
|
||||||
|
bool proceed = true;
|
||||||
|
if (overwrite_ != Overwrite.Always)
|
||||||
|
{
|
||||||
|
if (File.Exists(targetName))
|
||||||
|
{
|
||||||
|
if ((overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null))
|
||||||
|
{
|
||||||
|
proceed = confirmDelegate_(targetName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
proceed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proceed)
|
||||||
|
{
|
||||||
|
if (events_ != null)
|
||||||
|
{
|
||||||
|
continueRunning_ = events_.OnProcessFile(entry.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continueRunning_)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (FileStream outputStream = File.Create(targetName))
|
||||||
|
{
|
||||||
|
if (buffer_ == null)
|
||||||
|
{
|
||||||
|
buffer_ = new byte[4096];
|
||||||
|
}
|
||||||
|
if ((events_ != null) && (events_.Progress != null))
|
||||||
|
{
|
||||||
|
StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_,
|
||||||
|
events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events_ != null)
|
||||||
|
{
|
||||||
|
continueRunning_ = events_.OnCompletedFile(entry.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restoreDateTimeOnExtract_)
|
||||||
|
{
|
||||||
|
File.SetLastWriteTime(targetName, entry.DateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1))
|
||||||
|
{
|
||||||
|
var fileAttributes = (FileAttributes)entry.ExternalFileAttributes;
|
||||||
|
// TODO: FastZip - Setting of other file attributes on extraction is a little trickier.
|
||||||
|
fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.Hidden);
|
||||||
|
File.SetAttributes(targetName, fileAttributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (events_ != null)
|
||||||
|
{
|
||||||
|
continueRunning_ = events_.OnFileFailure(targetName, ex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continueRunning_ = false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractEntry(ZipEntry entry)
|
||||||
|
{
|
||||||
|
bool doExtraction = entry.IsCompressionMethodSupported();
|
||||||
|
string targetName = entry.Name;
|
||||||
|
|
||||||
|
if (doExtraction)
|
||||||
|
{
|
||||||
|
if (entry.IsFile)
|
||||||
|
{
|
||||||
|
targetName = extractNameTransform_.TransformFile(targetName);
|
||||||
|
}
|
||||||
|
else if (entry.IsDirectory)
|
||||||
|
{
|
||||||
|
targetName = extractNameTransform_.TransformDirectory(targetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
doExtraction = !(string.IsNullOrEmpty(targetName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fire delegate/throw exception were compression method not supported, or name is invalid?
|
||||||
|
|
||||||
|
string dirName = null;
|
||||||
|
|
||||||
|
if (doExtraction)
|
||||||
|
{
|
||||||
|
if (entry.IsDirectory)
|
||||||
|
{
|
||||||
|
dirName = targetName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dirName = Path.GetDirectoryName(Path.GetFullPath(targetName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doExtraction && !Directory.Exists(dirName))
|
||||||
|
{
|
||||||
|
if (!entry.IsDirectory || CreateEmptyDirectories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dirName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
doExtraction = false;
|
||||||
|
if (events_ != null)
|
||||||
|
{
|
||||||
|
if (entry.IsDirectory)
|
||||||
|
{
|
||||||
|
continueRunning_ = events_.OnDirectoryFailure(targetName, ex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continueRunning_ = events_.OnFileFailure(targetName, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continueRunning_ = false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doExtraction && entry.IsFile)
|
||||||
|
{
|
||||||
|
ExtractFileEntry(entry, targetName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int MakeExternalAttributes(FileInfo info)
|
||||||
|
{
|
||||||
|
return (int)info.Attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool NameIsValid(string name)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(name) &&
|
||||||
|
(name.IndexOfAny(Path.GetInvalidPathChars()) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Internal Processing
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private bool continueRunning_;
|
||||||
|
private byte[] buffer_;
|
||||||
|
private ZipOutputStream outputStream_;
|
||||||
|
private ZipFile zipFile_;
|
||||||
|
private string sourceDirectory_;
|
||||||
|
private NameFilter fileFilter_;
|
||||||
|
private NameFilter directoryFilter_;
|
||||||
|
private Overwrite overwrite_;
|
||||||
|
private ConfirmOverwriteDelegate confirmDelegate_;
|
||||||
|
|
||||||
|
private bool restoreDateTimeOnExtract_;
|
||||||
|
private bool restoreAttributesOnExtract_;
|
||||||
|
private bool createEmptyDirectories_;
|
||||||
|
private FastZipEvents events_;
|
||||||
|
private IEntryFactory entryFactory_ = new ZipEntryFactory();
|
||||||
|
private INameTransform extractNameTransform_;
|
||||||
|
private UseZip64 useZip64_ = UseZip64.Dynamic;
|
||||||
|
private CompressionLevel compressionLevel_ = CompressionLevel.DEFAULT_COMPRESSION;
|
||||||
|
|
||||||
|
private string password_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Core;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines factory methods for creating new <see cref="ZipEntry"></see> values.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEntryFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="ZipEntry"/> for a file given its name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the file to create an entry for.</param>
|
||||||
|
/// <returns>Returns a <see cref="ZipEntry">file entry</see> based on the <paramref name="fileName"/> passed.</returns>
|
||||||
|
ZipEntry MakeFileEntry(string fileName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="ZipEntry"/> for a file given its name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the file to create an entry for.</param>
|
||||||
|
/// <param name="useFileSystem">If true get details from the file system if the file exists.</param>
|
||||||
|
/// <returns>Returns a <see cref="ZipEntry">file entry</see> based on the <paramref name="fileName"/> passed.</returns>
|
||||||
|
ZipEntry MakeFileEntry(string fileName, bool useFileSystem);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="ZipEntry"/> for a file given its actual name and optional override name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the file to create an entry for.</param>
|
||||||
|
/// <param name="entryName">An alternative name to be used for the new entry. Null if not applicable.</param>
|
||||||
|
/// <param name="useFileSystem">If true get details from the file system if the file exists.</param>
|
||||||
|
/// <returns>Returns a <see cref="ZipEntry">file entry</see> based on the <paramref name="fileName"/> passed.</returns>
|
||||||
|
ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="ZipEntry"/> for a directory given its name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directoryName">The name of the directory to create an entry for.</param>
|
||||||
|
/// <returns>Returns a <see cref="ZipEntry">directory entry</see> based on the <paramref name="directoryName"/> passed.</returns>
|
||||||
|
ZipEntry MakeDirectoryEntry(string directoryName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="ZipEntry"/> for a directory given its name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directoryName">The name of the directory to create an entry for.</param>
|
||||||
|
/// <param name="useFileSystem">If true get details from the file system for this directory if it exists.</param>
|
||||||
|
/// <returns>Returns a <see cref="ZipEntry">directory entry</see> based on the <paramref name="directoryName"/> passed.</returns>
|
||||||
|
ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the <see cref="INameTransform"></see> applicable.
|
||||||
|
/// </summary>
|
||||||
|
INameTransform NameTransform { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,258 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Core;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// WindowsNameTransform transforms <see cref="ZipFile"/> names to windows compatible ones.
|
||||||
|
/// </summary>
|
||||||
|
public class WindowsNameTransform : INameTransform
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum windows path name permitted.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This may not valid for all windows systems - CE?, etc but I cant find the equivalent in the CLR.</remarks>
|
||||||
|
private const int MaxPath = 260;
|
||||||
|
|
||||||
|
private string _baseDirectory;
|
||||||
|
private bool _trimIncomingPaths;
|
||||||
|
private char _replacementChar = '_';
|
||||||
|
private bool _allowParentTraversal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In this case we need Windows' invalid path characters.
|
||||||
|
/// Path.GetInvalidPathChars() only returns a subset invalid on all platforms.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly char[] InvalidEntryChars = new char[] {
|
||||||
|
'"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005',
|
||||||
|
'\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f',
|
||||||
|
'\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016',
|
||||||
|
'\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d',
|
||||||
|
'\u001e', '\u001f',
|
||||||
|
// extra characters for masks, etc.
|
||||||
|
'*', '?', ':'
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises a new instance of <see cref="WindowsNameTransform"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseDirectory"></param>
|
||||||
|
/// <param name="allowParentTraversal">Allow parent directory traversal in file paths (e.g. ../file)</param>
|
||||||
|
public WindowsNameTransform(string baseDirectory, bool allowParentTraversal = false)
|
||||||
|
{
|
||||||
|
BaseDirectory = baseDirectory ?? throw new ArgumentNullException(nameof(baseDirectory), "Directory name is invalid");
|
||||||
|
AllowParentTraversal = allowParentTraversal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a default instance of <see cref="WindowsNameTransform"/>
|
||||||
|
/// </summary>
|
||||||
|
public WindowsNameTransform()
|
||||||
|
{
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value containing the target directory to prefix values with.
|
||||||
|
/// </summary>
|
||||||
|
public string BaseDirectory
|
||||||
|
{
|
||||||
|
get { return _baseDirectory; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
_baseDirectory = Path.GetFullPath(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allow parent directory traversal in file paths (e.g. ../file)
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowParentTraversal
|
||||||
|
{
|
||||||
|
get => _allowParentTraversal;
|
||||||
|
set => _allowParentTraversal = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating wether paths on incoming values should be removed.
|
||||||
|
/// </summary>
|
||||||
|
public bool TrimIncomingPaths
|
||||||
|
{
|
||||||
|
get { return _trimIncomingPaths; }
|
||||||
|
set { _trimIncomingPaths = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transform a Zip directory name to a windows directory name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The directory name to transform.</param>
|
||||||
|
/// <returns>The transformed name.</returns>
|
||||||
|
public string TransformDirectory(string name)
|
||||||
|
{
|
||||||
|
name = TransformFile(name);
|
||||||
|
if (name.Length > 0)
|
||||||
|
{
|
||||||
|
while (name.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
name = name.Remove(name.Length - 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidNameException("Cannot have an empty directory name");
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transform a Zip format file name to a windows style one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The file name to transform.</param>
|
||||||
|
/// <returns>The transformed name.</returns>
|
||||||
|
public string TransformFile(string name)
|
||||||
|
{
|
||||||
|
if (name != null)
|
||||||
|
{
|
||||||
|
name = MakeValidName(name, _replacementChar);
|
||||||
|
|
||||||
|
if (_trimIncomingPaths)
|
||||||
|
{
|
||||||
|
name = Path.GetFileName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This may exceed windows length restrictions.
|
||||||
|
// Combine will throw a PathTooLongException in that case.
|
||||||
|
if (_baseDirectory != null)
|
||||||
|
{
|
||||||
|
name = Path.Combine(_baseDirectory, name);
|
||||||
|
|
||||||
|
if (!_allowParentTraversal && !Path.GetFullPath(name).StartsWith(_baseDirectory, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidNameException("Parent traversal in paths is not allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name = string.Empty;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a name to see if it is a valid name for a windows filename as extracted from a Zip archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to test.</param>
|
||||||
|
/// <returns>Returns true if the name is a valid zip name; false otherwise.</returns>
|
||||||
|
/// <remarks>The filename isnt a true windows path in some fundamental ways like no absolute paths, no rooted paths etc.</remarks>
|
||||||
|
public static bool IsValidName(string name)
|
||||||
|
{
|
||||||
|
bool result =
|
||||||
|
(name != null) &&
|
||||||
|
(name.Length <= MaxPath) &&
|
||||||
|
(string.Compare(name, MakeValidName(name, '_'), StringComparison.Ordinal) == 0)
|
||||||
|
;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Force a name to be valid by replacing invalid characters with a fixed value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to make valid</param>
|
||||||
|
/// <param name="replacement">The replacement character to use for any invalid characters.</param>
|
||||||
|
/// <returns>Returns a valid name</returns>
|
||||||
|
public static string MakeValidName(string name, char replacement)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
name = WindowsPathUtils.DropPathRoot(name.Replace("/", Path.DirectorySeparatorChar.ToString()));
|
||||||
|
|
||||||
|
// Drop any leading slashes.
|
||||||
|
while ((name.Length > 0) && (name[0] == Path.DirectorySeparatorChar))
|
||||||
|
{
|
||||||
|
name = name.Remove(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop any trailing slashes.
|
||||||
|
while ((name.Length > 0) && (name[name.Length - 1] == Path.DirectorySeparatorChar))
|
||||||
|
{
|
||||||
|
name = name.Remove(name.Length - 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert consecutive \\ characters to \
|
||||||
|
int index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal);
|
||||||
|
while (index >= 0)
|
||||||
|
{
|
||||||
|
name = name.Remove(index, 1);
|
||||||
|
index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert any invalid characters using the replacement one.
|
||||||
|
index = name.IndexOfAny(InvalidEntryChars);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder(name);
|
||||||
|
|
||||||
|
while (index >= 0)
|
||||||
|
{
|
||||||
|
builder[index] = replacement;
|
||||||
|
|
||||||
|
if (index >= name.Length)
|
||||||
|
{
|
||||||
|
index = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = name.IndexOfAny(InvalidEntryChars, index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name = builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for names greater than MaxPath characters.
|
||||||
|
// TODO: Were is CLR version of MaxPath defined? Can't find it in Environment.
|
||||||
|
if (name.Length > MaxPath)
|
||||||
|
{
|
||||||
|
throw new PathTooLongException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or set the character to replace invalid characters during transformations.
|
||||||
|
/// </summary>
|
||||||
|
public char Replacement
|
||||||
|
{
|
||||||
|
get { return _replacementChar; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
for (int i = 0; i < InvalidEntryChars.Length; ++i)
|
||||||
|
{
|
||||||
|
if (InvalidEntryChars[i] == value)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("invalid path character");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((value == Path.DirectorySeparatorChar) || (value == Path.AltDirectorySeparatorChar))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("invalid replacement character");
|
||||||
|
}
|
||||||
|
|
||||||
|
_replacementChar = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,497 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
#region Enumerations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines how entries are tested to see if they should use Zip64 extensions or not.
|
||||||
|
/// </summary>
|
||||||
|
public enum UseZip64
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Zip64 will not be forced on entries during processing.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>An entry can have this overridden if required <see cref="ZipEntry.ForceZip64"></see></remarks>
|
||||||
|
Off,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zip64 should always be used.
|
||||||
|
/// </summary>
|
||||||
|
On,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// #ZipLib will determine use based on entry values when added to archive.
|
||||||
|
/// </summary>
|
||||||
|
Dynamic,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The kind of compression used for an entry in an archive
|
||||||
|
/// </summary>
|
||||||
|
public enum CompressionMethod
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A direct copy of the file contents is held in the archive
|
||||||
|
/// </summary>
|
||||||
|
Stored = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common Zip compression method using a sliding dictionary
|
||||||
|
/// of up to 32KB and secondary compression from Huffman/Shannon-Fano trees
|
||||||
|
/// </summary>
|
||||||
|
Deflated = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An extension to deflate with a 64KB window. Not supported by #Zip currently
|
||||||
|
/// </summary>
|
||||||
|
Deflate64 = 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BZip2 compression. Not supported by #Zip.
|
||||||
|
/// </summary>
|
||||||
|
BZip2 = 12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WinZip special for AES encryption, Now supported by #Zip.
|
||||||
|
/// </summary>
|
||||||
|
WinZipAES = 99,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies the encryption algorithm used for an entry
|
||||||
|
/// </summary>
|
||||||
|
public enum EncryptionAlgorithm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No encryption has been used.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encrypted using PKZIP 2.0 or 'classic' encryption.
|
||||||
|
/// </summary>
|
||||||
|
PkzipClassic = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DES encryption has been used.
|
||||||
|
/// </summary>
|
||||||
|
Des = 0x6601,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RC2 encryption has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
RC2 = 0x6602,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triple DES encryption with 168 bit keys has been used for this entry.
|
||||||
|
/// </summary>
|
||||||
|
TripleDes168 = 0x6603,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triple DES with 112 bit keys has been used for this entry.
|
||||||
|
/// </summary>
|
||||||
|
TripleDes112 = 0x6609,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AES 128 has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
Aes128 = 0x660e,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AES 192 has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
Aes192 = 0x660f,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AES 256 has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
Aes256 = 0x6610,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RC2 corrected has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
RC2Corrected = 0x6702,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blowfish has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
Blowfish = 0x6720,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Twofish has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
Twofish = 0x6721,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RC4 has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
RC4 = 0x6801,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An unknown algorithm has been used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
Unknown = 0xffff
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contents of the general bit flags field for an archive entry.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum GeneralBitFlags
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 0 if set indicates that the file is encrypted
|
||||||
|
/// </summary>
|
||||||
|
Encrypted = 0x0001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bits 1 and 2 - Two bits defining the compression method (only for Method 6 Imploding and 8,9 Deflating)
|
||||||
|
/// </summary>
|
||||||
|
Method = 0x0006,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 3 if set indicates a trailing data desciptor is appended to the entry data
|
||||||
|
/// </summary>
|
||||||
|
Descriptor = 0x0008,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 4 is reserved for use with method 8 for enhanced deflation
|
||||||
|
/// </summary>
|
||||||
|
ReservedPKware4 = 0x0010,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 5 if set indicates the file contains Pkzip compressed patched data.
|
||||||
|
/// Requires version 2.7 or greater.
|
||||||
|
/// </summary>
|
||||||
|
Patched = 0x0020,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 6 if set indicates strong encryption has been used for this entry.
|
||||||
|
/// </summary>
|
||||||
|
StrongEncryption = 0x0040,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 7 is currently unused
|
||||||
|
/// </summary>
|
||||||
|
Unused7 = 0x0080,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 8 is currently unused
|
||||||
|
/// </summary>
|
||||||
|
Unused8 = 0x0100,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 9 is currently unused
|
||||||
|
/// </summary>
|
||||||
|
Unused9 = 0x0200,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 10 is currently unused
|
||||||
|
/// </summary>
|
||||||
|
Unused10 = 0x0400,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 11 if set indicates the filename and
|
||||||
|
/// comment fields for this file must be encoded using UTF-8.
|
||||||
|
/// </summary>
|
||||||
|
UnicodeText = 0x0800,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 12 is documented as being reserved by PKware for enhanced compression.
|
||||||
|
/// </summary>
|
||||||
|
EnhancedCompress = 0x1000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 13 if set indicates that values in the local header are masked to hide
|
||||||
|
/// their actual values, and the central directory is encrypted.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Used when encrypting the central directory contents.
|
||||||
|
/// </remarks>
|
||||||
|
HeaderMasked = 0x2000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 14 is documented as being reserved for use by PKware
|
||||||
|
/// </summary>
|
||||||
|
ReservedPkware14 = 0x4000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bit 15 is documented as being reserved for use by PKware
|
||||||
|
/// </summary>
|
||||||
|
ReservedPkware15 = 0x8000
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Enumerations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class contains constants used for Zip format files
|
||||||
|
/// </summary>
|
||||||
|
public static class ZipConstants
|
||||||
|
{
|
||||||
|
#region Versions
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version made by field for entries in the central header when created by this library
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is also the Zip version for the library when comparing against the version required to extract
|
||||||
|
/// for an entry. See <see cref="ZipEntry.CanDecompress"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public const int VersionMadeBy = 51; // was 45 before AES
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version made by field for entries in the central header when created by this library
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is also the Zip version for the library when comparing against the version required to extract
|
||||||
|
/// for an entry. See <see cref="ZipInputStream.CanDecompressEntry">ZipInputStream.CanDecompressEntry</see>.
|
||||||
|
/// </remarks>
|
||||||
|
[Obsolete("Use VersionMadeBy instead")]
|
||||||
|
public const int VERSION_MADE_BY = 51;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum version required to support strong encryption
|
||||||
|
/// </summary>
|
||||||
|
public const int VersionStrongEncryption = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum version required to support strong encryption
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use VersionStrongEncryption instead")]
|
||||||
|
public const int VERSION_STRONG_ENCRYPTION = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Version indicating AES encryption
|
||||||
|
/// </summary>
|
||||||
|
public const int VERSION_AES = 51;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version required for Zip64 extensions (4.5 or higher)
|
||||||
|
/// </summary>
|
||||||
|
public const int VersionZip64 = 45;
|
||||||
|
|
||||||
|
#endregion Versions
|
||||||
|
|
||||||
|
#region Header Sizes
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of local entry header (excluding variable length fields at end)
|
||||||
|
/// </summary>
|
||||||
|
public const int LocalHeaderBaseSize = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of local entry header (excluding variable length fields at end)
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use LocalHeaderBaseSize instead")]
|
||||||
|
public const int LOCHDR = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of Zip64 data descriptor
|
||||||
|
/// </summary>
|
||||||
|
public const int Zip64DataDescriptorSize = 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of data descriptor
|
||||||
|
/// </summary>
|
||||||
|
public const int DataDescriptorSize = 16;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of data descriptor
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use DataDescriptorSize instead")]
|
||||||
|
public const int EXTHDR = 16;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of central header entry (excluding variable fields)
|
||||||
|
/// </summary>
|
||||||
|
public const int CentralHeaderBaseSize = 46;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of central header entry
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use CentralHeaderBaseSize instead")]
|
||||||
|
public const int CENHDR = 46;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of end of central record (excluding variable fields)
|
||||||
|
/// </summary>
|
||||||
|
public const int EndOfCentralRecordBaseSize = 22;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of end of central record (excluding variable fields)
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use EndOfCentralRecordBaseSize instead")]
|
||||||
|
public const int ENDHDR = 22;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of 'classic' cryptographic header stored before any entry data
|
||||||
|
/// </summary>
|
||||||
|
public const int CryptoHeaderSize = 12;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of cryptographic header stored before entry data
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use CryptoHeaderSize instead")]
|
||||||
|
public const int CRYPTO_HEADER_SIZE = 12;
|
||||||
|
|
||||||
|
#endregion Header Sizes
|
||||||
|
|
||||||
|
#region Header Signatures
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for local entry header
|
||||||
|
/// </summary>
|
||||||
|
public const int LocalHeaderSignature = 'P' | ('K' << 8) | (3 << 16) | (4 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for local entry header
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use LocalHeaderSignature instead")]
|
||||||
|
public const int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for spanning entry
|
||||||
|
/// </summary>
|
||||||
|
public const int SpanningSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for spanning entry
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use SpanningSignature instead")]
|
||||||
|
public const int SPANNINGSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for temporary spanning entry
|
||||||
|
/// </summary>
|
||||||
|
public const int SpanningTempSignature = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for temporary spanning entry
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use SpanningTempSignature instead")]
|
||||||
|
public const int SPANTEMPSIG = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for data descriptor
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is only used where the length, Crc, or compressed size isnt known when the
|
||||||
|
/// entry is created and the output stream doesnt support seeking.
|
||||||
|
/// The local entry cannot be 'patched' with the correct values in this case
|
||||||
|
/// so the values are recorded after the data prefixed by this header, as well as in the central directory.
|
||||||
|
/// </remarks>
|
||||||
|
public const int DataDescriptorSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for data descriptor
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is only used where the length, Crc, or compressed size isnt known when the
|
||||||
|
/// entry is created and the output stream doesnt support seeking.
|
||||||
|
/// The local entry cannot be 'patched' with the correct values in this case
|
||||||
|
/// so the values are recorded after the data prefixed by this header, as well as in the central directory.
|
||||||
|
/// </remarks>
|
||||||
|
[Obsolete("Use DataDescriptorSignature instead")]
|
||||||
|
public const int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for central header
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use CentralHeaderSignature instead")]
|
||||||
|
public const int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for central header
|
||||||
|
/// </summary>
|
||||||
|
public const int CentralHeaderSignature = 'P' | ('K' << 8) | (1 << 16) | (2 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for Zip64 central file header
|
||||||
|
/// </summary>
|
||||||
|
public const int Zip64CentralFileHeaderSignature = 'P' | ('K' << 8) | (6 << 16) | (6 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for Zip64 central file header
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use Zip64CentralFileHeaderSignature instead")]
|
||||||
|
public const int CENSIG64 = 'P' | ('K' << 8) | (6 << 16) | (6 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for Zip64 central directory locator
|
||||||
|
/// </summary>
|
||||||
|
public const int Zip64CentralDirLocatorSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature for archive extra data signature (were headers are encrypted).
|
||||||
|
/// </summary>
|
||||||
|
public const int ArchiveExtraDataSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Central header digitial signature
|
||||||
|
/// </summary>
|
||||||
|
public const int CentralHeaderDigitalSignature = 'P' | ('K' << 8) | (5 << 16) | (5 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Central header digitial signature
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use CentralHeaderDigitalSignaure instead")]
|
||||||
|
public const int CENDIGITALSIG = 'P' | ('K' << 8) | (5 << 16) | (5 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// End of central directory record signature
|
||||||
|
/// </summary>
|
||||||
|
public const int EndOfCentralDirectorySignature = 'P' | ('K' << 8) | (5 << 16) | (6 << 24);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// End of central directory record signature
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use EndOfCentralDirectorySignature instead")]
|
||||||
|
public const int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24);
|
||||||
|
|
||||||
|
#endregion Header Signatures
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default encoding used for string conversion. 0 gives the default system OEM code page.
|
||||||
|
/// Using the default code page isnt the full solution neccessarily
|
||||||
|
/// there are many variable factors, codepage 850 is often a good choice for
|
||||||
|
/// European users, however be careful about compatability.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use ZipStrings instead")]
|
||||||
|
public static int DefaultCodePage
|
||||||
|
{
|
||||||
|
get => ZipStrings.CodePage;
|
||||||
|
set => ZipStrings.CodePage = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Depracated wrapper for <see cref="ZipStrings.ConvertToString(byte[], int)"/></summary>
|
||||||
|
[Obsolete("Use ZipStrings.ConvertToString instead")]
|
||||||
|
public static string ConvertToString(byte[] data, int count)
|
||||||
|
=> ZipStrings.ConvertToString(data, count);
|
||||||
|
|
||||||
|
/// <summary> Depracated wrapper for <see cref="ZipStrings.ConvertToString(byte[])"/></summary>
|
||||||
|
[Obsolete("Use ZipStrings.ConvertToString instead")]
|
||||||
|
public static string ConvertToString(byte[] data)
|
||||||
|
=> ZipStrings.ConvertToString(data);
|
||||||
|
|
||||||
|
/// <summary> Depracated wrapper for <see cref="ZipStrings.ConvertToStringExt(int, byte[], int)"/></summary>
|
||||||
|
[Obsolete("Use ZipStrings.ConvertToStringExt instead")]
|
||||||
|
public static string ConvertToStringExt(int flags, byte[] data, int count)
|
||||||
|
=> ZipStrings.ConvertToStringExt(flags, data, count);
|
||||||
|
|
||||||
|
/// <summary> Depracated wrapper for <see cref="ZipStrings.ConvertToStringExt(int, byte[])"/></summary>
|
||||||
|
[Obsolete("Use ZipStrings.ConvertToStringExt instead")]
|
||||||
|
public static string ConvertToStringExt(int flags, byte[] data)
|
||||||
|
=> ZipStrings.ConvertToStringExt(flags, data);
|
||||||
|
|
||||||
|
/// <summary> Depracated wrapper for <see cref="ZipStrings.ConvertToArray(string)"/></summary>
|
||||||
|
[Obsolete("Use ZipStrings.ConvertToArray instead")]
|
||||||
|
public static byte[] ConvertToArray(string str)
|
||||||
|
=> ZipStrings.ConvertToArray(str);
|
||||||
|
|
||||||
|
/// <summary> Depracated wrapper for <see cref="ZipStrings.ConvertToArray(int, string)"/></summary>
|
||||||
|
[Obsolete("Use ZipStrings.ConvertToArray instead")]
|
||||||
|
public static byte[] ConvertToArray(int flags, string str)
|
||||||
|
=> ZipStrings.ConvertToArray(flags, str);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,375 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Core;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Basic implementation of <see cref="IEntryFactory"></see>
|
||||||
|
/// </summary>
|
||||||
|
public class ZipEntryFactory : IEntryFactory
|
||||||
|
{
|
||||||
|
#region Enumerations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the possible values to be used for the <see cref="ZipEntry.DateTime"/>.
|
||||||
|
/// </summary>
|
||||||
|
public enum TimeSetting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Use the recorded LastWriteTime value for the file.
|
||||||
|
/// </summary>
|
||||||
|
LastWriteTime,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use the recorded LastWriteTimeUtc value for the file
|
||||||
|
/// </summary>
|
||||||
|
LastWriteTimeUtc,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use the recorded CreateTime value for the file.
|
||||||
|
/// </summary>
|
||||||
|
CreateTime,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use the recorded CreateTimeUtc value for the file.
|
||||||
|
/// </summary>
|
||||||
|
CreateTimeUtc,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use the recorded LastAccessTime value for the file.
|
||||||
|
/// </summary>
|
||||||
|
LastAccessTime,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use the recorded LastAccessTimeUtc value for the file.
|
||||||
|
/// </summary>
|
||||||
|
LastAccessTimeUtc,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use a fixed value.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The actual <see cref="DateTime"/> value used can be
|
||||||
|
/// specified via the <see cref="ZipEntryFactory(DateTime)"/> constructor or
|
||||||
|
/// using the <see cref="ZipEntryFactory(TimeSetting)"/> with the setting set
|
||||||
|
/// to <see cref="TimeSetting.Fixed"/> which will use the <see cref="DateTime"/> when this class was constructed.
|
||||||
|
/// The <see cref="FixedDateTime"/> property can also be used to set this value.</remarks>
|
||||||
|
Fixed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Enumerations
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of the <see cref="ZipEntryFactory"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>A default <see cref="INameTransform"/>, and the LastWriteTime for files is used.</remarks>
|
||||||
|
public ZipEntryFactory()
|
||||||
|
{
|
||||||
|
nameTransform_ = new ZipNameTransform();
|
||||||
|
isUnicodeText_ = ZipStrings.UseUnicode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ZipEntryFactory"/> using the specified <see cref="TimeSetting"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeSetting">The <see cref="TimeSetting">time setting</see> to use when creating <see cref="ZipEntry">Zip entries</see>.</param>
|
||||||
|
public ZipEntryFactory(TimeSetting timeSetting) : this()
|
||||||
|
{
|
||||||
|
timeSetting_ = timeSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ZipEntryFactory"/> using the specified <see cref="DateTime"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">The time to set all <see cref="ZipEntry.DateTime"/> values to.</param>
|
||||||
|
public ZipEntryFactory(DateTime time) : this()
|
||||||
|
{
|
||||||
|
timeSetting_ = TimeSetting.Fixed;
|
||||||
|
FixedDateTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set the <see cref="INameTransform"/> to be used when creating new <see cref="ZipEntry"/> values.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Setting this property to null will cause a default <see cref="ZipNameTransform">name transform</see> to be used.
|
||||||
|
/// </remarks>
|
||||||
|
public INameTransform NameTransform
|
||||||
|
{
|
||||||
|
get { return nameTransform_; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
nameTransform_ = new ZipNameTransform();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nameTransform_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set the <see cref="TimeSetting"/> in use.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSetting Setting
|
||||||
|
{
|
||||||
|
get { return timeSetting_; }
|
||||||
|
set { timeSetting_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set the <see cref="DateTime"/> value to use when <see cref="Setting"/> is set to <see cref="TimeSetting.Fixed"/>
|
||||||
|
/// </summary>
|
||||||
|
public DateTime FixedDateTime
|
||||||
|
{
|
||||||
|
get { return fixedDateTime_; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.Year < 1970)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Value is too old to be valid", nameof(value));
|
||||||
|
}
|
||||||
|
fixedDateTime_ = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A bitmask defining the attributes to be retrieved from the actual file.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The default is to get all possible attributes from the actual file.</remarks>
|
||||||
|
public int GetAttributes
|
||||||
|
{
|
||||||
|
get { return getAttributes_; }
|
||||||
|
set { getAttributes_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A bitmask defining which attributes are to be set on.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>By default no attributes are set on.</remarks>
|
||||||
|
public int SetAttributes
|
||||||
|
{
|
||||||
|
get { return setAttributes_; }
|
||||||
|
set { setAttributes_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get set a value indicating wether unidoce text should be set on.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsUnicodeText
|
||||||
|
{
|
||||||
|
get { return isUnicodeText_; }
|
||||||
|
set { isUnicodeText_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Properties
|
||||||
|
|
||||||
|
#region IEntryFactory Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make a new <see cref="ZipEntry"/> for a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the file to create a new entry for.</param>
|
||||||
|
/// <returns>Returns a new <see cref="ZipEntry"/> based on the <paramref name="fileName"/>.</returns>
|
||||||
|
public ZipEntry MakeFileEntry(string fileName)
|
||||||
|
{
|
||||||
|
return MakeFileEntry(fileName, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make a new <see cref="ZipEntry"/> for a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the file to create a new entry for.</param>
|
||||||
|
/// <param name="useFileSystem">If true entry detail is retrieved from the file system if the file exists.</param>
|
||||||
|
/// <returns>Returns a new <see cref="ZipEntry"/> based on the <paramref name="fileName"/>.</returns>
|
||||||
|
public ZipEntry MakeFileEntry(string fileName, bool useFileSystem)
|
||||||
|
{
|
||||||
|
return MakeFileEntry(fileName, null, useFileSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make a new <see cref="ZipEntry"/> from a name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the file to create a new entry for.</param>
|
||||||
|
/// <param name="entryName">An alternative name to be used for the new entry. Null if not applicable.</param>
|
||||||
|
/// <param name="useFileSystem">If true entry detail is retrieved from the file system if the file exists.</param>
|
||||||
|
/// <returns>Returns a new <see cref="ZipEntry"/> based on the <paramref name="fileName"/>.</returns>
|
||||||
|
public ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem)
|
||||||
|
{
|
||||||
|
var result = new ZipEntry(nameTransform_.TransformFile(!string.IsNullOrEmpty(entryName) ? entryName : fileName));
|
||||||
|
result.IsUnicodeText = isUnicodeText_;
|
||||||
|
|
||||||
|
int externalAttributes = 0;
|
||||||
|
bool useAttributes = (setAttributes_ != 0);
|
||||||
|
|
||||||
|
FileInfo fi = null;
|
||||||
|
if (useFileSystem)
|
||||||
|
{
|
||||||
|
fi = new FileInfo(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fi != null) && fi.Exists)
|
||||||
|
{
|
||||||
|
switch (timeSetting_)
|
||||||
|
{
|
||||||
|
case TimeSetting.CreateTime:
|
||||||
|
result.DateTime = fi.CreationTime;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.CreateTimeUtc:
|
||||||
|
result.DateTime = fi.CreationTimeUtc;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.LastAccessTime:
|
||||||
|
result.DateTime = fi.LastAccessTime;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.LastAccessTimeUtc:
|
||||||
|
result.DateTime = fi.LastAccessTimeUtc;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.LastWriteTime:
|
||||||
|
result.DateTime = fi.LastWriteTime;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.LastWriteTimeUtc:
|
||||||
|
result.DateTime = fi.LastWriteTimeUtc;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.Fixed:
|
||||||
|
result.DateTime = fixedDateTime_;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ZipException("Unhandled time setting in MakeFileEntry");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Size = fi.Length;
|
||||||
|
|
||||||
|
useAttributes = true;
|
||||||
|
externalAttributes = ((int)fi.Attributes & getAttributes_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (timeSetting_ == TimeSetting.Fixed)
|
||||||
|
{
|
||||||
|
result.DateTime = fixedDateTime_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useAttributes)
|
||||||
|
{
|
||||||
|
externalAttributes |= setAttributes_;
|
||||||
|
result.ExternalFileAttributes = externalAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make a new <see cref="ZipEntry"></see> for a directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directoryName">The raw untransformed name for the new directory</param>
|
||||||
|
/// <returns>Returns a new <see cref="ZipEntry"></see> representing a directory.</returns>
|
||||||
|
public ZipEntry MakeDirectoryEntry(string directoryName)
|
||||||
|
{
|
||||||
|
return MakeDirectoryEntry(directoryName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make a new <see cref="ZipEntry"></see> for a directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directoryName">The raw untransformed name for the new directory</param>
|
||||||
|
/// <param name="useFileSystem">If true entry detail is retrieved from the file system if the file exists.</param>
|
||||||
|
/// <returns>Returns a new <see cref="ZipEntry"></see> representing a directory.</returns>
|
||||||
|
public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem)
|
||||||
|
{
|
||||||
|
var result = new ZipEntry(nameTransform_.TransformDirectory(directoryName));
|
||||||
|
result.IsUnicodeText = isUnicodeText_;
|
||||||
|
result.Size = 0;
|
||||||
|
|
||||||
|
int externalAttributes = 0;
|
||||||
|
|
||||||
|
DirectoryInfo di = null;
|
||||||
|
|
||||||
|
if (useFileSystem)
|
||||||
|
{
|
||||||
|
di = new DirectoryInfo(directoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((di != null) && di.Exists)
|
||||||
|
{
|
||||||
|
switch (timeSetting_)
|
||||||
|
{
|
||||||
|
case TimeSetting.CreateTime:
|
||||||
|
result.DateTime = di.CreationTime;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.CreateTimeUtc:
|
||||||
|
result.DateTime = di.CreationTimeUtc;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.LastAccessTime:
|
||||||
|
result.DateTime = di.LastAccessTime;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.LastAccessTimeUtc:
|
||||||
|
result.DateTime = di.LastAccessTimeUtc;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.LastWriteTime:
|
||||||
|
result.DateTime = di.LastWriteTime;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.LastWriteTimeUtc:
|
||||||
|
result.DateTime = di.LastWriteTimeUtc;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSetting.Fixed:
|
||||||
|
result.DateTime = fixedDateTime_;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ZipException("Unhandled time setting in MakeDirectoryEntry");
|
||||||
|
}
|
||||||
|
|
||||||
|
externalAttributes = ((int)di.Attributes & getAttributes_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (timeSetting_ == TimeSetting.Fixed)
|
||||||
|
{
|
||||||
|
result.DateTime = fixedDateTime_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always set directory attribute on.
|
||||||
|
externalAttributes |= (setAttributes_ | 16);
|
||||||
|
result.ExternalFileAttributes = externalAttributes;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion IEntryFactory Members
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private INameTransform nameTransform_;
|
||||||
|
private DateTime fixedDateTime_ = DateTime.Now;
|
||||||
|
private TimeSetting timeSetting_;
|
||||||
|
private bool isUnicodeText_;
|
||||||
|
|
||||||
|
private int getAttributes_ = -1;
|
||||||
|
private int setAttributes_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ZipException represents exceptions specific to Zip classes and code.
|
||||||
|
/// </summary>
|
||||||
|
public class ZipException : SharpZipBaseException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ZipException" />.
|
||||||
|
/// </summary>
|
||||||
|
public ZipException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ZipException" /> with its message string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
public ZipException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ZipException" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||||
|
/// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param>
|
||||||
|
public ZipException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,979 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
// TODO: Sort out wether tagged data is useful and what a good implementation might look like.
|
||||||
|
// Its just a sketch of an idea at the moment.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ExtraData tagged value interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITaggedData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the ID for this tagged data value.
|
||||||
|
/// </summary>
|
||||||
|
short TagID { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the contents of this instance from the data passed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to extract contents from.</param>
|
||||||
|
/// <param name="offset">The offset to begin extracting data from.</param>
|
||||||
|
/// <param name="count">The number of bytes to extract.</param>
|
||||||
|
void SetData(byte[] data, int offset, int count);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the data representing this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the data for this instance.</returns>
|
||||||
|
byte[] GetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A raw binary tagged value
|
||||||
|
/// </summary>
|
||||||
|
public class RawTaggedData : ITaggedData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">The tag ID.</param>
|
||||||
|
public RawTaggedData(short tag)
|
||||||
|
{
|
||||||
|
_tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ITaggedData Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the ID for this tagged data value.
|
||||||
|
/// </summary>
|
||||||
|
public short TagID
|
||||||
|
{
|
||||||
|
get { return _tag; }
|
||||||
|
set { _tag = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the data from the raw values provided.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The raw data to extract values from.</param>
|
||||||
|
/// <param name="offset">The index to start extracting values from.</param>
|
||||||
|
/// <param name="count">The number of bytes available.</param>
|
||||||
|
public void SetData(byte[] data, int offset, int count)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
_data = new byte[count];
|
||||||
|
Array.Copy(data, offset, _data, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the binary data representing this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The raw binary data representing this instance.</returns>
|
||||||
|
public byte[] GetData()
|
||||||
|
{
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion ITaggedData Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get /set the binary data representing this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The raw binary data representing this instance.</returns>
|
||||||
|
public byte[] Data
|
||||||
|
{
|
||||||
|
get { return _data; }
|
||||||
|
set { _data = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tag ID for this instance.
|
||||||
|
/// </summary>
|
||||||
|
private short _tag;
|
||||||
|
|
||||||
|
private byte[] _data;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class representing extended unix date time values.
|
||||||
|
/// </summary>
|
||||||
|
public class ExtendedUnixData : ITaggedData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Flags indicate which values are included in this instance.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum Flags : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The modification time is included
|
||||||
|
/// </summary>
|
||||||
|
ModificationTime = 0x01,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The access time is included
|
||||||
|
/// </summary>
|
||||||
|
AccessTime = 0x02,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The create time is included.
|
||||||
|
/// </summary>
|
||||||
|
CreateTime = 0x04,
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ITaggedData Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the ID
|
||||||
|
/// </summary>
|
||||||
|
public short TagID
|
||||||
|
{
|
||||||
|
get { return 0x5455; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the data from the raw values provided.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The raw data to extract values from.</param>
|
||||||
|
/// <param name="index">The index to start extracting values from.</param>
|
||||||
|
/// <param name="count">The number of bytes available.</param>
|
||||||
|
public void SetData(byte[] data, int index, int count)
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream(data, index, count, false))
|
||||||
|
using (ZipHelperStream helperStream = new ZipHelperStream(ms))
|
||||||
|
{
|
||||||
|
// bit 0 if set, modification time is present
|
||||||
|
// bit 1 if set, access time is present
|
||||||
|
// bit 2 if set, creation time is present
|
||||||
|
|
||||||
|
_flags = (Flags)helperStream.ReadByte();
|
||||||
|
if (((_flags & Flags.ModificationTime) != 0))
|
||||||
|
{
|
||||||
|
int iTime = helperStream.ReadLEInt();
|
||||||
|
|
||||||
|
_modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
|
||||||
|
new TimeSpan(0, 0, 0, iTime, 0);
|
||||||
|
|
||||||
|
// Central-header version is truncated after modification time
|
||||||
|
if (count <= 5) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((_flags & Flags.AccessTime) != 0)
|
||||||
|
{
|
||||||
|
int iTime = helperStream.ReadLEInt();
|
||||||
|
|
||||||
|
_lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
|
||||||
|
new TimeSpan(0, 0, 0, iTime, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((_flags & Flags.CreateTime) != 0)
|
||||||
|
{
|
||||||
|
int iTime = helperStream.ReadLEInt();
|
||||||
|
|
||||||
|
_createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
|
||||||
|
new TimeSpan(0, 0, 0, iTime, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the binary data representing this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The raw binary data representing this instance.</returns>
|
||||||
|
public byte[] GetData()
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
using (ZipHelperStream helperStream = new ZipHelperStream(ms))
|
||||||
|
{
|
||||||
|
helperStream.IsStreamOwner = false;
|
||||||
|
helperStream.WriteByte((byte)_flags); // Flags
|
||||||
|
if ((_flags & Flags.ModificationTime) != 0)
|
||||||
|
{
|
||||||
|
TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
var seconds = (int)span.TotalSeconds;
|
||||||
|
helperStream.WriteLEInt(seconds);
|
||||||
|
}
|
||||||
|
if ((_flags & Flags.AccessTime) != 0)
|
||||||
|
{
|
||||||
|
TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
var seconds = (int)span.TotalSeconds;
|
||||||
|
helperStream.WriteLEInt(seconds);
|
||||||
|
}
|
||||||
|
if ((_flags & Flags.CreateTime) != 0)
|
||||||
|
{
|
||||||
|
TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
var seconds = (int)span.TotalSeconds;
|
||||||
|
helperStream.WriteLEInt(seconds);
|
||||||
|
}
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion ITaggedData Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a <see cref="DateTime"> value to see if is valid and can be represented here.</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The <see cref="DateTime">value</see> to test.</param>
|
||||||
|
/// <returns>Returns true if the value is valid and can be represented; false if not.</returns>
|
||||||
|
/// <remarks>The standard Unix time is a signed integer data type, directly encoding the Unix time number,
|
||||||
|
/// which is the number of seconds since 1970-01-01.
|
||||||
|
/// Being 32 bits means the values here cover a range of about 136 years.
|
||||||
|
/// The minimum representable time is 1901-12-13 20:45:52,
|
||||||
|
/// and the maximum representable time is 2038-01-19 03:14:07.
|
||||||
|
/// </remarks>
|
||||||
|
public static bool IsValidValue(DateTime value)
|
||||||
|
{
|
||||||
|
return ((value >= new DateTime(1901, 12, 13, 20, 45, 52)) ||
|
||||||
|
(value <= new DateTime(2038, 1, 19, 03, 14, 07)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get /set the Modification Time
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||||
|
/// <seealso cref="IsValidValue"></seealso>
|
||||||
|
public DateTime ModificationTime
|
||||||
|
{
|
||||||
|
get { return _modificationTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!IsValidValue(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
_flags |= Flags.ModificationTime;
|
||||||
|
_modificationTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set the Access Time
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||||
|
/// <seealso cref="IsValidValue"></seealso>
|
||||||
|
public DateTime AccessTime
|
||||||
|
{
|
||||||
|
get { return _lastAccessTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!IsValidValue(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
_flags |= Flags.AccessTime;
|
||||||
|
_lastAccessTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / Set the Create Time
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||||
|
/// <seealso cref="IsValidValue"></seealso>
|
||||||
|
public DateTime CreateTime
|
||||||
|
{
|
||||||
|
get { return _createTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!IsValidValue(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
_flags |= Flags.CreateTime;
|
||||||
|
_createTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the <see cref="Flags">values</see> to include.
|
||||||
|
/// </summary>
|
||||||
|
public Flags Include
|
||||||
|
{
|
||||||
|
get { return _flags; }
|
||||||
|
set { _flags = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private Flags _flags;
|
||||||
|
private DateTime _modificationTime = new DateTime(1970, 1, 1);
|
||||||
|
private DateTime _lastAccessTime = new DateTime(1970, 1, 1);
|
||||||
|
private DateTime _createTime = new DateTime(1970, 1, 1);
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class handling NT date time values.
|
||||||
|
/// </summary>
|
||||||
|
public class NTTaggedData : ITaggedData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the ID for this tagged data value.
|
||||||
|
/// </summary>
|
||||||
|
public short TagID
|
||||||
|
{
|
||||||
|
get { return 10; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the data from the raw values provided.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The raw data to extract values from.</param>
|
||||||
|
/// <param name="index">The index to start extracting values from.</param>
|
||||||
|
/// <param name="count">The number of bytes available.</param>
|
||||||
|
public void SetData(byte[] data, int index, int count)
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream(data, index, count, false))
|
||||||
|
using (ZipHelperStream helperStream = new ZipHelperStream(ms))
|
||||||
|
{
|
||||||
|
helperStream.ReadLEInt(); // Reserved
|
||||||
|
while (helperStream.Position < helperStream.Length)
|
||||||
|
{
|
||||||
|
int ntfsTag = helperStream.ReadLEShort();
|
||||||
|
int ntfsLength = helperStream.ReadLEShort();
|
||||||
|
if (ntfsTag == 1)
|
||||||
|
{
|
||||||
|
if (ntfsLength >= 24)
|
||||||
|
{
|
||||||
|
long lastModificationTicks = helperStream.ReadLELong();
|
||||||
|
_lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks);
|
||||||
|
|
||||||
|
long lastAccessTicks = helperStream.ReadLELong();
|
||||||
|
_lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks);
|
||||||
|
|
||||||
|
long createTimeTicks = helperStream.ReadLELong();
|
||||||
|
_createTime = DateTime.FromFileTimeUtc(createTimeTicks);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// An unknown NTFS tag so simply skip it.
|
||||||
|
helperStream.Seek(ntfsLength, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the binary data representing this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The raw binary data representing this instance.</returns>
|
||||||
|
public byte[] GetData()
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
using (ZipHelperStream helperStream = new ZipHelperStream(ms))
|
||||||
|
{
|
||||||
|
helperStream.IsStreamOwner = false;
|
||||||
|
helperStream.WriteLEInt(0); // Reserved
|
||||||
|
helperStream.WriteLEShort(1); // Tag
|
||||||
|
helperStream.WriteLEShort(24); // Length = 3 x 8.
|
||||||
|
helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc());
|
||||||
|
helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc());
|
||||||
|
helperStream.WriteLELong(_createTime.ToFileTimeUtc());
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a <see cref="DateTime"> valuie to see if is valid and can be represented here.</see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The <see cref="DateTime">value</see> to test.</param>
|
||||||
|
/// <returns>Returns true if the value is valid and can be represented; false if not.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// NTFS filetimes are 64-bit unsigned integers, stored in Intel
|
||||||
|
/// (least significant byte first) byte order. They determine the
|
||||||
|
/// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch",
|
||||||
|
/// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit
|
||||||
|
/// </remarks>
|
||||||
|
public static bool IsValidValue(DateTime value)
|
||||||
|
{
|
||||||
|
bool result = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value.ToFileTimeUtc();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the <see cref="DateTime">last modification time</see>.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastModificationTime
|
||||||
|
{
|
||||||
|
get { return _lastModificationTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!IsValidValue(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
_lastModificationTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get /set the <see cref="DateTime">create time</see>
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreateTime
|
||||||
|
{
|
||||||
|
get { return _createTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!IsValidValue(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
_createTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get /set the <see cref="DateTime">last access time</see>.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastAccessTime
|
||||||
|
{
|
||||||
|
get { return _lastAccessTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!IsValidValue(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
_lastAccessTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0);
|
||||||
|
private DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0);
|
||||||
|
private DateTime _createTime = DateTime.FromFileTimeUtc(0);
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A factory that creates <see cref="ITaggedData">tagged data</see> instances.
|
||||||
|
/// </summary>
|
||||||
|
internal interface ITaggedDataFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get data for a specific tag value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">The tag ID to find.</param>
|
||||||
|
/// <param name="data">The data to search.</param>
|
||||||
|
/// <param name="offset">The offset to begin extracting data from.</param>
|
||||||
|
/// <param name="count">The number of bytes to extract.</param>
|
||||||
|
/// <returns>The located <see cref="ITaggedData">value found</see>, or null if not found.</returns>
|
||||||
|
ITaggedData Create(short tag, byte[] data, int offset, int count);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// <summary>
|
||||||
|
/// A class to handle the extra data field for Zip entries
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Extra data contains 0 or more values each prefixed by a header tag and length.
|
||||||
|
/// They contain zero or more bytes of actual data.
|
||||||
|
/// The data is held internally using a copy on write strategy. This is more efficient but
|
||||||
|
/// means that for extra data created by passing in data can have the values modified by the caller
|
||||||
|
/// in some circumstances.
|
||||||
|
/// </remarks>
|
||||||
|
sealed public class ZipExtraData : IDisposable
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a default instance.
|
||||||
|
/// </summary>
|
||||||
|
public ZipExtraData()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise with known extra data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The extra data.</param>
|
||||||
|
public ZipExtraData(byte[] data)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
_data = new byte[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the raw extra data value
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the raw byte[] extra data this instance represents.</returns>
|
||||||
|
public byte[] GetEntryData()
|
||||||
|
{
|
||||||
|
if (Length > ushort.MaxValue)
|
||||||
|
{
|
||||||
|
throw new ZipException("Data exceeds maximum length");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (byte[])_data.Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear the stored data.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if ((_data == null) || (_data.Length != 0))
|
||||||
|
{
|
||||||
|
_data = new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current extra data length.
|
||||||
|
/// </summary>
|
||||||
|
public int Length
|
||||||
|
{
|
||||||
|
get { return _data.Length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a read-only <see cref="Stream"/> for the associated tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">The tag to locate data for.</param>
|
||||||
|
/// <returns>Returns a <see cref="Stream"/> containing tag data or null if no tag was found.</returns>
|
||||||
|
public Stream GetStreamForTag(int tag)
|
||||||
|
{
|
||||||
|
Stream result = null;
|
||||||
|
if (Find(tag))
|
||||||
|
{
|
||||||
|
result = new MemoryStream(_data, _index, _readValueLength, false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the <see cref="ITaggedData">tagged data</see> for a tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The tag to search for.</typeparam>
|
||||||
|
/// <returns>Returns a <see cref="ITaggedData">tagged value</see> or null if none found.</returns>
|
||||||
|
public T GetData<T>()
|
||||||
|
where T : class, ITaggedData, new()
|
||||||
|
{
|
||||||
|
T result = new T();
|
||||||
|
if (Find(result.TagID))
|
||||||
|
{
|
||||||
|
result.SetData(_data, _readValueStart, _readValueLength);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the length of the last value found by <see cref="Find"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is only valid if <see cref="Find"/> has previously returned true.</remarks>
|
||||||
|
public int ValueLength
|
||||||
|
{
|
||||||
|
get { return _readValueLength; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the index for the current read value.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is only valid if <see cref="Find"/> has previously returned true.
|
||||||
|
/// Initially the result will be the index of the first byte of actual data. The value is updated after calls to
|
||||||
|
/// <see cref="ReadInt"/>, <see cref="ReadShort"/> and <see cref="ReadLong"/>. </remarks>
|
||||||
|
public int CurrentReadIndex
|
||||||
|
{
|
||||||
|
get { return _index; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the number of bytes remaining to be read for the current value;
|
||||||
|
/// </summary>
|
||||||
|
public int UnreadCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if ((_readValueStart > _data.Length) ||
|
||||||
|
(_readValueStart < 4))
|
||||||
|
{
|
||||||
|
throw new ZipException("Find must be called before calling a Read method");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _readValueStart + _readValueLength - _index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find an extra data value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerID">The identifier for the value to find.</param>
|
||||||
|
/// <returns>Returns true if the value was found; false otherwise.</returns>
|
||||||
|
public bool Find(int headerID)
|
||||||
|
{
|
||||||
|
_readValueStart = _data.Length;
|
||||||
|
_readValueLength = 0;
|
||||||
|
_index = 0;
|
||||||
|
|
||||||
|
int localLength = _readValueStart;
|
||||||
|
int localTag = headerID - 1;
|
||||||
|
|
||||||
|
// Trailing bytes that cant make up an entry (as there arent enough
|
||||||
|
// bytes for a tag and length) are ignored!
|
||||||
|
while ((localTag != headerID) && (_index < _data.Length - 3))
|
||||||
|
{
|
||||||
|
localTag = ReadShortInternal();
|
||||||
|
localLength = ReadShortInternal();
|
||||||
|
if (localTag != headerID)
|
||||||
|
{
|
||||||
|
_index += localLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = (localTag == headerID) && ((_index + localLength) <= _data.Length);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
_readValueStart = _index;
|
||||||
|
_readValueLength = localLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new entry to extra data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taggedData">The <see cref="ITaggedData"/> value to add.</param>
|
||||||
|
public void AddEntry(ITaggedData taggedData)
|
||||||
|
{
|
||||||
|
if (taggedData == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(taggedData));
|
||||||
|
}
|
||||||
|
AddEntry(taggedData.TagID, taggedData.GetData());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new entry to extra data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerID">The ID for this entry.</param>
|
||||||
|
/// <param name="fieldData">The data to add.</param>
|
||||||
|
/// <remarks>If the ID already exists its contents are replaced.</remarks>
|
||||||
|
public void AddEntry(int headerID, byte[] fieldData)
|
||||||
|
{
|
||||||
|
if ((headerID > ushort.MaxValue) || (headerID < 0))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(headerID));
|
||||||
|
}
|
||||||
|
|
||||||
|
int addLength = (fieldData == null) ? 0 : fieldData.Length;
|
||||||
|
|
||||||
|
if (addLength > ushort.MaxValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for new length before adjusting data.
|
||||||
|
int newLength = _data.Length + addLength + 4;
|
||||||
|
|
||||||
|
if (Find(headerID))
|
||||||
|
{
|
||||||
|
newLength -= (ValueLength + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newLength > ushort.MaxValue)
|
||||||
|
{
|
||||||
|
throw new ZipException("Data exceeds maximum length");
|
||||||
|
}
|
||||||
|
|
||||||
|
Delete(headerID);
|
||||||
|
|
||||||
|
byte[] newData = new byte[newLength];
|
||||||
|
_data.CopyTo(newData, 0);
|
||||||
|
int index = _data.Length;
|
||||||
|
_data = newData;
|
||||||
|
SetShort(ref index, headerID);
|
||||||
|
SetShort(ref index, addLength);
|
||||||
|
if (fieldData != null)
|
||||||
|
{
|
||||||
|
fieldData.CopyTo(newData, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start adding a new entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Add data using <see cref="AddData(byte[])"/>, <see cref="AddLeShort"/>, <see cref="AddLeInt"/>, or <see cref="AddLeLong"/>.
|
||||||
|
/// The new entry is completed and actually added by calling <see cref="AddNewEntry"/></remarks>
|
||||||
|
/// <seealso cref="AddEntry(ITaggedData)"/>
|
||||||
|
public void StartNewEntry()
|
||||||
|
{
|
||||||
|
_newEntry = new MemoryStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add entry data added since <see cref="StartNewEntry"/> using the ID passed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerID">The identifier to use for this entry.</param>
|
||||||
|
public void AddNewEntry(int headerID)
|
||||||
|
{
|
||||||
|
byte[] newData = _newEntry.ToArray();
|
||||||
|
_newEntry = null;
|
||||||
|
AddEntry(headerID, newData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a byte of data to the pending new entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The byte to add.</param>
|
||||||
|
/// <seealso cref="StartNewEntry"/>
|
||||||
|
public void AddData(byte data)
|
||||||
|
{
|
||||||
|
_newEntry.WriteByte(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add data to a pending new entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to add.</param>
|
||||||
|
/// <seealso cref="StartNewEntry"/>
|
||||||
|
public void AddData(byte[] data)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
_newEntry.Write(data, 0, data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a short value in little endian order to the pending new entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toAdd">The data to add.</param>
|
||||||
|
/// <seealso cref="StartNewEntry"/>
|
||||||
|
public void AddLeShort(int toAdd)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
_newEntry.WriteByte((byte)toAdd);
|
||||||
|
_newEntry.WriteByte((byte)(toAdd >> 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an integer value in little endian order to the pending new entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toAdd">The data to add.</param>
|
||||||
|
/// <seealso cref="StartNewEntry"/>
|
||||||
|
public void AddLeInt(int toAdd)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
AddLeShort((short)toAdd);
|
||||||
|
AddLeShort((short)(toAdd >> 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a long value in little endian order to the pending new entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toAdd">The data to add.</param>
|
||||||
|
/// <seealso cref="StartNewEntry"/>
|
||||||
|
public void AddLeLong(long toAdd)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
AddLeInt((int)(toAdd & 0xffffffff));
|
||||||
|
AddLeInt((int)(toAdd >> 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete an extra data field.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerID">The identifier of the field to delete.</param>
|
||||||
|
/// <returns>Returns true if the field was found and deleted.</returns>
|
||||||
|
public bool Delete(int headerID)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
if (Find(headerID))
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
int trueStart = _readValueStart - 4;
|
||||||
|
|
||||||
|
byte[] newData = new byte[_data.Length - (ValueLength + 4)];
|
||||||
|
Array.Copy(_data, 0, newData, 0, trueStart);
|
||||||
|
|
||||||
|
int trueEnd = trueStart + ValueLength + 4;
|
||||||
|
Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd);
|
||||||
|
_data = newData;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Reading Support
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a long in little endian form from the last <see cref="Find">found</see> data value
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the long value read.</returns>
|
||||||
|
public long ReadLong()
|
||||||
|
{
|
||||||
|
ReadCheck(8);
|
||||||
|
return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read an integer in little endian form from the last <see cref="Find">found</see> data value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the integer read.</returns>
|
||||||
|
public int ReadInt()
|
||||||
|
{
|
||||||
|
ReadCheck(4);
|
||||||
|
|
||||||
|
int result = _data[_index] + (_data[_index + 1] << 8) +
|
||||||
|
(_data[_index + 2] << 16) + (_data[_index + 3] << 24);
|
||||||
|
_index += 4;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a short value in little endian form from the last <see cref="Find">found</see> data value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the short value read.</returns>
|
||||||
|
public int ReadShort()
|
||||||
|
{
|
||||||
|
ReadCheck(2);
|
||||||
|
int result = _data[_index] + (_data[_index + 1] << 8);
|
||||||
|
_index += 2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a byte from an extra data
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The byte value read or -1 if the end of data has been reached.</returns>
|
||||||
|
public int ReadByte()
|
||||||
|
{
|
||||||
|
int result = -1;
|
||||||
|
if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index))
|
||||||
|
{
|
||||||
|
result = _data[_index];
|
||||||
|
_index += 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip data during reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount">The number of bytes to skip.</param>
|
||||||
|
public void Skip(int amount)
|
||||||
|
{
|
||||||
|
ReadCheck(amount);
|
||||||
|
_index += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadCheck(int length)
|
||||||
|
{
|
||||||
|
if ((_readValueStart > _data.Length) ||
|
||||||
|
(_readValueStart < 4))
|
||||||
|
{
|
||||||
|
throw new ZipException("Find must be called before calling a Read method");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_index > _readValueStart + _readValueLength - length)
|
||||||
|
{
|
||||||
|
throw new ZipException("End of extra data");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_index + length < 4)
|
||||||
|
{
|
||||||
|
throw new ZipException("Cannot read before start of tag");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal form of <see cref="ReadShort"/> that reads data at any location.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the short value read.</returns>
|
||||||
|
private int ReadShortInternal()
|
||||||
|
{
|
||||||
|
if (_index > _data.Length - 2)
|
||||||
|
{
|
||||||
|
throw new ZipException("End of extra data");
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = _data[_index] + (_data[_index + 1] << 8);
|
||||||
|
_index += 2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetShort(ref int index, int source)
|
||||||
|
{
|
||||||
|
_data[index] = (byte)source;
|
||||||
|
_data[index + 1] = (byte)(source >> 8);
|
||||||
|
index += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Reading Support
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of this instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_newEntry != null)
|
||||||
|
{
|
||||||
|
_newEntry.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion IDisposable Members
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private int _index;
|
||||||
|
private int _readValueStart;
|
||||||
|
private int _readValueLength;
|
||||||
|
|
||||||
|
private MemoryStream _newEntry;
|
||||||
|
private byte[] _data;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,629 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holds data pertinent to a data descriptor.
|
||||||
|
/// </summary>
|
||||||
|
public class DescriptorData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get /set the compressed size of data.
|
||||||
|
/// </summary>
|
||||||
|
public long CompressedSize
|
||||||
|
{
|
||||||
|
get { return compressedSize; }
|
||||||
|
set { compressedSize = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set the uncompressed size of data
|
||||||
|
/// </summary>
|
||||||
|
public long Size
|
||||||
|
{
|
||||||
|
get { return size; }
|
||||||
|
set { size = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get /set the crc value.
|
||||||
|
/// </summary>
|
||||||
|
public long Crc
|
||||||
|
{
|
||||||
|
get { return crc; }
|
||||||
|
set { crc = (value & 0xffffffff); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private long size;
|
||||||
|
private long compressedSize;
|
||||||
|
private long crc;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EntryPatchData
|
||||||
|
{
|
||||||
|
public long SizePatchOffset
|
||||||
|
{
|
||||||
|
get { return sizePatchOffset_; }
|
||||||
|
set { sizePatchOffset_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public long CrcPatchOffset
|
||||||
|
{
|
||||||
|
get { return crcPatchOffset_; }
|
||||||
|
set { crcPatchOffset_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private long sizePatchOffset_;
|
||||||
|
private long crcPatchOffset_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class assists with writing/reading from Zip files.
|
||||||
|
/// </summary>
|
||||||
|
internal class ZipHelperStream : Stream
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise an instance of this class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the file to open.</param>
|
||||||
|
public ZipHelperStream(string name)
|
||||||
|
{
|
||||||
|
stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite);
|
||||||
|
isOwner_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialise a new instance of <see cref="ZipHelperStream"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to use.</param>
|
||||||
|
public ZipHelperStream(Stream stream)
|
||||||
|
{
|
||||||
|
stream_ = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set a value indicating wether the the underlying stream is owned or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>If the stream is owned it is closed when this instance is closed.</remarks>
|
||||||
|
public bool IsStreamOwner
|
||||||
|
{
|
||||||
|
get { return isOwner_; }
|
||||||
|
set { isOwner_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Base Stream Methods
|
||||||
|
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get { return stream_.CanRead; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get { return stream_.CanSeek; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanTimeout
|
||||||
|
{
|
||||||
|
get { return stream_.CanTimeout; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get { return stream_.Length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get { return stream_.Position; }
|
||||||
|
set { stream_.Position = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get { return stream_.CanWrite; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
stream_.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
return stream_.Seek(offset, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
stream_.SetLength(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
return stream_.Read(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
stream_.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The underlying stream is closed only if <see cref="IsStreamOwner"/> is true.
|
||||||
|
/// </remarks>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
Stream toClose = stream_;
|
||||||
|
stream_ = null;
|
||||||
|
if (isOwner_ && (toClose != null))
|
||||||
|
{
|
||||||
|
isOwner_ = false;
|
||||||
|
toClose.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Base Stream Methods
|
||||||
|
|
||||||
|
// Write the local file header
|
||||||
|
// TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage
|
||||||
|
private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData)
|
||||||
|
{
|
||||||
|
CompressionMethod method = entry.CompressionMethod;
|
||||||
|
bool headerInfoAvailable = true; // How to get this?
|
||||||
|
bool patchEntryHeader = false;
|
||||||
|
|
||||||
|
WriteLEInt(ZipConstants.LocalHeaderSignature);
|
||||||
|
|
||||||
|
WriteLEShort(entry.Version);
|
||||||
|
WriteLEShort(entry.Flags);
|
||||||
|
WriteLEShort((byte)method);
|
||||||
|
WriteLEInt((int)entry.DosTime);
|
||||||
|
|
||||||
|
if (headerInfoAvailable == true)
|
||||||
|
{
|
||||||
|
WriteLEInt((int)entry.Crc);
|
||||||
|
if (entry.LocalHeaderRequiresZip64)
|
||||||
|
{
|
||||||
|
WriteLEInt(-1);
|
||||||
|
WriteLEInt(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize);
|
||||||
|
WriteLEInt((int)entry.Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (patchData != null)
|
||||||
|
{
|
||||||
|
patchData.CrcPatchOffset = stream_.Position;
|
||||||
|
}
|
||||||
|
WriteLEInt(0); // Crc
|
||||||
|
|
||||||
|
if (patchData != null)
|
||||||
|
{
|
||||||
|
patchData.SizePatchOffset = stream_.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For local header both sizes appear in Zip64 Extended Information
|
||||||
|
if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
|
||||||
|
{
|
||||||
|
WriteLEInt(-1);
|
||||||
|
WriteLEInt(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLEInt(0); // Compressed size
|
||||||
|
WriteLEInt(0); // Uncompressed size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
|
||||||
|
|
||||||
|
if (name.Length > 0xFFFF)
|
||||||
|
{
|
||||||
|
throw new ZipException("Entry name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ed = new ZipExtraData(entry.ExtraData);
|
||||||
|
|
||||||
|
if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader))
|
||||||
|
{
|
||||||
|
ed.StartNewEntry();
|
||||||
|
if (headerInfoAvailable)
|
||||||
|
{
|
||||||
|
ed.AddLeLong(entry.Size);
|
||||||
|
ed.AddLeLong(entry.CompressedSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ed.AddLeLong(-1);
|
||||||
|
ed.AddLeLong(-1);
|
||||||
|
}
|
||||||
|
ed.AddNewEntry(1);
|
||||||
|
|
||||||
|
if (!ed.Find(1))
|
||||||
|
{
|
||||||
|
throw new ZipException("Internal error cant find extra data");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchData != null)
|
||||||
|
{
|
||||||
|
patchData.SizePatchOffset = ed.CurrentReadIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ed.Delete(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] extra = ed.GetEntryData();
|
||||||
|
|
||||||
|
WriteLEShort(name.Length);
|
||||||
|
WriteLEShort(extra.Length);
|
||||||
|
|
||||||
|
if (name.Length > 0)
|
||||||
|
{
|
||||||
|
stream_.Write(name, 0, name.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
|
||||||
|
{
|
||||||
|
patchData.SizePatchOffset += stream_.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra.Length > 0)
|
||||||
|
{
|
||||||
|
stream_.Write(extra, 0, extra.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locates a block with the desired <paramref name="signature"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signature">The signature to find.</param>
|
||||||
|
/// <param name="endLocation">Location, marking the end of block.</param>
|
||||||
|
/// <param name="minimumBlockSize">Minimum size of the block.</param>
|
||||||
|
/// <param name="maximumVariableData">The maximum variable data.</param>
|
||||||
|
/// <returns>Eeturns the offset of the first byte after the signature; -1 if not found</returns>
|
||||||
|
public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
|
||||||
|
{
|
||||||
|
long pos = endLocation - minimumBlockSize;
|
||||||
|
if (pos < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long giveUpMarker = Math.Max(pos - maximumVariableData, 0);
|
||||||
|
|
||||||
|
// TODO: This loop could be optimised for speed.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (pos < giveUpMarker)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Seek(pos--, SeekOrigin.Begin);
|
||||||
|
} while (ReadLEInt() != signature);
|
||||||
|
|
||||||
|
return Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write Zip64 end of central directory records (File header and locator).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="noOfEntries">The number of entries in the central directory.</param>
|
||||||
|
/// <param name="sizeEntries">The size of entries in the central directory.</param>
|
||||||
|
/// <param name="centralDirOffset">The offset of the dentral directory.</param>
|
||||||
|
public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset)
|
||||||
|
{
|
||||||
|
long centralSignatureOffset = centralDirOffset + sizeEntries;
|
||||||
|
WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature);
|
||||||
|
WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12)
|
||||||
|
WriteLEShort(ZipConstants.VersionMadeBy); // Version made by
|
||||||
|
WriteLEShort(ZipConstants.VersionZip64); // Version to extract
|
||||||
|
WriteLEInt(0); // Number of this disk
|
||||||
|
WriteLEInt(0); // number of the disk with the start of the central directory
|
||||||
|
WriteLELong(noOfEntries); // No of entries on this disk
|
||||||
|
WriteLELong(noOfEntries); // Total No of entries in central directory
|
||||||
|
WriteLELong(sizeEntries); // Size of the central directory
|
||||||
|
WriteLELong(centralDirOffset); // offset of start of central directory
|
||||||
|
// zip64 extensible data sector not catered for here (variable size)
|
||||||
|
|
||||||
|
// Write the Zip64 end of central directory locator
|
||||||
|
WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature);
|
||||||
|
|
||||||
|
// no of the disk with the start of the zip64 end of central directory
|
||||||
|
WriteLEInt(0);
|
||||||
|
|
||||||
|
// relative offset of the zip64 end of central directory record
|
||||||
|
WriteLELong(centralSignatureOffset);
|
||||||
|
|
||||||
|
// total number of disks
|
||||||
|
WriteLEInt(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write the required records to end the central directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="noOfEntries">The number of entries in the directory.</param>
|
||||||
|
/// <param name="sizeEntries">The size of the entries in the directory.</param>
|
||||||
|
/// <param name="startOfCentralDirectory">The start of the central directory.</param>
|
||||||
|
/// <param name="comment">The archive comment. (This can be null).</param>
|
||||||
|
public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries,
|
||||||
|
long startOfCentralDirectory, byte[] comment)
|
||||||
|
{
|
||||||
|
if ((noOfEntries >= 0xffff) ||
|
||||||
|
(startOfCentralDirectory >= 0xffffffff) ||
|
||||||
|
(sizeEntries >= 0xffffffff))
|
||||||
|
{
|
||||||
|
WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLEInt(ZipConstants.EndOfCentralDirectorySignature);
|
||||||
|
|
||||||
|
// TODO: ZipFile Multi disk handling not done
|
||||||
|
WriteLEShort(0); // number of this disk
|
||||||
|
WriteLEShort(0); // no of disk with start of central dir
|
||||||
|
|
||||||
|
// Number of entries
|
||||||
|
if (noOfEntries >= 0xffff)
|
||||||
|
{
|
||||||
|
WriteLEUshort(0xffff); // Zip64 marker
|
||||||
|
WriteLEUshort(0xffff);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLEShort((short)noOfEntries); // entries in central dir for this disk
|
||||||
|
WriteLEShort((short)noOfEntries); // total entries in central directory
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size of the central directory
|
||||||
|
if (sizeEntries >= 0xffffffff)
|
||||||
|
{
|
||||||
|
WriteLEUint(0xffffffff); // Zip64 marker
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLEInt((int)sizeEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset of start of central directory
|
||||||
|
if (startOfCentralDirectory >= 0xffffffff)
|
||||||
|
{
|
||||||
|
WriteLEUint(0xffffffff); // Zip64 marker
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLEInt((int)startOfCentralDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
int commentLength = (comment != null) ? comment.Length : 0;
|
||||||
|
|
||||||
|
if (commentLength > 0xffff)
|
||||||
|
{
|
||||||
|
throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLEShort(commentLength);
|
||||||
|
|
||||||
|
if (commentLength > 0)
|
||||||
|
{
|
||||||
|
Write(comment, 0, comment.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region LE value reading/writing
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read an unsigned short in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the value read.</returns>
|
||||||
|
/// <exception cref="IOException">
|
||||||
|
/// An i/o error occurs.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="EndOfStreamException">
|
||||||
|
/// The file ends prematurely
|
||||||
|
/// </exception>
|
||||||
|
public int ReadLEShort()
|
||||||
|
{
|
||||||
|
int byteValue1 = stream_.ReadByte();
|
||||||
|
|
||||||
|
if (byteValue1 < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int byteValue2 = stream_.ReadByte();
|
||||||
|
if (byteValue2 < 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteValue1 | (byteValue2 << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read an int in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the value read.</returns>
|
||||||
|
/// <exception cref="IOException">
|
||||||
|
/// An i/o error occurs.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.IO.EndOfStreamException">
|
||||||
|
/// The file ends prematurely
|
||||||
|
/// </exception>
|
||||||
|
public int ReadLEInt()
|
||||||
|
{
|
||||||
|
return ReadLEShort() | (ReadLEShort() << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a long in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The value read.</returns>
|
||||||
|
public long ReadLELong()
|
||||||
|
{
|
||||||
|
return (uint)ReadLEInt() | ((long)ReadLEInt() << 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an unsigned short in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void WriteLEShort(int value)
|
||||||
|
{
|
||||||
|
stream_.WriteByte((byte)(value & 0xff));
|
||||||
|
stream_.WriteByte((byte)((value >> 8) & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a ushort in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void WriteLEUshort(ushort value)
|
||||||
|
{
|
||||||
|
stream_.WriteByte((byte)(value & 0xff));
|
||||||
|
stream_.WriteByte((byte)(value >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an int in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void WriteLEInt(int value)
|
||||||
|
{
|
||||||
|
WriteLEShort(value);
|
||||||
|
WriteLEShort(value >> 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a uint in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void WriteLEUint(uint value)
|
||||||
|
{
|
||||||
|
WriteLEUshort((ushort)(value & 0xffff));
|
||||||
|
WriteLEUshort((ushort)(value >> 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a long in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void WriteLELong(long value)
|
||||||
|
{
|
||||||
|
WriteLEInt((int)value);
|
||||||
|
WriteLEInt((int)(value >> 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a ulong in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void WriteLEUlong(ulong value)
|
||||||
|
{
|
||||||
|
WriteLEUint((uint)(value & 0xffffffff));
|
||||||
|
WriteLEUint((uint)(value >> 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion LE value reading/writing
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a data descriptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry">The entry to write a descriptor for.</param>
|
||||||
|
/// <returns>Returns the number of descriptor bytes written.</returns>
|
||||||
|
public int WriteDataDescriptor(ZipEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
// Add data descriptor if flagged as required
|
||||||
|
if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0)
|
||||||
|
{
|
||||||
|
// The signature is not PKZIP originally but is now described as optional
|
||||||
|
// in the PKZIP Appnote documenting trhe format.
|
||||||
|
WriteLEInt(ZipConstants.DataDescriptorSignature);
|
||||||
|
WriteLEInt(unchecked((int)(entry.Crc)));
|
||||||
|
|
||||||
|
result += 8;
|
||||||
|
|
||||||
|
if (entry.LocalHeaderRequiresZip64)
|
||||||
|
{
|
||||||
|
WriteLELong(entry.CompressedSize);
|
||||||
|
WriteLELong(entry.Size);
|
||||||
|
result += 16;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLEInt((int)entry.CompressedSize);
|
||||||
|
WriteLEInt((int)entry.Size);
|
||||||
|
result += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read data descriptor at the end of compressed data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="zip64">if set to <c>true</c> [zip64].</param>
|
||||||
|
/// <param name="data">The data to fill in.</param>
|
||||||
|
/// <returns>Returns the number of bytes read in the descriptor.</returns>
|
||||||
|
public void ReadDataDescriptor(bool zip64, DescriptorData data)
|
||||||
|
{
|
||||||
|
int intValue = ReadLEInt();
|
||||||
|
|
||||||
|
// In theory this may not be a descriptor according to PKZIP appnote.
|
||||||
|
// In practise its always there.
|
||||||
|
if (intValue != ZipConstants.DataDescriptorSignature)
|
||||||
|
{
|
||||||
|
throw new ZipException("Data descriptor signature not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Crc = ReadLEInt();
|
||||||
|
|
||||||
|
if (zip64)
|
||||||
|
{
|
||||||
|
data.CompressedSize = ReadLELong();
|
||||||
|
data.Size = ReadLELong();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data.CompressedSize = ReadLEInt();
|
||||||
|
data.Size = ReadLEInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private bool isOwner_;
|
||||||
|
private Stream stream_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,694 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using ICSharpCode.SharpZipLib.Encryption;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is an InflaterInputStream that reads the files baseInputStream an zip archive
|
||||||
|
/// one after another. It has a special method to get the zip entry of
|
||||||
|
/// the next file. The zip entry contains information about the file name
|
||||||
|
/// size, compressed size, Crc, etc.
|
||||||
|
/// It includes support for Stored and Deflated entries.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>Author of the original java version : Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
///
|
||||||
|
/// <example> This sample shows how to read a zip file
|
||||||
|
/// <code lang="C#">
|
||||||
|
/// using System;
|
||||||
|
/// using System.Text;
|
||||||
|
/// using System.IO;
|
||||||
|
///
|
||||||
|
/// using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
///
|
||||||
|
/// class MainClass
|
||||||
|
/// {
|
||||||
|
/// public static void Main(string[] args)
|
||||||
|
/// {
|
||||||
|
/// using ( ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]))) {
|
||||||
|
///
|
||||||
|
/// ZipEntry theEntry;
|
||||||
|
/// const int size = 2048;
|
||||||
|
/// byte[] data = new byte[2048];
|
||||||
|
///
|
||||||
|
/// while ((theEntry = s.GetNextEntry()) != null) {
|
||||||
|
/// if ( entry.IsFile ) {
|
||||||
|
/// Console.Write("Show contents (y/n) ?");
|
||||||
|
/// if (Console.ReadLine() == "y") {
|
||||||
|
/// while (true) {
|
||||||
|
/// size = s.Read(data, 0, data.Length);
|
||||||
|
/// if (size > 0) {
|
||||||
|
/// Console.Write(new ASCIIEncoding().GetString(data, 0, size));
|
||||||
|
/// } else {
|
||||||
|
/// break;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public class ZipInputStream : InflaterInputStream
|
||||||
|
{
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for reading bytes from a stream.
|
||||||
|
/// </summary>
|
||||||
|
private delegate int ReadDataHandler(byte[] b, int offset, int length);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current reader this instance.
|
||||||
|
/// </summary>
|
||||||
|
private ReadDataHandler internalReader;
|
||||||
|
|
||||||
|
private Crc32 crc = new Crc32();
|
||||||
|
private ZipEntry entry;
|
||||||
|
|
||||||
|
private long size;
|
||||||
|
private int method;
|
||||||
|
private int flags;
|
||||||
|
private string password;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new Zip input stream, for reading a zip archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseInputStream">The underlying <see cref="Stream"/> providing data.</param>
|
||||||
|
public ZipInputStream(Stream baseInputStream)
|
||||||
|
: base(baseInputStream, new Inflater(true))
|
||||||
|
{
|
||||||
|
internalReader = new ReadDataHandler(ReadingNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new Zip input stream, for reading a zip archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseInputStream">The underlying <see cref="Stream"/> providing data.</param>
|
||||||
|
/// <param name="bufferSize">Size of the buffer.</param>
|
||||||
|
public ZipInputStream(Stream baseInputStream, int bufferSize)
|
||||||
|
: base(baseInputStream, new Inflater(true), bufferSize)
|
||||||
|
{
|
||||||
|
internalReader = new ReadDataHandler(ReadingNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional password used for encryption when non-null
|
||||||
|
/// </summary>
|
||||||
|
/// <value>A password for all encrypted <see cref="ZipEntry">entries </see> in this <see cref="ZipInputStream"/></value>
|
||||||
|
public string Password
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
password = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating if there is a current entry and it can be decompressed
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The entry can only be decompressed if the library supports the zip features required to extract it.
|
||||||
|
/// See the <see cref="ZipEntry.Version">ZipEntry Version</see> property for more details.
|
||||||
|
/// </remarks>
|
||||||
|
public bool CanDecompressEntry
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (entry != null) && entry.CanDecompress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances to the next entry in the archive
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The next <see cref="ZipEntry">entry</see> in the archive or null if there are no more entries.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called.
|
||||||
|
/// </remarks>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// Input stream is closed
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ZipException">
|
||||||
|
/// Password is not set, password is invalid, compression method is invalid,
|
||||||
|
/// version required to extract is not supported
|
||||||
|
/// </exception>
|
||||||
|
public ZipEntry GetNextEntry()
|
||||||
|
{
|
||||||
|
if (crc == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
CloseEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
int header = inputBuffer.ReadLeInt();
|
||||||
|
|
||||||
|
if (header == ZipConstants.CentralHeaderSignature ||
|
||||||
|
header == ZipConstants.EndOfCentralDirectorySignature ||
|
||||||
|
header == ZipConstants.CentralHeaderDigitalSignature ||
|
||||||
|
header == ZipConstants.ArchiveExtraDataSignature ||
|
||||||
|
header == ZipConstants.Zip64CentralFileHeaderSignature)
|
||||||
|
{
|
||||||
|
// No more individual entries exist
|
||||||
|
Dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
|
||||||
|
// Spanning signature is same as descriptor signature and is untested as yet.
|
||||||
|
if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature))
|
||||||
|
{
|
||||||
|
header = inputBuffer.ReadLeInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header != ZipConstants.LocalHeaderSignature)
|
||||||
|
{
|
||||||
|
throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
|
||||||
|
|
||||||
|
flags = inputBuffer.ReadLeShort();
|
||||||
|
method = inputBuffer.ReadLeShort();
|
||||||
|
var dostime = (uint)inputBuffer.ReadLeInt();
|
||||||
|
int crc2 = inputBuffer.ReadLeInt();
|
||||||
|
csize = inputBuffer.ReadLeInt();
|
||||||
|
size = inputBuffer.ReadLeInt();
|
||||||
|
int nameLen = inputBuffer.ReadLeShort();
|
||||||
|
int extraLen = inputBuffer.ReadLeShort();
|
||||||
|
|
||||||
|
bool isCrypted = (flags & 1) == 1;
|
||||||
|
|
||||||
|
byte[] buffer = new byte[nameLen];
|
||||||
|
inputBuffer.ReadRawBuffer(buffer);
|
||||||
|
|
||||||
|
string name = ZipStrings.ConvertToStringExt(flags, buffer);
|
||||||
|
|
||||||
|
entry = new ZipEntry(name, versionRequiredToExtract)
|
||||||
|
{
|
||||||
|
Flags = flags,
|
||||||
|
CompressionMethod = (CompressionMethod)method
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((flags & 8) == 0)
|
||||||
|
{
|
||||||
|
entry.Crc = crc2 & 0xFFFFFFFFL;
|
||||||
|
entry.Size = size & 0xFFFFFFFFL;
|
||||||
|
entry.CompressedSize = csize & 0xFFFFFFFFL;
|
||||||
|
|
||||||
|
entry.CryptoCheckValue = (byte)((crc2 >> 24) & 0xff);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This allows for GNU, WinZip and possibly other archives, the PKZIP spec
|
||||||
|
// says these values are zero under these circumstances.
|
||||||
|
if (crc2 != 0)
|
||||||
|
{
|
||||||
|
entry.Crc = crc2 & 0xFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size != 0)
|
||||||
|
{
|
||||||
|
entry.Size = size & 0xFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csize != 0)
|
||||||
|
{
|
||||||
|
entry.CompressedSize = csize & 0xFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.DosTime = dostime;
|
||||||
|
|
||||||
|
// If local header requires Zip64 is true then the extended header should contain
|
||||||
|
// both values.
|
||||||
|
|
||||||
|
// Handle extra data if present. This can set/alter some fields of the entry.
|
||||||
|
if (extraLen > 0)
|
||||||
|
{
|
||||||
|
byte[] extra = new byte[extraLen];
|
||||||
|
inputBuffer.ReadRawBuffer(extra);
|
||||||
|
entry.ExtraData = extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.ProcessExtraData(true);
|
||||||
|
if (entry.CompressedSize >= 0)
|
||||||
|
{
|
||||||
|
csize = entry.CompressedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.Size >= 0)
|
||||||
|
{
|
||||||
|
size = entry.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == (int)CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size)))
|
||||||
|
{
|
||||||
|
throw new ZipException("Stored, but compressed != uncompressed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine how to handle reading of data if this is attempted.
|
||||||
|
if (entry.IsCompressionMethodSupported())
|
||||||
|
{
|
||||||
|
internalReader = new ReadDataHandler(InitialRead);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
internalReader = new ReadDataHandler(ReadingNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read data descriptor at the end of compressed data.
|
||||||
|
/// </summary>
|
||||||
|
private void ReadDataDescriptor()
|
||||||
|
{
|
||||||
|
if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature)
|
||||||
|
{
|
||||||
|
throw new ZipException("Data descriptor signature not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL;
|
||||||
|
|
||||||
|
if (entry.LocalHeaderRequiresZip64)
|
||||||
|
{
|
||||||
|
csize = inputBuffer.ReadLeLong();
|
||||||
|
size = inputBuffer.ReadLeLong();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
csize = inputBuffer.ReadLeInt();
|
||||||
|
size = inputBuffer.ReadLeInt();
|
||||||
|
}
|
||||||
|
entry.CompressedSize = csize;
|
||||||
|
entry.Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Complete cleanup as the final part of closing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testCrc">True if the crc value should be tested</param>
|
||||||
|
private void CompleteCloseEntry(bool testCrc)
|
||||||
|
{
|
||||||
|
StopDecrypting();
|
||||||
|
|
||||||
|
if ((flags & 8) != 0)
|
||||||
|
{
|
||||||
|
ReadDataDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
size = 0;
|
||||||
|
|
||||||
|
if (testCrc &&
|
||||||
|
((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1))
|
||||||
|
{
|
||||||
|
throw new ZipException("CRC mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
crc.Reset();
|
||||||
|
|
||||||
|
if (method == (int)CompressionMethod.Deflated)
|
||||||
|
{
|
||||||
|
inf.Reset();
|
||||||
|
}
|
||||||
|
entry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the current zip entry and moves to the next one.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// The stream is closed
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ZipException">
|
||||||
|
/// The Zip stream ends early
|
||||||
|
/// </exception>
|
||||||
|
public void CloseEntry()
|
||||||
|
{
|
||||||
|
if (crc == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == (int)CompressionMethod.Deflated)
|
||||||
|
{
|
||||||
|
if ((flags & 8) != 0)
|
||||||
|
{
|
||||||
|
// We don't know how much we must skip, read until end.
|
||||||
|
byte[] tmp = new byte[4096];
|
||||||
|
|
||||||
|
// Read will close this entry
|
||||||
|
while (Read(tmp, 0, tmp.Length) > 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
csize -= inf.TotalIn;
|
||||||
|
inputBuffer.Available += inf.RemainingInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((inputBuffer.Available > csize) && (csize >= 0))
|
||||||
|
{
|
||||||
|
inputBuffer.Available = (int)((long)inputBuffer.Available - csize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
csize -= inputBuffer.Available;
|
||||||
|
inputBuffer.Available = 0;
|
||||||
|
while (csize != 0)
|
||||||
|
{
|
||||||
|
long skipped = Skip(csize);
|
||||||
|
|
||||||
|
if (skipped <= 0)
|
||||||
|
{
|
||||||
|
throw new ZipException("Zip archive ends early.");
|
||||||
|
}
|
||||||
|
|
||||||
|
csize -= skipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompleteCloseEntry(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns 1 if there is an entry available
|
||||||
|
/// Otherwise returns 0.
|
||||||
|
/// </summary>
|
||||||
|
public override int Available
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return entry != null ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the current size that can be read from the current entry if available
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ZipException">Thrown if the entry size is not known.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown if no entry is currently available.</exception>
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
if (entry.Size >= 0)
|
||||||
|
{
|
||||||
|
return entry.Size;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ZipException("Length not available for the current entry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No current entry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a byte from the current zip entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The byte or -1 if end of stream is reached.
|
||||||
|
/// </returns>
|
||||||
|
public override int ReadByte()
|
||||||
|
{
|
||||||
|
byte[] b = new byte[1];
|
||||||
|
if (Read(b, 0, 1) <= 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return b[0] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle attempts to read by throwing an <see cref="InvalidOperationException"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destination">The destination array to store data in.</param>
|
||||||
|
/// <param name="offset">The offset at which data read should be stored.</param>
|
||||||
|
/// <param name="count">The maximum number of bytes to read.</param>
|
||||||
|
/// <returns>Returns the number of bytes actually read.</returns>
|
||||||
|
private int ReadingNotAvailable(byte[] destination, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unable to read from this stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle attempts to read from this entry by throwing an exception
|
||||||
|
/// </summary>
|
||||||
|
private int ReadingNotSupported(byte[] destination, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new ZipException("The compression method for this entry is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform the initial read on an entry which may include
|
||||||
|
/// reading encryption headers and setting up inflation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destination">The destination to fill with data read.</param>
|
||||||
|
/// <param name="offset">The offset to start reading at.</param>
|
||||||
|
/// <param name="count">The maximum number of bytes to read.</param>
|
||||||
|
/// <returns>The actual number of bytes read.</returns>
|
||||||
|
private int InitialRead(byte[] destination, int offset, int count)
|
||||||
|
{
|
||||||
|
if (!CanDecompressEntry)
|
||||||
|
{
|
||||||
|
throw new ZipException("Library cannot extract this entry. Version required is (" + entry.Version + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle encryption if required.
|
||||||
|
if (entry.IsCrypted)
|
||||||
|
{
|
||||||
|
if (password == null)
|
||||||
|
{
|
||||||
|
throw new ZipException("No password set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and set crypto transform...
|
||||||
|
var managed = new PkzipClassicManaged();
|
||||||
|
byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password));
|
||||||
|
|
||||||
|
inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
|
||||||
|
|
||||||
|
byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
|
||||||
|
inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
|
||||||
|
|
||||||
|
if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
|
||||||
|
{
|
||||||
|
throw new ZipException("Invalid password");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csize >= ZipConstants.CryptoHeaderSize)
|
||||||
|
{
|
||||||
|
csize -= ZipConstants.CryptoHeaderSize;
|
||||||
|
}
|
||||||
|
else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0)
|
||||||
|
{
|
||||||
|
throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inputBuffer.CryptoTransform = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((csize > 0) || ((flags & (int)GeneralBitFlags.Descriptor) != 0))
|
||||||
|
{
|
||||||
|
if ((method == (int)CompressionMethod.Deflated) && (inputBuffer.Available > 0))
|
||||||
|
{
|
||||||
|
inputBuffer.SetInflaterInput(inf);
|
||||||
|
}
|
||||||
|
|
||||||
|
internalReader = new ReadDataHandler(BodyRead);
|
||||||
|
return BodyRead(destination, offset, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
internalReader = new ReadDataHandler(ReadingNotAvailable);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a block of bytes from the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The destination for the bytes.</param>
|
||||||
|
/// <param name="offset">The index to start storing data.</param>
|
||||||
|
/// <param name="count">The number of bytes to attempt to read.</param>
|
||||||
|
/// <returns>Returns the number of bytes read.</returns>
|
||||||
|
/// <remarks>Zero bytes read means end of stream.</remarks>
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buffer.Length - offset) < count)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid offset/count combination");
|
||||||
|
}
|
||||||
|
|
||||||
|
return internalReader(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a block of bytes from the current zip entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on end of stream.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception name="IOException">
|
||||||
|
/// An i/o error occured.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ZipException">
|
||||||
|
/// The deflated stream is corrupted.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// The stream is not open.
|
||||||
|
/// </exception>
|
||||||
|
private int BodyRead(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (crc == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((entry == null) || (count <= 0))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + count > buffer.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Offset + count exceeds buffer size");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool finished = false;
|
||||||
|
|
||||||
|
switch (method)
|
||||||
|
{
|
||||||
|
case (int)CompressionMethod.Deflated:
|
||||||
|
count = base.Read(buffer, offset, count);
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
if (!inf.IsFinished)
|
||||||
|
{
|
||||||
|
throw new ZipException("Inflater not finished!");
|
||||||
|
}
|
||||||
|
inputBuffer.Available = inf.RemainingInput;
|
||||||
|
|
||||||
|
// A csize of -1 is from an unpatched local header
|
||||||
|
if ((flags & 8) == 0 &&
|
||||||
|
(inf.TotalIn != csize && csize != 0xFFFFFFFF && csize != -1 || inf.TotalOut != size))
|
||||||
|
{
|
||||||
|
throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut);
|
||||||
|
}
|
||||||
|
inf.Reset();
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case (int)CompressionMethod.Stored:
|
||||||
|
if ((count > csize) && (csize >= 0))
|
||||||
|
{
|
||||||
|
count = (int)csize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
count = inputBuffer.ReadClearTextBuffer(buffer, offset, count);
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
csize -= count;
|
||||||
|
size -= count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csize == 0)
|
||||||
|
{
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ZipException("EOF in stored block");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
crc.Update(new ArraySegment<byte>(buffer, offset, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finished)
|
||||||
|
{
|
||||||
|
CompleteCloseEntry(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the zip input stream
|
||||||
|
/// </summary>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
internalReader = new ReadDataHandler(ReadingNotAvailable);
|
||||||
|
crc = null;
|
||||||
|
entry = null;
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,250 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Core;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ZipNameTransform transforms names as per the Zip file naming convention.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The use of absolute names is supported although its use is not valid
|
||||||
|
/// according to Zip naming conventions, and should not be used if maximum compatability is desired.</remarks>
|
||||||
|
public class ZipNameTransform : INameTransform
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize a new instance of <see cref="ZipNameTransform"></see>
|
||||||
|
/// </summary>
|
||||||
|
public ZipNameTransform()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize a new instance of <see cref="ZipNameTransform"></see>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="trimPrefix">The string to trim from the front of paths if found.</param>
|
||||||
|
public ZipNameTransform(string trimPrefix)
|
||||||
|
{
|
||||||
|
TrimPrefix = trimPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static constructor.
|
||||||
|
/// </summary>
|
||||||
|
static ZipNameTransform()
|
||||||
|
{
|
||||||
|
char[] invalidPathChars;
|
||||||
|
invalidPathChars = Path.GetInvalidPathChars();
|
||||||
|
int howMany = invalidPathChars.Length + 2;
|
||||||
|
|
||||||
|
InvalidEntryCharsRelaxed = new char[howMany];
|
||||||
|
Array.Copy(invalidPathChars, 0, InvalidEntryCharsRelaxed, 0, invalidPathChars.Length);
|
||||||
|
InvalidEntryCharsRelaxed[howMany - 1] = '*';
|
||||||
|
InvalidEntryCharsRelaxed[howMany - 2] = '?';
|
||||||
|
|
||||||
|
howMany = invalidPathChars.Length + 4;
|
||||||
|
InvalidEntryChars = new char[howMany];
|
||||||
|
Array.Copy(invalidPathChars, 0, InvalidEntryChars, 0, invalidPathChars.Length);
|
||||||
|
InvalidEntryChars[howMany - 1] = ':';
|
||||||
|
InvalidEntryChars[howMany - 2] = '\\';
|
||||||
|
InvalidEntryChars[howMany - 3] = '*';
|
||||||
|
InvalidEntryChars[howMany - 4] = '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transform a windows directory name according to the Zip file naming conventions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The directory name to transform.</param>
|
||||||
|
/// <returns>The transformed name.</returns>
|
||||||
|
public string TransformDirectory(string name)
|
||||||
|
{
|
||||||
|
name = TransformFile(name);
|
||||||
|
if (name.Length > 0)
|
||||||
|
{
|
||||||
|
if (!name.EndsWith("/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
name += "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ZipException("Cannot have an empty directory name");
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transform a windows file name according to the Zip file naming conventions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The file name to transform.</param>
|
||||||
|
/// <returns>The transformed name.</returns>
|
||||||
|
public string TransformFile(string name)
|
||||||
|
{
|
||||||
|
if (name != null)
|
||||||
|
{
|
||||||
|
string lowerName = name.ToLower();
|
||||||
|
if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0))
|
||||||
|
{
|
||||||
|
name = name.Substring(trimPrefix_.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
name = name.Replace(@"\", "/");
|
||||||
|
name = WindowsPathUtils.DropPathRoot(name);
|
||||||
|
|
||||||
|
// Drop any leading slashes.
|
||||||
|
while ((name.Length > 0) && (name[0] == '/'))
|
||||||
|
{
|
||||||
|
name = name.Remove(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop any trailing slashes.
|
||||||
|
while ((name.Length > 0) && (name[name.Length - 1] == '/'))
|
||||||
|
{
|
||||||
|
name = name.Remove(name.Length - 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert consecutive // characters to /
|
||||||
|
int index = name.IndexOf("//", StringComparison.Ordinal);
|
||||||
|
while (index >= 0)
|
||||||
|
{
|
||||||
|
name = name.Remove(index, 1);
|
||||||
|
index = name.IndexOf("//", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
name = MakeValidName(name, '_');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name = string.Empty;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get/set the path prefix to be trimmed from paths if present.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The prefix is trimmed before any conversion from
|
||||||
|
/// a windows path is done.</remarks>
|
||||||
|
public string TrimPrefix
|
||||||
|
{
|
||||||
|
get { return trimPrefix_; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
trimPrefix_ = value;
|
||||||
|
if (trimPrefix_ != null)
|
||||||
|
{
|
||||||
|
trimPrefix_ = trimPrefix_.ToLower();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Force a name to be valid by replacing invalid characters with a fixed value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to force valid</param>
|
||||||
|
/// <param name="replacement">The replacement character to use.</param>
|
||||||
|
/// <returns>Returns a valid name</returns>
|
||||||
|
private static string MakeValidName(string name, char replacement)
|
||||||
|
{
|
||||||
|
int index = name.IndexOfAny(InvalidEntryChars);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder(name);
|
||||||
|
|
||||||
|
while (index >= 0)
|
||||||
|
{
|
||||||
|
builder[index] = replacement;
|
||||||
|
|
||||||
|
if (index >= name.Length)
|
||||||
|
{
|
||||||
|
index = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = name.IndexOfAny(InvalidEntryChars, index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name = builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.Length > 0xffff)
|
||||||
|
{
|
||||||
|
throw new PathTooLongException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a name to see if it is a valid name for a zip entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to test.</param>
|
||||||
|
/// <param name="relaxed">If true checking is relaxed about windows file names and absolute paths.</param>
|
||||||
|
/// <returns>Returns true if the name is a valid zip name; false otherwise.</returns>
|
||||||
|
/// <remarks>Zip path names are actually in Unix format, and should only contain relative paths.
|
||||||
|
/// This means that any path stored should not contain a drive or
|
||||||
|
/// device letter, or a leading slash. All slashes should forward slashes '/'.
|
||||||
|
/// An empty name is valid for a file where the input comes from standard input.
|
||||||
|
/// A null name is not considered valid.
|
||||||
|
/// </remarks>
|
||||||
|
public static bool IsValidName(string name, bool relaxed)
|
||||||
|
{
|
||||||
|
bool result = (name != null);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
if (relaxed)
|
||||||
|
{
|
||||||
|
result = name.IndexOfAny(InvalidEntryCharsRelaxed) < 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result =
|
||||||
|
(name.IndexOfAny(InvalidEntryChars) < 0) &&
|
||||||
|
(name.IndexOf('/') != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test a name to see if it is a valid name for a zip entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name to test.</param>
|
||||||
|
/// <returns>Returns true if the name is a valid zip name; false otherwise.</returns>
|
||||||
|
/// <remarks>Zip path names are actually in unix format,
|
||||||
|
/// and should only contain relative paths if a path is present.
|
||||||
|
/// This means that the path stored should not contain a drive or
|
||||||
|
/// device letter, or a leading slash. All slashes should forward slashes '/'.
|
||||||
|
/// An empty name is valid where the input comes from standard input.
|
||||||
|
/// A null name is not considered valid.
|
||||||
|
/// </remarks>
|
||||||
|
public static bool IsValidName(string name)
|
||||||
|
{
|
||||||
|
bool result =
|
||||||
|
(name != null) &&
|
||||||
|
(name.IndexOfAny(InvalidEntryChars) < 0) &&
|
||||||
|
(name.IndexOf('/') != 0)
|
||||||
|
;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
private string trimPrefix_;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
|
||||||
|
#region Class Fields
|
||||||
|
|
||||||
|
private static readonly char[] InvalidEntryChars;
|
||||||
|
private static readonly char[] InvalidEntryCharsRelaxed;
|
||||||
|
|
||||||
|
#endregion Class Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,949 @@
|
||||||
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is a DeflaterOutputStream that writes the files into a zip
|
||||||
|
/// archive one after another. It has a special method to start a new
|
||||||
|
/// zip entry. The zip entries contains information about the file name
|
||||||
|
/// size, compressed size, CRC, etc.
|
||||||
|
///
|
||||||
|
/// It includes support for Stored and Deflated entries.
|
||||||
|
/// This class is not thread safe.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>Author of the original java version : Jochen Hoenicke
|
||||||
|
/// </summary>
|
||||||
|
/// <example> This sample shows how to create a zip file
|
||||||
|
/// <code>
|
||||||
|
/// using System;
|
||||||
|
/// using System.IO;
|
||||||
|
///
|
||||||
|
/// using ICSharpCode.SharpZipLib.Core;
|
||||||
|
/// using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
///
|
||||||
|
/// class MainClass
|
||||||
|
/// {
|
||||||
|
/// public static void Main(string[] args)
|
||||||
|
/// {
|
||||||
|
/// string[] filenames = Directory.GetFiles(args[0]);
|
||||||
|
/// byte[] buffer = new byte[4096];
|
||||||
|
///
|
||||||
|
/// using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) {
|
||||||
|
///
|
||||||
|
/// s.SetLevel(9); // 0 - store only to 9 - means best compression
|
||||||
|
///
|
||||||
|
/// foreach (string file in filenames) {
|
||||||
|
/// ZipEntry entry = new ZipEntry(file);
|
||||||
|
/// s.PutNextEntry(entry);
|
||||||
|
///
|
||||||
|
/// using (FileStream fs = File.OpenRead(file)) {
|
||||||
|
/// StreamUtils.Copy(fs, s, buffer);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public class ZipOutputStream : DeflaterOutputStream
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new Zip output stream, writing a zip archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseOutputStream">
|
||||||
|
/// The output stream to which the archive contents are written.
|
||||||
|
/// </param>
|
||||||
|
public ZipOutputStream(Stream baseOutputStream)
|
||||||
|
: base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new Zip output stream, writing a zip archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseOutputStream">The output stream to which the archive contents are written.</param>
|
||||||
|
/// <param name="bufferSize">Size of the buffer to use.</param>
|
||||||
|
public ZipOutputStream(Stream baseOutputStream, int bufferSize)
|
||||||
|
: base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a flag value of true if the central header has been added for this archive; false if it has not been added.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>No further entries can be added once this has been done.</remarks>
|
||||||
|
public bool IsFinished
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return entries == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the zip file comment.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="comment">
|
||||||
|
/// The comment text for the entire archive.
|
||||||
|
/// </param>
|
||||||
|
/// <exception name ="ArgumentOutOfRangeException">
|
||||||
|
/// The converted comment is longer than 0xffff bytes.
|
||||||
|
/// </exception>
|
||||||
|
public void SetComment(string comment)
|
||||||
|
{
|
||||||
|
// TODO: Its not yet clear how to handle unicode comments here.
|
||||||
|
byte[] commentBytes = ZipStrings.ConvertToArray(comment);
|
||||||
|
if (commentBytes.Length > 0xffff)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(comment));
|
||||||
|
}
|
||||||
|
zipComment = commentBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the compression level. The new level will be activated
|
||||||
|
/// immediately.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The new compression level (1 to 9).</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Level specified is not supported.
|
||||||
|
/// </exception>
|
||||||
|
/// <see cref="ICSharpCode.SharpZipLib.Zip.Compression.Deflater"/>
|
||||||
|
public void SetLevel(int level)
|
||||||
|
{
|
||||||
|
deflater_.SetLevel(level);
|
||||||
|
defaultCompressionLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current deflater compression level
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current compression level</returns>
|
||||||
|
public int GetLevel()
|
||||||
|
{
|
||||||
|
return deflater_.GetLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get / set a value indicating how Zip64 Extension usage is determined when adding entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Older archivers may not understand Zip64 extensions.
|
||||||
|
/// If backwards compatability is an issue be careful when adding <see cref="ZipEntry.Size">entries</see> to an archive.
|
||||||
|
/// Setting this property to off is workable but less desirable as in those circumstances adding a file
|
||||||
|
/// larger then 4GB will fail.</remarks>
|
||||||
|
public UseZip64 UseZip64
|
||||||
|
{
|
||||||
|
get { return useZip64_; }
|
||||||
|
set { useZip64_ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an unsigned short in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
private void WriteLeShort(int value)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
baseOutputStream_.WriteByte((byte)(value & 0xff));
|
||||||
|
baseOutputStream_.WriteByte((byte)((value >> 8) & 0xff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an int in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
private void WriteLeInt(int value)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
WriteLeShort(value);
|
||||||
|
WriteLeShort(value >> 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write an int in little endian byte order.
|
||||||
|
/// </summary>
|
||||||
|
private void WriteLeLong(long value)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
WriteLeInt((int)value);
|
||||||
|
WriteLeInt((int)(value >> 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts a new Zip entry. It automatically closes the previous
|
||||||
|
/// entry if present.
|
||||||
|
/// All entry elements bar name are optional, but must be correct if present.
|
||||||
|
/// If the compression method is stored and the output is not patchable
|
||||||
|
/// the compression for that entry is automatically changed to deflate level 0
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry">
|
||||||
|
/// the entry.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// if entry passed is null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.IO.IOException">
|
||||||
|
/// if an I/O error occured.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// if stream was finished
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ZipException">
|
||||||
|
/// Too many entries in the Zip file<br/>
|
||||||
|
/// Entry name is too long<br/>
|
||||||
|
/// Finish has already been called<br/>
|
||||||
|
/// </exception>
|
||||||
|
public void PutNextEntry(ZipEntry entry)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ZipOutputStream was finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curEntry != null)
|
||||||
|
{
|
||||||
|
CloseEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.Count == int.MaxValue)
|
||||||
|
{
|
||||||
|
throw new ZipException("Too many entries for Zip file");
|
||||||
|
}
|
||||||
|
|
||||||
|
CompressionMethod method = entry.CompressionMethod;
|
||||||
|
int compressionLevel = defaultCompressionLevel;
|
||||||
|
|
||||||
|
// Clear flags that the library manages internally
|
||||||
|
entry.Flags &= (int)GeneralBitFlags.UnicodeText;
|
||||||
|
patchEntryHeader = false;
|
||||||
|
|
||||||
|
bool headerInfoAvailable;
|
||||||
|
|
||||||
|
// No need to compress - definitely no data.
|
||||||
|
if (entry.Size == 0)
|
||||||
|
{
|
||||||
|
entry.CompressedSize = entry.Size;
|
||||||
|
entry.Crc = 0;
|
||||||
|
method = CompressionMethod.Stored;
|
||||||
|
headerInfoAvailable = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc && entry.CompressedSize >= 0;
|
||||||
|
|
||||||
|
// Switch to deflation if storing isnt possible.
|
||||||
|
if (method == CompressionMethod.Stored)
|
||||||
|
{
|
||||||
|
if (!headerInfoAvailable)
|
||||||
|
{
|
||||||
|
if (!CanPatchEntries)
|
||||||
|
{
|
||||||
|
// Can't patch entries so storing is not possible.
|
||||||
|
method = CompressionMethod.Deflated;
|
||||||
|
compressionLevel = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // entry.size must be > 0
|
||||||
|
{
|
||||||
|
entry.CompressedSize = entry.Size;
|
||||||
|
headerInfoAvailable = entry.HasCrc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerInfoAvailable == false)
|
||||||
|
{
|
||||||
|
if (CanPatchEntries == false)
|
||||||
|
{
|
||||||
|
// Only way to record size and compressed size is to append a data descriptor
|
||||||
|
// after compressed data.
|
||||||
|
|
||||||
|
// Stored entries of this form have already been converted to deflating.
|
||||||
|
entry.Flags |= 8;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
patchEntryHeader = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Password != null)
|
||||||
|
{
|
||||||
|
entry.IsCrypted = true;
|
||||||
|
if (entry.Crc < 0)
|
||||||
|
{
|
||||||
|
// Need to append a data descriptor as the crc isnt available for use
|
||||||
|
// with encryption, the date is used instead. Setting the flag
|
||||||
|
// indicates this to the decompressor.
|
||||||
|
entry.Flags |= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Offset = offset;
|
||||||
|
entry.CompressionMethod = (CompressionMethod)method;
|
||||||
|
|
||||||
|
curMethod = method;
|
||||||
|
sizePatchPos = -1;
|
||||||
|
|
||||||
|
if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic)))
|
||||||
|
{
|
||||||
|
entry.ForceZip64();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the local file header
|
||||||
|
WriteLeInt(ZipConstants.LocalHeaderSignature);
|
||||||
|
|
||||||
|
WriteLeShort(entry.Version);
|
||||||
|
WriteLeShort(entry.Flags);
|
||||||
|
WriteLeShort((byte)entry.CompressionMethodForHeader);
|
||||||
|
WriteLeInt((int)entry.DosTime);
|
||||||
|
|
||||||
|
// TODO: Refactor header writing. Its done in several places.
|
||||||
|
if (headerInfoAvailable)
|
||||||
|
{
|
||||||
|
WriteLeInt((int)entry.Crc);
|
||||||
|
if (entry.LocalHeaderRequiresZip64)
|
||||||
|
{
|
||||||
|
WriteLeInt(-1);
|
||||||
|
WriteLeInt(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize);
|
||||||
|
WriteLeInt((int)entry.Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (patchEntryHeader)
|
||||||
|
{
|
||||||
|
crcPatchPos = baseOutputStream_.Position;
|
||||||
|
}
|
||||||
|
WriteLeInt(0); // Crc
|
||||||
|
|
||||||
|
if (patchEntryHeader)
|
||||||
|
{
|
||||||
|
sizePatchPos = baseOutputStream_.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For local header both sizes appear in Zip64 Extended Information
|
||||||
|
if (entry.LocalHeaderRequiresZip64 || patchEntryHeader)
|
||||||
|
{
|
||||||
|
WriteLeInt(-1);
|
||||||
|
WriteLeInt(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLeInt(0); // Compressed size
|
||||||
|
WriteLeInt(0); // Uncompressed size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
|
||||||
|
|
||||||
|
if (name.Length > 0xFFFF)
|
||||||
|
{
|
||||||
|
throw new ZipException("Entry name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ed = new ZipExtraData(entry.ExtraData);
|
||||||
|
|
||||||
|
if (entry.LocalHeaderRequiresZip64)
|
||||||
|
{
|
||||||
|
ed.StartNewEntry();
|
||||||
|
if (headerInfoAvailable)
|
||||||
|
{
|
||||||
|
ed.AddLeLong(entry.Size);
|
||||||
|
ed.AddLeLong(entry.CompressedSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ed.AddLeLong(-1);
|
||||||
|
ed.AddLeLong(-1);
|
||||||
|
}
|
||||||
|
ed.AddNewEntry(1);
|
||||||
|
|
||||||
|
if (!ed.Find(1))
|
||||||
|
{
|
||||||
|
throw new ZipException("Internal error cant find extra data");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchEntryHeader)
|
||||||
|
{
|
||||||
|
sizePatchPos = ed.CurrentReadIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ed.Delete(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.AESKeySize > 0)
|
||||||
|
{
|
||||||
|
AddExtraDataAES(entry, ed);
|
||||||
|
}
|
||||||
|
byte[] extra = ed.GetEntryData();
|
||||||
|
|
||||||
|
WriteLeShort(name.Length);
|
||||||
|
WriteLeShort(extra.Length);
|
||||||
|
|
||||||
|
if (name.Length > 0)
|
||||||
|
{
|
||||||
|
baseOutputStream_.Write(name, 0, name.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
|
||||||
|
{
|
||||||
|
sizePatchPos += baseOutputStream_.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra.Length > 0)
|
||||||
|
{
|
||||||
|
baseOutputStream_.Write(extra, 0, extra.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
|
||||||
|
// Fix offsetOfCentraldir for AES
|
||||||
|
if (entry.AESKeySize > 0)
|
||||||
|
offset += entry.AESOverheadSize;
|
||||||
|
|
||||||
|
// Activate the entry.
|
||||||
|
curEntry = entry;
|
||||||
|
crc.Reset();
|
||||||
|
if (method == CompressionMethod.Deflated)
|
||||||
|
{
|
||||||
|
deflater_.Reset();
|
||||||
|
deflater_.SetLevel(compressionLevel);
|
||||||
|
}
|
||||||
|
size = 0;
|
||||||
|
|
||||||
|
if (entry.IsCrypted)
|
||||||
|
{
|
||||||
|
if (entry.AESKeySize > 0)
|
||||||
|
{
|
||||||
|
WriteAESHeader(entry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (entry.Crc < 0)
|
||||||
|
{ // so testing Zip will says its ok
|
||||||
|
WriteEncryptionHeader(entry.DosTime << 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteEncryptionHeader(entry.Crc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the current entry, updating header and footer information as required
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="System.IO.IOException">
|
||||||
|
/// An I/O error occurs.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="System.InvalidOperationException">
|
||||||
|
/// No entry is active.
|
||||||
|
/// </exception>
|
||||||
|
public void CloseEntry()
|
||||||
|
{
|
||||||
|
if (curEntry == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No open entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
long csize = size;
|
||||||
|
|
||||||
|
// First finish the deflater, if appropriate
|
||||||
|
if (curMethod == CompressionMethod.Deflated)
|
||||||
|
{
|
||||||
|
if (size >= 0)
|
||||||
|
{
|
||||||
|
base.Finish();
|
||||||
|
csize = deflater_.TotalOut;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
deflater_.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (curMethod == CompressionMethod.Stored)
|
||||||
|
{
|
||||||
|
// This is done by Finsh() for Deflated entries, but we need to do it
|
||||||
|
// ourselves for Stored ones
|
||||||
|
base.GetAuthCodeIfAES();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the AES Authentication Code (a hash of the compressed and encrypted data)
|
||||||
|
if (curEntry.AESKeySize > 0)
|
||||||
|
{
|
||||||
|
baseOutputStream_.Write(AESAuthCode, 0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curEntry.Size < 0)
|
||||||
|
{
|
||||||
|
curEntry.Size = size;
|
||||||
|
}
|
||||||
|
else if (curEntry.Size != size)
|
||||||
|
{
|
||||||
|
throw new ZipException("size was " + size + ", but I expected " + curEntry.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curEntry.CompressedSize < 0)
|
||||||
|
{
|
||||||
|
curEntry.CompressedSize = csize;
|
||||||
|
}
|
||||||
|
else if (curEntry.CompressedSize != csize)
|
||||||
|
{
|
||||||
|
throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curEntry.Crc < 0)
|
||||||
|
{
|
||||||
|
curEntry.Crc = crc.Value;
|
||||||
|
}
|
||||||
|
else if (curEntry.Crc != crc.Value)
|
||||||
|
{
|
||||||
|
throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += csize;
|
||||||
|
|
||||||
|
if (curEntry.IsCrypted)
|
||||||
|
{
|
||||||
|
if (curEntry.AESKeySize > 0)
|
||||||
|
{
|
||||||
|
curEntry.CompressedSize += curEntry.AESOverheadSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
curEntry.CompressedSize += ZipConstants.CryptoHeaderSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch the header if possible
|
||||||
|
if (patchEntryHeader)
|
||||||
|
{
|
||||||
|
patchEntryHeader = false;
|
||||||
|
|
||||||
|
long curPos = baseOutputStream_.Position;
|
||||||
|
baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin);
|
||||||
|
WriteLeInt((int)curEntry.Crc);
|
||||||
|
|
||||||
|
if (curEntry.LocalHeaderRequiresZip64)
|
||||||
|
{
|
||||||
|
if (sizePatchPos == -1)
|
||||||
|
{
|
||||||
|
throw new ZipException("Entry requires zip64 but this has been turned off");
|
||||||
|
}
|
||||||
|
|
||||||
|
baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin);
|
||||||
|
WriteLeLong(curEntry.Size);
|
||||||
|
WriteLeLong(curEntry.CompressedSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLeInt((int)curEntry.CompressedSize);
|
||||||
|
WriteLeInt((int)curEntry.Size);
|
||||||
|
}
|
||||||
|
baseOutputStream_.Seek(curPos, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add data descriptor if flagged as required
|
||||||
|
if ((curEntry.Flags & 8) != 0)
|
||||||
|
{
|
||||||
|
WriteLeInt(ZipConstants.DataDescriptorSignature);
|
||||||
|
WriteLeInt(unchecked((int)curEntry.Crc));
|
||||||
|
|
||||||
|
if (curEntry.LocalHeaderRequiresZip64)
|
||||||
|
{
|
||||||
|
WriteLeLong(curEntry.CompressedSize);
|
||||||
|
WriteLeLong(curEntry.Size);
|
||||||
|
offset += ZipConstants.Zip64DataDescriptorSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLeInt((int)curEntry.CompressedSize);
|
||||||
|
WriteLeInt((int)curEntry.Size);
|
||||||
|
offset += ZipConstants.DataDescriptorSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.Add(curEntry);
|
||||||
|
curEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEncryptionHeader(long crcValue)
|
||||||
|
{
|
||||||
|
offset += ZipConstants.CryptoHeaderSize;
|
||||||
|
|
||||||
|
InitializePassword(Password);
|
||||||
|
|
||||||
|
byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
|
||||||
|
var rnd = new Random();
|
||||||
|
rnd.NextBytes(cryptBuffer);
|
||||||
|
cryptBuffer[11] = (byte)(crcValue >> 24);
|
||||||
|
|
||||||
|
EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
|
||||||
|
baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData)
|
||||||
|
{
|
||||||
|
// Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored.
|
||||||
|
const int VENDOR_VERSION = 2;
|
||||||
|
// Vendor ID is the two ASCII characters "AE".
|
||||||
|
const int VENDOR_ID = 0x4541; //not 6965;
|
||||||
|
extraData.StartNewEntry();
|
||||||
|
// Pack AES extra data field see http://www.winzip.com/aes_info.htm
|
||||||
|
//extraData.AddLeShort(7); // Data size (currently 7)
|
||||||
|
extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2
|
||||||
|
extraData.AddLeShort(VENDOR_ID); // "AE"
|
||||||
|
extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256
|
||||||
|
extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file
|
||||||
|
extraData.AddNewEntry(0x9901);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replaces WriteEncryptionHeader for AES
|
||||||
|
//
|
||||||
|
private void WriteAESHeader(ZipEntry entry)
|
||||||
|
{
|
||||||
|
byte[] salt;
|
||||||
|
byte[] pwdVerifier;
|
||||||
|
InitializeAESPassword(entry, Password, out salt, out pwdVerifier);
|
||||||
|
// File format for AES:
|
||||||
|
// Size (bytes) Content
|
||||||
|
// ------------ -------
|
||||||
|
// Variable Salt value
|
||||||
|
// 2 Password verification value
|
||||||
|
// Variable Encrypted file data
|
||||||
|
// 10 Authentication code
|
||||||
|
//
|
||||||
|
// Value in the "compressed size" fields of the local file header and the central directory entry
|
||||||
|
// is the total size of all the items listed above. In other words, it is the total size of the
|
||||||
|
// salt value, password verification value, encrypted data, and authentication code.
|
||||||
|
baseOutputStream_.Write(salt, 0, salt.Length);
|
||||||
|
baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the given buffer to the current entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer containing data to write.</param>
|
||||||
|
/// <param name="offset">The offset of the first byte to write.</param>
|
||||||
|
/// <param name="count">The number of bytes to write.</param>
|
||||||
|
/// <exception cref="ZipException">Archive size is invalid</exception>
|
||||||
|
/// <exception cref="System.InvalidOperationException">No entry is active.</exception>
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (curEntry == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No open entry.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buffer.Length - offset) < count)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid offset/count combination");
|
||||||
|
}
|
||||||
|
|
||||||
|
crc.Update(new ArraySegment<byte>(buffer, offset, count));
|
||||||
|
size += count;
|
||||||
|
|
||||||
|
switch (curMethod)
|
||||||
|
{
|
||||||
|
case CompressionMethod.Deflated:
|
||||||
|
base.Write(buffer, offset, count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CompressionMethod.Stored:
|
||||||
|
if (Password != null)
|
||||||
|
{
|
||||||
|
CopyAndEncrypt(buffer, offset, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
baseOutputStream_.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyAndEncrypt(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
const int CopyBufferSize = 4096;
|
||||||
|
byte[] localBuffer = new byte[CopyBufferSize];
|
||||||
|
while (count > 0)
|
||||||
|
{
|
||||||
|
int bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize;
|
||||||
|
|
||||||
|
Array.Copy(buffer, offset, localBuffer, 0, bufferCount);
|
||||||
|
EncryptBlock(localBuffer, 0, bufferCount);
|
||||||
|
baseOutputStream_.Write(localBuffer, 0, bufferCount);
|
||||||
|
count -= bufferCount;
|
||||||
|
offset += bufferCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finishes the stream. This will write the central directory at the
|
||||||
|
/// end of the zip file and flush the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is automatically called when the stream is closed.
|
||||||
|
/// </remarks>
|
||||||
|
/// <exception cref="System.IO.IOException">
|
||||||
|
/// An I/O error occurs.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ZipException">
|
||||||
|
/// Comment exceeds the maximum length<br/>
|
||||||
|
/// Entry name exceeds the maximum length
|
||||||
|
/// </exception>
|
||||||
|
public override void Finish()
|
||||||
|
{
|
||||||
|
if (entries == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curEntry != null)
|
||||||
|
{
|
||||||
|
CloseEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
long numEntries = entries.Count;
|
||||||
|
long sizeEntries = 0;
|
||||||
|
|
||||||
|
foreach (ZipEntry entry in entries)
|
||||||
|
{
|
||||||
|
WriteLeInt(ZipConstants.CentralHeaderSignature);
|
||||||
|
WriteLeShort(ZipConstants.VersionMadeBy);
|
||||||
|
WriteLeShort(entry.Version);
|
||||||
|
WriteLeShort(entry.Flags);
|
||||||
|
WriteLeShort((short)entry.CompressionMethodForHeader);
|
||||||
|
WriteLeInt((int)entry.DosTime);
|
||||||
|
WriteLeInt((int)entry.Crc);
|
||||||
|
|
||||||
|
if (entry.IsZip64Forced() ||
|
||||||
|
(entry.CompressedSize >= uint.MaxValue))
|
||||||
|
{
|
||||||
|
WriteLeInt(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLeInt((int)entry.CompressedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.IsZip64Forced() ||
|
||||||
|
(entry.Size >= uint.MaxValue))
|
||||||
|
{
|
||||||
|
WriteLeInt(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLeInt((int)entry.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name);
|
||||||
|
|
||||||
|
if (name.Length > 0xffff)
|
||||||
|
{
|
||||||
|
throw new ZipException("Name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ed = new ZipExtraData(entry.ExtraData);
|
||||||
|
|
||||||
|
if (entry.CentralHeaderRequiresZip64)
|
||||||
|
{
|
||||||
|
ed.StartNewEntry();
|
||||||
|
if (entry.IsZip64Forced() ||
|
||||||
|
(entry.Size >= 0xffffffff))
|
||||||
|
{
|
||||||
|
ed.AddLeLong(entry.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.IsZip64Forced() ||
|
||||||
|
(entry.CompressedSize >= 0xffffffff))
|
||||||
|
{
|
||||||
|
ed.AddLeLong(entry.CompressedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.Offset >= 0xffffffff)
|
||||||
|
{
|
||||||
|
ed.AddLeLong(entry.Offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
ed.AddNewEntry(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ed.Delete(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.AESKeySize > 0)
|
||||||
|
{
|
||||||
|
AddExtraDataAES(entry, ed);
|
||||||
|
}
|
||||||
|
byte[] extra = ed.GetEntryData();
|
||||||
|
|
||||||
|
byte[] entryComment =
|
||||||
|
(entry.Comment != null) ?
|
||||||
|
ZipStrings.ConvertToArray(entry.Flags, entry.Comment) :
|
||||||
|
new byte[0];
|
||||||
|
|
||||||
|
if (entryComment.Length > 0xffff)
|
||||||
|
{
|
||||||
|
throw new ZipException("Comment too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLeShort(name.Length);
|
||||||
|
WriteLeShort(extra.Length);
|
||||||
|
WriteLeShort(entryComment.Length);
|
||||||
|
WriteLeShort(0); // disk number
|
||||||
|
WriteLeShort(0); // internal file attributes
|
||||||
|
// external file attributes
|
||||||
|
|
||||||
|
if (entry.ExternalFileAttributes != -1)
|
||||||
|
{
|
||||||
|
WriteLeInt(entry.ExternalFileAttributes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (entry.IsDirectory)
|
||||||
|
{ // mark entry as directory (from nikolam.AT.perfectinfo.com)
|
||||||
|
WriteLeInt(16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLeInt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.Offset >= uint.MaxValue)
|
||||||
|
{
|
||||||
|
WriteLeInt(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteLeInt((int)entry.Offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.Length > 0)
|
||||||
|
{
|
||||||
|
baseOutputStream_.Write(name, 0, name.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra.Length > 0)
|
||||||
|
{
|
||||||
|
baseOutputStream_.Write(extra, 0, extra.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entryComment.Length > 0)
|
||||||
|
{
|
||||||
|
baseOutputStream_.Write(entryComment, 0, entryComment.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (ZipHelperStream zhs = new ZipHelperStream(baseOutputStream_))
|
||||||
|
{
|
||||||
|
zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment);
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Instance Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entries for the archive.
|
||||||
|
/// </summary>
|
||||||
|
private List<ZipEntry> entries = new List<ZipEntry>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to track the crc of data added to entries.
|
||||||
|
/// </summary>
|
||||||
|
private Crc32 crc = new Crc32();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current entry being added.
|
||||||
|
/// </summary>
|
||||||
|
private ZipEntry curEntry;
|
||||||
|
|
||||||
|
private int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION;
|
||||||
|
|
||||||
|
private CompressionMethod curMethod = CompressionMethod.Deflated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to track the size of data for an entry during writing.
|
||||||
|
/// </summary>
|
||||||
|
private long size;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Offset to be recorded for each entry in the central header.
|
||||||
|
/// </summary>
|
||||||
|
private long offset;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Comment for the entire archive recorded in central header.
|
||||||
|
/// </summary>
|
||||||
|
private byte[] zipComment = new byte[0];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag indicating that header patching is required for the current entry.
|
||||||
|
/// </summary>
|
||||||
|
private bool patchEntryHeader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position to patch crc
|
||||||
|
/// </summary>
|
||||||
|
private long crcPatchPos = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position to patch size.
|
||||||
|
/// </summary>
|
||||||
|
private long sizePatchPos = -1;
|
||||||
|
|
||||||
|
// Default is dynamic which is not backwards compatible and can cause problems
|
||||||
|
// with XP's built in compression which cant read Zip64 archives.
|
||||||
|
// However it does avoid the situation were a large file is added and cannot be completed correctly.
|
||||||
|
// NOTE: Setting the size for entries before they are added is the best solution!
|
||||||
|
private UseZip64 useZip64_ = UseZip64.Dynamic;
|
||||||
|
|
||||||
|
#endregion Instance Fields
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ICSharpCode.SharpZipLib.Zip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This static class contains functions for encoding and decoding zip file strings
|
||||||
|
/// </summary>
|
||||||
|
public static class ZipStrings
|
||||||
|
{
|
||||||
|
static ZipStrings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var platformCodepage = Encoding.GetEncoding(0).CodePage;
|
||||||
|
SystemDefaultCodePage = (platformCodepage == 1 || platformCodepage == 2 || platformCodepage == 3 || platformCodepage == 42) ? FallbackCodePage : platformCodepage;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
SystemDefaultCodePage = FallbackCodePage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Code page backing field</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The original Zip specification (https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) states
|
||||||
|
/// that file names should only be encoded with IBM Code Page 437 or UTF-8.
|
||||||
|
/// In practice, most zip apps use OEM or system encoding (typically cp437 on Windows).
|
||||||
|
/// Let's be good citizens and default to UTF-8 http://utf8everywhere.org/
|
||||||
|
/// </remarks>
|
||||||
|
private static int codePage = AutomaticCodePage;
|
||||||
|
|
||||||
|
/// Automatically select codepage while opening archive
|
||||||
|
/// see https://github.com/icsharpcode/SharpZipLib/pull/280#issuecomment-433608324
|
||||||
|
///
|
||||||
|
private const int AutomaticCodePage = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encoding used for string conversion. Setting this to 65001 (UTF-8) will
|
||||||
|
/// also set the Language encoding flag to indicate UTF-8 encoded file names.
|
||||||
|
/// </summary>
|
||||||
|
public static int CodePage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return codePage == AutomaticCodePage? Encoding.UTF8.CodePage:codePage;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if ((value < 0) || (value > 65535) ||
|
||||||
|
(value == 1) || (value == 2) || (value == 3) || (value == 42))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
codePage = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int FallbackCodePage = 437;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to get the operating system default codepage, or failing that, to
|
||||||
|
/// the fallback code page IBM 437.
|
||||||
|
/// </summary>
|
||||||
|
public static int SystemDefaultCodePage { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get wether the default codepage is set to UTF-8. Setting this property to false will
|
||||||
|
/// set the <see cref="CodePage"/> to <see cref="SystemDefaultCodePage"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// /// Get OEM codepage from NetFX, which parses the NLP file with culture info table etc etc.
|
||||||
|
/// But sometimes it yields the special value of 1 which is nicknamed <c>CodePageNoOEM</c> in <see cref="Encoding"/> sources (might also mean <c>CP_OEMCP</c>, but Encoding puts it so).
|
||||||
|
/// This was observed on Ukranian and Hindu systems.
|
||||||
|
/// Given this value, <see cref="Encoding.GetEncoding(int)"/> throws an <see cref="ArgumentException"/>.
|
||||||
|
/// So replace it with <see cref="FallbackCodePage"/>, (IBM 437 which is the default code page in a default Windows installation console.
|
||||||
|
/// </remarks>
|
||||||
|
public static bool UseUnicode
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return codePage == Encoding.UTF8.CodePage;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
codePage = Encoding.UTF8.CodePage;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
codePage = SystemDefaultCodePage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a portion of a byte array to a string using <see cref="CodePage"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">
|
||||||
|
/// Data to convert to string
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">
|
||||||
|
/// Number of bytes to convert starting from index 0
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// data[0]..data[count - 1] converted to a string
|
||||||
|
/// </returns>
|
||||||
|
public static string ConvertToString(byte[] data, int count)
|
||||||
|
=> data == null
|
||||||
|
? string.Empty
|
||||||
|
: Encoding.GetEncoding(CodePage).GetString(data, 0, count);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a byte array to a string using <see cref="CodePage"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">
|
||||||
|
/// Byte array to convert
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <paramref name="data">data</paramref>converted to a string
|
||||||
|
/// </returns>
|
||||||
|
public static string ConvertToString(byte[] data)
|
||||||
|
=> ConvertToString(data, data.Length);
|
||||||
|
|
||||||
|
private static Encoding EncodingFromFlag(int flags)
|
||||||
|
=> ((flags & (int)GeneralBitFlags.UnicodeText) != 0)
|
||||||
|
? Encoding.UTF8
|
||||||
|
: Encoding.GetEncoding(
|
||||||
|
// if CodePage wasn't set manually and no utf flag present
|
||||||
|
// then we must use SystemDefault (old behavior)
|
||||||
|
// otherwise, CodePage should be preferred over SystemDefault
|
||||||
|
// see https://github.com/icsharpcode/SharpZipLib/issues/274
|
||||||
|
codePage == AutomaticCodePage?
|
||||||
|
SystemDefaultCodePage:
|
||||||
|
codePage);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a byte array to a string using <see cref="CodePage"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flags">The applicable general purpose bits flags</param>
|
||||||
|
/// <param name="data">
|
||||||
|
/// Byte array to convert
|
||||||
|
/// </param>
|
||||||
|
/// <param name="count">The number of bytes to convert.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <paramref name="data">data</paramref>converted to a string
|
||||||
|
/// </returns>
|
||||||
|
public static string ConvertToStringExt(int flags, byte[] data, int count)
|
||||||
|
=> (data == null)
|
||||||
|
? string.Empty
|
||||||
|
: EncodingFromFlag(flags).GetString(data, 0, count);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a byte array to a string using <see cref="CodePage"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">
|
||||||
|
/// Byte array to convert
|
||||||
|
/// </param>
|
||||||
|
/// <param name="flags">The applicable general purpose bits flags</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <paramref name="data">data</paramref>converted to a string
|
||||||
|
/// </returns>
|
||||||
|
public static string ConvertToStringExt(int flags, byte[] data)
|
||||||
|
=> ConvertToStringExt(flags, data, data.Length);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a string to a byte array using <see cref="CodePage"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str">
|
||||||
|
/// String to convert to an array
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Converted array</returns>
|
||||||
|
public static byte[] ConvertToArray(string str)
|
||||||
|
=> str == null
|
||||||
|
? new byte[0]
|
||||||
|
: Encoding.GetEncoding(CodePage).GetBytes(str);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a string to a byte array using <see cref="CodePage"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flags">The applicable <see cref="GeneralBitFlags">general purpose bits flags</see></param>
|
||||||
|
/// <param name="str">
|
||||||
|
/// String to convert to an array
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Converted array</returns>
|
||||||
|
public static byte[] ConvertToArray(int flags, string str)
|
||||||
|
=> (string.IsNullOrEmpty(str))
|
||||||
|
? new byte[0]
|
||||||
|
: EncodingFromFlag(flags).GetBytes(str);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
|
||||||
|
namespace dezentrale
|
||||||
|
{
|
||||||
|
public class ImportExport
|
||||||
|
{
|
||||||
|
public static bool ZipExport(string outFile, string password = null)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(Program.config.MemberDirectory))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Cannot find directory '{Program.config.MemberDirectory}'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Depending on the directory this could be very large and would require more attention
|
||||||
|
// in a commercial package.
|
||||||
|
string[] filenames = Directory.GetFiles(Program.config.MemberDirectory, "member-*.xml");
|
||||||
|
|
||||||
|
if (File.Exists(outFile))
|
||||||
|
{
|
||||||
|
string backupName = $"{outFile}.bak";
|
||||||
|
if (File.Exists(backupName)) File.Delete(backupName);
|
||||||
|
File.Move(outFile, backupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'using' statements guarantee the stream is closed properly which is a big source
|
||||||
|
// of problems otherwise. Its exception safe as well which is great.
|
||||||
|
using (ZipOutputStream s = new ZipOutputStream(File.Create(outFile)))
|
||||||
|
{
|
||||||
|
/*#if DEBUG
|
||||||
|
s.SetLevel(0); // 0 - store only to 9 - means best compression
|
||||||
|
#else*/
|
||||||
|
s.SetLevel(9); // 0 - store only to 9 - means best compression
|
||||||
|
//#endif
|
||||||
|
s.Password = password; //null is a desired value for "no encryption"
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
|
||||||
|
foreach (string file in filenames)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Using GetFileName makes the result compatible with XP
|
||||||
|
// as the resulting path is not absolute.
|
||||||
|
var entry = new ZipEntry(Path.GetFileName(file));
|
||||||
|
|
||||||
|
// Setup the entry data as required.
|
||||||
|
|
||||||
|
// Crc and size are handled by the library for seakable streams
|
||||||
|
// so no need to do them here.
|
||||||
|
|
||||||
|
// Could also use the last write time or similar for the file.
|
||||||
|
entry.DateTime = DateTime.Now;
|
||||||
|
s.PutNextEntry(entry);
|
||||||
|
|
||||||
|
using (FileStream fs = File.OpenRead(file))
|
||||||
|
{
|
||||||
|
|
||||||
|
// Using a fixed size buffer here makes no noticeable difference for output
|
||||||
|
// but keeps a lid on memory usage.
|
||||||
|
int sourceBytes;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
sourceBytes = fs.Read(buffer, 0, buffer.Length);
|
||||||
|
s.Write(buffer, 0, sourceBytes);
|
||||||
|
} while (sourceBytes > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish/Close arent needed strictly as the using statement does this automatically
|
||||||
|
|
||||||
|
// Finish is important to ensure trailing information for a Zip file is appended. Without this
|
||||||
|
// the created file would be invalid.
|
||||||
|
s.Finish();
|
||||||
|
|
||||||
|
// Close is important to wrap things up and unlock the file.
|
||||||
|
s.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Exception during processing {ex.Message}");
|
||||||
|
|
||||||
|
// No need to rethrow the exception as for our purposes its handled.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public static bool ZipImport(string inFile, string password = null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
Program.cs
12
Program.cs
|
@ -10,7 +10,7 @@ namespace dezentrale
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
public static uint VersionNumber { get; private set; } = 0x19052500;
|
public static uint VersionNumber { get; private set; } = 0x19052700;
|
||||||
public static string VersionString { get; private set; } = $"{VersionNumber:x}";
|
public static string VersionString { get; private set; } = $"{VersionNumber:x}";
|
||||||
|
|
||||||
public static string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
public static string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
|
@ -27,6 +27,7 @@ namespace dezentrale
|
||||||
CommandLine = 0,
|
CommandLine = 0,
|
||||||
Gui = 1,
|
Gui = 1,
|
||||||
Cronjob = 2,
|
Cronjob = 2,
|
||||||
|
Export = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static eMode ProgramMode = eMode.Gui;
|
public static eMode ProgramMode = eMode.Gui;
|
||||||
|
@ -60,10 +61,11 @@ namespace dezentrale
|
||||||
case "mode":
|
case "mode":
|
||||||
switch(argv.ToLower())
|
switch(argv.ToLower())
|
||||||
{
|
{
|
||||||
case "cl": ProgramMode = eMode.CommandLine; Console.WriteLine($"Setting Mode to {ProgramMode}"); break;
|
case "cl": ProgramMode = eMode.CommandLine; Console.WriteLine($"Setting Mode to {ProgramMode}"); break;
|
||||||
case "gui": ProgramMode = eMode.Gui; Console.WriteLine($"Setting Mode to {ProgramMode}"); break;
|
case "gui": ProgramMode = eMode.Gui; Console.WriteLine($"Setting Mode to {ProgramMode}"); break;
|
||||||
case "cronjob":
|
case "cronjob":
|
||||||
case "auto": ProgramMode = eMode.Cronjob; Console.WriteLine($"Setting Mode to {ProgramMode}"); break;
|
case "auto": ProgramMode = eMode.Cronjob; Console.WriteLine($"Setting Mode to {ProgramMode}"); break;
|
||||||
|
case "export": ProgramMode = eMode.Export; Console.WriteLine($"Setting Mode to {ProgramMode}"); break;
|
||||||
default:
|
default:
|
||||||
Console.WriteLine("Invalid mode.");
|
Console.WriteLine("Invalid mode.");
|
||||||
printHelp = true;
|
printHelp = true;
|
||||||
|
@ -138,6 +140,8 @@ namespace dezentrale
|
||||||
//Console.WriteLine("Not implemented yet");
|
//Console.WriteLine("Not implemented yet");
|
||||||
Cronjob.Run();
|
Cronjob.Run();
|
||||||
break;
|
break;
|
||||||
|
case eMode.Export:
|
||||||
|
return ImportExport.ZipExport(Path.Combine(DmDirectory, config.ImportExportFile), config.ImportExportPassword) != false ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RootNamespace>dezentrale</RootNamespace>
|
<RootNamespace>dezentrale</RootNamespace>
|
||||||
<AssemblyName>dezentrale-members</AssemblyName>
|
<AssemblyName>dezentrale-members</AssemblyName>
|
||||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<TargetFrameworkProfile />
|
<TargetFrameworkProfile />
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<Optimize>false</Optimize>
|
<Optimize>false</Optimize>
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<DefineConstants>TRACE;DEBUG;NET45</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
<Optimize>true</Optimize>
|
<Optimize>true</Optimize>
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
<DefineConstants>TRACE;NET45</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
@ -61,6 +61,76 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Cronjob.cs" />
|
<Compile Include="Cronjob.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2Constants.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2Exception.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2InputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2OutputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Checksum\Adler32.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Checksum\BZip2Crc.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Checksum\Crc32.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Checksum\IChecksum.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\Exceptions\SharpZipBaseException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\Exceptions\StreamDecodingException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\Exceptions\StreamUnsupportedException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\Exceptions\UnexpectedEndOfStreamException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\Exceptions\ValueOutOfRangeException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\FileSystemScanner.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\INameTransform.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\InvalidNameException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\IScanFilter.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\NameFilter.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\PathFilter.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\StreamUtils.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Core\WindowsPathUtils.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Encryption\PkzipClassic.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Encryption\ZipAESStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Encryption\ZipAESTransform.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\GZip\GZip.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\GZip\GZipConstants.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\GZip\GZipException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\GZip\GzipInputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\GZip\GzipOutputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Lzw\LzwConstants.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Lzw\LzwException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Lzw\LzwInputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\InvalidHeaderException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\TarArchive.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\TarBuffer.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\TarEntry.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\TarException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\TarExtendedHeaderReader.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\TarHeader.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\TarInputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Tar\TarOutputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\Deflater.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\DeflaterConstants.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\DeflaterEngine.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\DeflaterHuffman.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\DeflaterPending.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\Inflater.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\InflaterDynHeader.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\InflaterHuffmanTree.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\PendingBuffer.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\Streams\DeflaterOutputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\Streams\InflaterInputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\Streams\OutputWindow.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\Compression\Streams\StreamManipulator.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\FastZip.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\IEntryFactory.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\WindowsNameTransform.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipConstants.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipEntry.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipEntryFactory.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipException.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipExtraData.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipFile.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipHelperStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipInputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipNameTransform.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipOutputStream.cs" />
|
||||||
|
<Compile Include="ICSharpCode.SharpZipLib\Zip\ZipStrings.cs" />
|
||||||
|
<Compile Include="ImportExport.cs" />
|
||||||
<Compile Include="model\BankTransfer.cs" />
|
<Compile Include="model\BankTransfer.cs" />
|
||||||
<Compile Include="model\BankTransferList.cs" />
|
<Compile Include="model\BankTransferList.cs" />
|
||||||
<Compile Include="model\CsvFile.cs" />
|
<Compile Include="model\CsvFile.cs" />
|
||||||
|
@ -102,5 +172,6 @@
|
||||||
<Install>true</Install>
|
<Install>true</Install>
|
||||||
</BootstrapperPackage>
|
</BootstrapperPackage>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
|
@ -8,12 +8,14 @@ namespace dezentrale.model
|
||||||
{
|
{
|
||||||
public class Configuration : XmlData
|
public class Configuration : XmlData
|
||||||
{
|
{
|
||||||
[XmlElement] public ConfigSmtp Smtp { get; set; } = new ConfigSmtp();
|
[XmlElement] public ConfigSmtp Smtp { get; set; } = new ConfigSmtp();
|
||||||
[XmlElement] public ConfigVSMail VS { get; set; } = new ConfigVSMail();
|
[XmlElement] public ConfigVSMail VS { get; set; } = new ConfigVSMail();
|
||||||
|
|
||||||
[XmlElement] public string MemberDirectory { get; set; } = "member-data";
|
[XmlElement] public string MemberDirectory { get; set; } = "member-data";
|
||||||
[XmlElement] public uint RegularPaymentAmount { get; set; } = 3200; //cents
|
[XmlElement] public uint RegularPaymentAmount { get; set; } = 3200; //cents
|
||||||
[XmlElement] public string LocalUser { get; set; } = "";
|
[XmlElement] public string LocalUser { get; set; } = "";
|
||||||
[XmlElement] public string KeylockCombination { get; set; } = "0000";
|
[XmlElement] public string KeylockCombination { get; set; } = "0000";
|
||||||
|
[XmlElement] public string ImportExportFile { get; set; } = "fnord.fnord";
|
||||||
|
[XmlElement] public string ImportExportPassword { get; set; } = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue