add
This commit is contained in:
8
Assets/CoolapeFrame/3rd/SharpZipLib-1.2.0/src.meta
Normal file
8
Assets/CoolapeFrame/3rd/SharpZipLib-1.2.0/src.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fd58bd1583bf46c7bb9e5786ce14a7a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7bf495be56564ebcbdcbc1e2c12c255
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6620816fa3c2740ccbe19425289a6bb8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36bf1efd201c149f880b8e2f526472ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5116b115cc34f4a3a85a38e46a774cf5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.BZip2
|
||||
{
|
||||
/// <summary>
|
||||
/// BZip2Exception represents exceptions specific to BZip2 classes and code.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the BZip2Exception class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected BZip2Exception(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec6844725099c40c6a4d26affd2a02b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afae2e6e565a346c7aa09133811d5dbe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5183bd4f3e1234db795ab7f73a5041a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e986f27051f504b16ad06e55a657110b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4352f5a246054cbfb026d92f47742e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3db2a91a8f7364eef9269bff9130c910
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2d51386aeb444bcb9fc196b6f437a31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bd9f410346c4448789c44e3aa8b6215
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd3011f324815495f97d4883ccbffc0a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 431785cde2f0a41dab285f155c099cc5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
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>
|
||||
[Serializable]
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the SharpZipBaseException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected SharpZipBaseException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2587dc945de194684bd2be78e35cf558
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that an error occured during decoding of a input stream due to corrupt
|
||||
/// data or (unintentional) library incompability.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the StreamDecodingException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected StreamDecodingException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d8f09b3301b941cdac0aab28d67382a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the input stream could not decoded due to known library incompability or missing features
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the StreamUnsupportedException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected StreamUnsupportedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e2eca5bc58fd4df8a5f18ff42cb6426
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the input stream could not decoded due to the stream ending before enough data had been provided
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the UnexpectedEndOfStreamException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected UnexpectedEndOfStreamException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7dc02c3f297b946ce9c1125e1eb99f16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a value was outside of the expected range when decoding an input stream
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ValueOutOfRangeException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected ValueOutOfRangeException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3e0095b9d0164e539e75b4eb35e7de1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80f791307b83440b2ab80f0752ec3db8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c6a26eb00fdd4295b36936e47da8d74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 951bb845e80f34a3ab2fbf7efcd6b611
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// InvalidNameException is thrown for invalid names such as directory traversal paths and names with invalid characters
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the InvalidNameException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected InvalidNameException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 565765cd4c7db420b9525a5fafcee1ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d65184d689ab043cda3d982c683699be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef9f01b76ab384a8cb6c754d616c2e83
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fedc6c8660cce4c318952e309a50456c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3dc3b394f2dd94d92935a1fdef49cd30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b1ff9c7d136b4cfeb6874fde63a9e4d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8180cabc4a33448d95217ac63a54690
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,220 @@
|
||||
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];
|
||||
|
||||
// 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;
|
||||
|
||||
// Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
|
||||
private const int CRYPTO_BLOCK_SIZE = 16;
|
||||
|
||||
// total length of block + auth code
|
||||
private const int BLOCK_AND_AUTH = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH;
|
||||
|
||||
private Stream _stream;
|
||||
private ZipAESTransform _transform;
|
||||
private byte[] _slideBuffer;
|
||||
private int _slideBufStartPos;
|
||||
private int _slideBufFreePos;
|
||||
|
||||
// Buffer block transforms to enable partial reads
|
||||
private byte[] _transformBuffer = null;// new byte[CRYPTO_BLOCK_SIZE];
|
||||
private int _transformBufferFreePos;
|
||||
private int _transformBufferStartPos;
|
||||
|
||||
// Do we have some buffered data available?
|
||||
private bool HasBufferedData =>_transformBuffer != null && _transformBufferStartPos < _transformBufferFreePos;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// Nothing to do
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
// If we have buffered data, read that first
|
||||
int nBytes = 0;
|
||||
if (HasBufferedData)
|
||||
{
|
||||
nBytes = ReadBufferedData(buffer, offset, count);
|
||||
|
||||
// Read all requested data from the buffer
|
||||
if (nBytes == count)
|
||||
return nBytes;
|
||||
|
||||
offset += nBytes;
|
||||
count -= nBytes;
|
||||
}
|
||||
|
||||
// Read more data from the input, if available
|
||||
if (_slideBuffer != null)
|
||||
nBytes += ReadAndTransform(buffer, offset, count);
|
||||
|
||||
return nBytes;
|
||||
}
|
||||
|
||||
// Read data from the underlying stream and decrypt it
|
||||
private int ReadAndTransform(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int nBytes = 0;
|
||||
while (nBytes < count)
|
||||
{
|
||||
int bytesLeftToRead = count - nBytes;
|
||||
|
||||
// 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 = BLOCK_AND_AUTH - 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 >= BLOCK_AND_AUTH)
|
||||
{
|
||||
var read = TransformAndBufferBlock(buffer, offset, bytesLeftToRead, CRYPTO_BLOCK_SIZE);
|
||||
nBytes += read;
|
||||
offset += read;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Last round.
|
||||
if (byteCount > AUTH_CODE_LENGTH)
|
||||
{
|
||||
// At least one byte of data plus auth code
|
||||
int finalBlock = byteCount - AUTH_CODE_LENGTH;
|
||||
nBytes += TransformAndBufferBlock(buffer, offset, bytesLeftToRead, 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.");
|
||||
}
|
||||
}
|
||||
|
||||
// don't need this any more, so use it as a 'complete' flag
|
||||
_slideBuffer = null;
|
||||
|
||||
break; // Reached the auth code
|
||||
}
|
||||
}
|
||||
return nBytes;
|
||||
}
|
||||
|
||||
// read some buffered data
|
||||
private int ReadBufferedData(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int copyCount = Math.Min(count, _transformBufferFreePos - _transformBufferStartPos);
|
||||
|
||||
Array.Copy(_transformBuffer, _transformBufferStartPos, buffer, offset, count);
|
||||
_transformBufferStartPos += copyCount;
|
||||
|
||||
return copyCount;
|
||||
}
|
||||
|
||||
// Perform the crypto transform, and buffer the data if less than one block has been requested.
|
||||
private int TransformAndBufferBlock(byte[] buffer, int offset, int count, int blockSize)
|
||||
{
|
||||
// If the requested data is greater than one block, transform it directly into the output
|
||||
// If it's smaller, do it into a temporary buffer and copy the requested part
|
||||
bool bufferRequired = (blockSize > count);
|
||||
|
||||
if (bufferRequired && _transformBuffer == null)
|
||||
_transformBuffer = new byte[CRYPTO_BLOCK_SIZE];
|
||||
|
||||
var targetBuffer = bufferRequired ? _transformBuffer : buffer;
|
||||
var targetOffset = bufferRequired ? 0 : offset;
|
||||
|
||||
// Transform the data
|
||||
_transform.TransformBlock(_slideBuffer,
|
||||
_slideBufStartPos,
|
||||
blockSize,
|
||||
targetBuffer,
|
||||
targetOffset);
|
||||
|
||||
_slideBufStartPos += blockSize;
|
||||
|
||||
if (!bufferRequired)
|
||||
{
|
||||
return blockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(_transformBuffer, 0, buffer, offset, count);
|
||||
_transformBufferStartPos = count;
|
||||
_transformBufferFreePos = blockSize;
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4f2f5d3f5758466d9a77ea638e41f1e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2019340bbe07d459f9bba918ee00204f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a8be8e6f57bf4c35a13654079bc298e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fede8bcc50bbb44ab90bb9541e6fdf54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6aa6622f860324d43b7bc61243818fb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.GZip
|
||||
{
|
||||
/// <summary>
|
||||
/// GZipException represents exceptions specific to GZip classes and code.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the GZipException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected GZipException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1df6af64e82941b18c7e29f706b719c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,390 @@
|
||||
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();
|
||||
}
|
||||
|
||||
// Attempting to read 0 bytes will never yield any bytesRead, so we return instead of looping forever
|
||||
if (bytesRead > 0 || count == 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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ccd6b70f6c034f9babe0d730642fa04
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5df203e543fb2446cbad5c0478a1fc2b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3f71533c9b984a87acd16abd94707ef
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ec20aaa34ede4b24996c8efaa77561b
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2f01f99bb8924d42a113ff1133e73a5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d00c79cf86e3e47acaa10a6293604cf4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Lzw
|
||||
{
|
||||
/// <summary>
|
||||
/// LzwException represents exceptions specific to LZW classes and code.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the LzwException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected LzwException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7b6f0de997aa46b798577288051edbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac99656d2ede240eba8daa0d4136c678
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 272f4d15328d84c238276bf977ac67cd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89dceccc821084f3ca52948807e3ef21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,959 @@
|
||||
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)
|
||||
{
|
||||
using (var outputStream = File.Create(destFile))
|
||||
{
|
||||
if (this.asciiTranslate)
|
||||
{
|
||||
// May need to translate the file.
|
||||
ExtractAndTranslateEntry(destFile, outputStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If translation is disabled, just copy the entry across directly.
|
||||
tarIn.CopyEntryContents(outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract a TAR entry, and perform an ASCII translation if required.
|
||||
private void ExtractAndTranslateEntry(string destFile, Stream outputStream)
|
||||
{
|
||||
bool asciiTrans = !IsBinary(destFile);
|
||||
|
||||
if (asciiTrans)
|
||||
{
|
||||
using (var outw = new StreamWriter(outputStream, new UTF8Encoding(false), 1024, true))
|
||||
{
|
||||
byte[] rdbuf = new byte[32 * 1024];
|
||||
|
||||
while (true)
|
||||
{
|
||||
int numRead = tarIn.Read(rdbuf, 0, rdbuf.Length);
|
||||
|
||||
if (numRead <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// No translation required.
|
||||
tarIn.CopyEntryContents(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (!String.IsNullOrEmpty(rootPath))
|
||||
{
|
||||
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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2822f3658ffeb4d498c1df7ed3110f5f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80e87b27138c94dd5bc4ed24acfd4394
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71d74fccb444244a5b0183dcca3e7e3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Tar
|
||||
{
|
||||
/// <summary>
|
||||
/// TarException represents exceptions specific to Tar classes and code.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the TarException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected TarException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ccdfee54c1d5b4ea5971d2b9e875fd11
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c2988d61755f4662b1898f848bb3f4e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfda2392b8aaf4099add24ae2b4b23bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43169ee68d4e24cc1bbc5616e2e431ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a910c12df350b4fe0af9e9c2de66abec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ead08a2de28e94719b78142a9b1c73e0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9936f103f67be4f939dd589880efe282
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user