| [ < ] | +[ > ] | ++ | [ << ] | +[ Up ] | +[ >> ] | ++ | + | + | + | [Top] | +[Contents] | +[Index] | +[ ? ] | +
This appendix describes genfile, an auxiliary program
+used in the GNU tar testsuite. If you are not interested in developing
+GNU tar, skip this appendix.
+
Initially, genfile was used to generate data files for
+the testsuite, hence its name. However, new operation modes were being
+implemented as the testsuite grew more sophisticated, and now
+genfile is a multi-purpose instrument.
+
There are three basic operation modes: +
+ This is the default mode. In this mode, genfile
+generates data files.
+
In this mode genfile displays status of specified files.
+
In this mode genfile executes the given program with
+`--checkpoint' option and executes a set of actions when
+specified checkpoints are reached.
+
| E.1 Generate Mode | File Generation Mode. + | |
| E.2 Status Mode | File Status Mode. + | |
| E.3 Exec Mode | Synchronous Execution mode. + |
| [ < ] | +[ > ] | ++ | [ << ] | +[ Up ] | +[ >> ] | ++ | + | + | + | [Top] | +[Contents] | +[Index] | +[ ? ] | +
In this mode genfile creates a data file for the test
+suite. The size of the file is given with the `--length'
+(`-l') option. By default the file contents is written to the
+standard output, this can be changed using `--file'
+(`-f') command line option. Thus, the following two commands
+are equivalent:
+
genfile --length 100 > outfile +genfile --length 100 --file outfile + |
If `--length' is not given, genfile will
+generate an empty (zero-length) file.
+
The command line option `--seek=N' istructs genfile
+to skip the given number of bytes (N) in the output file before
+writing to it. It is similar to the `seek=N' of the
+dd utility.
+
You can instruct genfile to create several files at one
+go, by giving it `--files-from' (`-T') option followed
+by a name of file containing a list of file names. Using dash
+(`-') instead of the file name causes genfile to read
+file list from the standard input. For example:
+
# Read file names from file `file.list' +genfile --files-from file.list +# Read file names from standard input +genfile --files-from - + |
The list file is supposed to contain one file name per line. To +use file lists separated by ASCII NUL character, use `--null' +(`-0') command line option: +
+genfile --null --files-from file.list + |
The default data pattern for filling the generated file consists +of first 256 letters of ASCII code, repeated enough times to fill the +entire file. This behavior can be changed with `--pattern' +option. This option takes a mandatory argument, specifying pattern +name to use. Currently two patterns are implemented: +
+The default pattern as described above. +
+Fills the file with zeroes. +
If no file name was given, the program exits with the code
+0. Otherwise, it exits with 0 only if it was able to
+create a file of the specified length.
+
Special option `--sparse' (`-s') instructs
+genfile to create a sparse file. Sparse files consist of
+data fragments, separated by holes or blocks of zeros. On
+many operating systems, actual disk storage is not allocated for
+holes, but they are counted in the length of the file. To create a
+sparse file, genfile should know where to put data fragments,
+and what data to use to fill them. So, when `--sparse' is given
+the rest of the command line specifies a so-called file map.
+
The file map consists of any number of fragment +descriptors. Each descriptor is composed of two values: a number, +specifying fragment offset from the end of the previous fragment or, +for the very first fragment, from the beginning of the file, and +contents string, i.e., a string of characters, specifying the +pattern to fill the fragment with. File offset can be suffixed with +the following quantifiers: +
+The number is expressed in kilobytes. +
The number is expressed in megabytes. +
The number is expressed in gigabytes. +
For each letter in contents string genfile will generate
+a block of data, filled with this letter and will write it to
+the fragment. The size of block is given by `--block-size'
+option. It defaults to 512. Thus, if the string consists of n
+characters, the resulting file fragment will contain
+n*block-size of data.
+
Last fragment descriptor can have only file offset part. In this
+case genfile will create a hole at the end of the file up to
+the given offset.
+
For example, consider the following invocation: +
+genfile --sparse --file sparsefile 0 ABCD 1M EFGHI 2000K + |
It will create 3101184-bytes long file of the following structure: +
+Offset | Length | Contents + |
0 | 4*512=2048 | Four 512-byte blocks, filled with +letters `A', `B', `C' and `D'. + |
2048 | 1046528 | Zero bytes + |
1050624 | 5*512=2560 | Five blocks, filled with letters +`E', `F', `G', `H', `I'. + |
1053184 | 2048000 | Zero bytes + |
The exit code of genfile --status command is 0
+only if created file is actually sparse.
+
| [ < ] | +[ > ] | ++ | [ << ] | +[ Up ] | +[ >> ] | ++ | + | + | + | [Top] | +[Contents] | +[Index] | +[ ? ] | +
In status mode, genfile prints file system status for
+each file specified in the command line. This mode is toggled by
+`--stat' (`-S') command line option. An optional argument to this
+option specifies output format: a comma-separated list of
+struct stat fields to be displayed. This list can contain
+following identifiers:
+
The file name. +
+Device number in decimal. +
+Inode number. +
+See Should we also support `%' notations as in stat(1)? +
+ + File mode in octal. Optional number specifies octal mask to
+be applied to the mode before outputting. For example, --stat
+mode.777 will preserve lower nine bits of it. Notice, that you can
+use any punctuation character in place of `.'.
+
Number of hard links. +
+User ID of owner. +
+Group ID of owner. +
+File size in decimal. +
+The size in bytes of each file block. +
+Number of blocks allocated. +
+Time of last access. +
+Time of last modification +
+Time of last status change +
+A boolean value indicating whether the file is `sparse'. +
Modification times are displayed in UTC as
+UNIX timestamps, unless suffixed with `H' (for
+"human-readable"), as in `ctimeH', in which case usual
+tar tv output format is used.
+
The default output format is: `name,dev,ino,mode, +nlink,uid,gid,size,blksize,blocks,atime,mtime,ctime'. +
+For example, the following command will display file names and +corresponding times of last access for each file in the current working +directory: +
+genfile --stat=name,atime * + |
| [ < ] | +[ > ] | ++ | [ << ] | +[ Up ] | +[ >> ] | ++ | + | + | + | [Top] | +[Contents] | +[Index] | +[ ? ] | +
This mode is designed for testing the behavior of paxutils
+commands when some of the files change during archiving. It is an
+experimental mode.
+
The `Exec Mode' is toggled by `--run' command line
+option (or its alias `-r'). The non-optional arguments to
+getopt give the command line to be executed. Normally,
+it should contain at least the `--checkpoint' option.
+
A set of options is provided for defining checkpoint values and +actions to be executed upon reaching them. Checkpoint values are +introduced with the `--checkpoint' command line +option. Argument to this option is the number of checkpoint in decimal. +
+Any number of actions may be specified after a +checkpoint. Available actions are +
+Truncate file to the size specified by previous +`--length' option (or 0, if it is not given). +
+Append data to file. The size of data and its pattern are +given by previous `--length' and `pattern' options. +
+Update the access and modification times of file. These +timestamps are changed to the current time, unless `--date' +option was given, in which case they are changed to the specified +time. Argument to `--date' option is a date specification in +an almost arbitrary format (see section Date input formats). +
+Execute given shell command. +
+Unlink the file. +
Option `--verbose' instructs genfile to print on
+standard output notifications about checkpoints being executed and to
+verbosely describe exit status of the command.
+
While the command is being executed its standard output remains +connected to descriptor 1. All messages it prints to file descriptor +2, except checkpoint notifications, are forwarded to standard +error. +
+ Genfile exits with the exit status of the executed command.
+
For compatibility with previous genfile versions, the
+`--run' option takes an optional argument. If used this way,
+its argument supplies the command line to be executed. There should
+be no non-optional arguments in the genfile command line.
+
The actual command line is constructed by inserting
+the `--checkpoint' option between the command name and its
+first argument (if any). Due to this, the argument to `--run'
+may not use traditional tar option syntax, i.e., the
+following is wrong:
+
# Wrong! +genfile --run='tar cf foo bar' + |
Use the following syntax instead: +
+genfile --run='tar -cf foo bar' actions... + |
The above command line is equivalent to +
+genfile actions... -- tar -cf foo bar + |
Notice, that the use of compatibility mode is deprecated. +
+| [ << ] | +[ >> ] | ++ | + | + | + | + | [Top] | +[Contents] | +[Index] | +[ ? ] | +
+
+ This document was generated on July, 28 2014 using texi2html 1.76.
+
+
+
+
| Class: | ICSharpCode.SharpZipLib.Checksum.Adler32 |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Checksum\Adler32.cs |
| Covered lines: | 35 |
| Uncovered lines: | 6 |
| Coverable lines: | 41 |
| Total lines: | 175 |
| Line coverage: | 85.3% |
| Branch coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| Reset() | 1 | 100 | 100 |
| Update(...) | 1 | 0 | 0 |
| Update(...) | 2 | 100 | 100 |
| Update(...) | 9 | 100 | 100 |
| .cctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Checksum | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// Computes Adler32 checksum for a stream of data. An Adler32 | |||
7 | /// checksum is not as reliable as a CRC32 checksum, but a lot faster to | |||
8 | /// compute. | |||
9 | /// | |||
10 | /// The specification for Adler32 may be found in RFC 1950. | |||
11 | /// ZLIB Compressed Data Format Specification version 3.3) | |||
12 | /// | |||
13 | /// | |||
14 | /// From that document: | |||
15 | /// | |||
16 | /// "ADLER32 (Adler-32 checksum) | |||
17 | /// This contains a checksum value of the uncompressed data | |||
18 | /// (excluding any dictionary data) computed according to Adler-32 | |||
19 | /// algorithm. This algorithm is a 32-bit extension and improvement | |||
20 | /// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 | |||
21 | /// standard. | |||
22 | /// | |||
23 | /// Adler-32 is composed of two sums accumulated per byte: s1 is | |||
24 | /// the sum of all bytes, s2 is the sum of all s1 values. Both sums | |||
25 | /// are done modulo 65521. s1 is initialized to 1, s2 to zero. The | |||
26 | /// Adler-32 checksum is stored as s2*65536 + s1 in most- | |||
27 | /// significant-byte first (network) order." | |||
28 | /// | |||
29 | /// "8.2. The Adler-32 algorithm | |||
30 | /// | |||
31 | /// The Adler-32 algorithm is much faster than the CRC32 algorithm yet | |||
32 | /// still provides an extremely low probability of undetected errors. | |||
33 | /// | |||
34 | /// The modulo on unsigned long accumulators can be delayed for 5552 | |||
35 | /// bytes, so the modulo operation time is negligible. If the bytes | |||
36 | /// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position | |||
37 | /// and order sensitive, unlike the first sum, which is just a | |||
38 | /// checksum. That 65521 is prime is important to avoid a possible | |||
39 | /// large class of two-byte errors that leave the check unchanged. | |||
40 | /// (The Fletcher checksum uses 255, which is not prime and which also | |||
41 | /// makes the Fletcher check insensitive to single byte changes 0 - | |||
42 | /// 255.) | |||
43 | /// | |||
44 | /// The sum s1 is initialized to 1 instead of zero to make the length | |||
45 | /// of the sequence part of s2, so that the length does not have to be | |||
46 | /// checked separately. (Any sequence of zeroes has a Fletcher | |||
47 | /// checksum of zero.)" | |||
48 | /// </summary> | |||
49 | /// <see cref="ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream"/> | |||
50 | /// <see cref="ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream"/> | |||
51 | public sealed class Adler32 : IChecksum | |||
52 | { | |||
53 | #region Instance Fields | |||
54 | /// <summary> | |||
55 | /// largest prime smaller than 65536 | |||
56 | /// </summary> | |||
| 1 | 57 | readonly static uint BASE = 65521; | ||
58 | | |||
59 | /// <summary> | |||
60 | /// The CRC data checksum so far. | |||
61 | /// </summary> | |||
62 | uint checkValue; | |||
63 | #endregion | |||
64 | | |||
65 | /// <summary> | |||
66 | /// Initialise a default instance of <see cref="Adler32"></see> | |||
67 | /// </summary> | |||
| 709 | 68 | public Adler32() | ||
69 | { | |||
| 709 | 70 | Reset(); | ||
| 709 | 71 | } | ||
72 | | |||
73 | /// <summary> | |||
74 | /// Resets the Adler32 data checksum as if no update was ever called. | |||
75 | /// </summary> | |||
76 | public void Reset() | |||
77 | { | |||
| 1143 | 78 | checkValue = 1; | ||
| 1143 | 79 | } | ||
80 | | |||
81 | /// <summary> | |||
82 | /// Returns the Adler32 data checksum computed so far. | |||
83 | /// </summary> | |||
84 | public long Value { | |||
85 | get { | |||
| 18 | 86 | return checkValue; | ||
87 | } | |||
88 | } | |||
89 | | |||
90 | /// <summary> | |||
91 | /// Updates the checksum with the byte b. | |||
92 | /// </summary> | |||
93 | /// <param name="bval"> | |||
94 | /// The data value to add. The high byte of the int is ignored. | |||
95 | /// </param> | |||
96 | public void Update(int bval) | |||
97 | { | |||
98 | // We could make a length 1 byte array and call update again, but I | |||
99 | // would rather not have that overhead | |||
| 0 | 100 | uint s1 = checkValue & 0xFFFF; | ||
| 0 | 101 | uint s2 = checkValue >> 16; | ||
102 | | |||
| 0 | 103 | s1 = (s1 + ((uint)bval & 0xFF)) % BASE; | ||
| 0 | 104 | s2 = (s1 + s2) % BASE; | ||
105 | | |||
| 0 | 106 | checkValue = (s2 << 16) + s1; | ||
| 0 | 107 | } | ||
108 | | |||
109 | /// <summary> | |||
110 | /// Updates the Adler32 data checksum with the bytes taken from | |||
111 | /// a block of data. | |||
112 | /// </summary> | |||
113 | /// <param name="buffer">Contains the data to update the checksum with.</param> | |||
114 | public void Update(byte[] buffer) | |||
115 | { | |||
| 2 | 116 | if (buffer == null) { | ||
| 1 | 117 | throw new ArgumentNullException(nameof(buffer)); | ||
118 | } | |||
119 | | |||
| 1 | 120 | Update(buffer, 0, buffer.Length); | ||
| 1 | 121 | } | ||
122 | | |||
123 | /// <summary> | |||
124 | /// Update Adler32 data checksum based on a portion of a block of data | |||
125 | /// </summary> | |||
126 | /// <param name = "buffer">Contains the data to update the CRC with.</param> | |||
127 | /// <param name = "offset">The offset into the buffer where the data starts</param> | |||
128 | /// <param name = "count">The number of data bytes to update the CRC with.</param> | |||
129 | public void Update(byte[] buffer, int offset, int count) | |||
130 | { | |||
| 6204 | 131 | if (buffer == null) { | ||
| 1 | 132 | throw new ArgumentNullException(nameof(buffer)); | ||
133 | } | |||
134 | | |||
| 6203 | 135 | if (offset < 0) { | ||
| 1 | 136 | throw new ArgumentOutOfRangeException(nameof(offset), "cannot be less than zero"); | ||
137 | } | |||
138 | | |||
| 6202 | 139 | if (offset >= buffer.Length) { | ||
| 1 | 140 | throw new ArgumentOutOfRangeException(nameof(offset), "not a valid index into buffer"); | ||
141 | } | |||
142 | | |||
| 6201 | 143 | if (count < 0) { | ||
| 1 | 144 | throw new ArgumentOutOfRangeException(nameof(count), "cannot be less than zero"); | ||
145 | } | |||
146 | | |||
| 6200 | 147 | if (offset + count > buffer.Length) { | ||
| 1 | 148 | throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); | ||
149 | } | |||
150 | | |||
151 | //(By Per Bothner) | |||
| 6199 | 152 | uint s1 = checkValue & 0xFFFF; | ||
| 6199 | 153 | uint s2 = checkValue >> 16; | ||
154 | | |||
| 13890 | 155 | while (count > 0) { | ||
156 | // We can defer the modulo operation: | |||
157 | // s1 maximally grows from 65521 to 65521 + 255 * 3800 | |||
158 | // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 | |||
| 7691 | 159 | int n = 3800; | ||
| 7691 | 160 | if (n > count) { | ||
| 6199 | 161 | n = count; | ||
162 | } | |||
| 7691 | 163 | count -= n; | ||
| 8355525 | 164 | while (--n >= 0) { | ||
| 8347834 | 165 | s1 = s1 + (uint)(buffer[offset++] & 0xff); | ||
| 8347834 | 166 | s2 = s2 + s1; | ||
167 | } | |||
| 7691 | 168 | s1 %= BASE; | ||
| 7691 | 169 | s2 %= BASE; | ||
170 | } | |||
171 | | |||
| 6199 | 172 | checkValue = (s2 << 16) | s1; | ||
| 6199 | 173 | } | ||
174 | } | |||
175 | } |
| Class: | ICSharpCode.SharpZipLib.BZip2.BZip2 |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\BZip2\BZip2.cs |
| Covered lines: | 0 |
| Uncovered lines: | 20 |
| Coverable lines: | 20 |
| Total lines: | 66 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| Decompress(...) | 5 | 0 | 0 |
| Compress(...) | 5 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.BZip2 | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// An example class to demonstrate compression and decompression of BZip2 streams. | |||
8 | /// </summary> | |||
9 | public static class BZip2 | |||
10 | { | |||
11 | /// <summary> | |||
12 | /// Decompress the <paramref name="inStream">input</paramref> writing | |||
13 | /// uncompressed data to the <paramref name="outStream">output stream</paramref> | |||
14 | /// </summary> | |||
15 | /// <param name="inStream">The readable stream containing data to decompress.</param> | |||
16 | /// <param name="outStream">The output stream to receive the decompressed data.</param> | |||
17 | /// <param name="isStreamOwner">Both streams are closed on completion if true.</param> | |||
18 | public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner) | |||
19 | { | |||
| 0 | 20 | if (inStream == null || outStream == null) { | ||
| 0 | 21 | throw new Exception("Null Stream"); | ||
22 | } | |||
23 | | |||
24 | try { | |||
| 0 | 25 | using (BZip2InputStream bzipInput = new BZip2InputStream(inStream)) { | ||
| 0 | 26 | bzipInput.IsStreamOwner = isStreamOwner; | ||
| 0 | 27 | Core.StreamUtils.Copy(bzipInput, outStream, new byte[4096]); | ||
| 0 | 28 | } | ||
29 | } finally { | |||
| 0 | 30 | if (isStreamOwner) { | ||
31 | // inStream is closed by the BZip2InputStream if stream owner | |||
| 0 | 32 | outStream.Close(); | ||
33 | } | |||
| 0 | 34 | } | ||
| 0 | 35 | } | ||
36 | | |||
37 | /// <summary> | |||
38 | /// Compress the <paramref name="inStream">input stream</paramref> sending | |||
39 | /// result data to <paramref name="outStream">output stream</paramref> | |||
40 | /// </summary> | |||
41 | /// <param name="inStream">The readable stream to compress.</param> | |||
42 | /// <param name="outStream">The output stream to receive the compressed data.</param> | |||
43 | /// <param name="isStreamOwner">Both streams are closed on completion if true.</param> | |||
44 | /// <param name="level">Block size acts as compression level (1 to 9) with 1 giving | |||
45 | /// the lowest compression and 9 the highest.</param> | |||
46 | public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int level) | |||
47 | { | |||
| 0 | 48 | if (inStream == null || outStream == null) { | ||
| 0 | 49 | throw new Exception("Null Stream"); | ||
50 | } | |||
51 | | |||
52 | try { | |||
| 0 | 53 | using (BZip2OutputStream bzipOutput = new BZip2OutputStream(outStream, level)) { | ||
| 0 | 54 | bzipOutput.IsStreamOwner = isStreamOwner; | ||
| 0 | 55 | Core.StreamUtils.Copy(inStream, bzipOutput, new byte[4096]); | ||
| 0 | 56 | } | ||
57 | } finally { | |||
| 0 | 58 | if (isStreamOwner) { | ||
59 | // outStream is closed by the BZip2OutputStream if stream owner | |||
| 0 | 60 | inStream.Close(); | ||
61 | } | |||
| 0 | 62 | } | ||
| 0 | 63 | } | ||
64 | | |||
65 | } | |||
66 | } |
| Class: | ICSharpCode.SharpZipLib.BZip2.BZip2Constants |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\BZip2\BZip2Constants.cs |
| Covered lines: | 0 |
| Uncovered lines: | 56 |
| Coverable lines: | 56 |
| Total lines: | 121 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 0 | 0 |
| .cctor() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | namespace ICSharpCode.SharpZipLib.BZip2 | |||
2 | { | |||
3 | /// <summary> | |||
4 | /// Defines internal values for both compression and decompression | |||
5 | /// </summary> | |||
6 | internal sealed class BZip2Constants | |||
7 | { | |||
8 | /// <summary> | |||
9 | /// Random numbers used to randomise repetitive blocks | |||
10 | /// </summary> | |||
| 0 | 11 | public readonly static int[] RandomNumbers = { | ||
| 0 | 12 | 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, | ||
| 0 | 13 | 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, | ||
| 0 | 14 | 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, | ||
| 0 | 15 | 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, | ||
| 0 | 16 | 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, | ||
| 0 | 17 | 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, | ||
| 0 | 18 | 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, | ||
| 0 | 19 | 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, | ||
| 0 | 20 | 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, | ||
| 0 | 21 | 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, | ||
| 0 | 22 | 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, | ||
| 0 | 23 | 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, | ||
| 0 | 24 | 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, | ||
| 0 | 25 | 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, | ||
| 0 | 26 | 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, | ||
| 0 | 27 | 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, | ||
| 0 | 28 | 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, | ||
| 0 | 29 | 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, | ||
| 0 | 30 | 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, | ||
| 0 | 31 | 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, | ||
| 0 | 32 | 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, | ||
| 0 | 33 | 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, | ||
| 0 | 34 | 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, | ||
| 0 | 35 | 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, | ||
| 0 | 36 | 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, | ||
| 0 | 37 | 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, | ||
| 0 | 38 | 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, | ||
| 0 | 39 | 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, | ||
| 0 | 40 | 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, | ||
| 0 | 41 | 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, | ||
| 0 | 42 | 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, | ||
| 0 | 43 | 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, | ||
| 0 | 44 | 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, | ||
| 0 | 45 | 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, | ||
| 0 | 46 | 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, | ||
| 0 | 47 | 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, | ||
| 0 | 48 | 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, | ||
| 0 | 49 | 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, | ||
| 0 | 50 | 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, | ||
| 0 | 51 | 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, | ||
| 0 | 52 | 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, | ||
| 0 | 53 | 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, | ||
| 0 | 54 | 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, | ||
| 0 | 55 | 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, | ||
| 0 | 56 | 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, | ||
| 0 | 57 | 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, | ||
| 0 | 58 | 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, | ||
| 0 | 59 | 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, | ||
| 0 | 60 | 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, | ||
| 0 | 61 | 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, | ||
| 0 | 62 | 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, | ||
| 0 | 63 | 936, 638 | ||
| 0 | 64 | }; | ||
65 | | |||
66 | /// <summary> | |||
67 | /// When multiplied by compression parameter (1-9) gives the block size for compression | |||
68 | /// 9 gives the best compression but uses the most memory. | |||
69 | /// </summary> | |||
70 | public const int BaseBlockSize = 100000; | |||
71 | | |||
72 | /// <summary> | |||
73 | /// Backend constant | |||
74 | /// </summary> | |||
75 | public const int MaximumAlphaSize = 258; | |||
76 | | |||
77 | /// <summary> | |||
78 | /// Backend constant | |||
79 | /// </summary> | |||
80 | public const int MaximumCodeLength = 23; | |||
81 | | |||
82 | /// <summary> | |||
83 | /// Backend constant | |||
84 | /// </summary> | |||
85 | public const int RunA = 0; | |||
86 | | |||
87 | /// <summary> | |||
88 | /// Backend constant | |||
89 | /// </summary> | |||
90 | public const int RunB = 1; | |||
91 | | |||
92 | /// <summary> | |||
93 | /// Backend constant | |||
94 | /// </summary> | |||
95 | public const int GroupCount = 6; | |||
96 | | |||
97 | /// <summary> | |||
98 | /// Backend constant | |||
99 | /// </summary> | |||
100 | public const int GroupSize = 50; | |||
101 | | |||
102 | /// <summary> | |||
103 | /// Backend constant | |||
104 | /// </summary> | |||
105 | public const int NumberOfIterations = 4; | |||
106 | | |||
107 | /// <summary> | |||
108 | /// Backend constant | |||
109 | /// </summary> | |||
110 | public const int MaximumSelectors = (2 + (900000 / GroupSize)); | |||
111 | | |||
112 | /// <summary> | |||
113 | /// Backend constant | |||
114 | /// </summary> | |||
115 | public const int OvershootBytes = 20; | |||
116 | | |||
| 0 | 117 | private BZip2Constants() | ||
118 | { | |||
| 0 | 119 | } | ||
120 | } | |||
121 | } |
| Class: | ICSharpCode.SharpZipLib.Checksum.BZip2Crc |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Checksum\BZip2Crc.cs |
| Covered lines: | 93 |
| Uncovered lines: | 0 |
| Coverable lines: | 93 |
| Total lines: | 200 |
| Line coverage: | 100% |
| Branch coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| Reset() | 1 | 100 | 100 |
| Update(...) | 1 | 100 | 100 |
| Update(...) | 2 | 100 | 100 |
| Update(...) | 7 | 100 | 100 |
| .cctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Checksum | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// CRC-32 with unreversed data and reversed output | |||
7 | /// </summary> | |||
8 | /// <remarks> | |||
9 | /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: | |||
10 | /// 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. | |||
11 | /// | |||
12 | /// Polynomials over GF(2) are represented in binary, one bit per coefficient, | |||
13 | /// with the lowest powers in the most significant bit. Then adding polynomials | |||
14 | /// is just exclusive-or, and multiplying a polynomial by x is a right shift by | |||
15 | /// one. If we call the above polynomial p, and represent a byte as the | |||
16 | /// polynomial q, also with the lowest power in the most significant bit (so the | |||
17 | /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, | |||
18 | /// where a mod b means the remainder after dividing a by b. | |||
19 | /// | |||
20 | /// This calculation is done using the shift-register method of multiplying and | |||
21 | /// taking the remainder. The register is initialized to zero, and for each | |||
22 | /// incoming bit, x^32 is added mod p to the register if the bit is a one (where | |||
23 | /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by | |||
24 | /// x (which is shifting right by one and adding x^32 mod p if the bit shifted | |||
25 | /// out is a one). We start with the highest power (least significant bit) of | |||
26 | /// q and repeat for all eight bits of q. | |||
27 | /// | |||
28 | /// The table is simply the CRC of all possible eight bit values. This is all | |||
29 | /// the information needed to generate CRC's on data a byte at a time for all | |||
30 | /// combinations of CRC register values and incoming bytes. | |||
31 | /// </remarks> | |||
32 | public sealed class BZip2Crc : IChecksum | |||
33 | { | |||
34 | #region Instance Fields | |||
| 1 | 35 | readonly static uint crcInit = 0xFFFFFFFF; | ||
| 1 | 36 | readonly static uint crcXor = 0x00000000; | ||
37 | | |||
| 1 | 38 | readonly static uint[] crcTable = { | ||
| 1 | 39 | 0X00000000, 0X04C11DB7, 0X09823B6E, 0X0D4326D9, | ||
| 1 | 40 | 0X130476DC, 0X17C56B6B, 0X1A864DB2, 0X1E475005, | ||
| 1 | 41 | 0X2608EDB8, 0X22C9F00F, 0X2F8AD6D6, 0X2B4BCB61, | ||
| 1 | 42 | 0X350C9B64, 0X31CD86D3, 0X3C8EA00A, 0X384FBDBD, | ||
| 1 | 43 | 0X4C11DB70, 0X48D0C6C7, 0X4593E01E, 0X4152FDA9, | ||
| 1 | 44 | 0X5F15ADAC, 0X5BD4B01B, 0X569796C2, 0X52568B75, | ||
| 1 | 45 | 0X6A1936C8, 0X6ED82B7F, 0X639B0DA6, 0X675A1011, | ||
| 1 | 46 | 0X791D4014, 0X7DDC5DA3, 0X709F7B7A, 0X745E66CD, | ||
| 1 | 47 | 0X9823B6E0, 0X9CE2AB57, 0X91A18D8E, 0X95609039, | ||
| 1 | 48 | 0X8B27C03C, 0X8FE6DD8B, 0X82A5FB52, 0X8664E6E5, | ||
| 1 | 49 | 0XBE2B5B58, 0XBAEA46EF, 0XB7A96036, 0XB3687D81, | ||
| 1 | 50 | 0XAD2F2D84, 0XA9EE3033, 0XA4AD16EA, 0XA06C0B5D, | ||
| 1 | 51 | 0XD4326D90, 0XD0F37027, 0XDDB056FE, 0XD9714B49, | ||
| 1 | 52 | 0XC7361B4C, 0XC3F706FB, 0XCEB42022, 0XCA753D95, | ||
| 1 | 53 | 0XF23A8028, 0XF6FB9D9F, 0XFBB8BB46, 0XFF79A6F1, | ||
| 1 | 54 | 0XE13EF6F4, 0XE5FFEB43, 0XE8BCCD9A, 0XEC7DD02D, | ||
| 1 | 55 | 0X34867077, 0X30476DC0, 0X3D044B19, 0X39C556AE, | ||
| 1 | 56 | 0X278206AB, 0X23431B1C, 0X2E003DC5, 0X2AC12072, | ||
| 1 | 57 | 0X128E9DCF, 0X164F8078, 0X1B0CA6A1, 0X1FCDBB16, | ||
| 1 | 58 | 0X018AEB13, 0X054BF6A4, 0X0808D07D, 0X0CC9CDCA, | ||
| 1 | 59 | 0X7897AB07, 0X7C56B6B0, 0X71159069, 0X75D48DDE, | ||
| 1 | 60 | 0X6B93DDDB, 0X6F52C06C, 0X6211E6B5, 0X66D0FB02, | ||
| 1 | 61 | 0X5E9F46BF, 0X5A5E5B08, 0X571D7DD1, 0X53DC6066, | ||
| 1 | 62 | 0X4D9B3063, 0X495A2DD4, 0X44190B0D, 0X40D816BA, | ||
| 1 | 63 | 0XACA5C697, 0XA864DB20, 0XA527FDF9, 0XA1E6E04E, | ||
| 1 | 64 | 0XBFA1B04B, 0XBB60ADFC, 0XB6238B25, 0XB2E29692, | ||
| 1 | 65 | 0X8AAD2B2F, 0X8E6C3698, 0X832F1041, 0X87EE0DF6, | ||
| 1 | 66 | 0X99A95DF3, 0X9D684044, 0X902B669D, 0X94EA7B2A, | ||
| 1 | 67 | 0XE0B41DE7, 0XE4750050, 0XE9362689, 0XEDF73B3E, | ||
| 1 | 68 | 0XF3B06B3B, 0XF771768C, 0XFA325055, 0XFEF34DE2, | ||
| 1 | 69 | 0XC6BCF05F, 0XC27DEDE8, 0XCF3ECB31, 0XCBFFD686, | ||
| 1 | 70 | 0XD5B88683, 0XD1799B34, 0XDC3ABDED, 0XD8FBA05A, | ||
| 1 | 71 | 0X690CE0EE, 0X6DCDFD59, 0X608EDB80, 0X644FC637, | ||
| 1 | 72 | 0X7A089632, 0X7EC98B85, 0X738AAD5C, 0X774BB0EB, | ||
| 1 | 73 | 0X4F040D56, 0X4BC510E1, 0X46863638, 0X42472B8F, | ||
| 1 | 74 | 0X5C007B8A, 0X58C1663D, 0X558240E4, 0X51435D53, | ||
| 1 | 75 | 0X251D3B9E, 0X21DC2629, 0X2C9F00F0, 0X285E1D47, | ||
| 1 | 76 | 0X36194D42, 0X32D850F5, 0X3F9B762C, 0X3B5A6B9B, | ||
| 1 | 77 | 0X0315D626, 0X07D4CB91, 0X0A97ED48, 0X0E56F0FF, | ||
| 1 | 78 | 0X1011A0FA, 0X14D0BD4D, 0X19939B94, 0X1D528623, | ||
| 1 | 79 | 0XF12F560E, 0XF5EE4BB9, 0XF8AD6D60, 0XFC6C70D7, | ||
| 1 | 80 | 0XE22B20D2, 0XE6EA3D65, 0XEBA91BBC, 0XEF68060B, | ||
| 1 | 81 | 0XD727BBB6, 0XD3E6A601, 0XDEA580D8, 0XDA649D6F, | ||
| 1 | 82 | 0XC423CD6A, 0XC0E2D0DD, 0XCDA1F604, 0XC960EBB3, | ||
| 1 | 83 | 0XBD3E8D7E, 0XB9FF90C9, 0XB4BCB610, 0XB07DABA7, | ||
| 1 | 84 | 0XAE3AFBA2, 0XAAFBE615, 0XA7B8C0CC, 0XA379DD7B, | ||
| 1 | 85 | 0X9B3660C6, 0X9FF77D71, 0X92B45BA8, 0X9675461F, | ||
| 1 | 86 | 0X8832161A, 0X8CF30BAD, 0X81B02D74, 0X857130C3, | ||
| 1 | 87 | 0X5D8A9099, 0X594B8D2E, 0X5408ABF7, 0X50C9B640, | ||
| 1 | 88 | 0X4E8EE645, 0X4A4FFBF2, 0X470CDD2B, 0X43CDC09C, | ||
| 1 | 89 | 0X7B827D21, 0X7F436096, 0X7200464F, 0X76C15BF8, | ||
| 1 | 90 | 0X68860BFD, 0X6C47164A, 0X61043093, 0X65C52D24, | ||
| 1 | 91 | 0X119B4BE9, 0X155A565E, 0X18197087, 0X1CD86D30, | ||
| 1 | 92 | 0X029F3D35, 0X065E2082, 0X0B1D065B, 0X0FDC1BEC, | ||
| 1 | 93 | 0X3793A651, 0X3352BBE6, 0X3E119D3F, 0X3AD08088, | ||
| 1 | 94 | 0X2497D08D, 0X2056CD3A, 0X2D15EBE3, 0X29D4F654, | ||
| 1 | 95 | 0XC5A92679, 0XC1683BCE, 0XCC2B1D17, 0XC8EA00A0, | ||
| 1 | 96 | 0XD6AD50A5, 0XD26C4D12, 0XDF2F6BCB, 0XDBEE767C, | ||
| 1 | 97 | 0XE3A1CBC1, 0XE760D676, 0XEA23F0AF, 0XEEE2ED18, | ||
| 1 | 98 | 0XF0A5BD1D, 0XF464A0AA, 0XF9278673, 0XFDE69BC4, | ||
| 1 | 99 | 0X89B8FD09, 0X8D79E0BE, 0X803AC667, 0X84FBDBD0, | ||
| 1 | 100 | 0X9ABC8BD5, 0X9E7D9662, 0X933EB0BB, 0X97FFAD0C, | ||
| 1 | 101 | 0XAFB010B1, 0XAB710D06, 0XA6322BDF, 0XA2F33668, | ||
| 1 | 102 | 0XBCB4666D, 0XB8757BDA, 0XB5365D03, 0XB1F740B4 | ||
| 1 | 103 | }; | ||
104 | | |||
105 | /// <summary> | |||
106 | /// The CRC data checksum so far. | |||
107 | /// </summary> | |||
108 | uint checkValue; | |||
109 | #endregion | |||
110 | | |||
111 | /// <summary> | |||
112 | /// Initialise a default instance of <see cref="BZip2Crc"></see> | |||
113 | /// </summary> | |||
| 3 | 114 | public BZip2Crc() | ||
115 | { | |||
| 3 | 116 | Reset(); | ||
| 3 | 117 | } | ||
118 | | |||
119 | /// <summary> | |||
120 | /// Resets the CRC data checksum as if no update was ever called. | |||
121 | /// </summary> | |||
122 | public void Reset() | |||
123 | { | |||
| 5 | 124 | checkValue = crcInit; | ||
| 5 | 125 | } | ||
126 | | |||
127 | /// <summary> | |||
128 | /// Returns the CRC data checksum computed so far. | |||
129 | /// </summary> | |||
130 | /// <remarks>Reversed Out = true</remarks> | |||
131 | public long Value { | |||
132 | get { | |||
133 | // Tehcnically, the output should be: | |||
134 | //return (long)(~checkValue ^ crcXor); | |||
135 | // but x ^ 0 = x, so there is no point in adding | |||
136 | // the XOR operation | |||
| 3 | 137 | return (long)(~checkValue); | ||
138 | } | |||
139 | } | |||
140 | | |||
141 | /// <summary> | |||
142 | /// Updates the checksum with the int bval. | |||
143 | /// </summary> | |||
144 | /// <param name = "bval"> | |||
145 | /// the byte is taken as the lower 8 bits of bval | |||
146 | /// </param> | |||
147 | /// <remarks>Reversed Data = false</remarks> | |||
148 | public void Update(int bval) | |||
149 | { | |||
| 10 | 150 | checkValue = unchecked(crcTable[(byte)(((checkValue >> 24) & 0xFF) ^ bval)] ^ (checkValue << 8)); | ||
| 10 | 151 | } | ||
152 | | |||
153 | /// <summary> | |||
154 | /// Updates the CRC data checksum with the bytes taken from | |||
155 | /// a block of data. | |||
156 | /// </summary> | |||
157 | /// <param name="buffer">Contains the data to update the CRC with.</param> | |||
158 | public void Update(byte[] buffer) | |||
159 | { | |||
| 2 | 160 | if (buffer == null) { | ||
| 1 | 161 | throw new ArgumentNullException(nameof(buffer)); | ||
162 | } | |||
163 | | |||
| 1 | 164 | Update(buffer, 0, buffer.Length); | ||
| 1 | 165 | } | ||
166 | | |||
167 | /// <summary> | |||
168 | /// Update CRC data checksum based on a portion of a block of data | |||
169 | /// </summary> | |||
170 | /// <param name = "buffer">Contains the data to update the CRC with.</param> | |||
171 | /// <param name = "offset">The offset into the buffer where the data starts</param> | |||
172 | /// <param name = "count">The number of data bytes to update the CRC with.</param> | |||
173 | public void Update(byte[] buffer, int offset, int count) | |||
174 | { | |||
| 6 | 175 | if (buffer == null) { | ||
| 1 | 176 | throw new ArgumentNullException(nameof(buffer)); | ||
177 | } | |||
178 | | |||
| 5 | 179 | if (offset < 0) { | ||
| 1 | 180 | throw new ArgumentOutOfRangeException(nameof(offset), "cannot be less than zero"); | ||
181 | } | |||
182 | | |||
| 4 | 183 | if (offset >= buffer.Length) { | ||
| 1 | 184 | throw new ArgumentOutOfRangeException(nameof(offset), "not a valid index into buffer"); | ||
185 | } | |||
186 | | |||
| 3 | 187 | if (count < 0) { | ||
| 1 | 188 | throw new ArgumentOutOfRangeException(nameof(count), "cannot be less than zero"); | ||
189 | } | |||
190 | | |||
| 2 | 191 | if (offset + count > buffer.Length) { | ||
| 1 | 192 | throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); | ||
193 | } | |||
194 | | |||
| 20 | 195 | for (int i = 0; i < count; ++i) { | ||
| 9 | 196 | Update(buffer[offset++]); | ||
197 | } | |||
| 1 | 198 | } | ||
199 | } | |||
200 | } |
| Class: | ICSharpCode.SharpZipLib.BZip2.BZip2Exception |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\BZip2\BZip2Exception.cs |
| Covered lines: | 0 |
| Uncovered lines: | 8 |
| Coverable lines: | 8 |
| Total lines: | 48 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Runtime.Serialization; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.BZip2 | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// BZip2Exception represents exceptions specific to BZip2 classes and code. | |||
8 | /// </summary> | |||
9 | [Serializable] | |||
10 | public class BZip2Exception : SharpZipBaseException | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Deserialization constructor | |||
14 | /// </summary> | |||
15 | /// <param name="info"><see cref="SerializationInfo"/> for this constructor</param> | |||
16 | /// <param name="context"><see cref="StreamingContext"/> for this constructor</param> | |||
17 | protected BZip2Exception(SerializationInfo info, StreamingContext context) | |||
| 0 | 18 | : base(info, context) | ||
19 | { | |||
| 0 | 20 | } | ||
21 | | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="BZip2Exception" />. | |||
24 | /// </summary> | |||
| 0 | 25 | public BZip2Exception() | ||
26 | { | |||
| 0 | 27 | } | ||
28 | | |||
29 | /// <summary> | |||
30 | /// Initialise a new instance of <see cref="BZip2Exception" /> with its message string. | |||
31 | /// </summary> | |||
32 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
33 | public BZip2Exception(string message) | |||
| 0 | 34 | : base(message) | ||
35 | { | |||
| 0 | 36 | } | ||
37 | | |||
38 | /// <summary> | |||
39 | /// Initialise a new instance of <see cref="BZip2Exception" />. | |||
40 | /// </summary> | |||
41 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
42 | /// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param> | |||
43 | public BZip2Exception(string message, Exception innerException) | |||
| 0 | 44 | : base(message, innerException) | ||
45 | { | |||
| 0 | 46 | } | ||
47 | } | |||
48 | } |
| Class: | ICSharpCode.SharpZipLib.BZip2.BZip2InputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\BZip2\BZip2InputStream.cs |
| Covered lines: | 111 |
| Uncovered lines: | 315 |
| Coverable lines: | 426 |
| Total lines: | 909 |
| Line coverage: | 26% |
| Branch coverage: | 15.7% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 2 | 100 | 100 |
| Flush() | 2 | 0 | 0 |
| Seek(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| Write(...) | 1 | 0 | 0 |
| WriteByte(...) | 1 | 0 | 0 |
| Read(...) | 4 | 60 | 57.14 |
| Close() | 3 | 100 | 60 |
| ReadByte() | 7 | 16.67 | 18.18 |
| MakeMaps() | 3 | 0 | 0 |
| Initialize() | 6 | 80 | 54.55 |
| InitBlock() | 13 | 47.37 | 28 |
| EndBlock() | 2 | 0 | 0 |
| Complete() | 2 | 80 | 66.67 |
| BsSetStream(...) | 1 | 100 | 100 |
| FillBuffer() | 2 | 63.64 | 66.67 |
| BsR(...) | 2 | 100 | 100 |
| BsGetUChar() | 1 | 100 | 100 |
| BsGetIntVS(...) | 1 | 0 | 0 |
| BsGetInt32() | 1 | 100 | 100 |
| RecvDecodingTables() | 18 | 0 | 0 |
| GetAndMoveToFrontDecode() | 26 | 0 | 0 |
| SetupBlock() | 4 | 84 | 85.71 |
| SetupRandPartA() | 6 | 0 | 0 |
| SetupNoRandPartA() | 2 | 69.23 | 66.67 |
| SetupRandPartB() | 7 | 0 | 0 |
| SetupRandPartC() | 2 | 0 | 0 |
| SetupNoRandPartB() | 3 | 0 | 0 |
| SetupNoRandPartC() | 2 | 0 | 0 |
| SetDecompressStructureSizes(...) | 6 | 77.78 | 54.55 |
| CompressedStreamEOF() | 1 | 0 | 0 |
| BlockOverrun() | 1 | 0 | 0 |
| BadBlockHeader() | 1 | 0 | 0 |
| CrcError() | 1 | 0 | 0 |
| HbCreateDecodeTables(...) | 10 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.BZip2 | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// An input stream that decompresses files in the BZip2 format | |||
9 | /// </summary> | |||
10 | public class BZip2InputStream : Stream | |||
11 | { | |||
12 | #region Constants | |||
13 | const int START_BLOCK_STATE = 1; | |||
14 | const int RAND_PART_A_STATE = 2; | |||
15 | const int RAND_PART_B_STATE = 3; | |||
16 | const int RAND_PART_C_STATE = 4; | |||
17 | const int NO_RAND_PART_A_STATE = 5; | |||
18 | const int NO_RAND_PART_B_STATE = 6; | |||
19 | const int NO_RAND_PART_C_STATE = 7; | |||
20 | #endregion | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Construct instance for reading from stream | |||
24 | /// </summary> | |||
25 | /// <param name="stream">Data source</param> | |||
| 1 | 26 | public BZip2InputStream(Stream stream) | ||
27 | { | |||
28 | // init arrays | |||
| 14 | 29 | for (int i = 0; i < BZip2Constants.GroupCount; ++i) { | ||
| 6 | 30 | limit[i] = new int[BZip2Constants.MaximumAlphaSize]; | ||
| 6 | 31 | baseArray[i] = new int[BZip2Constants.MaximumAlphaSize]; | ||
| 6 | 32 | perm[i] = new int[BZip2Constants.MaximumAlphaSize]; | ||
33 | } | |||
34 | | |||
| 1 | 35 | BsSetStream(stream); | ||
| 1 | 36 | Initialize(); | ||
| 1 | 37 | InitBlock(); | ||
| 1 | 38 | SetupBlock(); | ||
| 1 | 39 | } | ||
40 | | |||
41 | #endregion | |||
42 | | |||
43 | /// <summary> | |||
44 | /// Get/set flag indicating ownership of underlying stream. | |||
45 | /// When the flag is true <see cref="Close"></see> will close the underlying stream also. | |||
46 | /// </summary> | |||
47 | public bool IsStreamOwner { | |||
| 1 | 48 | get { return isStreamOwner; } | ||
| 0 | 49 | set { isStreamOwner = value; } | ||
50 | } | |||
51 | | |||
52 | | |||
53 | #region Stream Overrides | |||
54 | /// <summary> | |||
55 | /// Gets a value indicating if the stream supports reading | |||
56 | /// </summary> | |||
57 | public override bool CanRead { | |||
58 | get { | |||
| 0 | 59 | return baseStream.CanRead; | ||
60 | } | |||
61 | } | |||
62 | | |||
63 | /// <summary> | |||
64 | /// Gets a value indicating whether the current stream supports seeking. | |||
65 | /// </summary> | |||
66 | public override bool CanSeek { | |||
67 | get { | |||
| 0 | 68 | return baseStream.CanSeek; | ||
69 | } | |||
70 | } | |||
71 | | |||
72 | /// <summary> | |||
73 | /// Gets a value indicating whether the current stream supports writing. | |||
74 | /// This property always returns false | |||
75 | /// </summary> | |||
76 | public override bool CanWrite { | |||
77 | get { | |||
| 0 | 78 | return false; | ||
79 | } | |||
80 | } | |||
81 | | |||
82 | /// <summary> | |||
83 | /// Gets the length in bytes of the stream. | |||
84 | /// </summary> | |||
85 | public override long Length { | |||
86 | get { | |||
| 0 | 87 | return baseStream.Length; | ||
88 | } | |||
89 | } | |||
90 | | |||
91 | /// <summary> | |||
92 | /// Gets or sets the streams position. | |||
93 | /// Setting the position is not supported and will throw a NotSupportException | |||
94 | /// </summary> | |||
95 | /// <exception cref="NotSupportedException">Any attempt to set the position</exception> | |||
96 | public override long Position { | |||
97 | get { | |||
| 0 | 98 | return baseStream.Position; | ||
99 | } | |||
100 | set { | |||
| 0 | 101 | throw new NotSupportedException("BZip2InputStream position cannot be set"); | ||
102 | } | |||
103 | } | |||
104 | | |||
105 | /// <summary> | |||
106 | /// Flushes the stream. | |||
107 | /// </summary> | |||
108 | public override void Flush() | |||
109 | { | |||
| 0 | 110 | if (baseStream != null) { | ||
| 0 | 111 | baseStream.Flush(); | ||
112 | } | |||
| 0 | 113 | } | ||
114 | | |||
115 | /// <summary> | |||
116 | /// Set the streams position. This operation is not supported and will throw a NotSupportedException | |||
117 | /// </summary> | |||
118 | /// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param> | |||
119 | /// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain the | |||
120 | /// <returns>The new position of the stream.</returns> | |||
121 | /// <exception cref="NotSupportedException">Any access</exception> | |||
122 | public override long Seek(long offset, SeekOrigin origin) | |||
123 | { | |||
| 0 | 124 | throw new NotSupportedException("BZip2InputStream Seek not supported"); | ||
125 | } | |||
126 | | |||
127 | /// <summary> | |||
128 | /// Sets the length of this stream to the given value. | |||
129 | /// This operation is not supported and will throw a NotSupportedExceptionortedException | |||
130 | /// </summary> | |||
131 | /// <param name="value">The new length for the stream.</param> | |||
132 | /// <exception cref="NotSupportedException">Any access</exception> | |||
133 | public override void SetLength(long value) | |||
134 | { | |||
| 0 | 135 | throw new NotSupportedException("BZip2InputStream SetLength not supported"); | ||
136 | } | |||
137 | | |||
138 | /// <summary> | |||
139 | /// Writes a block of bytes to this stream using data from a buffer. | |||
140 | /// This operation is not supported and will throw a NotSupportedException | |||
141 | /// </summary> | |||
142 | /// <param name="buffer">The buffer to source data from.</param> | |||
143 | /// <param name="offset">The offset to start obtaining data from.</param> | |||
144 | /// <param name="count">The number of bytes of data to write.</param> | |||
145 | /// <exception cref="NotSupportedException">Any access</exception> | |||
146 | public override void Write(byte[] buffer, int offset, int count) | |||
147 | { | |||
| 0 | 148 | throw new NotSupportedException("BZip2InputStream Write not supported"); | ||
149 | } | |||
150 | | |||
151 | /// <summary> | |||
152 | /// Writes a byte to the current position in the file stream. | |||
153 | /// This operation is not supported and will throw a NotSupportedException | |||
154 | /// </summary> | |||
155 | /// <param name="value">The value to write.</param> | |||
156 | /// <exception cref="NotSupportedException">Any access</exception> | |||
157 | public override void WriteByte(byte value) | |||
158 | { | |||
| 0 | 159 | throw new NotSupportedException("BZip2InputStream WriteByte not supported"); | ||
160 | } | |||
161 | | |||
162 | /// <summary> | |||
163 | /// Read a sequence of bytes and advances the read position by one byte. | |||
164 | /// </summary> | |||
165 | /// <param name="buffer">Array of bytes to store values in</param> | |||
166 | /// <param name="offset">Offset in array to begin storing data</param> | |||
167 | /// <param name="count">The maximum number of bytes to read</param> | |||
168 | /// <returns>The total number of bytes read into the buffer. This might be less | |||
169 | /// than the number of bytes requested if that number of bytes are not | |||
170 | /// currently available or zero if the end of the stream is reached. | |||
171 | /// </returns> | |||
172 | public override int Read(byte[] buffer, int offset, int count) | |||
173 | { | |||
| 1 | 174 | if (buffer == null) { | ||
| 0 | 175 | throw new ArgumentNullException(nameof(buffer)); | ||
176 | } | |||
177 | | |||
| 2 | 178 | for (int i = 0; i < count; ++i) { | ||
| 1 | 179 | int rb = ReadByte(); | ||
| 1 | 180 | if (rb == -1) { | ||
| 1 | 181 | return i; | ||
182 | } | |||
| 0 | 183 | buffer[offset + i] = (byte)rb; | ||
184 | } | |||
| 0 | 185 | return count; | ||
186 | } | |||
187 | | |||
188 | /// <summary> | |||
189 | /// Closes the stream, releasing any associated resources. | |||
190 | /// </summary> | |||
191 | public override void Close() | |||
192 | { | |||
| 1 | 193 | if (IsStreamOwner && (baseStream != null)) { | ||
| 1 | 194 | baseStream.Close(); | ||
195 | } | |||
| 1 | 196 | } | ||
197 | /// <summary> | |||
198 | /// Read a byte from stream advancing position | |||
199 | /// </summary> | |||
200 | /// <returns>byte read or -1 on end of stream</returns> | |||
201 | public override int ReadByte() | |||
202 | { | |||
| 1 | 203 | if (streamEnd) { | ||
| 1 | 204 | return -1; // ok | ||
205 | } | |||
206 | | |||
| 0 | 207 | int retChar = currentChar; | ||
| 0 | 208 | switch (currentState) { | ||
209 | case RAND_PART_B_STATE: | |||
| 0 | 210 | SetupRandPartB(); | ||
| 0 | 211 | break; | ||
212 | case RAND_PART_C_STATE: | |||
| 0 | 213 | SetupRandPartC(); | ||
| 0 | 214 | break; | ||
215 | case NO_RAND_PART_B_STATE: | |||
| 0 | 216 | SetupNoRandPartB(); | ||
| 0 | 217 | break; | ||
218 | case NO_RAND_PART_C_STATE: | |||
| 0 | 219 | SetupNoRandPartC(); | ||
220 | break; | |||
221 | case START_BLOCK_STATE: | |||
222 | case NO_RAND_PART_A_STATE: | |||
223 | case RAND_PART_A_STATE: | |||
224 | break; | |||
225 | } | |||
| 0 | 226 | return retChar; | ||
227 | } | |||
228 | | |||
229 | #endregion | |||
230 | | |||
231 | void MakeMaps() | |||
232 | { | |||
| 0 | 233 | nInUse = 0; | ||
| 0 | 234 | for (int i = 0; i < 256; ++i) { | ||
| 0 | 235 | if (inUse[i]) { | ||
| 0 | 236 | seqToUnseq[nInUse] = (byte)i; | ||
| 0 | 237 | unseqToSeq[i] = (byte)nInUse; | ||
| 0 | 238 | nInUse++; | ||
239 | } | |||
240 | } | |||
| 0 | 241 | } | ||
242 | | |||
243 | void Initialize() | |||
244 | { | |||
| 1 | 245 | char magic1 = BsGetUChar(); | ||
| 1 | 246 | char magic2 = BsGetUChar(); | ||
247 | | |||
| 1 | 248 | char magic3 = BsGetUChar(); | ||
| 1 | 249 | char magic4 = BsGetUChar(); | ||
250 | | |||
| 1 | 251 | if (magic1 != 'B' || magic2 != 'Z' || magic3 != 'h' || magic4 < '1' || magic4 > '9') { | ||
| 0 | 252 | streamEnd = true; | ||
| 0 | 253 | return; | ||
254 | } | |||
255 | | |||
| 1 | 256 | SetDecompressStructureSizes(magic4 - '0'); | ||
| 1 | 257 | computedCombinedCRC = 0; | ||
| 1 | 258 | } | ||
259 | | |||
260 | void InitBlock() | |||
261 | { | |||
| 1 | 262 | char magic1 = BsGetUChar(); | ||
| 1 | 263 | char magic2 = BsGetUChar(); | ||
| 1 | 264 | char magic3 = BsGetUChar(); | ||
| 1 | 265 | char magic4 = BsGetUChar(); | ||
| 1 | 266 | char magic5 = BsGetUChar(); | ||
| 1 | 267 | char magic6 = BsGetUChar(); | ||
268 | | |||
| 1 | 269 | if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) { | ||
| 1 | 270 | Complete(); | ||
| 1 | 271 | return; | ||
272 | } | |||
273 | | |||
| 0 | 274 | if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) { | ||
| 0 | 275 | BadBlockHeader(); | ||
| 0 | 276 | streamEnd = true; | ||
| 0 | 277 | return; | ||
278 | } | |||
279 | | |||
| 0 | 280 | storedBlockCRC = BsGetInt32(); | ||
281 | | |||
| 0 | 282 | blockRandomised = (BsR(1) == 1); | ||
283 | | |||
| 0 | 284 | GetAndMoveToFrontDecode(); | ||
285 | | |||
| 0 | 286 | mCrc.Reset(); | ||
| 0 | 287 | currentState = START_BLOCK_STATE; | ||
| 0 | 288 | } | ||
289 | | |||
290 | void EndBlock() | |||
291 | { | |||
| 0 | 292 | computedBlockCRC = (int)mCrc.Value; | ||
293 | | |||
294 | // -- A bad CRC is considered a fatal error. -- | |||
| 0 | 295 | if (storedBlockCRC != computedBlockCRC) { | ||
| 0 | 296 | CrcError(); | ||
297 | } | |||
298 | | |||
299 | // 1528150659 | |||
| 0 | 300 | computedCombinedCRC = ((computedCombinedCRC << 1) & 0xFFFFFFFF) | (computedCombinedCRC >> 31); | ||
| 0 | 301 | computedCombinedCRC = computedCombinedCRC ^ (uint)computedBlockCRC; | ||
| 0 | 302 | } | ||
303 | | |||
304 | void Complete() | |||
305 | { | |||
| 1 | 306 | storedCombinedCRC = BsGetInt32(); | ||
| 1 | 307 | if (storedCombinedCRC != (int)computedCombinedCRC) { | ||
| 0 | 308 | CrcError(); | ||
309 | } | |||
310 | | |||
| 1 | 311 | streamEnd = true; | ||
| 1 | 312 | } | ||
313 | | |||
314 | void BsSetStream(Stream stream) | |||
315 | { | |||
| 1 | 316 | baseStream = stream; | ||
| 1 | 317 | bsLive = 0; | ||
| 1 | 318 | bsBuff = 0; | ||
| 1 | 319 | } | ||
320 | | |||
321 | void FillBuffer() | |||
322 | { | |||
| 14 | 323 | int thech = 0; | ||
324 | | |||
325 | try { | |||
| 14 | 326 | thech = baseStream.ReadByte(); | ||
| 14 | 327 | } catch (Exception) { | ||
| 0 | 328 | CompressedStreamEOF(); | ||
| 0 | 329 | } | ||
330 | | |||
| 14 | 331 | if (thech == -1) { | ||
| 0 | 332 | CompressedStreamEOF(); | ||
333 | } | |||
334 | | |||
| 14 | 335 | bsBuff = (bsBuff << 8) | (thech & 0xFF); | ||
| 14 | 336 | bsLive += 8; | ||
| 14 | 337 | } | ||
338 | | |||
339 | int BsR(int n) | |||
340 | { | |||
| 28 | 341 | while (bsLive < n) { | ||
| 14 | 342 | FillBuffer(); | ||
343 | } | |||
344 | | |||
| 14 | 345 | int v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); | ||
| 14 | 346 | bsLive -= n; | ||
| 14 | 347 | return v; | ||
348 | } | |||
349 | | |||
350 | char BsGetUChar() | |||
351 | { | |||
| 10 | 352 | return (char)BsR(8); | ||
353 | } | |||
354 | | |||
355 | int BsGetIntVS(int numBits) | |||
356 | { | |||
| 0 | 357 | return BsR(numBits); | ||
358 | } | |||
359 | | |||
360 | int BsGetInt32() | |||
361 | { | |||
| 1 | 362 | int result = BsR(8); | ||
| 1 | 363 | result = (result << 8) | BsR(8); | ||
| 1 | 364 | result = (result << 8) | BsR(8); | ||
| 1 | 365 | result = (result << 8) | BsR(8); | ||
| 1 | 366 | return result; | ||
367 | } | |||
368 | | |||
369 | void RecvDecodingTables() | |||
370 | { | |||
| 0 | 371 | char[][] len = new char[BZip2Constants.GroupCount][]; | ||
| 0 | 372 | for (int i = 0; i < BZip2Constants.GroupCount; ++i) { | ||
| 0 | 373 | len[i] = new char[BZip2Constants.MaximumAlphaSize]; | ||
374 | } | |||
375 | | |||
| 0 | 376 | bool[] inUse16 = new bool[16]; | ||
377 | | |||
378 | //--- Receive the mapping table --- | |||
| 0 | 379 | for (int i = 0; i < 16; i++) { | ||
| 0 | 380 | inUse16[i] = (BsR(1) == 1); | ||
381 | } | |||
382 | | |||
| 0 | 383 | for (int i = 0; i < 16; i++) { | ||
| 0 | 384 | if (inUse16[i]) { | ||
| 0 | 385 | for (int j = 0; j < 16; j++) { | ||
| 0 | 386 | inUse[i * 16 + j] = (BsR(1) == 1); | ||
387 | } | |||
| 0 | 388 | } else { | ||
| 0 | 389 | for (int j = 0; j < 16; j++) { | ||
| 0 | 390 | inUse[i * 16 + j] = false; | ||
391 | } | |||
392 | } | |||
393 | } | |||
394 | | |||
| 0 | 395 | MakeMaps(); | ||
| 0 | 396 | int alphaSize = nInUse + 2; | ||
397 | | |||
398 | //--- Now the selectors --- | |||
| 0 | 399 | int nGroups = BsR(3); | ||
| 0 | 400 | int nSelectors = BsR(15); | ||
401 | | |||
| 0 | 402 | for (int i = 0; i < nSelectors; i++) { | ||
| 0 | 403 | int j = 0; | ||
| 0 | 404 | while (BsR(1) == 1) { | ||
| 0 | 405 | j++; | ||
406 | } | |||
| 0 | 407 | selectorMtf[i] = (byte)j; | ||
408 | } | |||
409 | | |||
410 | //--- Undo the MTF values for the selectors. --- | |||
| 0 | 411 | byte[] pos = new byte[BZip2Constants.GroupCount]; | ||
| 0 | 412 | for (int v = 0; v < nGroups; v++) { | ||
| 0 | 413 | pos[v] = (byte)v; | ||
414 | } | |||
415 | | |||
| 0 | 416 | for (int i = 0; i < nSelectors; i++) { | ||
| 0 | 417 | int v = selectorMtf[i]; | ||
| 0 | 418 | byte tmp = pos[v]; | ||
| 0 | 419 | while (v > 0) { | ||
| 0 | 420 | pos[v] = pos[v - 1]; | ||
| 0 | 421 | v--; | ||
422 | } | |||
| 0 | 423 | pos[0] = tmp; | ||
| 0 | 424 | selector[i] = tmp; | ||
425 | } | |||
426 | | |||
427 | //--- Now the coding tables --- | |||
| 0 | 428 | for (int t = 0; t < nGroups; t++) { | ||
| 0 | 429 | int curr = BsR(5); | ||
| 0 | 430 | for (int i = 0; i < alphaSize; i++) { | ||
| 0 | 431 | while (BsR(1) == 1) { | ||
| 0 | 432 | if (BsR(1) == 0) { | ||
| 0 | 433 | curr++; | ||
| 0 | 434 | } else { | ||
| 0 | 435 | curr--; | ||
436 | } | |||
437 | } | |||
| 0 | 438 | len[t][i] = (char)curr; | ||
439 | } | |||
440 | } | |||
441 | | |||
442 | //--- Create the Huffman decoding tables --- | |||
| 0 | 443 | for (int t = 0; t < nGroups; t++) { | ||
| 0 | 444 | int minLen = 32; | ||
| 0 | 445 | int maxLen = 0; | ||
| 0 | 446 | for (int i = 0; i < alphaSize; i++) { | ||
| 0 | 447 | maxLen = Math.Max(maxLen, len[t][i]); | ||
| 0 | 448 | minLen = Math.Min(minLen, len[t][i]); | ||
449 | } | |||
| 0 | 450 | HbCreateDecodeTables(limit[t], baseArray[t], perm[t], len[t], minLen, maxLen, alphaSize); | ||
| 0 | 451 | minLens[t] = minLen; | ||
452 | } | |||
| 0 | 453 | } | ||
454 | | |||
455 | void GetAndMoveToFrontDecode() | |||
456 | { | |||
| 0 | 457 | byte[] yy = new byte[256]; | ||
458 | int nextSym; | |||
459 | | |||
| 0 | 460 | int limitLast = BZip2Constants.BaseBlockSize * blockSize100k; | ||
| 0 | 461 | origPtr = BsGetIntVS(24); | ||
462 | | |||
| 0 | 463 | RecvDecodingTables(); | ||
| 0 | 464 | int EOB = nInUse + 1; | ||
| 0 | 465 | int groupNo = -1; | ||
| 0 | 466 | int groupPos = 0; | ||
467 | | |||
468 | /*-- | |||
469 | Setting up the unzftab entries here is not strictly | |||
470 | necessary, but it does save having to do it later | |||
471 | in a separate pass, and so saves a block's worth of | |||
472 | cache misses. | |||
473 | --*/ | |||
| 0 | 474 | for (int i = 0; i <= 255; i++) { | ||
| 0 | 475 | unzftab[i] = 0; | ||
476 | } | |||
477 | | |||
| 0 | 478 | for (int i = 0; i <= 255; i++) { | ||
| 0 | 479 | yy[i] = (byte)i; | ||
480 | } | |||
481 | | |||
| 0 | 482 | last = -1; | ||
483 | | |||
| 0 | 484 | if (groupPos == 0) { | ||
| 0 | 485 | groupNo++; | ||
| 0 | 486 | groupPos = BZip2Constants.GroupSize; | ||
487 | } | |||
488 | | |||
| 0 | 489 | groupPos--; | ||
| 0 | 490 | int zt = selector[groupNo]; | ||
| 0 | 491 | int zn = minLens[zt]; | ||
| 0 | 492 | int zvec = BsR(zn); | ||
493 | int zj; | |||
494 | | |||
| 0 | 495 | while (zvec > limit[zt][zn]) { | ||
| 0 | 496 | if (zn > 20) { // the longest code | ||
| 0 | 497 | throw new BZip2Exception("Bzip data error"); | ||
498 | } | |||
| 0 | 499 | zn++; | ||
| 0 | 500 | while (bsLive < 1) { | ||
| 0 | 501 | FillBuffer(); | ||
502 | } | |||
| 0 | 503 | zj = (bsBuff >> (bsLive - 1)) & 1; | ||
| 0 | 504 | bsLive--; | ||
| 0 | 505 | zvec = (zvec << 1) | zj; | ||
506 | } | |||
| 0 | 507 | if (zvec - baseArray[zt][zn] < 0 || zvec - baseArray[zt][zn] >= BZip2Constants.MaximumAlphaSize) { | ||
| 0 | 508 | throw new BZip2Exception("Bzip data error"); | ||
509 | } | |||
| 0 | 510 | nextSym = perm[zt][zvec - baseArray[zt][zn]]; | ||
511 | | |||
512 | while (true) { | |||
| 0 | 513 | if (nextSym == EOB) { | ||
514 | break; | |||
515 | } | |||
516 | | |||
| 0 | 517 | if (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB) { | ||
| 0 | 518 | int s = -1; | ||
| 0 | 519 | int n = 1; | ||
520 | do { | |||
| 0 | 521 | if (nextSym == BZip2Constants.RunA) { | ||
| 0 | 522 | s += (0 + 1) * n; | ||
| 0 | 523 | } else if (nextSym == BZip2Constants.RunB) { | ||
| 0 | 524 | s += (1 + 1) * n; | ||
525 | } | |||
526 | | |||
| 0 | 527 | n <<= 1; | ||
528 | | |||
| 0 | 529 | if (groupPos == 0) { | ||
| 0 | 530 | groupNo++; | ||
| 0 | 531 | groupPos = BZip2Constants.GroupSize; | ||
532 | } | |||
533 | | |||
| 0 | 534 | groupPos--; | ||
535 | | |||
| 0 | 536 | zt = selector[groupNo]; | ||
| 0 | 537 | zn = minLens[zt]; | ||
| 0 | 538 | zvec = BsR(zn); | ||
539 | | |||
| 0 | 540 | while (zvec > limit[zt][zn]) { | ||
| 0 | 541 | zn++; | ||
| 0 | 542 | while (bsLive < 1) { | ||
| 0 | 543 | FillBuffer(); | ||
544 | } | |||
| 0 | 545 | zj = (bsBuff >> (bsLive - 1)) & 1; | ||
| 0 | 546 | bsLive--; | ||
| 0 | 547 | zvec = (zvec << 1) | zj; | ||
548 | } | |||
| 0 | 549 | nextSym = perm[zt][zvec - baseArray[zt][zn]]; | ||
| 0 | 550 | } while (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB); | ||
551 | | |||
| 0 | 552 | s++; | ||
| 0 | 553 | byte ch = seqToUnseq[yy[0]]; | ||
| 0 | 554 | unzftab[ch] += s; | ||
555 | | |||
| 0 | 556 | while (s > 0) { | ||
| 0 | 557 | last++; | ||
| 0 | 558 | ll8[last] = ch; | ||
| 0 | 559 | s--; | ||
560 | } | |||
561 | | |||
| 0 | 562 | if (last >= limitLast) { | ||
| 0 | 563 | BlockOverrun(); | ||
564 | } | |||
| 0 | 565 | continue; | ||
566 | } else { | |||
| 0 | 567 | last++; | ||
| 0 | 568 | if (last >= limitLast) { | ||
| 0 | 569 | BlockOverrun(); | ||
570 | } | |||
571 | | |||
| 0 | 572 | byte tmp = yy[nextSym - 1]; | ||
| 0 | 573 | unzftab[seqToUnseq[tmp]]++; | ||
| 0 | 574 | ll8[last] = seqToUnseq[tmp]; | ||
575 | | |||
| 0 | 576 | for (int j = nextSym - 1; j > 0; --j) { | ||
| 0 | 577 | yy[j] = yy[j - 1]; | ||
578 | } | |||
| 0 | 579 | yy[0] = tmp; | ||
580 | | |||
| 0 | 581 | if (groupPos == 0) { | ||
| 0 | 582 | groupNo++; | ||
| 0 | 583 | groupPos = BZip2Constants.GroupSize; | ||
584 | } | |||
585 | | |||
| 0 | 586 | groupPos--; | ||
| 0 | 587 | zt = selector[groupNo]; | ||
| 0 | 588 | zn = minLens[zt]; | ||
| 0 | 589 | zvec = BsR(zn); | ||
| 0 | 590 | while (zvec > limit[zt][zn]) { | ||
| 0 | 591 | zn++; | ||
| 0 | 592 | while (bsLive < 1) { | ||
| 0 | 593 | FillBuffer(); | ||
594 | } | |||
| 0 | 595 | zj = (bsBuff >> (bsLive - 1)) & 1; | ||
| 0 | 596 | bsLive--; | ||
| 0 | 597 | zvec = (zvec << 1) | zj; | ||
598 | } | |||
| 0 | 599 | nextSym = perm[zt][zvec - baseArray[zt][zn]]; | ||
| 0 | 600 | continue; | ||
601 | } | |||
602 | } | |||
| 0 | 603 | } | ||
604 | | |||
605 | void SetupBlock() | |||
606 | { | |||
| 1 | 607 | int[] cftab = new int[257]; | ||
608 | | |||
| 1 | 609 | cftab[0] = 0; | ||
| 1 | 610 | Array.Copy(unzftab, 0, cftab, 1, 256); | ||
611 | | |||
| 514 | 612 | for (int i = 1; i <= 256; i++) { | ||
| 256 | 613 | cftab[i] += cftab[i - 1]; | ||
614 | } | |||
615 | | |||
| 4 | 616 | for (int i = 0; i <= last; i++) { | ||
| 1 | 617 | byte ch = ll8[i]; | ||
| 1 | 618 | tt[cftab[ch]] = i; | ||
| 1 | 619 | cftab[ch]++; | ||
620 | } | |||
621 | | |||
| 1 | 622 | cftab = null; | ||
623 | | |||
| 1 | 624 | tPos = tt[origPtr]; | ||
625 | | |||
| 1 | 626 | count = 0; | ||
| 1 | 627 | i2 = 0; | ||
| 1 | 628 | ch2 = 256; /*-- not a char and not EOF --*/ | ||
629 | | |||
| 1 | 630 | if (blockRandomised) { | ||
| 0 | 631 | rNToGo = 0; | ||
| 0 | 632 | rTPos = 0; | ||
| 0 | 633 | SetupRandPartA(); | ||
| 0 | 634 | } else { | ||
| 1 | 635 | SetupNoRandPartA(); | ||
636 | } | |||
| 1 | 637 | } | ||
638 | | |||
639 | void SetupRandPartA() | |||
640 | { | |||
| 0 | 641 | if (i2 <= last) { | ||
| 0 | 642 | chPrev = ch2; | ||
| 0 | 643 | ch2 = ll8[tPos]; | ||
| 0 | 644 | tPos = tt[tPos]; | ||
| 0 | 645 | if (rNToGo == 0) { | ||
| 0 | 646 | rNToGo = BZip2Constants.RandomNumbers[rTPos]; | ||
| 0 | 647 | rTPos++; | ||
| 0 | 648 | if (rTPos == 512) { | ||
| 0 | 649 | rTPos = 0; | ||
650 | } | |||
651 | } | |||
| 0 | 652 | rNToGo--; | ||
| 0 | 653 | ch2 ^= (int)((rNToGo == 1) ? 1 : 0); | ||
| 0 | 654 | i2++; | ||
655 | | |||
| 0 | 656 | currentChar = ch2; | ||
| 0 | 657 | currentState = RAND_PART_B_STATE; | ||
| 0 | 658 | mCrc.Update(ch2); | ||
| 0 | 659 | } else { | ||
| 0 | 660 | EndBlock(); | ||
| 0 | 661 | InitBlock(); | ||
| 0 | 662 | SetupBlock(); | ||
663 | } | |||
| 0 | 664 | } | ||
665 | | |||
666 | void SetupNoRandPartA() | |||
667 | { | |||
| 1 | 668 | if (i2 <= last) { | ||
| 1 | 669 | chPrev = ch2; | ||
| 1 | 670 | ch2 = ll8[tPos]; | ||
| 1 | 671 | tPos = tt[tPos]; | ||
| 1 | 672 | i2++; | ||
673 | | |||
| 1 | 674 | currentChar = ch2; | ||
| 1 | 675 | currentState = NO_RAND_PART_B_STATE; | ||
| 1 | 676 | mCrc.Update(ch2); | ||
| 1 | 677 | } else { | ||
| 0 | 678 | EndBlock(); | ||
| 0 | 679 | InitBlock(); | ||
| 0 | 680 | SetupBlock(); | ||
681 | } | |||
| 0 | 682 | } | ||
683 | | |||
684 | void SetupRandPartB() | |||
685 | { | |||
| 0 | 686 | if (ch2 != chPrev) { | ||
| 0 | 687 | currentState = RAND_PART_A_STATE; | ||
| 0 | 688 | count = 1; | ||
| 0 | 689 | SetupRandPartA(); | ||
| 0 | 690 | } else { | ||
| 0 | 691 | count++; | ||
| 0 | 692 | if (count >= 4) { | ||
| 0 | 693 | z = ll8[tPos]; | ||
| 0 | 694 | tPos = tt[tPos]; | ||
| 0 | 695 | if (rNToGo == 0) { | ||
| 0 | 696 | rNToGo = BZip2Constants.RandomNumbers[rTPos]; | ||
| 0 | 697 | rTPos++; | ||
| 0 | 698 | if (rTPos == 512) { | ||
| 0 | 699 | rTPos = 0; | ||
700 | } | |||
701 | } | |||
| 0 | 702 | rNToGo--; | ||
| 0 | 703 | z ^= (byte)((rNToGo == 1) ? 1 : 0); | ||
| 0 | 704 | j2 = 0; | ||
| 0 | 705 | currentState = RAND_PART_C_STATE; | ||
| 0 | 706 | SetupRandPartC(); | ||
| 0 | 707 | } else { | ||
| 0 | 708 | currentState = RAND_PART_A_STATE; | ||
| 0 | 709 | SetupRandPartA(); | ||
710 | } | |||
711 | } | |||
| 0 | 712 | } | ||
713 | | |||
714 | void SetupRandPartC() | |||
715 | { | |||
| 0 | 716 | if (j2 < (int)z) { | ||
| 0 | 717 | currentChar = ch2; | ||
| 0 | 718 | mCrc.Update(ch2); | ||
| 0 | 719 | j2++; | ||
| 0 | 720 | } else { | ||
| 0 | 721 | currentState = RAND_PART_A_STATE; | ||
| 0 | 722 | i2++; | ||
| 0 | 723 | count = 0; | ||
| 0 | 724 | SetupRandPartA(); | ||
725 | } | |||
| 0 | 726 | } | ||
727 | | |||
728 | void SetupNoRandPartB() | |||
729 | { | |||
| 0 | 730 | if (ch2 != chPrev) { | ||
| 0 | 731 | currentState = NO_RAND_PART_A_STATE; | ||
| 0 | 732 | count = 1; | ||
| 0 | 733 | SetupNoRandPartA(); | ||
| 0 | 734 | } else { | ||
| 0 | 735 | count++; | ||
| 0 | 736 | if (count >= 4) { | ||
| 0 | 737 | z = ll8[tPos]; | ||
| 0 | 738 | tPos = tt[tPos]; | ||
| 0 | 739 | currentState = NO_RAND_PART_C_STATE; | ||
| 0 | 740 | j2 = 0; | ||
| 0 | 741 | SetupNoRandPartC(); | ||
| 0 | 742 | } else { | ||
| 0 | 743 | currentState = NO_RAND_PART_A_STATE; | ||
| 0 | 744 | SetupNoRandPartA(); | ||
745 | } | |||
746 | } | |||
| 0 | 747 | } | ||
748 | | |||
749 | void SetupNoRandPartC() | |||
750 | { | |||
| 0 | 751 | if (j2 < (int)z) { | ||
| 0 | 752 | currentChar = ch2; | ||
| 0 | 753 | mCrc.Update(ch2); | ||
| 0 | 754 | j2++; | ||
| 0 | 755 | } else { | ||
| 0 | 756 | currentState = NO_RAND_PART_A_STATE; | ||
| 0 | 757 | i2++; | ||
| 0 | 758 | count = 0; | ||
| 0 | 759 | SetupNoRandPartA(); | ||
760 | } | |||
| 0 | 761 | } | ||
762 | | |||
763 | void SetDecompressStructureSizes(int newSize100k) | |||
764 | { | |||
| 1 | 765 | if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k && blockSize100k <= 9)) { | ||
| 0 | 766 | throw new BZip2Exception("Invalid block size"); | ||
767 | } | |||
768 | | |||
| 1 | 769 | blockSize100k = newSize100k; | ||
770 | | |||
| 1 | 771 | if (newSize100k == 0) { | ||
| 0 | 772 | return; | ||
773 | } | |||
774 | | |||
| 1 | 775 | int n = BZip2Constants.BaseBlockSize * newSize100k; | ||
| 1 | 776 | ll8 = new byte[n]; | ||
| 1 | 777 | tt = new int[n]; | ||
| 1 | 778 | } | ||
779 | | |||
780 | static void CompressedStreamEOF() | |||
781 | { | |||
| 0 | 782 | throw new EndOfStreamException("BZip2 input stream end of compressed stream"); | ||
783 | } | |||
784 | | |||
785 | static void BlockOverrun() | |||
786 | { | |||
| 0 | 787 | throw new BZip2Exception("BZip2 input stream block overrun"); | ||
788 | } | |||
789 | | |||
790 | static void BadBlockHeader() | |||
791 | { | |||
| 0 | 792 | throw new BZip2Exception("BZip2 input stream bad block header"); | ||
793 | } | |||
794 | | |||
795 | static void CrcError() | |||
796 | { | |||
| 0 | 797 | throw new BZip2Exception("BZip2 input stream crc error"); | ||
798 | } | |||
799 | | |||
800 | static void HbCreateDecodeTables(int[] limit, int[] baseArray, int[] perm, char[] length, int minLen, int maxLen, in | |||
801 | { | |||
| 0 | 802 | int pp = 0; | ||
803 | | |||
| 0 | 804 | for (int i = minLen; i <= maxLen; ++i) { | ||
| 0 | 805 | for (int j = 0; j < alphaSize; ++j) { | ||
| 0 | 806 | if (length[j] == i) { | ||
| 0 | 807 | perm[pp] = j; | ||
| 0 | 808 | ++pp; | ||
809 | } | |||
810 | } | |||
811 | } | |||
812 | | |||
| 0 | 813 | for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) { | ||
| 0 | 814 | baseArray[i] = 0; | ||
815 | } | |||
816 | | |||
| 0 | 817 | for (int i = 0; i < alphaSize; i++) { | ||
| 0 | 818 | ++baseArray[length[i] + 1]; | ||
819 | } | |||
820 | | |||
| 0 | 821 | for (int i = 1; i < BZip2Constants.MaximumCodeLength; i++) { | ||
| 0 | 822 | baseArray[i] += baseArray[i - 1]; | ||
823 | } | |||
824 | | |||
| 0 | 825 | for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) { | ||
| 0 | 826 | limit[i] = 0; | ||
827 | } | |||
828 | | |||
| 0 | 829 | int vec = 0; | ||
830 | | |||
| 0 | 831 | for (int i = minLen; i <= maxLen; i++) { | ||
| 0 | 832 | vec += (baseArray[i + 1] - baseArray[i]); | ||
| 0 | 833 | limit[i] = vec - 1; | ||
| 0 | 834 | vec <<= 1; | ||
835 | } | |||
836 | | |||
| 0 | 837 | for (int i = minLen + 1; i <= maxLen; i++) { | ||
| 0 | 838 | baseArray[i] = ((limit[i - 1] + 1) << 1) - baseArray[i]; | ||
839 | } | |||
| 0 | 840 | } | ||
841 | | |||
842 | #region Instance Fields | |||
843 | /*-- | |||
844 | index of the last char in the block, so | |||
845 | the block size == last + 1. | |||
846 | --*/ | |||
847 | int last; | |||
848 | | |||
849 | /*-- | |||
850 | index in zptr[] of original string after sorting. | |||
851 | --*/ | |||
852 | int origPtr; | |||
853 | | |||
854 | /*-- | |||
855 | always: in the range 0 .. 9. | |||
856 | The current block size is 100000 * this number. | |||
857 | --*/ | |||
858 | int blockSize100k; | |||
859 | | |||
860 | bool blockRandomised; | |||
861 | | |||
862 | int bsBuff; | |||
863 | int bsLive; | |||
| 1 | 864 | IChecksum mCrc = new BZip2Crc(); | ||
865 | | |||
| 1 | 866 | bool[] inUse = new bool[256]; | ||
867 | int nInUse; | |||
868 | | |||
| 1 | 869 | byte[] seqToUnseq = new byte[256]; | ||
| 1 | 870 | byte[] unseqToSeq = new byte[256]; | ||
871 | | |||
| 1 | 872 | byte[] selector = new byte[BZip2Constants.MaximumSelectors]; | ||
| 1 | 873 | byte[] selectorMtf = new byte[BZip2Constants.MaximumSelectors]; | ||
874 | | |||
875 | int[] tt; | |||
876 | byte[] ll8; | |||
877 | | |||
878 | /*-- | |||
879 | freq table collected to save a pass over the data | |||
880 | during decompression. | |||
881 | --*/ | |||
| 1 | 882 | int[] unzftab = new int[256]; | ||
883 | | |||
| 1 | 884 | int[][] limit = new int[BZip2Constants.GroupCount][]; | ||
| 1 | 885 | int[][] baseArray = new int[BZip2Constants.GroupCount][]; | ||
| 1 | 886 | int[][] perm = new int[BZip2Constants.GroupCount][]; | ||
| 1 | 887 | int[] minLens = new int[BZip2Constants.GroupCount]; | ||
888 | | |||
889 | Stream baseStream; | |||
890 | bool streamEnd; | |||
891 | | |||
| 1 | 892 | int currentChar = -1; | ||
893 | | |||
| 1 | 894 | int currentState = START_BLOCK_STATE; | ||
895 | | |||
896 | int storedBlockCRC, storedCombinedCRC; | |||
897 | int computedBlockCRC; | |||
898 | uint computedCombinedCRC; | |||
899 | | |||
900 | int count, chPrev, ch2; | |||
901 | int tPos; | |||
902 | int rNToGo; | |||
903 | int rTPos; | |||
904 | int i2, j2; | |||
905 | byte z; | |||
| 1 | 906 | bool isStreamOwner = true; | ||
907 | #endregion | |||
908 | } | |||
909 | } |
| Class: | ICSharpCode.SharpZipLib.BZip2.BZip2OutputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\BZip2\BZip2OutputStream.cs |
| Covered lines: | 107 |
| Uncovered lines: | 794 |
| Coverable lines: | 901 |
| Total lines: | 1793 |
| Line coverage: | 11.8% |
| Branch coverage: | 3.6% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 3 | 90.91 | 60 |
| Finalize() | 1 | 0 | 0 |
| Seek(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| ReadByte() | 1 | 0 | 0 |
| Read(...) | 1 | 0 | 0 |
| Write(...) | 6 | 0 | 0 |
| WriteByte(...) | 4 | 0 | 0 |
| Close() | 1 | 100 | 100 |
| MakeMaps() | 3 | 0 | 0 |
| WriteRun() | 7 | 0 | 0 |
| Dispose(...) | 5 | 93.33 | 55.56 |
| Flush() | 1 | 100 | 100 |
| Initialize() | 1 | 100 | 100 |
| InitBlock() | 2 | 100 | 100 |
| EndBlock() | 3 | 10 | 40 |
| EndCompression() | 1 | 100 | 100 |
| BsSetStream(...) | 1 | 100 | 100 |
| BsFinishedWithStream() | 2 | 100 | 100 |
| BsW(...) | 2 | 100 | 100 |
| BsPutUChar(...) | 1 | 100 | 100 |
| BsPutint(...) | 1 | 100 | 100 |
| BsPutIntVS(...) | 1 | 0 | 0 |
| SendMTFValues() | 67 | 0 | 0 |
| MoveToFrontCodeAndSend() | 1 | 0 | 0 |
| SimpleSort(...) | 15 | 0 | 0 |
| Vswap(...) | 2 | 0 | 0 |
| QSort3(...) | 17 | 0 | 0 |
| MainSort() | 32 | 0 | 0 |
| RandomiseBlock() | 7 | 0 | 0 |
| DoReversibleTransformation() | 6 | 0 | 0 |
| FullGtU(...) | 18 | 0 | 0 |
| AllocateCompressStructures() | 4 | 100 | 57.14 |
| GenerateMTFValues() | 14 | 0 | 0 |
| Panic() | 1 | 0 | 0 |
| HbMakeCodeLengths(...) | 23 | 0 | 0 |
| HbAssignCodes(...) | 4 | 0 | 0 |
| Med3(...) | 4 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.BZip2 | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// An output stream that compresses into the BZip2 format | |||
9 | /// including file header chars into another stream. | |||
10 | /// </summary> | |||
11 | public class BZip2OutputStream : Stream | |||
12 | { | |||
13 | #region Constants | |||
14 | const int SETMASK = (1 << 21); | |||
15 | const int CLEARMASK = (~SETMASK); | |||
16 | const int GREATER_ICOST = 15; | |||
17 | const int LESSER_ICOST = 0; | |||
18 | const int SMALL_THRESH = 20; | |||
19 | const int DEPTH_THRESH = 10; | |||
20 | | |||
21 | /*-- | |||
22 | If you are ever unlucky/improbable enough | |||
23 | to get a stack overflow whilst sorting, | |||
24 | increase the following constant and try | |||
25 | again. In practice I have never seen the | |||
26 | stack go above 27 elems, so the following | |||
27 | limit seems very generous. | |||
28 | --*/ | |||
29 | const int QSORT_STACK_SIZE = 1000; | |||
30 | | |||
31 | /*-- | |||
32 | Knuth's increments seem to work better | |||
33 | than Incerpi-Sedgewick here. Possibly | |||
34 | because the number of elems to sort is | |||
35 | usually small, typically <= 20. | |||
36 | --*/ | |||
| 1 | 37 | readonly int[] increments = { | ||
| 1 | 38 | 1, 4, 13, 40, 121, 364, 1093, 3280, | ||
| 1 | 39 | 9841, 29524, 88573, 265720, | ||
| 1 | 40 | 797161, 2391484 | ||
| 1 | 41 | }; | ||
42 | #endregion | |||
43 | | |||
44 | #region Constructors | |||
45 | /// <summary> | |||
46 | /// Construct a default output stream with maximum block size | |||
47 | /// </summary> | |||
48 | /// <param name="stream">The stream to write BZip data onto.</param> | |||
| 1 | 49 | public BZip2OutputStream(Stream stream) : this(stream, 9) | ||
50 | { | |||
| 1 | 51 | } | ||
52 | | |||
53 | /// <summary> | |||
54 | /// Initialise a new instance of the <see cref="BZip2OutputStream"></see> | |||
55 | /// for the specified stream, using the given blocksize. | |||
56 | /// </summary> | |||
57 | /// <param name="stream">The stream to write compressed data to.</param> | |||
58 | /// <param name="blockSize">The block size to use.</param> | |||
59 | /// <remarks> | |||
60 | /// Valid block sizes are in the range 1..9, with 1 giving | |||
61 | /// the lowest compression and 9 the highest. | |||
62 | /// </remarks> | |||
| 1 | 63 | public BZip2OutputStream(Stream stream, int blockSize) | ||
64 | { | |||
| 1 | 65 | BsSetStream(stream); | ||
66 | | |||
| 1 | 67 | workFactor = 50; | ||
| 1 | 68 | if (blockSize > 9) { | ||
| 0 | 69 | blockSize = 9; | ||
70 | } | |||
71 | | |||
| 1 | 72 | if (blockSize < 1) { | ||
| 0 | 73 | blockSize = 1; | ||
74 | } | |||
| 1 | 75 | blockSize100k = blockSize; | ||
| 1 | 76 | AllocateCompressStructures(); | ||
| 1 | 77 | Initialize(); | ||
| 1 | 78 | InitBlock(); | ||
| 1 | 79 | } | ||
80 | #endregion | |||
81 | | |||
82 | #region Destructor | |||
83 | /// <summary> | |||
84 | /// Ensures that resources are freed and other cleanup operations | |||
85 | /// are performed when the garbage collector reclaims the BZip2OutputStream. | |||
86 | /// </summary> | |||
87 | ~BZip2OutputStream() | |||
88 | { | |||
| 0 | 89 | Dispose(false); | ||
| 0 | 90 | } | ||
91 | #endregion | |||
92 | | |||
93 | /// <summary> | |||
94 | /// Get/set flag indicating ownership of underlying stream. | |||
95 | /// When the flag is true <see cref="Close"></see> will close the underlying stream also. | |||
96 | /// </summary> | |||
97 | public bool IsStreamOwner { | |||
| 1 | 98 | get { return isStreamOwner; } | ||
| 0 | 99 | set { isStreamOwner = value; } | ||
100 | } | |||
101 | | |||
102 | | |||
103 | #region Stream overrides | |||
104 | /// <summary> | |||
105 | /// Gets a value indicating whether the current stream supports reading | |||
106 | /// </summary> | |||
107 | public override bool CanRead { | |||
108 | get { | |||
| 0 | 109 | return false; | ||
110 | } | |||
111 | } | |||
112 | | |||
113 | /// <summary> | |||
114 | /// Gets a value indicating whether the current stream supports seeking | |||
115 | /// </summary> | |||
116 | public override bool CanSeek { | |||
117 | get { | |||
| 0 | 118 | return false; | ||
119 | } | |||
120 | } | |||
121 | | |||
122 | /// <summary> | |||
123 | /// Gets a value indicating whether the current stream supports writing | |||
124 | /// </summary> | |||
125 | public override bool CanWrite { | |||
126 | get { | |||
| 0 | 127 | return baseStream.CanWrite; | ||
128 | } | |||
129 | } | |||
130 | | |||
131 | /// <summary> | |||
132 | /// Gets the length in bytes of the stream | |||
133 | /// </summary> | |||
134 | public override long Length { | |||
135 | get { | |||
| 0 | 136 | return baseStream.Length; | ||
137 | } | |||
138 | } | |||
139 | | |||
140 | /// <summary> | |||
141 | /// Gets or sets the current position of this stream. | |||
142 | /// </summary> | |||
143 | public override long Position { | |||
144 | get { | |||
| 0 | 145 | return baseStream.Position; | ||
146 | } | |||
147 | set { | |||
| 0 | 148 | throw new NotSupportedException("BZip2OutputStream position cannot be set"); | ||
149 | } | |||
150 | } | |||
151 | | |||
152 | /// <summary> | |||
153 | /// Sets the current position of this stream to the given value. | |||
154 | /// </summary> | |||
155 | /// <param name="offset">The point relative to the offset from which to being seeking.</param> | |||
156 | /// <param name="origin">The reference point from which to begin seeking.</param> | |||
157 | /// <returns>The new position in the stream.</returns> | |||
158 | public override long Seek(long offset, SeekOrigin origin) | |||
159 | { | |||
| 0 | 160 | throw new NotSupportedException("BZip2OutputStream Seek not supported"); | ||
161 | } | |||
162 | | |||
163 | /// <summary> | |||
164 | /// Sets the length of this stream to the given value. | |||
165 | /// </summary> | |||
166 | /// <param name="value">The new stream length.</param> | |||
167 | public override void SetLength(long value) | |||
168 | { | |||
| 0 | 169 | throw new NotSupportedException("BZip2OutputStream SetLength not supported"); | ||
170 | } | |||
171 | | |||
172 | /// <summary> | |||
173 | /// Read a byte from the stream advancing the position. | |||
174 | /// </summary> | |||
175 | /// <returns>The byte read cast to an int; -1 if end of stream.</returns> | |||
176 | public override int ReadByte() | |||
177 | { | |||
| 0 | 178 | throw new NotSupportedException("BZip2OutputStream ReadByte not supported"); | ||
179 | } | |||
180 | | |||
181 | /// <summary> | |||
182 | /// Read a block of bytes | |||
183 | /// </summary> | |||
184 | /// <param name="buffer">The buffer to read into.</param> | |||
185 | /// <param name="offset">The offset in the buffer to start storing data at.</param> | |||
186 | /// <param name="count">The maximum number of bytes to read.</param> | |||
187 | /// <returns>The total number of bytes read. This might be less than the number of bytes | |||
188 | /// requested if that number of bytes are not currently available, or zero | |||
189 | /// if the end of the stream is reached.</returns> | |||
190 | public override int Read(byte[] buffer, int offset, int count) | |||
191 | { | |||
| 0 | 192 | throw new NotSupportedException("BZip2OutputStream Read not supported"); | ||
193 | } | |||
194 | | |||
195 | /// <summary> | |||
196 | /// Write a block of bytes to the stream | |||
197 | /// </summary> | |||
198 | /// <param name="buffer">The buffer containing data to write.</param> | |||
199 | /// <param name="offset">The offset of the first byte to write.</param> | |||
200 | /// <param name="count">The number of bytes to write.</param> | |||
201 | public override void Write(byte[] buffer, int offset, int count) | |||
202 | { | |||
| 0 | 203 | if (buffer == null) { | ||
| 0 | 204 | throw new ArgumentNullException(nameof(buffer)); | ||
205 | } | |||
206 | | |||
| 0 | 207 | if (offset < 0) { | ||
| 0 | 208 | throw new ArgumentOutOfRangeException(nameof(offset)); | ||
209 | } | |||
210 | | |||
| 0 | 211 | if (count < 0) { | ||
| 0 | 212 | throw new ArgumentOutOfRangeException(nameof(count)); | ||
213 | } | |||
214 | | |||
| 0 | 215 | if (buffer.Length - offset < count) { | ||
| 0 | 216 | throw new ArgumentException("Offset/count out of range"); | ||
217 | } | |||
218 | | |||
| 0 | 219 | for (int i = 0; i < count; ++i) { | ||
| 0 | 220 | WriteByte(buffer[offset + i]); | ||
221 | } | |||
| 0 | 222 | } | ||
223 | | |||
224 | /// <summary> | |||
225 | /// Write a byte to the stream. | |||
226 | /// </summary> | |||
227 | /// <param name="value">The byte to write to the stream.</param> | |||
228 | public override void WriteByte(byte value) | |||
229 | { | |||
| 0 | 230 | int b = (256 + value) % 256; | ||
| 0 | 231 | if (currentChar != -1) { | ||
| 0 | 232 | if (currentChar == b) { | ||
| 0 | 233 | runLength++; | ||
| 0 | 234 | if (runLength > 254) { | ||
| 0 | 235 | WriteRun(); | ||
| 0 | 236 | currentChar = -1; | ||
| 0 | 237 | runLength = 0; | ||
238 | } | |||
| 0 | 239 | } else { | ||
| 0 | 240 | WriteRun(); | ||
| 0 | 241 | runLength = 1; | ||
| 0 | 242 | currentChar = b; | ||
243 | } | |||
| 0 | 244 | } else { | ||
| 0 | 245 | currentChar = b; | ||
| 0 | 246 | runLength++; | ||
247 | } | |||
| 0 | 248 | } | ||
249 | | |||
250 | /// <summary> | |||
251 | /// End the current block and end compression. | |||
252 | /// Close the stream and free any resources | |||
253 | /// </summary> | |||
254 | public override void Close() | |||
255 | { | |||
| 1 | 256 | Dispose(true); | ||
| 1 | 257 | GC.SuppressFinalize(this); | ||
| 1 | 258 | } | ||
259 | | |||
260 | #endregion | |||
261 | void MakeMaps() | |||
262 | { | |||
| 0 | 263 | nInUse = 0; | ||
| 0 | 264 | for (int i = 0; i < 256; i++) { | ||
| 0 | 265 | if (inUse[i]) { | ||
| 0 | 266 | seqToUnseq[nInUse] = (char)i; | ||
| 0 | 267 | unseqToSeq[i] = (char)nInUse; | ||
| 0 | 268 | nInUse++; | ||
269 | } | |||
270 | } | |||
| 0 | 271 | } | ||
272 | | |||
273 | /// <summary> | |||
274 | /// Get the number of bytes written to output. | |||
275 | /// </summary> | |||
276 | void WriteRun() | |||
277 | { | |||
| 0 | 278 | if (last < allowableBlockSize) { | ||
| 0 | 279 | inUse[currentChar] = true; | ||
| 0 | 280 | for (int i = 0; i < runLength; i++) { | ||
| 0 | 281 | mCrc.Update(currentChar); | ||
282 | } | |||
283 | | |||
| 0 | 284 | switch (runLength) { | ||
285 | case 1: | |||
| 0 | 286 | last++; | ||
| 0 | 287 | block[last + 1] = (byte)currentChar; | ||
| 0 | 288 | break; | ||
289 | case 2: | |||
| 0 | 290 | last++; | ||
| 0 | 291 | block[last + 1] = (byte)currentChar; | ||
| 0 | 292 | last++; | ||
| 0 | 293 | block[last + 1] = (byte)currentChar; | ||
| 0 | 294 | break; | ||
295 | case 3: | |||
| 0 | 296 | last++; | ||
| 0 | 297 | block[last + 1] = (byte)currentChar; | ||
| 0 | 298 | last++; | ||
| 0 | 299 | block[last + 1] = (byte)currentChar; | ||
| 0 | 300 | last++; | ||
| 0 | 301 | block[last + 1] = (byte)currentChar; | ||
| 0 | 302 | break; | ||
303 | default: | |||
| 0 | 304 | inUse[runLength - 4] = true; | ||
| 0 | 305 | last++; | ||
| 0 | 306 | block[last + 1] = (byte)currentChar; | ||
| 0 | 307 | last++; | ||
| 0 | 308 | block[last + 1] = (byte)currentChar; | ||
| 0 | 309 | last++; | ||
| 0 | 310 | block[last + 1] = (byte)currentChar; | ||
| 0 | 311 | last++; | ||
| 0 | 312 | block[last + 1] = (byte)currentChar; | ||
| 0 | 313 | last++; | ||
| 0 | 314 | block[last + 1] = (byte)(runLength - 4); | ||
| 0 | 315 | break; | ||
316 | } | |||
317 | } else { | |||
| 0 | 318 | EndBlock(); | ||
| 0 | 319 | InitBlock(); | ||
| 0 | 320 | WriteRun(); | ||
321 | } | |||
| 0 | 322 | } | ||
323 | | |||
324 | /// <summary> | |||
325 | /// Get the number of bytes written to the output. | |||
326 | /// </summary> | |||
327 | public int BytesWritten { | |||
| 0 | 328 | get { return bytesOut; } | ||
329 | } | |||
330 | | |||
331 | /// <summary> | |||
332 | /// Releases the unmanaged resources used by the <see cref="BZip2OutputStream"/> and optionally releases the managed | |||
333 | /// </summary> | |||
334 | /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged re | |||
335 | override protected void Dispose(bool disposing) | |||
336 | { | |||
337 | try { | |||
| 1 | 338 | base.Dispose(disposing); | ||
| 1 | 339 | if (!disposed_) { | ||
| 1 | 340 | disposed_ = true; | ||
341 | | |||
| 1 | 342 | if (runLength > 0) { | ||
| 0 | 343 | WriteRun(); | ||
344 | } | |||
345 | | |||
| 1 | 346 | currentChar = -1; | ||
| 1 | 347 | EndBlock(); | ||
| 1 | 348 | EndCompression(); | ||
| 1 | 349 | Flush(); | ||
350 | } | |||
| 1 | 351 | } finally { | ||
| 1 | 352 | if (disposing) { | ||
| 1 | 353 | if (IsStreamOwner) { | ||
| 1 | 354 | baseStream.Close(); | ||
355 | } | |||
356 | } | |||
| 1 | 357 | } | ||
| 1 | 358 | } | ||
359 | | |||
360 | /// <summary> | |||
361 | /// Flush output buffers | |||
362 | /// </summary> | |||
363 | public override void Flush() | |||
364 | { | |||
| 1 | 365 | baseStream.Flush(); | ||
| 1 | 366 | } | ||
367 | | |||
368 | void Initialize() | |||
369 | { | |||
| 1 | 370 | bytesOut = 0; | ||
| 1 | 371 | nBlocksRandomised = 0; | ||
372 | | |||
373 | /*--- Write header `magic' bytes indicating file-format == huffmanised, | |||
374 | followed by a digit indicating blockSize100k. | |||
375 | ---*/ | |||
376 | | |||
| 1 | 377 | BsPutUChar('B'); | ||
| 1 | 378 | BsPutUChar('Z'); | ||
379 | | |||
| 1 | 380 | BsPutUChar('h'); | ||
| 1 | 381 | BsPutUChar('0' + blockSize100k); | ||
382 | | |||
| 1 | 383 | combinedCRC = 0; | ||
| 1 | 384 | } | ||
385 | | |||
386 | void InitBlock() | |||
387 | { | |||
| 1 | 388 | mCrc.Reset(); | ||
| 1 | 389 | last = -1; | ||
390 | | |||
| 514 | 391 | for (int i = 0; i < 256; i++) { | ||
| 256 | 392 | inUse[i] = false; | ||
393 | } | |||
394 | | |||
395 | /*--- 20 is just a paranoia constant ---*/ | |||
| 1 | 396 | allowableBlockSize = BZip2Constants.BaseBlockSize * blockSize100k - 20; | ||
| 1 | 397 | } | ||
398 | | |||
399 | void EndBlock() | |||
400 | { | |||
| 1 | 401 | if (last < 0) { // dont do anything for empty files, (makes empty files compatible with original Bzip) | ||
| 1 | 402 | return; | ||
403 | } | |||
404 | | |||
| 0 | 405 | blockCRC = unchecked((uint)mCrc.Value); | ||
| 0 | 406 | combinedCRC = (combinedCRC << 1) | (combinedCRC >> 31); | ||
| 0 | 407 | combinedCRC ^= blockCRC; | ||
408 | | |||
409 | /*-- sort the block and establish position of original string --*/ | |||
| 0 | 410 | DoReversibleTransformation(); | ||
411 | | |||
412 | /*-- | |||
413 | A 6-byte block header, the value chosen arbitrarily | |||
414 | as 0x314159265359 :-). A 32 bit value does not really | |||
415 | give a strong enough guarantee that the value will not | |||
416 | appear by chance in the compressed datastream. Worst-case | |||
417 | probability of this event, for a 900k block, is about | |||
418 | 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. | |||
419 | For a compressed file of size 100Gb -- about 100000 blocks -- | |||
420 | only a 48-bit marker will do. NB: normal compression/ | |||
421 | decompression do *not* rely on these statistical properties. | |||
422 | They are only important when trying to recover blocks from | |||
423 | damaged files. | |||
424 | --*/ | |||
| 0 | 425 | BsPutUChar(0x31); | ||
| 0 | 426 | BsPutUChar(0x41); | ||
| 0 | 427 | BsPutUChar(0x59); | ||
| 0 | 428 | BsPutUChar(0x26); | ||
| 0 | 429 | BsPutUChar(0x53); | ||
| 0 | 430 | BsPutUChar(0x59); | ||
431 | | |||
432 | /*-- Now the block's CRC, so it is in a known place. --*/ | |||
433 | unchecked { | |||
| 0 | 434 | BsPutint((int)blockCRC); | ||
435 | } | |||
436 | | |||
437 | /*-- Now a single bit indicating randomisation. --*/ | |||
| 0 | 438 | if (blockRandomised) { | ||
| 0 | 439 | BsW(1, 1); | ||
| 0 | 440 | nBlocksRandomised++; | ||
| 0 | 441 | } else { | ||
| 0 | 442 | BsW(1, 0); | ||
443 | } | |||
444 | | |||
445 | /*-- Finally, block's contents proper. --*/ | |||
| 0 | 446 | MoveToFrontCodeAndSend(); | ||
| 0 | 447 | } | ||
448 | | |||
449 | void EndCompression() | |||
450 | { | |||
451 | /*-- | |||
452 | Now another magic 48-bit number, 0x177245385090, to | |||
453 | indicate the end of the last block. (sqrt(pi), if | |||
454 | you want to know. I did want to use e, but it contains | |||
455 | too much repetition -- 27 18 28 18 28 46 -- for me | |||
456 | to feel statistically comfortable. Call me paranoid.) | |||
457 | --*/ | |||
| 1 | 458 | BsPutUChar(0x17); | ||
| 1 | 459 | BsPutUChar(0x72); | ||
| 1 | 460 | BsPutUChar(0x45); | ||
| 1 | 461 | BsPutUChar(0x38); | ||
| 1 | 462 | BsPutUChar(0x50); | ||
| 1 | 463 | BsPutUChar(0x90); | ||
464 | | |||
465 | unchecked { | |||
| 1 | 466 | BsPutint((int)combinedCRC); | ||
467 | } | |||
468 | | |||
| 1 | 469 | BsFinishedWithStream(); | ||
| 1 | 470 | } | ||
471 | | |||
472 | void BsSetStream(Stream stream) | |||
473 | { | |||
| 1 | 474 | baseStream = stream; | ||
| 1 | 475 | bsLive = 0; | ||
| 1 | 476 | bsBuff = 0; | ||
| 1 | 477 | bytesOut = 0; | ||
| 1 | 478 | } | ||
479 | | |||
480 | void BsFinishedWithStream() | |||
481 | { | |||
| 2 | 482 | while (bsLive > 0) { | ||
| 1 | 483 | int ch = (bsBuff >> 24); | ||
| 1 | 484 | baseStream.WriteByte((byte)ch); // write 8-bit | ||
| 1 | 485 | bsBuff <<= 8; | ||
| 1 | 486 | bsLive -= 8; | ||
| 1 | 487 | bytesOut++; | ||
488 | } | |||
| 1 | 489 | } | ||
490 | | |||
491 | void BsW(int n, int v) | |||
492 | { | |||
| 27 | 493 | while (bsLive >= 8) { | ||
| 13 | 494 | int ch = (bsBuff >> 24); | ||
| 13 | 495 | unchecked { baseStream.WriteByte((byte)ch); } // write 8-bit | ||
| 13 | 496 | bsBuff <<= 8; | ||
| 13 | 497 | bsLive -= 8; | ||
| 13 | 498 | ++bytesOut; | ||
499 | } | |||
| 14 | 500 | bsBuff |= (v << (32 - bsLive - n)); | ||
| 14 | 501 | bsLive += n; | ||
| 14 | 502 | } | ||
503 | | |||
504 | void BsPutUChar(int c) | |||
505 | { | |||
| 10 | 506 | BsW(8, c); | ||
| 10 | 507 | } | ||
508 | | |||
509 | void BsPutint(int u) | |||
510 | { | |||
| 1 | 511 | BsW(8, (u >> 24) & 0xFF); | ||
| 1 | 512 | BsW(8, (u >> 16) & 0xFF); | ||
| 1 | 513 | BsW(8, (u >> 8) & 0xFF); | ||
| 1 | 514 | BsW(8, u & 0xFF); | ||
| 1 | 515 | } | ||
516 | | |||
517 | void BsPutIntVS(int numBits, int c) | |||
518 | { | |||
| 0 | 519 | BsW(numBits, c); | ||
| 0 | 520 | } | ||
521 | | |||
522 | void SendMTFValues() | |||
523 | { | |||
| 0 | 524 | char[][] len = new char[BZip2Constants.GroupCount][]; | ||
| 0 | 525 | for (int i = 0; i < BZip2Constants.GroupCount; ++i) { | ||
| 0 | 526 | len[i] = new char[BZip2Constants.MaximumAlphaSize]; | ||
527 | } | |||
528 | | |||
529 | int gs, ge, totc, bt, bc, iter; | |||
| 0 | 530 | int nSelectors = 0, alphaSize, minLen, maxLen, selCtr; | ||
531 | int nGroups; | |||
532 | | |||
| 0 | 533 | alphaSize = nInUse + 2; | ||
| 0 | 534 | for (int t = 0; t < BZip2Constants.GroupCount; t++) { | ||
| 0 | 535 | for (int v = 0; v < alphaSize; v++) { | ||
| 0 | 536 | len[t][v] = (char)GREATER_ICOST; | ||
537 | } | |||
538 | } | |||
539 | | |||
540 | /*--- Decide how many coding tables to use ---*/ | |||
| 0 | 541 | if (nMTF <= 0) { | ||
| 0 | 542 | Panic(); | ||
543 | } | |||
544 | | |||
| 0 | 545 | if (nMTF < 200) { | ||
| 0 | 546 | nGroups = 2; | ||
| 0 | 547 | } else if (nMTF < 600) { | ||
| 0 | 548 | nGroups = 3; | ||
| 0 | 549 | } else if (nMTF < 1200) { | ||
| 0 | 550 | nGroups = 4; | ||
| 0 | 551 | } else if (nMTF < 2400) { | ||
| 0 | 552 | nGroups = 5; | ||
| 0 | 553 | } else { | ||
| 0 | 554 | nGroups = 6; | ||
555 | } | |||
556 | | |||
557 | /*--- Generate an initial set of coding tables ---*/ | |||
| 0 | 558 | int nPart = nGroups; | ||
| 0 | 559 | int remF = nMTF; | ||
| 0 | 560 | gs = 0; | ||
| 0 | 561 | while (nPart > 0) { | ||
| 0 | 562 | int tFreq = remF / nPart; | ||
| 0 | 563 | int aFreq = 0; | ||
| 0 | 564 | ge = gs - 1; | ||
| 0 | 565 | while (aFreq < tFreq && ge < alphaSize - 1) { | ||
| 0 | 566 | ge++; | ||
| 0 | 567 | aFreq += mtfFreq[ge]; | ||
568 | } | |||
569 | | |||
| 0 | 570 | if (ge > gs && nPart != nGroups && nPart != 1 && ((nGroups - nPart) % 2 == 1)) { | ||
| 0 | 571 | aFreq -= mtfFreq[ge]; | ||
| 0 | 572 | ge--; | ||
573 | } | |||
574 | | |||
| 0 | 575 | for (int v = 0; v < alphaSize; v++) { | ||
| 0 | 576 | if (v >= gs && v <= ge) { | ||
| 0 | 577 | len[nPart - 1][v] = (char)LESSER_ICOST; | ||
| 0 | 578 | } else { | ||
| 0 | 579 | len[nPart - 1][v] = (char)GREATER_ICOST; | ||
580 | } | |||
581 | } | |||
582 | | |||
| 0 | 583 | nPart--; | ||
| 0 | 584 | gs = ge + 1; | ||
| 0 | 585 | remF -= aFreq; | ||
586 | } | |||
587 | | |||
| 0 | 588 | int[][] rfreq = new int[BZip2Constants.GroupCount][]; | ||
| 0 | 589 | for (int i = 0; i < BZip2Constants.GroupCount; ++i) { | ||
| 0 | 590 | rfreq[i] = new int[BZip2Constants.MaximumAlphaSize]; | ||
591 | } | |||
592 | | |||
| 0 | 593 | int[] fave = new int[BZip2Constants.GroupCount]; | ||
| 0 | 594 | short[] cost = new short[BZip2Constants.GroupCount]; | ||
595 | /*--- | |||
596 | Iterate up to N_ITERS times to improve the tables. | |||
597 | ---*/ | |||
| 0 | 598 | for (iter = 0; iter < BZip2Constants.NumberOfIterations; ++iter) { | ||
| 0 | 599 | for (int t = 0; t < nGroups; ++t) { | ||
| 0 | 600 | fave[t] = 0; | ||
601 | } | |||
602 | | |||
| 0 | 603 | for (int t = 0; t < nGroups; ++t) { | ||
| 0 | 604 | for (int v = 0; v < alphaSize; ++v) { | ||
| 0 | 605 | rfreq[t][v] = 0; | ||
606 | } | |||
607 | } | |||
608 | | |||
| 0 | 609 | nSelectors = 0; | ||
| 0 | 610 | totc = 0; | ||
| 0 | 611 | gs = 0; | ||
| 0 | 612 | while (true) { | ||
613 | /*--- Set group start & end marks. --*/ | |||
| 0 | 614 | if (gs >= nMTF) { | ||
615 | break; | |||
616 | } | |||
| 0 | 617 | ge = gs + BZip2Constants.GroupSize - 1; | ||
| 0 | 618 | if (ge >= nMTF) { | ||
| 0 | 619 | ge = nMTF - 1; | ||
620 | } | |||
621 | | |||
622 | /*-- | |||
623 | Calculate the cost of this group as coded | |||
624 | by each of the coding tables. | |||
625 | --*/ | |||
| 0 | 626 | for (int t = 0; t < nGroups; t++) { | ||
| 0 | 627 | cost[t] = 0; | ||
628 | } | |||
629 | | |||
| 0 | 630 | if (nGroups == 6) { | ||
631 | short cost0, cost1, cost2, cost3, cost4, cost5; | |||
| 0 | 632 | cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; | ||
| 0 | 633 | for (int i = gs; i <= ge; ++i) { | ||
| 0 | 634 | short icv = szptr[i]; | ||
| 0 | 635 | cost0 += (short)len[0][icv]; | ||
| 0 | 636 | cost1 += (short)len[1][icv]; | ||
| 0 | 637 | cost2 += (short)len[2][icv]; | ||
| 0 | 638 | cost3 += (short)len[3][icv]; | ||
| 0 | 639 | cost4 += (short)len[4][icv]; | ||
| 0 | 640 | cost5 += (short)len[5][icv]; | ||
641 | } | |||
| 0 | 642 | cost[0] = cost0; | ||
| 0 | 643 | cost[1] = cost1; | ||
| 0 | 644 | cost[2] = cost2; | ||
| 0 | 645 | cost[3] = cost3; | ||
| 0 | 646 | cost[4] = cost4; | ||
| 0 | 647 | cost[5] = cost5; | ||
| 0 | 648 | } else { | ||
| 0 | 649 | for (int i = gs; i <= ge; ++i) { | ||
| 0 | 650 | short icv = szptr[i]; | ||
| 0 | 651 | for (int t = 0; t < nGroups; t++) { | ||
| 0 | 652 | cost[t] += (short)len[t][icv]; | ||
653 | } | |||
654 | } | |||
655 | } | |||
656 | | |||
657 | /*-- | |||
658 | Find the coding table which is best for this group, | |||
659 | and record its identity in the selector table. | |||
660 | --*/ | |||
| 0 | 661 | bc = 999999999; | ||
| 0 | 662 | bt = -1; | ||
| 0 | 663 | for (int t = 0; t < nGroups; ++t) { | ||
| 0 | 664 | if (cost[t] < bc) { | ||
| 0 | 665 | bc = cost[t]; | ||
| 0 | 666 | bt = t; | ||
667 | } | |||
668 | } | |||
| 0 | 669 | totc += bc; | ||
| 0 | 670 | fave[bt]++; | ||
| 0 | 671 | selector[nSelectors] = (char)bt; | ||
| 0 | 672 | nSelectors++; | ||
673 | | |||
674 | /*-- | |||
675 | Increment the symbol frequencies for the selected table. | |||
676 | --*/ | |||
| 0 | 677 | for (int i = gs; i <= ge; ++i) { | ||
| 0 | 678 | ++rfreq[bt][szptr[i]]; | ||
679 | } | |||
680 | | |||
| 0 | 681 | gs = ge + 1; | ||
682 | } | |||
683 | | |||
684 | /*-- | |||
685 | Recompute the tables based on the accumulated frequencies. | |||
686 | --*/ | |||
| 0 | 687 | for (int t = 0; t < nGroups; ++t) { | ||
| 0 | 688 | HbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20); | ||
689 | } | |||
690 | } | |||
691 | | |||
| 0 | 692 | rfreq = null; | ||
| 0 | 693 | fave = null; | ||
| 0 | 694 | cost = null; | ||
695 | | |||
| 0 | 696 | if (!(nGroups < 8)) { | ||
| 0 | 697 | Panic(); | ||
698 | } | |||
699 | | |||
| 0 | 700 | if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / BZip2Constants.GroupSize)))) { | ||
| 0 | 701 | Panic(); | ||
702 | } | |||
703 | | |||
704 | /*--- Compute MTF values for the selectors. ---*/ | |||
| 0 | 705 | char[] pos = new char[BZip2Constants.GroupCount]; | ||
706 | char ll_i, tmp2, tmp; | |||
707 | | |||
| 0 | 708 | for (int i = 0; i < nGroups; i++) { | ||
| 0 | 709 | pos[i] = (char)i; | ||
710 | } | |||
711 | | |||
| 0 | 712 | for (int i = 0; i < nSelectors; i++) { | ||
| 0 | 713 | ll_i = selector[i]; | ||
| 0 | 714 | int j = 0; | ||
| 0 | 715 | tmp = pos[j]; | ||
| 0 | 716 | while (ll_i != tmp) { | ||
| 0 | 717 | j++; | ||
| 0 | 718 | tmp2 = tmp; | ||
| 0 | 719 | tmp = pos[j]; | ||
| 0 | 720 | pos[j] = tmp2; | ||
721 | } | |||
| 0 | 722 | pos[0] = tmp; | ||
| 0 | 723 | selectorMtf[i] = (char)j; | ||
724 | } | |||
725 | | |||
| 0 | 726 | int[][] code = new int[BZip2Constants.GroupCount][]; | ||
727 | | |||
| 0 | 728 | for (int i = 0; i < BZip2Constants.GroupCount; ++i) { | ||
| 0 | 729 | code[i] = new int[BZip2Constants.MaximumAlphaSize]; | ||
730 | } | |||
731 | | |||
732 | /*--- Assign actual codes for the tables. --*/ | |||
| 0 | 733 | for (int t = 0; t < nGroups; t++) { | ||
| 0 | 734 | minLen = 32; | ||
| 0 | 735 | maxLen = 0; | ||
| 0 | 736 | for (int i = 0; i < alphaSize; i++) { | ||
| 0 | 737 | if (len[t][i] > maxLen) { | ||
| 0 | 738 | maxLen = len[t][i]; | ||
739 | } | |||
| 0 | 740 | if (len[t][i] < minLen) { | ||
| 0 | 741 | minLen = len[t][i]; | ||
742 | } | |||
743 | } | |||
| 0 | 744 | if (maxLen > 20) { | ||
| 0 | 745 | Panic(); | ||
746 | } | |||
| 0 | 747 | if (minLen < 1) { | ||
| 0 | 748 | Panic(); | ||
749 | } | |||
| 0 | 750 | HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize); | ||
751 | } | |||
752 | | |||
753 | /*--- Transmit the mapping table. ---*/ | |||
| 0 | 754 | bool[] inUse16 = new bool[16]; | ||
| 0 | 755 | for (int i = 0; i < 16; ++i) { | ||
| 0 | 756 | inUse16[i] = false; | ||
| 0 | 757 | for (int j = 0; j < 16; ++j) { | ||
| 0 | 758 | if (inUse[i * 16 + j]) { | ||
| 0 | 759 | inUse16[i] = true; | ||
760 | } | |||
761 | } | |||
762 | } | |||
763 | | |||
| 0 | 764 | for (int i = 0; i < 16; ++i) { | ||
| 0 | 765 | if (inUse16[i]) { | ||
| 0 | 766 | BsW(1, 1); | ||
| 0 | 767 | } else { | ||
| 0 | 768 | BsW(1, 0); | ||
769 | } | |||
770 | } | |||
771 | | |||
| 0 | 772 | for (int i = 0; i < 16; ++i) { | ||
| 0 | 773 | if (inUse16[i]) { | ||
| 0 | 774 | for (int j = 0; j < 16; ++j) { | ||
| 0 | 775 | if (inUse[i * 16 + j]) { | ||
| 0 | 776 | BsW(1, 1); | ||
| 0 | 777 | } else { | ||
| 0 | 778 | BsW(1, 0); | ||
779 | } | |||
780 | } | |||
781 | } | |||
782 | } | |||
783 | | |||
784 | /*--- Now the selectors. ---*/ | |||
| 0 | 785 | BsW(3, nGroups); | ||
| 0 | 786 | BsW(15, nSelectors); | ||
| 0 | 787 | for (int i = 0; i < nSelectors; ++i) { | ||
| 0 | 788 | for (int j = 0; j < selectorMtf[i]; ++j) { | ||
| 0 | 789 | BsW(1, 1); | ||
790 | } | |||
| 0 | 791 | BsW(1, 0); | ||
792 | } | |||
793 | | |||
794 | /*--- Now the coding tables. ---*/ | |||
| 0 | 795 | for (int t = 0; t < nGroups; ++t) { | ||
| 0 | 796 | int curr = len[t][0]; | ||
| 0 | 797 | BsW(5, curr); | ||
| 0 | 798 | for (int i = 0; i < alphaSize; ++i) { | ||
| 0 | 799 | while (curr < len[t][i]) { | ||
| 0 | 800 | BsW(2, 2); | ||
| 0 | 801 | curr++; /* 10 */ | ||
802 | } | |||
| 0 | 803 | while (curr > len[t][i]) { | ||
| 0 | 804 | BsW(2, 3); | ||
| 0 | 805 | curr--; /* 11 */ | ||
806 | } | |||
| 0 | 807 | BsW(1, 0); | ||
808 | } | |||
809 | } | |||
810 | | |||
811 | /*--- And finally, the block data proper ---*/ | |||
| 0 | 812 | selCtr = 0; | ||
| 0 | 813 | gs = 0; | ||
| 0 | 814 | while (true) { | ||
| 0 | 815 | if (gs >= nMTF) { | ||
816 | break; | |||
817 | } | |||
| 0 | 818 | ge = gs + BZip2Constants.GroupSize - 1; | ||
| 0 | 819 | if (ge >= nMTF) { | ||
| 0 | 820 | ge = nMTF - 1; | ||
821 | } | |||
822 | | |||
| 0 | 823 | for (int i = gs; i <= ge; i++) { | ||
| 0 | 824 | BsW(len[selector[selCtr]][szptr[i]], code[selector[selCtr]][szptr[i]]); | ||
825 | } | |||
826 | | |||
| 0 | 827 | gs = ge + 1; | ||
| 0 | 828 | ++selCtr; | ||
829 | } | |||
| 0 | 830 | if (!(selCtr == nSelectors)) { | ||
| 0 | 831 | Panic(); | ||
832 | } | |||
| 0 | 833 | } | ||
834 | | |||
835 | void MoveToFrontCodeAndSend() | |||
836 | { | |||
| 0 | 837 | BsPutIntVS(24, origPtr); | ||
| 0 | 838 | GenerateMTFValues(); | ||
| 0 | 839 | SendMTFValues(); | ||
| 0 | 840 | } | ||
841 | | |||
842 | void SimpleSort(int lo, int hi, int d) | |||
843 | { | |||
844 | int i, j, h, bigN, hp; | |||
845 | int v; | |||
846 | | |||
| 0 | 847 | bigN = hi - lo + 1; | ||
| 0 | 848 | if (bigN < 2) { | ||
| 0 | 849 | return; | ||
850 | } | |||
851 | | |||
| 0 | 852 | hp = 0; | ||
| 0 | 853 | while (increments[hp] < bigN) { | ||
| 0 | 854 | hp++; | ||
855 | } | |||
| 0 | 856 | hp--; | ||
857 | | |||
| 0 | 858 | for (; hp >= 0; hp--) { | ||
| 0 | 859 | h = increments[hp]; | ||
860 | | |||
| 0 | 861 | i = lo + h; | ||
862 | while (true) { | |||
863 | /*-- copy 1 --*/ | |||
| 0 | 864 | if (i > hi) | ||
865 | break; | |||
| 0 | 866 | v = zptr[i]; | ||
| 0 | 867 | j = i; | ||
| 0 | 868 | while (FullGtU(zptr[j - h] + d, v + d)) { | ||
| 0 | 869 | zptr[j] = zptr[j - h]; | ||
| 0 | 870 | j = j - h; | ||
| 0 | 871 | if (j <= (lo + h - 1)) | ||
872 | break; | |||
873 | } | |||
| 0 | 874 | zptr[j] = v; | ||
| 0 | 875 | i++; | ||
876 | | |||
877 | /*-- copy 2 --*/ | |||
| 0 | 878 | if (i > hi) { | ||
879 | break; | |||
880 | } | |||
| 0 | 881 | v = zptr[i]; | ||
| 0 | 882 | j = i; | ||
| 0 | 883 | while (FullGtU(zptr[j - h] + d, v + d)) { | ||
| 0 | 884 | zptr[j] = zptr[j - h]; | ||
| 0 | 885 | j = j - h; | ||
| 0 | 886 | if (j <= (lo + h - 1)) { | ||
887 | break; | |||
888 | } | |||
889 | } | |||
| 0 | 890 | zptr[j] = v; | ||
| 0 | 891 | i++; | ||
892 | | |||
893 | /*-- copy 3 --*/ | |||
| 0 | 894 | if (i > hi) { | ||
895 | break; | |||
896 | } | |||
| 0 | 897 | v = zptr[i]; | ||
| 0 | 898 | j = i; | ||
| 0 | 899 | while (FullGtU(zptr[j - h] + d, v + d)) { | ||
| 0 | 900 | zptr[j] = zptr[j - h]; | ||
| 0 | 901 | j = j - h; | ||
| 0 | 902 | if (j <= (lo + h - 1)) { | ||
903 | break; | |||
904 | } | |||
905 | } | |||
| 0 | 906 | zptr[j] = v; | ||
| 0 | 907 | i++; | ||
908 | | |||
| 0 | 909 | if (workDone > workLimit && firstAttempt) { | ||
| 0 | 910 | return; | ||
911 | } | |||
912 | } | |||
913 | } | |||
| 0 | 914 | } | ||
915 | | |||
916 | void Vswap(int p1, int p2, int n) | |||
917 | { | |||
| 0 | 918 | int temp = 0; | ||
| 0 | 919 | while (n > 0) { | ||
| 0 | 920 | temp = zptr[p1]; | ||
| 0 | 921 | zptr[p1] = zptr[p2]; | ||
| 0 | 922 | zptr[p2] = temp; | ||
| 0 | 923 | p1++; | ||
| 0 | 924 | p2++; | ||
| 0 | 925 | n--; | ||
926 | } | |||
| 0 | 927 | } | ||
928 | | |||
929 | void QSort3(int loSt, int hiSt, int dSt) | |||
930 | { | |||
931 | int unLo, unHi, ltLo, gtHi, med, n, m; | |||
932 | int lo, hi, d; | |||
933 | | |||
| 0 | 934 | StackElement[] stack = new StackElement[QSORT_STACK_SIZE]; | ||
935 | | |||
| 0 | 936 | int sp = 0; | ||
937 | | |||
| 0 | 938 | stack[sp].ll = loSt; | ||
| 0 | 939 | stack[sp].hh = hiSt; | ||
| 0 | 940 | stack[sp].dd = dSt; | ||
| 0 | 941 | sp++; | ||
942 | | |||
| 0 | 943 | while (sp > 0) { | ||
| 0 | 944 | if (sp >= QSORT_STACK_SIZE) { | ||
| 0 | 945 | Panic(); | ||
946 | } | |||
947 | | |||
| 0 | 948 | sp--; | ||
| 0 | 949 | lo = stack[sp].ll; | ||
| 0 | 950 | hi = stack[sp].hh; | ||
| 0 | 951 | d = stack[sp].dd; | ||
952 | | |||
| 0 | 953 | if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) { | ||
| 0 | 954 | SimpleSort(lo, hi, d); | ||
| 0 | 955 | if (workDone > workLimit && firstAttempt) { | ||
| 0 | 956 | return; | ||
957 | } | |||
958 | continue; | |||
959 | } | |||
960 | | |||
| 0 | 961 | med = Med3(block[zptr[lo] + d + 1], | ||
| 0 | 962 | block[zptr[hi] + d + 1], | ||
| 0 | 963 | block[zptr[(lo + hi) >> 1] + d + 1]); | ||
964 | | |||
| 0 | 965 | unLo = ltLo = lo; | ||
| 0 | 966 | unHi = gtHi = hi; | ||
967 | | |||
| 0 | 968 | while (true) { | ||
| 0 | 969 | while (true) { | ||
| 0 | 970 | if (unLo > unHi) { | ||
971 | break; | |||
972 | } | |||
| 0 | 973 | n = ((int)block[zptr[unLo] + d + 1]) - med; | ||
| 0 | 974 | if (n == 0) { | ||
| 0 | 975 | int temp = zptr[unLo]; | ||
| 0 | 976 | zptr[unLo] = zptr[ltLo]; | ||
| 0 | 977 | zptr[ltLo] = temp; | ||
| 0 | 978 | ltLo++; | ||
| 0 | 979 | unLo++; | ||
| 0 | 980 | continue; | ||
981 | } | |||
| 0 | 982 | if (n > 0) { | ||
983 | break; | |||
984 | } | |||
| 0 | 985 | unLo++; | ||
986 | } | |||
987 | | |||
| 0 | 988 | while (true) { | ||
| 0 | 989 | if (unLo > unHi) { | ||
990 | break; | |||
991 | } | |||
| 0 | 992 | n = ((int)block[zptr[unHi] + d + 1]) - med; | ||
| 0 | 993 | if (n == 0) { | ||
| 0 | 994 | int temp = zptr[unHi]; | ||
| 0 | 995 | zptr[unHi] = zptr[gtHi]; | ||
| 0 | 996 | zptr[gtHi] = temp; | ||
| 0 | 997 | gtHi--; | ||
| 0 | 998 | unHi--; | ||
| 0 | 999 | continue; | ||
1000 | } | |||
| 0 | 1001 | if (n < 0) { | ||
1002 | break; | |||
1003 | } | |||
| 0 | 1004 | unHi--; | ||
1005 | } | |||
1006 | | |||
| 0 | 1007 | if (unLo > unHi) { | ||
1008 | break; | |||
1009 | } | |||
1010 | | |||
1011 | { | |||
| 0 | 1012 | int temp = zptr[unLo]; | ||
| 0 | 1013 | zptr[unLo] = zptr[unHi]; | ||
| 0 | 1014 | zptr[unHi] = temp; | ||
| 0 | 1015 | unLo++; | ||
| 0 | 1016 | unHi--; | ||
1017 | } | |||
1018 | } | |||
1019 | | |||
| 0 | 1020 | if (gtHi < ltLo) { | ||
| 0 | 1021 | stack[sp].ll = lo; | ||
| 0 | 1022 | stack[sp].hh = hi; | ||
| 0 | 1023 | stack[sp].dd = d + 1; | ||
| 0 | 1024 | sp++; | ||
| 0 | 1025 | continue; | ||
1026 | } | |||
1027 | | |||
| 0 | 1028 | n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo); | ||
| 0 | 1029 | Vswap(lo, unLo - n, n); | ||
| 0 | 1030 | m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi); | ||
| 0 | 1031 | Vswap(unLo, hi - m + 1, m); | ||
1032 | | |||
| 0 | 1033 | n = lo + unLo - ltLo - 1; | ||
| 0 | 1034 | m = hi - (gtHi - unHi) + 1; | ||
1035 | | |||
| 0 | 1036 | stack[sp].ll = lo; | ||
| 0 | 1037 | stack[sp].hh = n; | ||
| 0 | 1038 | stack[sp].dd = d; | ||
| 0 | 1039 | sp++; | ||
1040 | | |||
| 0 | 1041 | stack[sp].ll = n + 1; | ||
| 0 | 1042 | stack[sp].hh = m - 1; | ||
| 0 | 1043 | stack[sp].dd = d + 1; | ||
| 0 | 1044 | sp++; | ||
1045 | | |||
| 0 | 1046 | stack[sp].ll = m; | ||
| 0 | 1047 | stack[sp].hh = hi; | ||
| 0 | 1048 | stack[sp].dd = d; | ||
| 0 | 1049 | sp++; | ||
1050 | } | |||
| 0 | 1051 | } | ||
1052 | | |||
1053 | void MainSort() | |||
1054 | { | |||
1055 | int i, j, ss, sb; | |||
| 0 | 1056 | int[] runningOrder = new int[256]; | ||
| 0 | 1057 | int[] copy = new int[256]; | ||
| 0 | 1058 | bool[] bigDone = new bool[256]; | ||
1059 | int c1, c2; | |||
1060 | int numQSorted; | |||
1061 | | |||
1062 | /*-- | |||
1063 | In the various block-sized structures, live data runs | |||
1064 | from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, | |||
1065 | set up the overshoot area for block. | |||
1066 | --*/ | |||
1067 | | |||
1068 | // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); | |||
| 0 | 1069 | for (i = 0; i < BZip2Constants.OvershootBytes; i++) { | ||
| 0 | 1070 | block[last + i + 2] = block[(i % (last + 1)) + 1]; | ||
1071 | } | |||
| 0 | 1072 | for (i = 0; i <= last + BZip2Constants.OvershootBytes; i++) { | ||
| 0 | 1073 | quadrant[i] = 0; | ||
1074 | } | |||
1075 | | |||
| 0 | 1076 | block[0] = (byte)(block[last + 1]); | ||
1077 | | |||
| 0 | 1078 | if (last < 4000) { | ||
1079 | /*-- | |||
1080 | Use simpleSort(), since the full sorting mechanism | |||
1081 | has quite a large constant overhead. | |||
1082 | --*/ | |||
| 0 | 1083 | for (i = 0; i <= last; i++) { | ||
| 0 | 1084 | zptr[i] = i; | ||
1085 | } | |||
| 0 | 1086 | firstAttempt = false; | ||
| 0 | 1087 | workDone = workLimit = 0; | ||
| 0 | 1088 | SimpleSort(0, last, 0); | ||
| 0 | 1089 | } else { | ||
| 0 | 1090 | numQSorted = 0; | ||
| 0 | 1091 | for (i = 0; i <= 255; i++) { | ||
| 0 | 1092 | bigDone[i] = false; | ||
1093 | } | |||
| 0 | 1094 | for (i = 0; i <= 65536; i++) { | ||
| 0 | 1095 | ftab[i] = 0; | ||
1096 | } | |||
1097 | | |||
| 0 | 1098 | c1 = block[0]; | ||
| 0 | 1099 | for (i = 0; i <= last; i++) { | ||
| 0 | 1100 | c2 = block[i + 1]; | ||
| 0 | 1101 | ftab[(c1 << 8) + c2]++; | ||
| 0 | 1102 | c1 = c2; | ||
1103 | } | |||
1104 | | |||
| 0 | 1105 | for (i = 1; i <= 65536; i++) { | ||
| 0 | 1106 | ftab[i] += ftab[i - 1]; | ||
1107 | } | |||
1108 | | |||
| 0 | 1109 | c1 = block[1]; | ||
| 0 | 1110 | for (i = 0; i < last; i++) { | ||
| 0 | 1111 | c2 = block[i + 2]; | ||
| 0 | 1112 | j = (c1 << 8) + c2; | ||
| 0 | 1113 | c1 = c2; | ||
| 0 | 1114 | ftab[j]--; | ||
| 0 | 1115 | zptr[ftab[j]] = i; | ||
1116 | } | |||
1117 | | |||
| 0 | 1118 | j = ((block[last + 1]) << 8) + (block[1]); | ||
| 0 | 1119 | ftab[j]--; | ||
| 0 | 1120 | zptr[ftab[j]] = last; | ||
1121 | | |||
1122 | /*-- | |||
1123 | Now ftab contains the first loc of every small bucket. | |||
1124 | Calculate the running order, from smallest to largest | |||
1125 | big bucket. | |||
1126 | --*/ | |||
1127 | | |||
| 0 | 1128 | for (i = 0; i <= 255; i++) { | ||
| 0 | 1129 | runningOrder[i] = i; | ||
1130 | } | |||
1131 | | |||
1132 | int vv; | |||
| 0 | 1133 | int h = 1; | ||
1134 | do { | |||
| 0 | 1135 | h = 3 * h + 1; | ||
| 0 | 1136 | } while (h <= 256); | ||
1137 | do { | |||
| 0 | 1138 | h = h / 3; | ||
| 0 | 1139 | for (i = h; i <= 255; i++) { | ||
| 0 | 1140 | vv = runningOrder[i]; | ||
| 0 | 1141 | j = i; | ||
| 0 | 1142 | while ((ftab[((runningOrder[j - h]) + 1) << 8] - ftab[(runningOrder[j - h]) << 8]) > (ftab[((vv) + 1) << 8] | ||
| 0 | 1143 | runningOrder[j] = runningOrder[j - h]; | ||
| 0 | 1144 | j = j - h; | ||
| 0 | 1145 | if (j <= (h - 1)) { | ||
1146 | break; | |||
1147 | } | |||
1148 | } | |||
| 0 | 1149 | runningOrder[j] = vv; | ||
1150 | } | |||
| 0 | 1151 | } while (h != 1); | ||
1152 | | |||
1153 | /*-- | |||
1154 | The main sorting loop. | |||
1155 | --*/ | |||
| 0 | 1156 | for (i = 0; i <= 255; i++) { | ||
1157 | | |||
1158 | /*-- | |||
1159 | Process big buckets, starting with the least full. | |||
1160 | --*/ | |||
| 0 | 1161 | ss = runningOrder[i]; | ||
1162 | | |||
1163 | /*-- | |||
1164 | Complete the big bucket [ss] by quicksorting | |||
1165 | any unsorted small buckets [ss, j]. Hopefully | |||
1166 | previous pointer-scanning phases have already | |||
1167 | completed many of the small buckets [ss, j], so | |||
1168 | we don't have to sort them at all. | |||
1169 | --*/ | |||
| 0 | 1170 | for (j = 0; j <= 255; j++) { | ||
| 0 | 1171 | sb = (ss << 8) + j; | ||
| 0 | 1172 | if (!((ftab[sb] & SETMASK) == SETMASK)) { | ||
| 0 | 1173 | int lo = ftab[sb] & CLEARMASK; | ||
| 0 | 1174 | int hi = (ftab[sb + 1] & CLEARMASK) - 1; | ||
| 0 | 1175 | if (hi > lo) { | ||
| 0 | 1176 | QSort3(lo, hi, 2); | ||
| 0 | 1177 | numQSorted += (hi - lo + 1); | ||
| 0 | 1178 | if (workDone > workLimit && firstAttempt) { | ||
| 0 | 1179 | return; | ||
1180 | } | |||
1181 | } | |||
| 0 | 1182 | ftab[sb] |= SETMASK; | ||
1183 | } | |||
1184 | } | |||
1185 | | |||
1186 | /*-- | |||
1187 | The ss big bucket is now done. Record this fact, | |||
1188 | and update the quadrant descriptors. Remember to | |||
1189 | update quadrants in the overshoot area too, if | |||
1190 | necessary. The "if (i < 255)" test merely skips | |||
1191 | this updating for the last bucket processed, since | |||
1192 | updating for the last bucket is pointless. | |||
1193 | --*/ | |||
| 0 | 1194 | bigDone[ss] = true; | ||
1195 | | |||
| 0 | 1196 | if (i < 255) { | ||
| 0 | 1197 | int bbStart = ftab[ss << 8] & CLEARMASK; | ||
| 0 | 1198 | int bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart; | ||
| 0 | 1199 | int shifts = 0; | ||
1200 | | |||
| 0 | 1201 | while ((bbSize >> shifts) > 65534) { | ||
| 0 | 1202 | shifts++; | ||
1203 | } | |||
1204 | | |||
| 0 | 1205 | for (j = 0; j < bbSize; j++) { | ||
| 0 | 1206 | int a2update = zptr[bbStart + j]; | ||
| 0 | 1207 | int qVal = (j >> shifts); | ||
| 0 | 1208 | quadrant[a2update] = qVal; | ||
| 0 | 1209 | if (a2update < BZip2Constants.OvershootBytes) { | ||
| 0 | 1210 | quadrant[a2update + last + 1] = qVal; | ||
1211 | } | |||
1212 | } | |||
1213 | | |||
| 0 | 1214 | if (!(((bbSize - 1) >> shifts) <= 65535)) { | ||
| 0 | 1215 | Panic(); | ||
1216 | } | |||
1217 | } | |||
1218 | | |||
1219 | /*-- | |||
1220 | Now scan this big bucket so as to synthesise the | |||
1221 | sorted order for small buckets [t, ss] for all t != ss. | |||
1222 | --*/ | |||
| 0 | 1223 | for (j = 0; j <= 255; j++) { | ||
| 0 | 1224 | copy[j] = ftab[(j << 8) + ss] & CLEARMASK; | ||
1225 | } | |||
1226 | | |||
| 0 | 1227 | for (j = ftab[ss << 8] & CLEARMASK; j < (ftab[(ss + 1) << 8] & CLEARMASK); j++) { | ||
| 0 | 1228 | c1 = block[zptr[j]]; | ||
| 0 | 1229 | if (!bigDone[c1]) { | ||
| 0 | 1230 | zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; | ||
| 0 | 1231 | copy[c1]++; | ||
1232 | } | |||
1233 | } | |||
1234 | | |||
| 0 | 1235 | for (j = 0; j <= 255; j++) { | ||
| 0 | 1236 | ftab[(j << 8) + ss] |= SETMASK; | ||
1237 | } | |||
1238 | } | |||
1239 | } | |||
| 0 | 1240 | } | ||
1241 | | |||
1242 | void RandomiseBlock() | |||
1243 | { | |||
1244 | int i; | |||
| 0 | 1245 | int rNToGo = 0; | ||
| 0 | 1246 | int rTPos = 0; | ||
| 0 | 1247 | for (i = 0; i < 256; i++) { | ||
| 0 | 1248 | inUse[i] = false; | ||
1249 | } | |||
1250 | | |||
| 0 | 1251 | for (i = 0; i <= last; i++) { | ||
| 0 | 1252 | if (rNToGo == 0) { | ||
| 0 | 1253 | rNToGo = (int)BZip2Constants.RandomNumbers[rTPos]; | ||
| 0 | 1254 | rTPos++; | ||
| 0 | 1255 | if (rTPos == 512) { | ||
| 0 | 1256 | rTPos = 0; | ||
1257 | } | |||
1258 | } | |||
| 0 | 1259 | rNToGo--; | ||
| 0 | 1260 | block[i + 1] ^= (byte)((rNToGo == 1) ? 1 : 0); | ||
1261 | // handle 16 bit signed numbers | |||
| 0 | 1262 | block[i + 1] &= 0xFF; | ||
1263 | | |||
| 0 | 1264 | inUse[block[i + 1]] = true; | ||
1265 | } | |||
| 0 | 1266 | } | ||
1267 | | |||
1268 | void DoReversibleTransformation() | |||
1269 | { | |||
| 0 | 1270 | workLimit = workFactor * last; | ||
| 0 | 1271 | workDone = 0; | ||
| 0 | 1272 | blockRandomised = false; | ||
| 0 | 1273 | firstAttempt = true; | ||
1274 | | |||
| 0 | 1275 | MainSort(); | ||
1276 | | |||
| 0 | 1277 | if (workDone > workLimit && firstAttempt) { | ||
| 0 | 1278 | RandomiseBlock(); | ||
| 0 | 1279 | workLimit = workDone = 0; | ||
| 0 | 1280 | blockRandomised = true; | ||
| 0 | 1281 | firstAttempt = false; | ||
| 0 | 1282 | MainSort(); | ||
1283 | } | |||
1284 | | |||
| 0 | 1285 | origPtr = -1; | ||
| 0 | 1286 | for (int i = 0; i <= last; i++) { | ||
| 0 | 1287 | if (zptr[i] == 0) { | ||
| 0 | 1288 | origPtr = i; | ||
| 0 | 1289 | break; | ||
1290 | } | |||
1291 | } | |||
1292 | | |||
| 0 | 1293 | if (origPtr == -1) { | ||
| 0 | 1294 | Panic(); | ||
1295 | } | |||
| 0 | 1296 | } | ||
1297 | | |||
1298 | bool FullGtU(int i1, int i2) | |||
1299 | { | |||
1300 | int k; | |||
1301 | byte c1, c2; | |||
1302 | int s1, s2; | |||
1303 | | |||
| 0 | 1304 | c1 = block[i1 + 1]; | ||
| 0 | 1305 | c2 = block[i2 + 1]; | ||
| 0 | 1306 | if (c1 != c2) { | ||
| 0 | 1307 | return c1 > c2; | ||
1308 | } | |||
| 0 | 1309 | i1++; | ||
| 0 | 1310 | i2++; | ||
1311 | | |||
| 0 | 1312 | c1 = block[i1 + 1]; | ||
| 0 | 1313 | c2 = block[i2 + 1]; | ||
| 0 | 1314 | if (c1 != c2) { | ||
| 0 | 1315 | return c1 > c2; | ||
1316 | } | |||
| 0 | 1317 | i1++; | ||
| 0 | 1318 | i2++; | ||
1319 | | |||
| 0 | 1320 | c1 = block[i1 + 1]; | ||
| 0 | 1321 | c2 = block[i2 + 1]; | ||
| 0 | 1322 | if (c1 != c2) { | ||
| 0 | 1323 | return c1 > c2; | ||
1324 | } | |||
| 0 | 1325 | i1++; | ||
| 0 | 1326 | i2++; | ||
1327 | | |||
| 0 | 1328 | c1 = block[i1 + 1]; | ||
| 0 | 1329 | c2 = block[i2 + 1]; | ||
| 0 | 1330 | if (c1 != c2) { | ||
| 0 | 1331 | return c1 > c2; | ||
1332 | } | |||
| 0 | 1333 | i1++; | ||
| 0 | 1334 | i2++; | ||
1335 | | |||
| 0 | 1336 | c1 = block[i1 + 1]; | ||
| 0 | 1337 | c2 = block[i2 + 1]; | ||
| 0 | 1338 | if (c1 != c2) { | ||
| 0 | 1339 | return c1 > c2; | ||
1340 | } | |||
| 0 | 1341 | i1++; | ||
| 0 | 1342 | i2++; | ||
1343 | | |||
| 0 | 1344 | c1 = block[i1 + 1]; | ||
| 0 | 1345 | c2 = block[i2 + 1]; | ||
| 0 | 1346 | if (c1 != c2) { | ||
| 0 | 1347 | return c1 > c2; | ||
1348 | } | |||
| 0 | 1349 | i1++; | ||
| 0 | 1350 | i2++; | ||
1351 | | |||
| 0 | 1352 | k = last + 1; | ||
1353 | | |||
1354 | do { | |||
| 0 | 1355 | c1 = block[i1 + 1]; | ||
| 0 | 1356 | c2 = block[i2 + 1]; | ||
| 0 | 1357 | if (c1 != c2) { | ||
| 0 | 1358 | return c1 > c2; | ||
1359 | } | |||
| 0 | 1360 | s1 = quadrant[i1]; | ||
| 0 | 1361 | s2 = quadrant[i2]; | ||
| 0 | 1362 | if (s1 != s2) { | ||
| 0 | 1363 | return s1 > s2; | ||
1364 | } | |||
| 0 | 1365 | i1++; | ||
| 0 | 1366 | i2++; | ||
1367 | | |||
| 0 | 1368 | c1 = block[i1 + 1]; | ||
| 0 | 1369 | c2 = block[i2 + 1]; | ||
| 0 | 1370 | if (c1 != c2) { | ||
| 0 | 1371 | return c1 > c2; | ||
1372 | } | |||
| 0 | 1373 | s1 = quadrant[i1]; | ||
| 0 | 1374 | s2 = quadrant[i2]; | ||
| 0 | 1375 | if (s1 != s2) { | ||
| 0 | 1376 | return s1 > s2; | ||
1377 | } | |||
| 0 | 1378 | i1++; | ||
| 0 | 1379 | i2++; | ||
1380 | | |||
| 0 | 1381 | c1 = block[i1 + 1]; | ||
| 0 | 1382 | c2 = block[i2 + 1]; | ||
| 0 | 1383 | if (c1 != c2) { | ||
| 0 | 1384 | return c1 > c2; | ||
1385 | } | |||
| 0 | 1386 | s1 = quadrant[i1]; | ||
| 0 | 1387 | s2 = quadrant[i2]; | ||
| 0 | 1388 | if (s1 != s2) { | ||
| 0 | 1389 | return s1 > s2; | ||
1390 | } | |||
| 0 | 1391 | i1++; | ||
| 0 | 1392 | i2++; | ||
1393 | | |||
| 0 | 1394 | c1 = block[i1 + 1]; | ||
| 0 | 1395 | c2 = block[i2 + 1]; | ||
| 0 | 1396 | if (c1 != c2) { | ||
| 0 | 1397 | return c1 > c2; | ||
1398 | } | |||
| 0 | 1399 | s1 = quadrant[i1]; | ||
| 0 | 1400 | s2 = quadrant[i2]; | ||
| 0 | 1401 | if (s1 != s2) { | ||
| 0 | 1402 | return s1 > s2; | ||
1403 | } | |||
| 0 | 1404 | i1++; | ||
| 0 | 1405 | i2++; | ||
1406 | | |||
| 0 | 1407 | if (i1 > last) { | ||
| 0 | 1408 | i1 -= last; | ||
| 0 | 1409 | i1--; | ||
1410 | } | |||
| 0 | 1411 | if (i2 > last) { | ||
| 0 | 1412 | i2 -= last; | ||
| 0 | 1413 | i2--; | ||
1414 | } | |||
1415 | | |||
| 0 | 1416 | k -= 4; | ||
| 0 | 1417 | ++workDone; | ||
| 0 | 1418 | } while (k >= 0); | ||
1419 | | |||
| 0 | 1420 | return false; | ||
1421 | } | |||
1422 | | |||
1423 | void AllocateCompressStructures() | |||
1424 | { | |||
| 1 | 1425 | int n = BZip2Constants.BaseBlockSize * blockSize100k; | ||
| 1 | 1426 | block = new byte[(n + 1 + BZip2Constants.OvershootBytes)]; | ||
| 1 | 1427 | quadrant = new int[(n + BZip2Constants.OvershootBytes)]; | ||
| 1 | 1428 | zptr = new int[n]; | ||
| 1 | 1429 | ftab = new int[65537]; | ||
1430 | | |||
| 1 | 1431 | if (block == null || quadrant == null || zptr == null || ftab == null) { | ||
1432 | // int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; | |||
1433 | // compressOutOfMemory ( totalDraw, n ); | |||
1434 | } | |||
1435 | | |||
1436 | /* | |||
1437 | The back end needs a place to store the MTF values | |||
1438 | whilst it calculates the coding tables. We could | |||
1439 | put them in the zptr array. However, these values | |||
1440 | will fit in a short, so we overlay szptr at the | |||
1441 | start of zptr, in the hope of reducing the number | |||
1442 | of cache misses induced by the multiple traversals | |||
1443 | of the MTF values when calculating coding tables. | |||
1444 | Seems to improve compression speed by about 1%. | |||
1445 | */ | |||
1446 | // szptr = zptr; | |||
1447 | | |||
1448 | | |||
| 1 | 1449 | szptr = new short[2 * n]; | ||
| 1 | 1450 | } | ||
1451 | | |||
1452 | void GenerateMTFValues() | |||
1453 | { | |||
| 0 | 1454 | char[] yy = new char[256]; | ||
1455 | int i, j; | |||
1456 | char tmp; | |||
1457 | char tmp2; | |||
1458 | int zPend; | |||
1459 | int wr; | |||
1460 | int EOB; | |||
1461 | | |||
| 0 | 1462 | MakeMaps(); | ||
| 0 | 1463 | EOB = nInUse + 1; | ||
1464 | | |||
| 0 | 1465 | for (i = 0; i <= EOB; i++) { | ||
| 0 | 1466 | mtfFreq[i] = 0; | ||
1467 | } | |||
1468 | | |||
| 0 | 1469 | wr = 0; | ||
| 0 | 1470 | zPend = 0; | ||
| 0 | 1471 | for (i = 0; i < nInUse; i++) { | ||
| 0 | 1472 | yy[i] = (char)i; | ||
1473 | } | |||
1474 | | |||
1475 | | |||
| 0 | 1476 | for (i = 0; i <= last; i++) { | ||
1477 | char ll_i; | |||
1478 | | |||
| 0 | 1479 | ll_i = unseqToSeq[block[zptr[i]]]; | ||
1480 | | |||
| 0 | 1481 | j = 0; | ||
| 0 | 1482 | tmp = yy[j]; | ||
| 0 | 1483 | while (ll_i != tmp) { | ||
| 0 | 1484 | j++; | ||
| 0 | 1485 | tmp2 = tmp; | ||
| 0 | 1486 | tmp = yy[j]; | ||
| 0 | 1487 | yy[j] = tmp2; | ||
1488 | } | |||
| 0 | 1489 | yy[0] = tmp; | ||
1490 | | |||
| 0 | 1491 | if (j == 0) { | ||
| 0 | 1492 | zPend++; | ||
| 0 | 1493 | } else { | ||
| 0 | 1494 | if (zPend > 0) { | ||
| 0 | 1495 | zPend--; | ||
| 0 | 1496 | while (true) { | ||
| 0 | 1497 | switch (zPend % 2) { | ||
1498 | case 0: | |||
| 0 | 1499 | szptr[wr] = (short)BZip2Constants.RunA; | ||
| 0 | 1500 | wr++; | ||
| 0 | 1501 | mtfFreq[BZip2Constants.RunA]++; | ||
| 0 | 1502 | break; | ||
1503 | case 1: | |||
| 0 | 1504 | szptr[wr] = (short)BZip2Constants.RunB; | ||
| 0 | 1505 | wr++; | ||
| 0 | 1506 | mtfFreq[BZip2Constants.RunB]++; | ||
1507 | break; | |||
1508 | } | |||
| 0 | 1509 | if (zPend < 2) { | ||
1510 | break; | |||
1511 | } | |||
| 0 | 1512 | zPend = (zPend - 2) / 2; | ||
1513 | } | |||
| 0 | 1514 | zPend = 0; | ||
1515 | } | |||
| 0 | 1516 | szptr[wr] = (short)(j + 1); | ||
| 0 | 1517 | wr++; | ||
| 0 | 1518 | mtfFreq[j + 1]++; | ||
1519 | } | |||
1520 | } | |||
1521 | | |||
| 0 | 1522 | if (zPend > 0) { | ||
| 0 | 1523 | zPend--; | ||
| 0 | 1524 | while (true) { | ||
| 0 | 1525 | switch (zPend % 2) { | ||
1526 | case 0: | |||
| 0 | 1527 | szptr[wr] = (short)BZip2Constants.RunA; | ||
| 0 | 1528 | wr++; | ||
| 0 | 1529 | mtfFreq[BZip2Constants.RunA]++; | ||
| 0 | 1530 | break; | ||
1531 | case 1: | |||
| 0 | 1532 | szptr[wr] = (short)BZip2Constants.RunB; | ||
| 0 | 1533 | wr++; | ||
| 0 | 1534 | mtfFreq[BZip2Constants.RunB]++; | ||
1535 | break; | |||
1536 | } | |||
| 0 | 1537 | if (zPend < 2) { | ||
1538 | break; | |||
1539 | } | |||
| 0 | 1540 | zPend = (zPend - 2) / 2; | ||
1541 | } | |||
1542 | } | |||
1543 | | |||
| 0 | 1544 | szptr[wr] = (short)EOB; | ||
| 0 | 1545 | wr++; | ||
| 0 | 1546 | mtfFreq[EOB]++; | ||
1547 | | |||
| 0 | 1548 | nMTF = wr; | ||
| 0 | 1549 | } | ||
1550 | | |||
1551 | static void Panic() | |||
1552 | { | |||
| 0 | 1553 | throw new BZip2Exception("BZip2 output stream panic"); | ||
1554 | } | |||
1555 | | |||
1556 | static void HbMakeCodeLengths(char[] len, int[] freq, int alphaSize, int maxLen) | |||
1557 | { | |||
1558 | /*-- | |||
1559 | Nodes and heap entries run from 1. Entry 0 | |||
1560 | for both the heap and nodes is a sentinel. | |||
1561 | --*/ | |||
1562 | int nNodes, nHeap, n1, n2, j, k; | |||
1563 | bool tooLong; | |||
1564 | | |||
| 0 | 1565 | int[] heap = new int[BZip2Constants.MaximumAlphaSize + 2]; | ||
| 0 | 1566 | int[] weight = new int[BZip2Constants.MaximumAlphaSize * 2]; | ||
| 0 | 1567 | int[] parent = new int[BZip2Constants.MaximumAlphaSize * 2]; | ||
1568 | | |||
| 0 | 1569 | for (int i = 0; i < alphaSize; ++i) { | ||
| 0 | 1570 | weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8; | ||
1571 | } | |||
1572 | | |||
| 0 | 1573 | while (true) { | ||
| 0 | 1574 | nNodes = alphaSize; | ||
| 0 | 1575 | nHeap = 0; | ||
1576 | | |||
| 0 | 1577 | heap[0] = 0; | ||
| 0 | 1578 | weight[0] = 0; | ||
| 0 | 1579 | parent[0] = -2; | ||
1580 | | |||
| 0 | 1581 | for (int i = 1; i <= alphaSize; ++i) { | ||
| 0 | 1582 | parent[i] = -1; | ||
| 0 | 1583 | nHeap++; | ||
| 0 | 1584 | heap[nHeap] = i; | ||
| 0 | 1585 | int zz = nHeap; | ||
| 0 | 1586 | int tmp = heap[zz]; | ||
| 0 | 1587 | while (weight[tmp] < weight[heap[zz >> 1]]) { | ||
| 0 | 1588 | heap[zz] = heap[zz >> 1]; | ||
| 0 | 1589 | zz >>= 1; | ||
1590 | } | |||
| 0 | 1591 | heap[zz] = tmp; | ||
1592 | } | |||
| 0 | 1593 | if (!(nHeap < (BZip2Constants.MaximumAlphaSize + 2))) { | ||
| 0 | 1594 | Panic(); | ||
1595 | } | |||
1596 | | |||
| 0 | 1597 | while (nHeap > 1) { | ||
| 0 | 1598 | n1 = heap[1]; | ||
| 0 | 1599 | heap[1] = heap[nHeap]; | ||
| 0 | 1600 | nHeap--; | ||
| 0 | 1601 | int zz = 1; | ||
| 0 | 1602 | int yy = 0; | ||
| 0 | 1603 | int tmp = heap[zz]; | ||
| 0 | 1604 | while (true) { | ||
| 0 | 1605 | yy = zz << 1; | ||
| 0 | 1606 | if (yy > nHeap) { | ||
1607 | break; | |||
1608 | } | |||
| 0 | 1609 | if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) { | ||
| 0 | 1610 | yy++; | ||
1611 | } | |||
| 0 | 1612 | if (weight[tmp] < weight[heap[yy]]) { | ||
1613 | break; | |||
1614 | } | |||
1615 | | |||
| 0 | 1616 | heap[zz] = heap[yy]; | ||
| 0 | 1617 | zz = yy; | ||
1618 | } | |||
| 0 | 1619 | heap[zz] = tmp; | ||
| 0 | 1620 | n2 = heap[1]; | ||
| 0 | 1621 | heap[1] = heap[nHeap]; | ||
| 0 | 1622 | nHeap--; | ||
1623 | | |||
| 0 | 1624 | zz = 1; | ||
| 0 | 1625 | yy = 0; | ||
| 0 | 1626 | tmp = heap[zz]; | ||
| 0 | 1627 | while (true) { | ||
| 0 | 1628 | yy = zz << 1; | ||
| 0 | 1629 | if (yy > nHeap) { | ||
1630 | break; | |||
1631 | } | |||
| 0 | 1632 | if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) { | ||
| 0 | 1633 | yy++; | ||
1634 | } | |||
| 0 | 1635 | if (weight[tmp] < weight[heap[yy]]) { | ||
1636 | break; | |||
1637 | } | |||
| 0 | 1638 | heap[zz] = heap[yy]; | ||
| 0 | 1639 | zz = yy; | ||
1640 | } | |||
| 0 | 1641 | heap[zz] = tmp; | ||
| 0 | 1642 | nNodes++; | ||
| 0 | 1643 | parent[n1] = parent[n2] = nNodes; | ||
1644 | | |||
| 0 | 1645 | weight[nNodes] = (int)((weight[n1] & 0xffffff00) + (weight[n2] & 0xffffff00)) | | ||
| 0 | 1646 | (int)(1 + (((weight[n1] & 0x000000ff) > (weight[n2] & 0x000000ff)) ? (weight[n1] & 0x000000ff) : (weight[n2] | ||
1647 | | |||
| 0 | 1648 | parent[nNodes] = -1; | ||
| 0 | 1649 | nHeap++; | ||
| 0 | 1650 | heap[nHeap] = nNodes; | ||
1651 | | |||
| 0 | 1652 | zz = nHeap; | ||
| 0 | 1653 | tmp = heap[zz]; | ||
| 0 | 1654 | while (weight[tmp] < weight[heap[zz >> 1]]) { | ||
| 0 | 1655 | heap[zz] = heap[zz >> 1]; | ||
| 0 | 1656 | zz >>= 1; | ||
1657 | } | |||
| 0 | 1658 | heap[zz] = tmp; | ||
1659 | } | |||
| 0 | 1660 | if (!(nNodes < (BZip2Constants.MaximumAlphaSize * 2))) { | ||
| 0 | 1661 | Panic(); | ||
1662 | } | |||
1663 | | |||
| 0 | 1664 | tooLong = false; | ||
| 0 | 1665 | for (int i = 1; i <= alphaSize; ++i) { | ||
| 0 | 1666 | j = 0; | ||
| 0 | 1667 | k = i; | ||
| 0 | 1668 | while (parent[k] >= 0) { | ||
| 0 | 1669 | k = parent[k]; | ||
| 0 | 1670 | j++; | ||
1671 | } | |||
| 0 | 1672 | len[i - 1] = (char)j; | ||
| 0 | 1673 | tooLong |= j > maxLen; | ||
1674 | } | |||
1675 | | |||
| 0 | 1676 | if (!tooLong) { | ||
1677 | break; | |||
1678 | } | |||
1679 | | |||
| 0 | 1680 | for (int i = 1; i < alphaSize; ++i) { | ||
| 0 | 1681 | j = weight[i] >> 8; | ||
| 0 | 1682 | j = 1 + (j / 2); | ||
| 0 | 1683 | weight[i] = j << 8; | ||
1684 | } | |||
1685 | } | |||
| 0 | 1686 | } | ||
1687 | | |||
1688 | static void HbAssignCodes(int[] code, char[] length, int minLen, int maxLen, int alphaSize) | |||
1689 | { | |||
| 0 | 1690 | int vec = 0; | ||
| 0 | 1691 | for (int n = minLen; n <= maxLen; ++n) { | ||
| 0 | 1692 | for (int i = 0; i < alphaSize; ++i) { | ||
| 0 | 1693 | if (length[i] == n) { | ||
| 0 | 1694 | code[i] = vec; | ||
| 0 | 1695 | ++vec; | ||
1696 | } | |||
1697 | } | |||
| 0 | 1698 | vec <<= 1; | ||
1699 | } | |||
| 0 | 1700 | } | ||
1701 | | |||
1702 | static byte Med3(byte a, byte b, byte c) | |||
1703 | { | |||
1704 | byte t; | |||
| 0 | 1705 | if (a > b) { | ||
| 0 | 1706 | t = a; | ||
| 0 | 1707 | a = b; | ||
| 0 | 1708 | b = t; | ||
1709 | } | |||
| 0 | 1710 | if (b > c) { | ||
| 0 | 1711 | t = b; | ||
| 0 | 1712 | b = c; | ||
| 0 | 1713 | c = t; | ||
1714 | } | |||
| 0 | 1715 | if (a > b) { | ||
| 0 | 1716 | b = a; | ||
1717 | } | |||
| 0 | 1718 | return b; | ||
1719 | } | |||
1720 | | |||
1721 | struct StackElement | |||
1722 | { | |||
1723 | public int ll; | |||
1724 | public int hh; | |||
1725 | public int dd; | |||
1726 | } | |||
1727 | | |||
1728 | #region Instance Fields | |||
| 1 | 1729 | bool isStreamOwner = true; | ||
1730 | | |||
1731 | /*-- | |||
1732 | index of the last char in the block, so | |||
1733 | the block size == last + 1. | |||
1734 | --*/ | |||
1735 | int last; | |||
1736 | | |||
1737 | /*-- | |||
1738 | index in zptr[] of original string after sorting. | |||
1739 | --*/ | |||
1740 | int origPtr; | |||
1741 | | |||
1742 | /*-- | |||
1743 | always: in the range 0 .. 9. | |||
1744 | The current block size is 100000 * this number. | |||
1745 | --*/ | |||
1746 | int blockSize100k; | |||
1747 | | |||
1748 | bool blockRandomised; | |||
1749 | | |||
1750 | int bytesOut; | |||
1751 | int bsBuff; | |||
1752 | int bsLive; | |||
| 1 | 1753 | IChecksum mCrc = new BZip2Crc(); | ||
1754 | | |||
| 1 | 1755 | bool[] inUse = new bool[256]; | ||
1756 | int nInUse; | |||
1757 | | |||
| 1 | 1758 | char[] seqToUnseq = new char[256]; | ||
| 1 | 1759 | char[] unseqToSeq = new char[256]; | ||
1760 | | |||
| 1 | 1761 | char[] selector = new char[BZip2Constants.MaximumSelectors]; | ||
| 1 | 1762 | char[] selectorMtf = new char[BZip2Constants.MaximumSelectors]; | ||
1763 | | |||
1764 | byte[] block; | |||
1765 | int[] quadrant; | |||
1766 | int[] zptr; | |||
1767 | short[] szptr; | |||
1768 | int[] ftab; | |||
1769 | | |||
1770 | int nMTF; | |||
1771 | | |||
| 1 | 1772 | int[] mtfFreq = new int[BZip2Constants.MaximumAlphaSize]; | ||
1773 | | |||
1774 | /* | |||
1775 | * Used when sorting. If too many long comparisons | |||
1776 | * happen, we stop sorting, randomise the block | |||
1777 | * slightly, and try again. | |||
1778 | */ | |||
1779 | int workFactor; | |||
1780 | int workDone; | |||
1781 | int workLimit; | |||
1782 | bool firstAttempt; | |||
1783 | int nBlocksRandomised; | |||
1784 | | |||
| 1 | 1785 | int currentChar = -1; | ||
1786 | int runLength; | |||
1787 | uint blockCRC, combinedCRC; | |||
1788 | int allowableBlockSize; | |||
1789 | Stream baseStream; | |||
1790 | bool disposed_; | |||
1791 | #endregion | |||
1792 | } | |||
1793 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.BaseArchiveStorage |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs |
| Covered lines: | 4 |
| Uncovered lines: | 0 |
| Coverable lines: | 4 |
| Total lines: | 4263 |
| Line coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.IO; | |||
4 | using System.Text; | |||
5 | using System.Globalization; | |||
6 | using System.Security.Cryptography; | |||
7 | using ICSharpCode.SharpZipLib.Encryption; | |||
8 | using ICSharpCode.SharpZipLib.Core; | |||
9 | using ICSharpCode.SharpZipLib.Checksum; | |||
10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
11 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
12 | | |||
13 | namespace ICSharpCode.SharpZipLib.Zip | |||
14 | { | |||
15 | #region Keys Required Event Args | |||
16 | /// <summary> | |||
17 | /// Arguments used with KeysRequiredEvent | |||
18 | /// </summary> | |||
19 | public class KeysRequiredEventArgs : EventArgs | |||
20 | { | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
24 | /// </summary> | |||
25 | /// <param name="name">The name of the file for which keys are required.</param> | |||
26 | public KeysRequiredEventArgs(string name) | |||
27 | { | |||
28 | fileName = name; | |||
29 | } | |||
30 | | |||
31 | /// <summary> | |||
32 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
33 | /// </summary> | |||
34 | /// <param name="name">The name of the file for which keys are required.</param> | |||
35 | /// <param name="keyValue">The current key value.</param> | |||
36 | public KeysRequiredEventArgs(string name, byte[] keyValue) | |||
37 | { | |||
38 | fileName = name; | |||
39 | key = keyValue; | |||
40 | } | |||
41 | | |||
42 | #endregion | |||
43 | #region Properties | |||
44 | /// <summary> | |||
45 | /// Gets the name of the file for which keys are required. | |||
46 | /// </summary> | |||
47 | public string FileName { | |||
48 | get { return fileName; } | |||
49 | } | |||
50 | | |||
51 | /// <summary> | |||
52 | /// Gets or sets the key value | |||
53 | /// </summary> | |||
54 | public byte[] Key { | |||
55 | get { return key; } | |||
56 | set { key = value; } | |||
57 | } | |||
58 | #endregion | |||
59 | | |||
60 | #region Instance Fields | |||
61 | string fileName; | |||
62 | byte[] key; | |||
63 | #endregion | |||
64 | } | |||
65 | #endregion | |||
66 | | |||
67 | #region Test Definitions | |||
68 | /// <summary> | |||
69 | /// The strategy to apply to testing. | |||
70 | /// </summary> | |||
71 | public enum TestStrategy | |||
72 | { | |||
73 | /// <summary> | |||
74 | /// Find the first error only. | |||
75 | /// </summary> | |||
76 | FindFirstError, | |||
77 | /// <summary> | |||
78 | /// Find all possible errors. | |||
79 | /// </summary> | |||
80 | FindAllErrors, | |||
81 | } | |||
82 | | |||
83 | /// <summary> | |||
84 | /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. | |||
85 | /// </summary> | |||
86 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
87 | public enum TestOperation | |||
88 | { | |||
89 | /// <summary> | |||
90 | /// Setting up testing. | |||
91 | /// </summary> | |||
92 | Initialising, | |||
93 | | |||
94 | /// <summary> | |||
95 | /// Testing an individual entries header | |||
96 | /// </summary> | |||
97 | EntryHeader, | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Testing an individual entries data | |||
101 | /// </summary> | |||
102 | EntryData, | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Testing an individual entry has completed. | |||
106 | /// </summary> | |||
107 | EntryComplete, | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Running miscellaneous tests | |||
111 | /// </summary> | |||
112 | MiscellaneousTests, | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Testing is complete | |||
116 | /// </summary> | |||
117 | Complete, | |||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. | |||
122 | /// </summary> | |||
123 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
124 | public class TestStatus | |||
125 | { | |||
126 | #region Constructors | |||
127 | /// <summary> | |||
128 | /// Initialise a new instance of <see cref="TestStatus"/> | |||
129 | /// </summary> | |||
130 | /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> | |||
131 | public TestStatus(ZipFile file) | |||
132 | { | |||
133 | file_ = file; | |||
134 | } | |||
135 | #endregion | |||
136 | | |||
137 | #region Properties | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Get the current <see cref="TestOperation"/> in progress. | |||
141 | /// </summary> | |||
142 | public TestOperation Operation { | |||
143 | get { return operation_; } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get the <see cref="ZipFile"/> this status is applicable to. | |||
148 | /// </summary> | |||
149 | public ZipFile File { | |||
150 | get { return file_; } | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Get the current/last entry tested. | |||
155 | /// </summary> | |||
156 | public ZipEntry Entry { | |||
157 | get { return entry_; } | |||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Get the number of errors detected so far. | |||
162 | /// </summary> | |||
163 | public int ErrorCount { | |||
164 | get { return errorCount_; } | |||
165 | } | |||
166 | | |||
167 | /// <summary> | |||
168 | /// Get the number of bytes tested so far for the current entry. | |||
169 | /// </summary> | |||
170 | public long BytesTested { | |||
171 | get { return bytesTested_; } | |||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Get a value indicating wether the last entry test was valid. | |||
176 | /// </summary> | |||
177 | public bool EntryValid { | |||
178 | get { return entryValid_; } | |||
179 | } | |||
180 | #endregion | |||
181 | | |||
182 | #region Internal API | |||
183 | internal void AddError() | |||
184 | { | |||
185 | errorCount_++; | |||
186 | entryValid_ = false; | |||
187 | } | |||
188 | | |||
189 | internal void SetOperation(TestOperation operation) | |||
190 | { | |||
191 | operation_ = operation; | |||
192 | } | |||
193 | | |||
194 | internal void SetEntry(ZipEntry entry) | |||
195 | { | |||
196 | entry_ = entry; | |||
197 | entryValid_ = true; | |||
198 | bytesTested_ = 0; | |||
199 | } | |||
200 | | |||
201 | internal void SetBytesTested(long value) | |||
202 | { | |||
203 | bytesTested_ = value; | |||
204 | } | |||
205 | #endregion | |||
206 | | |||
207 | #region Instance Fields | |||
208 | ZipFile file_; | |||
209 | ZipEntry entry_; | |||
210 | bool entryValid_; | |||
211 | int errorCount_; | |||
212 | long bytesTested_; | |||
213 | TestOperation operation_; | |||
214 | #endregion | |||
215 | } | |||
216 | | |||
217 | /// <summary> | |||
218 | /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if | |||
219 | /// </summary> | |||
220 | /// <remarks>If the message is non-null an error has occured. If the message is null | |||
221 | /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> | |||
222 | public delegate void ZipTestResultHandler(TestStatus status, string message); | |||
223 | #endregion | |||
224 | | |||
225 | #region Update Definitions | |||
226 | /// <summary> | |||
227 | /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. | |||
228 | /// </summary> | |||
229 | public enum FileUpdateMode | |||
230 | { | |||
231 | /// <summary> | |||
232 | /// Perform all updates on temporary files ensuring that the original file is saved. | |||
233 | /// </summary> | |||
234 | Safe, | |||
235 | /// <summary> | |||
236 | /// Update the archive directly, which is faster but less safe. | |||
237 | /// </summary> | |||
238 | Direct, | |||
239 | } | |||
240 | #endregion | |||
241 | | |||
242 | #region ZipFile Class | |||
243 | /// <summary> | |||
244 | /// This class represents a Zip archive. You can ask for the contained | |||
245 | /// entries, or get an input stream for a file entry. The entry is | |||
246 | /// automatically decompressed. | |||
247 | /// | |||
248 | /// You can also update the archive adding or deleting entries. | |||
249 | /// | |||
250 | /// This class is thread safe for input: You can open input streams for arbitrary | |||
251 | /// entries in different threads. | |||
252 | /// <br/> | |||
253 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
254 | /// </summary> | |||
255 | /// <example> | |||
256 | /// <code> | |||
257 | /// using System; | |||
258 | /// using System.Text; | |||
259 | /// using System.Collections; | |||
260 | /// using System.IO; | |||
261 | /// | |||
262 | /// using ICSharpCode.SharpZipLib.Zip; | |||
263 | /// | |||
264 | /// class MainClass | |||
265 | /// { | |||
266 | /// static public void Main(string[] args) | |||
267 | /// { | |||
268 | /// using (ZipFile zFile = new ZipFile(args[0])) { | |||
269 | /// Console.WriteLine("Listing of : " + zFile.Name); | |||
270 | /// Console.WriteLine(""); | |||
271 | /// Console.WriteLine("Raw Size Size Date Time Name"); | |||
272 | /// Console.WriteLine("-------- -------- -------- ------ ---------"); | |||
273 | /// foreach (ZipEntry e in zFile) { | |||
274 | /// if ( e.IsFile ) { | |||
275 | /// DateTime d = e.DateTime; | |||
276 | /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, | |||
277 | /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), | |||
278 | /// e.Name); | |||
279 | /// } | |||
280 | /// } | |||
281 | /// } | |||
282 | /// } | |||
283 | /// } | |||
284 | /// </code> | |||
285 | /// </example> | |||
286 | public class ZipFile : IEnumerable, IDisposable | |||
287 | { | |||
288 | #region KeyHandling | |||
289 | | |||
290 | /// <summary> | |||
291 | /// Delegate for handling keys/password setting during compresion/decompression. | |||
292 | /// </summary> | |||
293 | public delegate void KeysRequiredEventHandler( | |||
294 | object sender, | |||
295 | KeysRequiredEventArgs e | |||
296 | ); | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Event handler for handling encryption keys. | |||
300 | /// </summary> | |||
301 | public KeysRequiredEventHandler KeysRequired; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Handles getting of encryption keys when required. | |||
305 | /// </summary> | |||
306 | /// <param name="fileName">The file for which encryption keys are required.</param> | |||
307 | void OnKeysRequired(string fileName) | |||
308 | { | |||
309 | if (KeysRequired != null) { | |||
310 | var krea = new KeysRequiredEventArgs(fileName, key); | |||
311 | KeysRequired(this, krea); | |||
312 | key = krea.Key; | |||
313 | } | |||
314 | } | |||
315 | | |||
316 | /// <summary> | |||
317 | /// Get/set the encryption key value. | |||
318 | /// </summary> | |||
319 | byte[] Key { | |||
320 | get { return key; } | |||
321 | set { key = value; } | |||
322 | } | |||
323 | | |||
324 | /// <summary> | |||
325 | /// Password to be used for encrypting/decrypting files. | |||
326 | /// </summary> | |||
327 | /// <remarks>Set to null if no password is required.</remarks> | |||
328 | public string Password { | |||
329 | set { | |||
330 | if (string.IsNullOrEmpty(value)) { | |||
331 | key = null; | |||
332 | } else { | |||
333 | rawPassword_ = value; | |||
334 | key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); | |||
335 | } | |||
336 | } | |||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Get a value indicating wether encryption keys are currently available. | |||
341 | /// </summary> | |||
342 | bool HaveKeys { | |||
343 | get { return key != null; } | |||
344 | } | |||
345 | #endregion | |||
346 | | |||
347 | #region Constructors | |||
348 | /// <summary> | |||
349 | /// Opens a Zip file with the given name for reading. | |||
350 | /// </summary> | |||
351 | /// <param name="name">The name of the file to open.</param> | |||
352 | /// <exception cref="ArgumentNullException">The argument supplied is null.</exception> | |||
353 | /// <exception cref="IOException"> | |||
354 | /// An i/o error occurs | |||
355 | /// </exception> | |||
356 | /// <exception cref="ZipException"> | |||
357 | /// The file doesn't contain a valid zip archive. | |||
358 | /// </exception> | |||
359 | public ZipFile(string name) | |||
360 | { | |||
361 | if (name == null) { | |||
362 | throw new ArgumentNullException(nameof(name)); | |||
363 | } | |||
364 | | |||
365 | name_ = name; | |||
366 | | |||
367 | baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
368 | isStreamOwner = true; | |||
369 | | |||
370 | try { | |||
371 | ReadEntries(); | |||
372 | } catch { | |||
373 | DisposeInternal(true); | |||
374 | throw; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Opens a Zip file reading the given <see cref="FileStream"/>. | |||
380 | /// </summary> | |||
381 | /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> | |||
382 | /// <exception cref="ArgumentNullException">The supplied argument is null.</exception> | |||
383 | /// <exception cref="IOException"> | |||
384 | /// An i/o error occurs. | |||
385 | /// </exception> | |||
386 | /// <exception cref="ZipException"> | |||
387 | /// The file doesn't contain a valid zip archive. | |||
388 | /// </exception> | |||
389 | public ZipFile(FileStream file) | |||
390 | { | |||
391 | if (file == null) { | |||
392 | throw new ArgumentNullException(nameof(file)); | |||
393 | } | |||
394 | | |||
395 | if (!file.CanSeek) { | |||
396 | throw new ArgumentException("Stream is not seekable", nameof(file)); | |||
397 | } | |||
398 | | |||
399 | baseStream_ = file; | |||
400 | name_ = file.Name; | |||
401 | isStreamOwner = true; | |||
402 | | |||
403 | try { | |||
404 | ReadEntries(); | |||
405 | } catch { | |||
406 | DisposeInternal(true); | |||
407 | throw; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Opens a Zip file reading the given <see cref="Stream"/>. | |||
413 | /// </summary> | |||
414 | /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> | |||
415 | /// <exception cref="IOException"> | |||
416 | /// An i/o error occurs | |||
417 | /// </exception> | |||
418 | /// <exception cref="ZipException"> | |||
419 | /// The stream doesn't contain a valid zip archive.<br/> | |||
420 | /// </exception> | |||
421 | /// <exception cref="ArgumentException"> | |||
422 | /// The <see cref="Stream">stream</see> doesnt support seeking. | |||
423 | /// </exception> | |||
424 | /// <exception cref="ArgumentNullException"> | |||
425 | /// The <see cref="Stream">stream</see> argument is null. | |||
426 | /// </exception> | |||
427 | public ZipFile(Stream stream) | |||
428 | { | |||
429 | if (stream == null) { | |||
430 | throw new ArgumentNullException(nameof(stream)); | |||
431 | } | |||
432 | | |||
433 | if (!stream.CanSeek) { | |||
434 | throw new ArgumentException("Stream is not seekable", nameof(stream)); | |||
435 | } | |||
436 | | |||
437 | baseStream_ = stream; | |||
438 | isStreamOwner = true; | |||
439 | | |||
440 | if (baseStream_.Length > 0) { | |||
441 | try { | |||
442 | ReadEntries(); | |||
443 | } catch { | |||
444 | DisposeInternal(true); | |||
445 | throw; | |||
446 | } | |||
447 | } else { | |||
448 | entries_ = new ZipEntry[0]; | |||
449 | isNewArchive_ = true; | |||
450 | } | |||
451 | } | |||
452 | | |||
453 | /// <summary> | |||
454 | /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. | |||
455 | /// </summary> | |||
456 | internal ZipFile() | |||
457 | { | |||
458 | entries_ = new ZipEntry[0]; | |||
459 | isNewArchive_ = true; | |||
460 | } | |||
461 | | |||
462 | #endregion | |||
463 | | |||
464 | #region Destructors and Closing | |||
465 | /// <summary> | |||
466 | /// Finalize this instance. | |||
467 | /// </summary> | |||
468 | ~ZipFile() | |||
469 | { | |||
470 | Dispose(false); | |||
471 | } | |||
472 | | |||
473 | /// <summary> | |||
474 | /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying | |||
475 | /// Once closed, no further instance methods should be called. | |||
476 | /// </summary> | |||
477 | /// <exception cref="System.IO.IOException"> | |||
478 | /// An i/o error occurs. | |||
479 | /// </exception> | |||
480 | public void Close() | |||
481 | { | |||
482 | DisposeInternal(true); | |||
483 | GC.SuppressFinalize(this); | |||
484 | } | |||
485 | | |||
486 | #endregion | |||
487 | | |||
488 | #region Creators | |||
489 | /// <summary> | |||
490 | /// Create a new <see cref="ZipFile"/> whose data will be stored in a file. | |||
491 | /// </summary> | |||
492 | /// <param name="fileName">The name of the archive to create.</param> | |||
493 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
494 | /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception> | |||
495 | public static ZipFile Create(string fileName) | |||
496 | { | |||
497 | if (fileName == null) { | |||
498 | throw new ArgumentNullException(nameof(fileName)); | |||
499 | } | |||
500 | | |||
501 | FileStream fs = File.Create(fileName); | |||
502 | | |||
503 | var result = new ZipFile(); | |||
504 | result.name_ = fileName; | |||
505 | result.baseStream_ = fs; | |||
506 | result.isStreamOwner = true; | |||
507 | return result; | |||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. | |||
512 | /// </summary> | |||
513 | /// <param name="outStream">The stream providing data storage.</param> | |||
514 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
515 | /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception> | |||
516 | /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception> | |||
517 | public static ZipFile Create(Stream outStream) | |||
518 | { | |||
519 | if (outStream == null) { | |||
520 | throw new ArgumentNullException(nameof(outStream)); | |||
521 | } | |||
522 | | |||
523 | if (!outStream.CanWrite) { | |||
524 | throw new ArgumentException("Stream is not writeable", nameof(outStream)); | |||
525 | } | |||
526 | | |||
527 | if (!outStream.CanSeek) { | |||
528 | throw new ArgumentException("Stream is not seekable", nameof(outStream)); | |||
529 | } | |||
530 | | |||
531 | var result = new ZipFile(); | |||
532 | result.baseStream_ = outStream; | |||
533 | return result; | |||
534 | } | |||
535 | | |||
536 | #endregion | |||
537 | | |||
538 | #region Properties | |||
539 | /// <summary> | |||
540 | /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. | |||
541 | /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. | |||
542 | /// </summary> | |||
543 | /// <remarks> | |||
544 | /// The default value is true in all cases. | |||
545 | /// </remarks> | |||
546 | public bool IsStreamOwner { | |||
547 | get { return isStreamOwner; } | |||
548 | set { isStreamOwner = value; } | |||
549 | } | |||
550 | | |||
551 | /// <summary> | |||
552 | /// Get a value indicating wether | |||
553 | /// this archive is embedded in another file or not. | |||
554 | /// </summary> | |||
555 | public bool IsEmbeddedArchive { | |||
556 | // Not strictly correct in all circumstances currently | |||
557 | get { return offsetOfFirstEntry > 0; } | |||
558 | } | |||
559 | | |||
560 | /// <summary> | |||
561 | /// Get a value indicating that this archive is a new one. | |||
562 | /// </summary> | |||
563 | public bool IsNewArchive { | |||
564 | get { return isNewArchive_; } | |||
565 | } | |||
566 | | |||
567 | /// <summary> | |||
568 | /// Gets the comment for the zip file. | |||
569 | /// </summary> | |||
570 | public string ZipFileComment { | |||
571 | get { return comment_; } | |||
572 | } | |||
573 | | |||
574 | /// <summary> | |||
575 | /// Gets the name of this zip file. | |||
576 | /// </summary> | |||
577 | public string Name { | |||
578 | get { return name_; } | |||
579 | } | |||
580 | | |||
581 | /// <summary> | |||
582 | /// Gets the number of entries in this zip file. | |||
583 | /// </summary> | |||
584 | /// <exception cref="InvalidOperationException"> | |||
585 | /// The Zip file has been closed. | |||
586 | /// </exception> | |||
587 | [Obsolete("Use the Count property instead")] | |||
588 | public int Size { | |||
589 | get { | |||
590 | return entries_.Length; | |||
591 | } | |||
592 | } | |||
593 | | |||
594 | /// <summary> | |||
595 | /// Get the number of entries contained in this <see cref="ZipFile"/>. | |||
596 | /// </summary> | |||
597 | public long Count { | |||
598 | get { | |||
599 | return entries_.Length; | |||
600 | } | |||
601 | } | |||
602 | | |||
603 | /// <summary> | |||
604 | /// Indexer property for ZipEntries | |||
605 | /// </summary> | |||
606 | [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] | |||
607 | public ZipEntry this[int index] { | |||
608 | get { | |||
609 | return (ZipEntry)entries_[index].Clone(); | |||
610 | } | |||
611 | } | |||
612 | | |||
613 | #endregion | |||
614 | | |||
615 | #region Input Handling | |||
616 | /// <summary> | |||
617 | /// Gets an enumerator for the Zip entries in this Zip file. | |||
618 | /// </summary> | |||
619 | /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> | |||
620 | /// <exception cref="ObjectDisposedException"> | |||
621 | /// The Zip file has been closed. | |||
622 | /// </exception> | |||
623 | public IEnumerator GetEnumerator() | |||
624 | { | |||
625 | if (isDisposed_) { | |||
626 | throw new ObjectDisposedException("ZipFile"); | |||
627 | } | |||
628 | | |||
629 | return new ZipEntryEnumerator(entries_); | |||
630 | } | |||
631 | | |||
632 | /// <summary> | |||
633 | /// Return the index of the entry with a matching name | |||
634 | /// </summary> | |||
635 | /// <param name="name">Entry name to find</param> | |||
636 | /// <param name="ignoreCase">If true the comparison is case insensitive</param> | |||
637 | /// <returns>The index position of the matching entry or -1 if not found</returns> | |||
638 | /// <exception cref="ObjectDisposedException"> | |||
639 | /// The Zip file has been closed. | |||
640 | /// </exception> | |||
641 | public int FindEntry(string name, bool ignoreCase) | |||
642 | { | |||
643 | if (isDisposed_) { | |||
644 | throw new ObjectDisposedException("ZipFile"); | |||
645 | } | |||
646 | | |||
647 | // TODO: This will be slow as the next ice age for huge archives! | |||
648 | for (int i = 0; i < entries_.Length; i++) { | |||
649 | if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) { | |||
650 | return i; | |||
651 | } | |||
652 | } | |||
653 | return -1; | |||
654 | } | |||
655 | | |||
656 | /// <summary> | |||
657 | /// Searches for a zip entry in this archive with the given name. | |||
658 | /// String comparisons are case insensitive | |||
659 | /// </summary> | |||
660 | /// <param name="name"> | |||
661 | /// The name to find. May contain directory components separated by slashes ('/'). | |||
662 | /// </param> | |||
663 | /// <returns> | |||
664 | /// A clone of the zip entry, or null if no entry with that name exists. | |||
665 | /// </returns> | |||
666 | /// <exception cref="ObjectDisposedException"> | |||
667 | /// The Zip file has been closed. | |||
668 | /// </exception> | |||
669 | public ZipEntry GetEntry(string name) | |||
670 | { | |||
671 | if (isDisposed_) { | |||
672 | throw new ObjectDisposedException("ZipFile"); | |||
673 | } | |||
674 | | |||
675 | int index = FindEntry(name, true); | |||
676 | return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; | |||
677 | } | |||
678 | | |||
679 | /// <summary> | |||
680 | /// Gets an input stream for reading the given zip entry data in an uncompressed form. | |||
681 | /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). | |||
682 | /// </summary> | |||
683 | /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> | |||
684 | /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> | |||
685 | /// <exception cref="ObjectDisposedException"> | |||
686 | /// The ZipFile has already been closed | |||
687 | /// </exception> | |||
688 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
689 | /// The compression method for the entry is unknown | |||
690 | /// </exception> | |||
691 | /// <exception cref="IndexOutOfRangeException"> | |||
692 | /// The entry is not found in the ZipFile | |||
693 | /// </exception> | |||
694 | public Stream GetInputStream(ZipEntry entry) | |||
695 | { | |||
696 | if (entry == null) { | |||
697 | throw new ArgumentNullException(nameof(entry)); | |||
698 | } | |||
699 | | |||
700 | if (isDisposed_) { | |||
701 | throw new ObjectDisposedException("ZipFile"); | |||
702 | } | |||
703 | | |||
704 | long index = entry.ZipFileIndex; | |||
705 | if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { | |||
706 | index = FindEntry(entry.Name, true); | |||
707 | if (index < 0) { | |||
708 | throw new ZipException("Entry cannot be found"); | |||
709 | } | |||
710 | } | |||
711 | return GetInputStream(index); | |||
712 | } | |||
713 | | |||
714 | /// <summary> | |||
715 | /// Creates an input stream reading a zip entry | |||
716 | /// </summary> | |||
717 | /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> | |||
718 | /// <returns> | |||
719 | /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> | |||
720 | /// </returns> | |||
721 | /// <exception cref="ObjectDisposedException"> | |||
722 | /// The ZipFile has already been closed | |||
723 | /// </exception> | |||
724 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
725 | /// The compression method for the entry is unknown | |||
726 | /// </exception> | |||
727 | /// <exception cref="IndexOutOfRangeException"> | |||
728 | /// The entry is not found in the ZipFile | |||
729 | /// </exception> | |||
730 | public Stream GetInputStream(long entryIndex) | |||
731 | { | |||
732 | if (isDisposed_) { | |||
733 | throw new ObjectDisposedException("ZipFile"); | |||
734 | } | |||
735 | | |||
736 | long start = LocateEntry(entries_[entryIndex]); | |||
737 | CompressionMethod method = entries_[entryIndex].CompressionMethod; | |||
738 | Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); | |||
739 | | |||
740 | if (entries_[entryIndex].IsCrypted == true) { | |||
741 | result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); | |||
742 | if (result == null) { | |||
743 | throw new ZipException("Unable to decrypt this entry"); | |||
744 | } | |||
745 | } | |||
746 | | |||
747 | switch (method) { | |||
748 | case CompressionMethod.Stored: | |||
749 | // read as is. | |||
750 | break; | |||
751 | | |||
752 | case CompressionMethod.Deflated: | |||
753 | // No need to worry about ownership and closing as underlying stream close does nothing. | |||
754 | result = new InflaterInputStream(result, new Inflater(true)); | |||
755 | break; | |||
756 | | |||
757 | default: | |||
758 | throw new ZipException("Unsupported compression method " + method); | |||
759 | } | |||
760 | | |||
761 | return result; | |||
762 | } | |||
763 | | |||
764 | #endregion | |||
765 | | |||
766 | #region Archive Testing | |||
767 | /// <summary> | |||
768 | /// Test an archive for integrity/validity | |||
769 | /// </summary> | |||
770 | /// <param name="testData">Perform low level data Crc check</param> | |||
771 | /// <returns>true if all tests pass, false otherwise</returns> | |||
772 | /// <remarks>Testing will terminate on the first error found.</remarks> | |||
773 | public bool TestArchive(bool testData) | |||
774 | { | |||
775 | return TestArchive(testData, TestStrategy.FindFirstError, null); | |||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Test an archive for integrity/validity | |||
780 | /// </summary> | |||
781 | /// <param name="testData">Perform low level data Crc check</param> | |||
782 | /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> | |||
783 | /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> | |||
784 | /// <returns>true if all tests pass, false otherwise</returns> | |||
785 | /// <exception cref="ObjectDisposedException">The object has already been closed.</exception> | |||
786 | public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) | |||
787 | { | |||
788 | if (isDisposed_) { | |||
789 | throw new ObjectDisposedException("ZipFile"); | |||
790 | } | |||
791 | | |||
792 | var status = new TestStatus(this); | |||
793 | | |||
794 | if (resultHandler != null) { | |||
795 | resultHandler(status, null); | |||
796 | } | |||
797 | | |||
798 | HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; | |||
799 | | |||
800 | bool testing = true; | |||
801 | | |||
802 | try { | |||
803 | int entryIndex = 0; | |||
804 | | |||
805 | while (testing && (entryIndex < Count)) { | |||
806 | if (resultHandler != null) { | |||
807 | status.SetEntry(this[entryIndex]); | |||
808 | status.SetOperation(TestOperation.EntryHeader); | |||
809 | resultHandler(status, null); | |||
810 | } | |||
811 | | |||
812 | try { | |||
813 | TestLocalHeader(this[entryIndex], test); | |||
814 | } catch (ZipException ex) { | |||
815 | status.AddError(); | |||
816 | | |||
817 | if (resultHandler != null) { | |||
818 | resultHandler(status, | |||
819 | string.Format("Exception during test - '{0}'", ex.Message)); | |||
820 | } | |||
821 | | |||
822 | testing &= strategy != TestStrategy.FindFirstError; | |||
823 | } | |||
824 | | |||
825 | if (testing && testData && this[entryIndex].IsFile) { | |||
826 | if (resultHandler != null) { | |||
827 | status.SetOperation(TestOperation.EntryData); | |||
828 | resultHandler(status, null); | |||
829 | } | |||
830 | | |||
831 | var crc = new Crc32(); | |||
832 | | |||
833 | using (Stream entryStream = this.GetInputStream(this[entryIndex])) { | |||
834 | | |||
835 | byte[] buffer = new byte[4096]; | |||
836 | long totalBytes = 0; | |||
837 | int bytesRead; | |||
838 | while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { | |||
839 | crc.Update(buffer, 0, bytesRead); | |||
840 | | |||
841 | if (resultHandler != null) { | |||
842 | totalBytes += bytesRead; | |||
843 | status.SetBytesTested(totalBytes); | |||
844 | resultHandler(status, null); | |||
845 | } | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | if (this[entryIndex].Crc != crc.Value) { | |||
850 | status.AddError(); | |||
851 | | |||
852 | if (resultHandler != null) { | |||
853 | resultHandler(status, "CRC mismatch"); | |||
854 | } | |||
855 | | |||
856 | testing &= strategy != TestStrategy.FindFirstError; | |||
857 | } | |||
858 | | |||
859 | if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
860 | var helper = new ZipHelperStream(baseStream_); | |||
861 | var data = new DescriptorData(); | |||
862 | helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); | |||
863 | if (this[entryIndex].Crc != data.Crc) { | |||
864 | status.AddError(); | |||
865 | } | |||
866 | | |||
867 | if (this[entryIndex].CompressedSize != data.CompressedSize) { | |||
868 | status.AddError(); | |||
869 | } | |||
870 | | |||
871 | if (this[entryIndex].Size != data.Size) { | |||
872 | status.AddError(); | |||
873 | } | |||
874 | } | |||
875 | } | |||
876 | | |||
877 | if (resultHandler != null) { | |||
878 | status.SetOperation(TestOperation.EntryComplete); | |||
879 | resultHandler(status, null); | |||
880 | } | |||
881 | | |||
882 | entryIndex += 1; | |||
883 | } | |||
884 | | |||
885 | if (resultHandler != null) { | |||
886 | status.SetOperation(TestOperation.MiscellaneousTests); | |||
887 | resultHandler(status, null); | |||
888 | } | |||
889 | | |||
890 | // TODO: the 'Corrina Johns' test where local headers are missing from | |||
891 | // the central directory. They are therefore invisible to many archivers. | |||
892 | } catch (Exception ex) { | |||
893 | status.AddError(); | |||
894 | | |||
895 | if (resultHandler != null) { | |||
896 | resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); | |||
897 | } | |||
898 | } | |||
899 | | |||
900 | if (resultHandler != null) { | |||
901 | status.SetOperation(TestOperation.Complete); | |||
902 | status.SetEntry(null); | |||
903 | resultHandler(status, null); | |||
904 | } | |||
905 | | |||
906 | return (status.ErrorCount == 0); | |||
907 | } | |||
908 | | |||
909 | [Flags] | |||
910 | enum HeaderTest | |||
911 | { | |||
912 | Extract = 0x01, // Check that this header represents an entry whose data can be extracted | |||
913 | Header = 0x02, // Check that this header contents are valid | |||
914 | } | |||
915 | | |||
916 | /// <summary> | |||
917 | /// Test a local header against that provided from the central directory | |||
918 | /// </summary> | |||
919 | /// <param name="entry"> | |||
920 | /// The entry to test against | |||
921 | /// </param> | |||
922 | /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> | |||
923 | /// <returns>The offset of the entries data in the file</returns> | |||
924 | long TestLocalHeader(ZipEntry entry, HeaderTest tests) | |||
925 | { | |||
926 | lock (baseStream_) { | |||
927 | bool testHeader = (tests & HeaderTest.Header) != 0; | |||
928 | bool testData = (tests & HeaderTest.Extract) != 0; | |||
929 | | |||
930 | baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); | |||
931 | if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { | |||
932 | throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset) | |||
933 | } | |||
934 | | |||
935 | var extractVersion = (short)(ReadLEUshort() & 0x00ff); | |||
936 | var localFlags = (short)ReadLEUshort(); | |||
937 | var compressionMethod = (short)ReadLEUshort(); | |||
938 | var fileTime = (short)ReadLEUshort(); | |||
939 | var fileDate = (short)ReadLEUshort(); | |||
940 | uint crcValue = ReadLEUint(); | |||
941 | long compressedSize = ReadLEUint(); | |||
942 | long size = ReadLEUint(); | |||
943 | int storedNameLength = ReadLEUshort(); | |||
944 | int extraDataLength = ReadLEUshort(); | |||
945 | | |||
946 | byte[] nameData = new byte[storedNameLength]; | |||
947 | StreamUtils.ReadFully(baseStream_, nameData); | |||
948 | | |||
949 | byte[] extraData = new byte[extraDataLength]; | |||
950 | StreamUtils.ReadFully(baseStream_, extraData); | |||
951 | | |||
952 | var localExtraData = new ZipExtraData(extraData); | |||
953 | | |||
954 | // Extra data / zip64 checks | |||
955 | if (localExtraData.Find(1)) { | |||
956 | // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 | |||
957 | // and size or compressedSize = MaxValue, due to rogue creators. | |||
958 | | |||
959 | size = localExtraData.ReadLong(); | |||
960 | compressedSize = localExtraData.ReadLong(); | |||
961 | | |||
962 | if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
963 | // These may be valid if patched later | |||
964 | if ((size != -1) && (size != entry.Size)) { | |||
965 | throw new ZipException("Size invalid for descriptor"); | |||
966 | } | |||
967 | | |||
968 | if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) { | |||
969 | throw new ZipException("Compressed size invalid for descriptor"); | |||
970 | } | |||
971 | } | |||
972 | } else { | |||
973 | // No zip64 extra data but entry requires it. | |||
974 | if ((extractVersion >= ZipConstants.VersionZip64) && | |||
975 | (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) { | |||
976 | throw new ZipException("Required Zip64 extended information missing"); | |||
977 | } | |||
978 | } | |||
979 | | |||
980 | if (testData) { | |||
981 | if (entry.IsFile) { | |||
982 | if (!entry.IsCompressionMethodSupported()) { | |||
983 | throw new ZipException("Compression method not supported"); | |||
984 | } | |||
985 | | |||
986 | if ((extractVersion > ZipConstants.VersionMadeBy) | |||
987 | || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { | |||
988 | throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract | |||
989 | } | |||
990 | | |||
991 | if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance | |||
992 | throw new ZipException("The library does not support the zip version required to extract this entry"); | |||
993 | } | |||
994 | } | |||
995 | } | |||
996 | | |||
997 | if (testHeader) { | |||
998 | if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. | |||
999 | (extractVersion != 10) && | |||
1000 | (extractVersion != 11) && | |||
1001 | (extractVersion != 20) && | |||
1002 | (extractVersion != 21) && | |||
1003 | (extractVersion != 25) && | |||
1004 | (extractVersion != 27) && | |||
1005 | (extractVersion != 45) && | |||
1006 | (extractVersion != 46) && | |||
1007 | (extractVersion != 50) && | |||
1008 | (extractVersion != 51) && | |||
1009 | (extractVersion != 52) && | |||
1010 | (extractVersion != 61) && | |||
1011 | (extractVersion != 62) && | |||
1012 | (extractVersion != 63) | |||
1013 | ) { | |||
1014 | throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersi | |||
1015 | } | |||
1016 | | |||
1017 | // Local entry flags dont have reserved bit set on. | |||
1018 | if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R | |||
1019 | throw new ZipException("Reserved bit flags cannot be set."); | |||
1020 | } | |||
1021 | | |||
1022 | // Encryption requires extract version >= 20 | |||
1023 | if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { | |||
1024 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0}) | |||
1025 | } | |||
1026 | | |||
1027 | // Strong encryption requires encryption flag to be set and extract version >= 50. | |||
1028 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1029 | if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) { | |||
1030 | throw new ZipException("Strong encryption flag set but encryption flag is not set"); | |||
1031 | } | |||
1032 | | |||
1033 | if (extractVersion < 50) { | |||
1034 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0 | |||
1035 | } | |||
1036 | } | |||
1037 | | |||
1038 | // Patched entries require extract version >= 27 | |||
1039 | if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { | |||
1040 | throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); | |||
1041 | } | |||
1042 | | |||
1043 | // Central header flags match local entry flags. | |||
1044 | if (localFlags != entry.Flags) { | |||
1045 | throw new ZipException("Central header/local header flags mismatch"); | |||
1046 | } | |||
1047 | | |||
1048 | // Central header compression method matches local entry | |||
1049 | if (entry.CompressionMethod != (CompressionMethod)compressionMethod) { | |||
1050 | throw new ZipException("Central header/local header compression method mismatch"); | |||
1051 | } | |||
1052 | | |||
1053 | if (entry.Version != extractVersion) { | |||
1054 | throw new ZipException("Extract version mismatch"); | |||
1055 | } | |||
1056 | | |||
1057 | // Strong encryption and extract version match | |||
1058 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1059 | if (extractVersion < 62) { | |||
1060 | throw new ZipException("Strong encryption flag set but version not high enough"); | |||
1061 | } | |||
1062 | } | |||
1063 | | |||
1064 | if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) { | |||
1065 | if ((fileTime != 0) || (fileDate != 0)) { | |||
1066 | throw new ZipException("Header masked set but date/time values non-zero"); | |||
1067 | } | |||
1068 | } | |||
1069 | | |||
1070 | if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) { | |||
1071 | if (crcValue != (uint)entry.Crc) { | |||
1072 | throw new ZipException("Central header/local header crc mismatch"); | |||
1073 | } | |||
1074 | } | |||
1075 | | |||
1076 | // Crc valid for empty entry. | |||
1077 | // This will also apply to streamed entries where size isnt known and the header cant be patched | |||
1078 | if ((size == 0) && (compressedSize == 0)) { | |||
1079 | if (crcValue != 0) { | |||
1080 | throw new ZipException("Invalid CRC for empty entry"); | |||
1081 | } | |||
1082 | } | |||
1083 | | |||
1084 | // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS str | |||
1085 | // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably | |||
1086 | if (entry.Name.Length > storedNameLength) { | |||
1087 | throw new ZipException("File name length mismatch"); | |||
1088 | } | |||
1089 | | |||
1090 | // Name data has already been read convert it and compare. | |||
1091 | string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); | |||
1092 | | |||
1093 | // Central directory and local entry name match | |||
1094 | if (localName != entry.Name) { | |||
1095 | throw new ZipException("Central header and local header file name mismatch"); | |||
1096 | } | |||
1097 | | |||
1098 | // Directories have zero actual size but can have compressed size | |||
1099 | if (entry.IsDirectory) { | |||
1100 | if (size > 0) { | |||
1101 | throw new ZipException("Directory cannot have size"); | |||
1102 | } | |||
1103 | | |||
1104 | // There may be other cases where the compressed size can be greater than this? | |||
1105 | // If so until details are known we will be strict. | |||
1106 | if (entry.IsCrypted) { | |||
1107 | if (compressedSize > ZipConstants.CryptoHeaderSize + 2) { | |||
1108 | throw new ZipException("Directory compressed size invalid"); | |||
1109 | } | |||
1110 | } else if (compressedSize > 2) { | |||
1111 | // When not compressed the directory size can validly be 2 bytes | |||
1112 | // if the true size wasnt known when data was originally being written. | |||
1113 | // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes | |||
1114 | throw new ZipException("Directory compressed size invalid"); | |||
1115 | } | |||
1116 | } | |||
1117 | | |||
1118 | if (!ZipNameTransform.IsValidName(localName, true)) { | |||
1119 | throw new ZipException("Name is invalid"); | |||
1120 | } | |||
1121 | } | |||
1122 | | |||
1123 | // Tests that apply to both data and header. | |||
1124 | | |||
1125 | // Size can be verified only if it is known in the local header. | |||
1126 | // it will always be known in the central header. | |||
1127 | if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || | |||
1128 | ((size > 0 || compressedSize > 0) && entry.Size > 0)) { | |||
1129 | | |||
1130 | if ((size != 0) | |||
1131 | && (size != entry.Size)) { | |||
1132 | throw new ZipException( | |||
1133 | string.Format("Size mismatch between central header({0}) and local header({1})", | |||
1134 | entry.Size, size)); | |||
1135 | } | |||
1136 | | |||
1137 | if ((compressedSize != 0) | |||
1138 | && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) { | |||
1139 | throw new ZipException( | |||
1140 | string.Format("Compressed size mismatch between central header({0}) and local header({1})", | |||
1141 | entry.CompressedSize, compressedSize)); | |||
1142 | } | |||
1143 | } | |||
1144 | | |||
1145 | int extraLength = storedNameLength + extraDataLength; | |||
1146 | return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; | |||
1147 | } | |||
1148 | } | |||
1149 | | |||
1150 | #endregion | |||
1151 | | |||
1152 | #region Updating | |||
1153 | | |||
1154 | const int DefaultBufferSize = 4096; | |||
1155 | | |||
1156 | /// <summary> | |||
1157 | /// The kind of update to apply. | |||
1158 | /// </summary> | |||
1159 | enum UpdateCommand | |||
1160 | { | |||
1161 | Copy, // Copy original file contents. | |||
1162 | Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. | |||
1163 | Add, // Add a new file to the archive. | |||
1164 | } | |||
1165 | | |||
1166 | #region Properties | |||
1167 | /// <summary> | |||
1168 | /// Get / set the <see cref="INameTransform"/> to apply to names when updating. | |||
1169 | /// </summary> | |||
1170 | public INameTransform NameTransform { | |||
1171 | get { | |||
1172 | return updateEntryFactory_.NameTransform; | |||
1173 | } | |||
1174 | | |||
1175 | set { | |||
1176 | updateEntryFactory_.NameTransform = value; | |||
1177 | } | |||
1178 | } | |||
1179 | | |||
1180 | /// <summary> | |||
1181 | /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values | |||
1182 | /// during updates. | |||
1183 | /// </summary> | |||
1184 | public IEntryFactory EntryFactory { | |||
1185 | get { | |||
1186 | return updateEntryFactory_; | |||
1187 | } | |||
1188 | | |||
1189 | set { | |||
1190 | if (value == null) { | |||
1191 | updateEntryFactory_ = new ZipEntryFactory(); | |||
1192 | } else { | |||
1193 | updateEntryFactory_ = value; | |||
1194 | } | |||
1195 | } | |||
1196 | } | |||
1197 | | |||
1198 | /// <summary> | |||
1199 | /// Get /set the buffer size to be used when updating this zip file. | |||
1200 | /// </summary> | |||
1201 | public int BufferSize { | |||
1202 | get { return bufferSize_; } | |||
1203 | set { | |||
1204 | if (value < 1024) { | |||
1205 | throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); | |||
1206 | } | |||
1207 | | |||
1208 | if (bufferSize_ != value) { | |||
1209 | bufferSize_ = value; | |||
1210 | copyBuffer_ = null; | |||
1211 | } | |||
1212 | } | |||
1213 | } | |||
1214 | | |||
1215 | /// <summary> | |||
1216 | /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. | |||
1217 | /// </summary> | |||
1218 | public bool IsUpdating { | |||
1219 | get { return updates_ != null; } | |||
1220 | } | |||
1221 | | |||
1222 | /// <summary> | |||
1223 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
1224 | /// </summary> | |||
1225 | public UseZip64 UseZip64 { | |||
1226 | get { return useZip64_; } | |||
1227 | set { useZip64_ = value; } | |||
1228 | } | |||
1229 | | |||
1230 | #endregion | |||
1231 | | |||
1232 | #region Immediate updating | |||
1233 | // TBD: Direct form of updating | |||
1234 | // | |||
1235 | // public void Update(IEntryMatcher deleteMatcher) | |||
1236 | // { | |||
1237 | // } | |||
1238 | // | |||
1239 | // public void Update(IScanner addScanner) | |||
1240 | // { | |||
1241 | // } | |||
1242 | #endregion | |||
1243 | | |||
1244 | #region Deferred Updating | |||
1245 | /// <summary> | |||
1246 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1247 | /// </summary> | |||
1248 | /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</p | |||
1249 | /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param | |||
1250 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1251 | /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception> | |||
1252 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1253 | public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) | |||
1254 | { | |||
1255 | if (archiveStorage == null) { | |||
1256 | throw new ArgumentNullException(nameof(archiveStorage)); | |||
1257 | } | |||
1258 | | |||
1259 | if (dataSource == null) { | |||
1260 | throw new ArgumentNullException(nameof(dataSource)); | |||
1261 | } | |||
1262 | | |||
1263 | if (isDisposed_) { | |||
1264 | throw new ObjectDisposedException("ZipFile"); | |||
1265 | } | |||
1266 | | |||
1267 | if (IsEmbeddedArchive) { | |||
1268 | throw new ZipException("Cannot update embedded/SFX archives"); | |||
1269 | } | |||
1270 | | |||
1271 | archiveStorage_ = archiveStorage; | |||
1272 | updateDataSource_ = dataSource; | |||
1273 | | |||
1274 | // NOTE: the baseStream_ may not currently support writing or seeking. | |||
1275 | | |||
1276 | updateIndex_ = new Hashtable(); | |||
1277 | | |||
1278 | updates_ = new ArrayList(entries_.Length); | |||
1279 | foreach (ZipEntry entry in entries_) { | |||
1280 | int index = updates_.Add(new ZipUpdate(entry)); | |||
1281 | updateIndex_.Add(entry.Name, index); | |||
1282 | } | |||
1283 | | |||
1284 | // We must sort by offset before using offset's calculated sizes | |||
1285 | updates_.Sort(new UpdateComparer()); | |||
1286 | | |||
1287 | int idx = 0; | |||
1288 | foreach (ZipUpdate update in updates_) { | |||
1289 | //If last entry, there is no next entry offset to use | |||
1290 | if (idx == updates_.Count - 1) | |||
1291 | break; | |||
1292 | | |||
1293 | update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; | |||
1294 | idx++; | |||
1295 | } | |||
1296 | updateCount_ = updates_.Count; | |||
1297 | | |||
1298 | contentsEdited_ = false; | |||
1299 | commentEdited_ = false; | |||
1300 | newComment_ = null; | |||
1301 | } | |||
1302 | | |||
1303 | /// <summary> | |||
1304 | /// Begin updating to this <see cref="ZipFile"/> archive. | |||
1305 | /// </summary> | |||
1306 | /// <param name="archiveStorage">The storage to use during the update.</param> | |||
1307 | public void BeginUpdate(IArchiveStorage archiveStorage) | |||
1308 | { | |||
1309 | BeginUpdate(archiveStorage, new DynamicDiskDataSource()); | |||
1310 | } | |||
1311 | | |||
1312 | /// <summary> | |||
1313 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1314 | /// </summary> | |||
1315 | /// <seealso cref="BeginUpdate(IArchiveStorage)"/> | |||
1316 | /// <seealso cref="CommitUpdate"></seealso> | |||
1317 | /// <seealso cref="AbortUpdate"></seealso> | |||
1318 | public void BeginUpdate() | |||
1319 | { | |||
1320 | if (Name == null) { | |||
1321 | BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); | |||
1322 | } else { | |||
1323 | BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); | |||
1324 | } | |||
1325 | } | |||
1326 | | |||
1327 | /// <summary> | |||
1328 | /// Commit current updates, updating this archive. | |||
1329 | /// </summary> | |||
1330 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1331 | /// <seealso cref="AbortUpdate"></seealso> | |||
1332 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1333 | public void CommitUpdate() | |||
1334 | { | |||
1335 | if (isDisposed_) { | |||
1336 | throw new ObjectDisposedException("ZipFile"); | |||
1337 | } | |||
1338 | | |||
1339 | CheckUpdating(); | |||
1340 | | |||
1341 | try { | |||
1342 | updateIndex_.Clear(); | |||
1343 | updateIndex_ = null; | |||
1344 | | |||
1345 | if (contentsEdited_) { | |||
1346 | RunUpdates(); | |||
1347 | } else if (commentEdited_) { | |||
1348 | UpdateCommentOnly(); | |||
1349 | } else { | |||
1350 | // Create an empty archive if none existed originally. | |||
1351 | if (entries_.Length == 0) { | |||
1352 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
1353 | using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) { | |||
1354 | zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); | |||
1355 | } | |||
1356 | } | |||
1357 | } | |||
1358 | | |||
1359 | } finally { | |||
1360 | PostUpdateCleanup(); | |||
1361 | } | |||
1362 | } | |||
1363 | | |||
1364 | /// <summary> | |||
1365 | /// Abort updating leaving the archive unchanged. | |||
1366 | /// </summary> | |||
1367 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1368 | /// <seealso cref="CommitUpdate"></seealso> | |||
1369 | public void AbortUpdate() | |||
1370 | { | |||
1371 | PostUpdateCleanup(); | |||
1372 | } | |||
1373 | | |||
1374 | /// <summary> | |||
1375 | /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. | |||
1376 | /// </summary> | |||
1377 | /// <param name="comment">The comment to record.</param> | |||
1378 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1379 | public void SetComment(string comment) | |||
1380 | { | |||
1381 | if (isDisposed_) { | |||
1382 | throw new ObjectDisposedException("ZipFile"); | |||
1383 | } | |||
1384 | | |||
1385 | CheckUpdating(); | |||
1386 | | |||
1387 | newComment_ = new ZipString(comment); | |||
1388 | | |||
1389 | if (newComment_.RawLength > 0xffff) { | |||
1390 | newComment_ = null; | |||
1391 | throw new ZipException("Comment length exceeds maximum - 65535"); | |||
1392 | } | |||
1393 | | |||
1394 | // We dont take account of the original and current comment appearing to be the same | |||
1395 | // as encoding may be different. | |||
1396 | commentEdited_ = true; | |||
1397 | } | |||
1398 | | |||
1399 | #endregion | |||
1400 | | |||
1401 | #region Adding Entries | |||
1402 | | |||
1403 | void AddUpdate(ZipUpdate update) | |||
1404 | { | |||
1405 | contentsEdited_ = true; | |||
1406 | | |||
1407 | int index = FindExistingUpdate(update.Entry.Name); | |||
1408 | | |||
1409 | if (index >= 0) { | |||
1410 | if (updates_[index] == null) { | |||
1411 | updateCount_ += 1; | |||
1412 | } | |||
1413 | | |||
1414 | // Direct replacement is faster than delete and add. | |||
1415 | updates_[index] = update; | |||
1416 | } else { | |||
1417 | index = updates_.Add(update); | |||
1418 | updateCount_ += 1; | |||
1419 | updateIndex_.Add(update.Entry.Name, index); | |||
1420 | } | |||
1421 | } | |||
1422 | | |||
1423 | /// <summary> | |||
1424 | /// Add a new entry to the archive. | |||
1425 | /// </summary> | |||
1426 | /// <param name="fileName">The name of the file to add.</param> | |||
1427 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1428 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> | |||
1429 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1430 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1431 | /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception> | |||
1432 | public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) | |||
1433 | { | |||
1434 | if (fileName == null) { | |||
1435 | throw new ArgumentNullException(nameof(fileName)); | |||
1436 | } | |||
1437 | | |||
1438 | if (isDisposed_) { | |||
1439 | throw new ObjectDisposedException("ZipFile"); | |||
1440 | } | |||
1441 | | |||
1442 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1443 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1444 | } | |||
1445 | | |||
1446 | CheckUpdating(); | |||
1447 | contentsEdited_ = true; | |||
1448 | | |||
1449 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1450 | entry.IsUnicodeText = useUnicodeText; | |||
1451 | entry.CompressionMethod = compressionMethod; | |||
1452 | | |||
1453 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1454 | } | |||
1455 | | |||
1456 | /// <summary> | |||
1457 | /// Add a new entry to the archive. | |||
1458 | /// </summary> | |||
1459 | /// <param name="fileName">The name of the file to add.</param> | |||
1460 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1461 | /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception> | |||
1462 | /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception> | |||
1463 | public void Add(string fileName, CompressionMethod compressionMethod) | |||
1464 | { | |||
1465 | if (fileName == null) { | |||
1466 | throw new ArgumentNullException(nameof(fileName)); | |||
1467 | } | |||
1468 | | |||
1469 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1470 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1471 | } | |||
1472 | | |||
1473 | CheckUpdating(); | |||
1474 | contentsEdited_ = true; | |||
1475 | | |||
1476 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1477 | entry.CompressionMethod = compressionMethod; | |||
1478 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1479 | } | |||
1480 | | |||
1481 | /// <summary> | |||
1482 | /// Add a file to the archive. | |||
1483 | /// </summary> | |||
1484 | /// <param name="fileName">The name of the file to add.</param> | |||
1485 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1486 | public void Add(string fileName) | |||
1487 | { | |||
1488 | if (fileName == null) { | |||
1489 | throw new ArgumentNullException(nameof(fileName)); | |||
1490 | } | |||
1491 | | |||
1492 | CheckUpdating(); | |||
1493 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); | |||
1494 | } | |||
1495 | | |||
1496 | /// <summary> | |||
1497 | /// Add a file to the archive. | |||
1498 | /// </summary> | |||
1499 | /// <param name="fileName">The name of the file to add.</param> | |||
1500 | /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param> | |||
1501 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1502 | public void Add(string fileName, string entryName) | |||
1503 | { | |||
1504 | if (fileName == null) { | |||
1505 | throw new ArgumentNullException(nameof(fileName)); | |||
1506 | } | |||
1507 | | |||
1508 | if (entryName == null) { | |||
1509 | throw new ArgumentNullException(nameof(entryName)); | |||
1510 | } | |||
1511 | | |||
1512 | CheckUpdating(); | |||
1513 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); | |||
1514 | } | |||
1515 | | |||
1516 | | |||
1517 | /// <summary> | |||
1518 | /// Add a file entry with data. | |||
1519 | /// </summary> | |||
1520 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1521 | /// <param name="entryName">The name to give to the entry.</param> | |||
1522 | public void Add(IStaticDataSource dataSource, string entryName) | |||
1523 | { | |||
1524 | if (dataSource == null) { | |||
1525 | throw new ArgumentNullException(nameof(dataSource)); | |||
1526 | } | |||
1527 | | |||
1528 | if (entryName == null) { | |||
1529 | throw new ArgumentNullException(nameof(entryName)); | |||
1530 | } | |||
1531 | | |||
1532 | CheckUpdating(); | |||
1533 | AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); | |||
1534 | } | |||
1535 | | |||
1536 | /// <summary> | |||
1537 | /// Add a file entry with data. | |||
1538 | /// </summary> | |||
1539 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1540 | /// <param name="entryName">The name to give to the entry.</param> | |||
1541 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1542 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
1543 | { | |||
1544 | if (dataSource == null) { | |||
1545 | throw new ArgumentNullException(nameof(dataSource)); | |||
1546 | } | |||
1547 | | |||
1548 | if (entryName == null) { | |||
1549 | throw new ArgumentNullException(nameof(entryName)); | |||
1550 | } | |||
1551 | | |||
1552 | CheckUpdating(); | |||
1553 | | |||
1554 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1555 | entry.CompressionMethod = compressionMethod; | |||
1556 | | |||
1557 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1558 | } | |||
1559 | | |||
1560 | /// <summary> | |||
1561 | /// Add a file entry with data. | |||
1562 | /// </summary> | |||
1563 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1564 | /// <param name="entryName">The name to give to the entry.</param> | |||
1565 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1566 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> | |||
1567 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicode | |||
1568 | { | |||
1569 | if (dataSource == null) { | |||
1570 | throw new ArgumentNullException(nameof(dataSource)); | |||
1571 | } | |||
1572 | | |||
1573 | if (entryName == null) { | |||
1574 | throw new ArgumentNullException(nameof(entryName)); | |||
1575 | } | |||
1576 | | |||
1577 | CheckUpdating(); | |||
1578 | | |||
1579 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1580 | entry.IsUnicodeText = useUnicodeText; | |||
1581 | entry.CompressionMethod = compressionMethod; | |||
1582 | | |||
1583 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1584 | } | |||
1585 | | |||
1586 | /// <summary> | |||
1587 | /// Add a <see cref="ZipEntry"/> that contains no data. | |||
1588 | /// </summary> | |||
1589 | /// <param name="entry">The entry to add.</param> | |||
1590 | /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> | |||
1591 | public void Add(ZipEntry entry) | |||
1592 | { | |||
1593 | if (entry == null) { | |||
1594 | throw new ArgumentNullException(nameof(entry)); | |||
1595 | } | |||
1596 | | |||
1597 | CheckUpdating(); | |||
1598 | | |||
1599 | if ((entry.Size != 0) || (entry.CompressedSize != 0)) { | |||
1600 | throw new ZipException("Entry cannot have any data"); | |||
1601 | } | |||
1602 | | |||
1603 | AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); | |||
1604 | } | |||
1605 | | |||
1606 | /// <summary> | |||
1607 | /// Add a directory entry to the archive. | |||
1608 | /// </summary> | |||
1609 | /// <param name="directoryName">The directory to add.</param> | |||
1610 | public void AddDirectory(string directoryName) | |||
1611 | { | |||
1612 | if (directoryName == null) { | |||
1613 | throw new ArgumentNullException(nameof(directoryName)); | |||
1614 | } | |||
1615 | | |||
1616 | CheckUpdating(); | |||
1617 | | |||
1618 | ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); | |||
1619 | AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); | |||
1620 | } | |||
1621 | | |||
1622 | #endregion | |||
1623 | | |||
1624 | #region Modifying Entries | |||
1625 | /* Modify not yet ready for public consumption. | |||
1626 | Direct modification of an entry should not overwrite original data before its read. | |||
1627 | Safe mode is trivial in this sense. | |||
1628 | public void Modify(ZipEntry original, ZipEntry updated) | |||
1629 | { | |||
1630 | if ( original == null ) { | |||
1631 | throw new ArgumentNullException("original"); | |||
1632 | } | |||
1633 | | |||
1634 | if ( updated == null ) { | |||
1635 | throw new ArgumentNullException("updated"); | |||
1636 | } | |||
1637 | | |||
1638 | CheckUpdating(); | |||
1639 | contentsEdited_ = true; | |||
1640 | updates_.Add(new ZipUpdate(original, updated)); | |||
1641 | } | |||
1642 | */ | |||
1643 | #endregion | |||
1644 | | |||
1645 | #region Deleting Entries | |||
1646 | /// <summary> | |||
1647 | /// Delete an entry by name | |||
1648 | /// </summary> | |||
1649 | /// <param name="fileName">The filename to delete</param> | |||
1650 | /// <returns>True if the entry was found and deleted; false otherwise.</returns> | |||
1651 | public bool Delete(string fileName) | |||
1652 | { | |||
1653 | if (fileName == null) { | |||
1654 | throw new ArgumentNullException(nameof(fileName)); | |||
1655 | } | |||
1656 | | |||
1657 | CheckUpdating(); | |||
1658 | | |||
1659 | bool result = false; | |||
1660 | int index = FindExistingUpdate(fileName); | |||
1661 | if ((index >= 0) && (updates_[index] != null)) { | |||
1662 | result = true; | |||
1663 | contentsEdited_ = true; | |||
1664 | updates_[index] = null; | |||
1665 | updateCount_ -= 1; | |||
1666 | } else { | |||
1667 | throw new ZipException("Cannot find entry to delete"); | |||
1668 | } | |||
1669 | return result; | |||
1670 | } | |||
1671 | | |||
1672 | /// <summary> | |||
1673 | /// Delete a <see cref="ZipEntry"/> from the archive. | |||
1674 | /// </summary> | |||
1675 | /// <param name="entry">The entry to delete.</param> | |||
1676 | public void Delete(ZipEntry entry) | |||
1677 | { | |||
1678 | if (entry == null) { | |||
1679 | throw new ArgumentNullException(nameof(entry)); | |||
1680 | } | |||
1681 | | |||
1682 | CheckUpdating(); | |||
1683 | | |||
1684 | int index = FindExistingUpdate(entry); | |||
1685 | if (index >= 0) { | |||
1686 | contentsEdited_ = true; | |||
1687 | updates_[index] = null; | |||
1688 | updateCount_ -= 1; | |||
1689 | } else { | |||
1690 | throw new ZipException("Cannot find entry to delete"); | |||
1691 | } | |||
1692 | } | |||
1693 | | |||
1694 | #endregion | |||
1695 | | |||
1696 | #region Update Support | |||
1697 | | |||
1698 | #region Writing Values/Headers | |||
1699 | void WriteLEShort(int value) | |||
1700 | { | |||
1701 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1702 | baseStream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
1703 | } | |||
1704 | | |||
1705 | /// <summary> | |||
1706 | /// Write an unsigned short in little endian byte order. | |||
1707 | /// </summary> | |||
1708 | void WriteLEUshort(ushort value) | |||
1709 | { | |||
1710 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1711 | baseStream_.WriteByte((byte)(value >> 8)); | |||
1712 | } | |||
1713 | | |||
1714 | /// <summary> | |||
1715 | /// Write an int in little endian byte order. | |||
1716 | /// </summary> | |||
1717 | void WriteLEInt(int value) | |||
1718 | { | |||
1719 | WriteLEShort(value & 0xffff); | |||
1720 | WriteLEShort(value >> 16); | |||
1721 | } | |||
1722 | | |||
1723 | /// <summary> | |||
1724 | /// Write an unsigned int in little endian byte order. | |||
1725 | /// </summary> | |||
1726 | void WriteLEUint(uint value) | |||
1727 | { | |||
1728 | WriteLEUshort((ushort)(value & 0xffff)); | |||
1729 | WriteLEUshort((ushort)(value >> 16)); | |||
1730 | } | |||
1731 | | |||
1732 | /// <summary> | |||
1733 | /// Write a long in little endian byte order. | |||
1734 | /// </summary> | |||
1735 | void WriteLeLong(long value) | |||
1736 | { | |||
1737 | WriteLEInt((int)(value & 0xffffffff)); | |||
1738 | WriteLEInt((int)(value >> 32)); | |||
1739 | } | |||
1740 | | |||
1741 | void WriteLEUlong(ulong value) | |||
1742 | { | |||
1743 | WriteLEUint((uint)(value & 0xffffffff)); | |||
1744 | WriteLEUint((uint)(value >> 32)); | |||
1745 | } | |||
1746 | | |||
1747 | void WriteLocalEntryHeader(ZipUpdate update) | |||
1748 | { | |||
1749 | ZipEntry entry = update.OutEntry; | |||
1750 | | |||
1751 | // TODO: Local offset will require adjusting for multi-disk zip files. | |||
1752 | entry.Offset = baseStream_.Position; | |||
1753 | | |||
1754 | // TODO: Need to clear any entry flags that dont make sense or throw an exception here. | |||
1755 | if (update.Command != UpdateCommand.Copy) { | |||
1756 | if (entry.CompressionMethod == CompressionMethod.Deflated) { | |||
1757 | if (entry.Size == 0) { | |||
1758 | // No need to compress - no data. | |||
1759 | entry.CompressedSize = entry.Size; | |||
1760 | entry.Crc = 0; | |||
1761 | entry.CompressionMethod = CompressionMethod.Stored; | |||
1762 | } | |||
1763 | } else if (entry.CompressionMethod == CompressionMethod.Stored) { | |||
1764 | entry.Flags &= ~(int)GeneralBitFlags.Descriptor; | |||
1765 | } | |||
1766 | | |||
1767 | if (HaveKeys) { | |||
1768 | entry.IsCrypted = true; | |||
1769 | if (entry.Crc < 0) { | |||
1770 | entry.Flags |= (int)GeneralBitFlags.Descriptor; | |||
1771 | } | |||
1772 | } else { | |||
1773 | entry.IsCrypted = false; | |||
1774 | } | |||
1775 | | |||
1776 | switch (useZip64_) { | |||
1777 | case UseZip64.Dynamic: | |||
1778 | if (entry.Size < 0) { | |||
1779 | entry.ForceZip64(); | |||
1780 | } | |||
1781 | break; | |||
1782 | | |||
1783 | case UseZip64.On: | |||
1784 | entry.ForceZip64(); | |||
1785 | break; | |||
1786 | | |||
1787 | case UseZip64.Off: | |||
1788 | // Do nothing. The entry itself may be using Zip64 independantly. | |||
1789 | break; | |||
1790 | } | |||
1791 | } | |||
1792 | | |||
1793 | // Write the local file header | |||
1794 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
1795 | | |||
1796 | WriteLEShort(entry.Version); | |||
1797 | WriteLEShort(entry.Flags); | |||
1798 | | |||
1799 | WriteLEShort((byte)entry.CompressionMethod); | |||
1800 | WriteLEInt((int)entry.DosTime); | |||
1801 | | |||
1802 | if (!entry.HasCrc) { | |||
1803 | // Note patch address for updating CRC later. | |||
1804 | update.CrcPatchOffset = baseStream_.Position; | |||
1805 | WriteLEInt((int)0); | |||
1806 | } else { | |||
1807 | WriteLEInt(unchecked((int)entry.Crc)); | |||
1808 | } | |||
1809 | | |||
1810 | if (entry.LocalHeaderRequiresZip64) { | |||
1811 | WriteLEInt(-1); | |||
1812 | WriteLEInt(-1); | |||
1813 | } else { | |||
1814 | if ((entry.CompressedSize < 0) || (entry.Size < 0)) { | |||
1815 | update.SizePatchOffset = baseStream_.Position; | |||
1816 | } | |||
1817 | | |||
1818 | WriteLEInt((int)entry.CompressedSize); | |||
1819 | WriteLEInt((int)entry.Size); | |||
1820 | } | |||
1821 | | |||
1822 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1823 | | |||
1824 | if (name.Length > 0xFFFF) { | |||
1825 | throw new ZipException("Entry name too long."); | |||
1826 | } | |||
1827 | | |||
1828 | var ed = new ZipExtraData(entry.ExtraData); | |||
1829 | | |||
1830 | if (entry.LocalHeaderRequiresZip64) { | |||
1831 | ed.StartNewEntry(); | |||
1832 | | |||
1833 | // Local entry header always includes size and compressed size. | |||
1834 | // NOTE the order of these fields is reversed when compared to the normal headers! | |||
1835 | ed.AddLeLong(entry.Size); | |||
1836 | ed.AddLeLong(entry.CompressedSize); | |||
1837 | ed.AddNewEntry(1); | |||
1838 | } else { | |||
1839 | ed.Delete(1); | |||
1840 | } | |||
1841 | | |||
1842 | entry.ExtraData = ed.GetEntryData(); | |||
1843 | | |||
1844 | WriteLEShort(name.Length); | |||
1845 | WriteLEShort(entry.ExtraData.Length); | |||
1846 | | |||
1847 | if (name.Length > 0) { | |||
1848 | baseStream_.Write(name, 0, name.Length); | |||
1849 | } | |||
1850 | | |||
1851 | if (entry.LocalHeaderRequiresZip64) { | |||
1852 | if (!ed.Find(1)) { | |||
1853 | throw new ZipException("Internal error cannot find extra data"); | |||
1854 | } | |||
1855 | | |||
1856 | update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; | |||
1857 | } | |||
1858 | | |||
1859 | if (entry.ExtraData.Length > 0) { | |||
1860 | baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); | |||
1861 | } | |||
1862 | } | |||
1863 | | |||
1864 | int WriteCentralDirectoryHeader(ZipEntry entry) | |||
1865 | { | |||
1866 | if (entry.CompressedSize < 0) { | |||
1867 | throw new ZipException("Attempt to write central directory entry with unknown csize"); | |||
1868 | } | |||
1869 | | |||
1870 | if (entry.Size < 0) { | |||
1871 | throw new ZipException("Attempt to write central directory entry with unknown size"); | |||
1872 | } | |||
1873 | | |||
1874 | if (entry.Crc < 0) { | |||
1875 | throw new ZipException("Attempt to write central directory entry with unknown crc"); | |||
1876 | } | |||
1877 | | |||
1878 | // Write the central file header | |||
1879 | WriteLEInt(ZipConstants.CentralHeaderSignature); | |||
1880 | | |||
1881 | // Version made by | |||
1882 | WriteLEShort(ZipConstants.VersionMadeBy); | |||
1883 | | |||
1884 | // Version required to extract | |||
1885 | WriteLEShort(entry.Version); | |||
1886 | | |||
1887 | WriteLEShort(entry.Flags); | |||
1888 | | |||
1889 | unchecked { | |||
1890 | WriteLEShort((byte)entry.CompressionMethod); | |||
1891 | WriteLEInt((int)entry.DosTime); | |||
1892 | WriteLEInt((int)entry.Crc); | |||
1893 | } | |||
1894 | | |||
1895 | if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { | |||
1896 | WriteLEInt(-1); | |||
1897 | } else { | |||
1898 | WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); | |||
1899 | } | |||
1900 | | |||
1901 | if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { | |||
1902 | WriteLEInt(-1); | |||
1903 | } else { | |||
1904 | WriteLEInt((int)entry.Size); | |||
1905 | } | |||
1906 | | |||
1907 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1908 | | |||
1909 | if (name.Length > 0xFFFF) { | |||
1910 | throw new ZipException("Entry name is too long."); | |||
1911 | } | |||
1912 | | |||
1913 | WriteLEShort(name.Length); | |||
1914 | | |||
1915 | // Central header extra data is different to local header version so regenerate. | |||
1916 | var ed = new ZipExtraData(entry.ExtraData); | |||
1917 | | |||
1918 | if (entry.CentralHeaderRequiresZip64) { | |||
1919 | ed.StartNewEntry(); | |||
1920 | | |||
1921 | if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1922 | ed.AddLeLong(entry.Size); | |||
1923 | } | |||
1924 | | |||
1925 | if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1926 | ed.AddLeLong(entry.CompressedSize); | |||
1927 | } | |||
1928 | | |||
1929 | if (entry.Offset >= 0xffffffff) { | |||
1930 | ed.AddLeLong(entry.Offset); | |||
1931 | } | |||
1932 | | |||
1933 | // Number of disk on which this file starts isnt supported and is never written here. | |||
1934 | ed.AddNewEntry(1); | |||
1935 | } else { | |||
1936 | // Should have already be done when local header was added. | |||
1937 | ed.Delete(1); | |||
1938 | } | |||
1939 | | |||
1940 | byte[] centralExtraData = ed.GetEntryData(); | |||
1941 | | |||
1942 | WriteLEShort(centralExtraData.Length); | |||
1943 | WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); | |||
1944 | | |||
1945 | WriteLEShort(0); // disk number | |||
1946 | WriteLEShort(0); // internal file attributes | |||
1947 | | |||
1948 | // External file attributes... | |||
1949 | if (entry.ExternalFileAttributes != -1) { | |||
1950 | WriteLEInt(entry.ExternalFileAttributes); | |||
1951 | } else { | |||
1952 | if (entry.IsDirectory) { | |||
1953 | WriteLEUint(16); | |||
1954 | } else { | |||
1955 | WriteLEUint(0); | |||
1956 | } | |||
1957 | } | |||
1958 | | |||
1959 | if (entry.Offset >= 0xffffffff) { | |||
1960 | WriteLEUint(0xffffffff); | |||
1961 | } else { | |||
1962 | WriteLEUint((uint)(int)entry.Offset); | |||
1963 | } | |||
1964 | | |||
1965 | if (name.Length > 0) { | |||
1966 | baseStream_.Write(name, 0, name.Length); | |||
1967 | } | |||
1968 | | |||
1969 | if (centralExtraData.Length > 0) { | |||
1970 | baseStream_.Write(centralExtraData, 0, centralExtraData.Length); | |||
1971 | } | |||
1972 | | |||
1973 | byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; | |||
1974 | | |||
1975 | if (rawComment.Length > 0) { | |||
1976 | baseStream_.Write(rawComment, 0, rawComment.Length); | |||
1977 | } | |||
1978 | | |||
1979 | return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; | |||
1980 | } | |||
1981 | #endregion | |||
1982 | | |||
1983 | void PostUpdateCleanup() | |||
1984 | { | |||
1985 | updateDataSource_ = null; | |||
1986 | updates_ = null; | |||
1987 | updateIndex_ = null; | |||
1988 | | |||
1989 | if (archiveStorage_ != null) { | |||
1990 | archiveStorage_.Dispose(); | |||
1991 | archiveStorage_ = null; | |||
1992 | } | |||
1993 | } | |||
1994 | | |||
1995 | string GetTransformedFileName(string name) | |||
1996 | { | |||
1997 | INameTransform transform = NameTransform; | |||
1998 | return (transform != null) ? | |||
1999 | transform.TransformFile(name) : | |||
2000 | name; | |||
2001 | } | |||
2002 | | |||
2003 | string GetTransformedDirectoryName(string name) | |||
2004 | { | |||
2005 | INameTransform transform = NameTransform; | |||
2006 | return (transform != null) ? | |||
2007 | transform.TransformDirectory(name) : | |||
2008 | name; | |||
2009 | } | |||
2010 | | |||
2011 | /// <summary> | |||
2012 | /// Get a raw memory buffer. | |||
2013 | /// </summary> | |||
2014 | /// <returns>Returns a raw memory buffer.</returns> | |||
2015 | byte[] GetBuffer() | |||
2016 | { | |||
2017 | if (copyBuffer_ == null) { | |||
2018 | copyBuffer_ = new byte[bufferSize_]; | |||
2019 | } | |||
2020 | return copyBuffer_; | |||
2021 | } | |||
2022 | | |||
2023 | void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) | |||
2024 | { | |||
2025 | int bytesToCopy = GetDescriptorSize(update); | |||
2026 | | |||
2027 | if (bytesToCopy > 0) { | |||
2028 | byte[] buffer = GetBuffer(); | |||
2029 | | |||
2030 | while (bytesToCopy > 0) { | |||
2031 | int readSize = Math.Min(buffer.Length, bytesToCopy); | |||
2032 | | |||
2033 | int bytesRead = source.Read(buffer, 0, readSize); | |||
2034 | if (bytesRead > 0) { | |||
2035 | dest.Write(buffer, 0, bytesRead); | |||
2036 | bytesToCopy -= bytesRead; | |||
2037 | } else { | |||
2038 | throw new ZipException("Unxpected end of stream"); | |||
2039 | } | |||
2040 | } | |||
2041 | } | |||
2042 | } | |||
2043 | | |||
2044 | void CopyBytes(ZipUpdate update, Stream destination, Stream source, | |||
2045 | long bytesToCopy, bool updateCrc) | |||
2046 | { | |||
2047 | if (destination == source) { | |||
2048 | throw new InvalidOperationException("Destination and source are the same"); | |||
2049 | } | |||
2050 | | |||
2051 | // NOTE: Compressed size is updated elsewhere. | |||
2052 | var crc = new Crc32(); | |||
2053 | byte[] buffer = GetBuffer(); | |||
2054 | | |||
2055 | long targetBytes = bytesToCopy; | |||
2056 | long totalBytesRead = 0; | |||
2057 | | |||
2058 | int bytesRead; | |||
2059 | do { | |||
2060 | int readSize = buffer.Length; | |||
2061 | | |||
2062 | if (bytesToCopy < readSize) { | |||
2063 | readSize = (int)bytesToCopy; | |||
2064 | } | |||
2065 | | |||
2066 | bytesRead = source.Read(buffer, 0, readSize); | |||
2067 | if (bytesRead > 0) { | |||
2068 | if (updateCrc) { | |||
2069 | crc.Update(buffer, 0, bytesRead); | |||
2070 | } | |||
2071 | destination.Write(buffer, 0, bytesRead); | |||
2072 | bytesToCopy -= bytesRead; | |||
2073 | totalBytesRead += bytesRead; | |||
2074 | } | |||
2075 | } | |||
2076 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2077 | | |||
2078 | if (totalBytesRead != targetBytes) { | |||
2079 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2080 | } | |||
2081 | | |||
2082 | if (updateCrc) { | |||
2083 | update.OutEntry.Crc = crc.Value; | |||
2084 | } | |||
2085 | } | |||
2086 | | |||
2087 | /// <summary> | |||
2088 | /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. | |||
2089 | /// </summary> | |||
2090 | /// <param name="update">The update to get the size for.</param> | |||
2091 | /// <returns>The descriptor size, zero if there isnt one.</returns> | |||
2092 | int GetDescriptorSize(ZipUpdate update) | |||
2093 | { | |||
2094 | int result = 0; | |||
2095 | if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
2096 | result = ZipConstants.DataDescriptorSize - 4; | |||
2097 | if (update.Entry.LocalHeaderRequiresZip64) { | |||
2098 | result = ZipConstants.Zip64DataDescriptorSize - 4; | |||
2099 | } | |||
2100 | } | |||
2101 | return result; | |||
2102 | } | |||
2103 | | |||
2104 | void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) | |||
2105 | { | |||
2106 | int bytesToCopy = GetDescriptorSize(update); | |||
2107 | | |||
2108 | while (bytesToCopy > 0) { | |||
2109 | var readSize = (int)bytesToCopy; | |||
2110 | byte[] buffer = GetBuffer(); | |||
2111 | | |||
2112 | stream.Position = sourcePosition; | |||
2113 | int bytesRead = stream.Read(buffer, 0, readSize); | |||
2114 | if (bytesRead > 0) { | |||
2115 | stream.Position = destinationPosition; | |||
2116 | stream.Write(buffer, 0, bytesRead); | |||
2117 | bytesToCopy -= bytesRead; | |||
2118 | destinationPosition += bytesRead; | |||
2119 | sourcePosition += bytesRead; | |||
2120 | } else { | |||
2121 | throw new ZipException("Unxpected end of stream"); | |||
2122 | } | |||
2123 | } | |||
2124 | } | |||
2125 | | |||
2126 | void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou | |||
2127 | { | |||
2128 | long bytesToCopy = update.Entry.CompressedSize; | |||
2129 | | |||
2130 | // NOTE: Compressed size is updated elsewhere. | |||
2131 | var crc = new Crc32(); | |||
2132 | byte[] buffer = GetBuffer(); | |||
2133 | | |||
2134 | long targetBytes = bytesToCopy; | |||
2135 | long totalBytesRead = 0; | |||
2136 | | |||
2137 | int bytesRead; | |||
2138 | do { | |||
2139 | int readSize = buffer.Length; | |||
2140 | | |||
2141 | if (bytesToCopy < readSize) { | |||
2142 | readSize = (int)bytesToCopy; | |||
2143 | } | |||
2144 | | |||
2145 | stream.Position = sourcePosition; | |||
2146 | bytesRead = stream.Read(buffer, 0, readSize); | |||
2147 | if (bytesRead > 0) { | |||
2148 | if (updateCrc) { | |||
2149 | crc.Update(buffer, 0, bytesRead); | |||
2150 | } | |||
2151 | stream.Position = destinationPosition; | |||
2152 | stream.Write(buffer, 0, bytesRead); | |||
2153 | | |||
2154 | destinationPosition += bytesRead; | |||
2155 | sourcePosition += bytesRead; | |||
2156 | bytesToCopy -= bytesRead; | |||
2157 | totalBytesRead += bytesRead; | |||
2158 | } | |||
2159 | } | |||
2160 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2161 | | |||
2162 | if (totalBytesRead != targetBytes) { | |||
2163 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2164 | } | |||
2165 | | |||
2166 | if (updateCrc) { | |||
2167 | update.OutEntry.Crc = crc.Value; | |||
2168 | } | |||
2169 | } | |||
2170 | | |||
2171 | int FindExistingUpdate(ZipEntry entry) | |||
2172 | { | |||
2173 | int result = -1; | |||
2174 | string convertedName = GetTransformedFileName(entry.Name); | |||
2175 | | |||
2176 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2177 | result = (int)updateIndex_[convertedName]; | |||
2178 | } | |||
2179 | /* | |||
2180 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2181 | // for CF? | |||
2182 | for (int index = 0; index < updates_.Count; ++index) | |||
2183 | { | |||
2184 | ZipUpdate zu = ( ZipUpdate )updates_[index]; | |||
2185 | if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && | |||
2186 | (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { | |||
2187 | result = index; | |||
2188 | break; | |||
2189 | } | |||
2190 | } | |||
2191 | */ | |||
2192 | return result; | |||
2193 | } | |||
2194 | | |||
2195 | int FindExistingUpdate(string fileName) | |||
2196 | { | |||
2197 | int result = -1; | |||
2198 | | |||
2199 | string convertedName = GetTransformedFileName(fileName); | |||
2200 | | |||
2201 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2202 | result = (int)updateIndex_[convertedName]; | |||
2203 | } | |||
2204 | | |||
2205 | /* | |||
2206 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2207 | // for CF? | |||
2208 | for ( int index = 0; index < updates_.Count; ++index ) { | |||
2209 | if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, | |||
2210 | true, CultureInfo.InvariantCulture) == 0 ) { | |||
2211 | result = index; | |||
2212 | break; | |||
2213 | } | |||
2214 | } | |||
2215 | */ | |||
2216 | | |||
2217 | return result; | |||
2218 | } | |||
2219 | | |||
2220 | /// <summary> | |||
2221 | /// Get an output stream for the specified <see cref="ZipEntry"/> | |||
2222 | /// </summary> | |||
2223 | /// <param name="entry">The entry to get an output stream for.</param> | |||
2224 | /// <returns>The output stream obtained for the entry.</returns> | |||
2225 | Stream GetOutputStream(ZipEntry entry) | |||
2226 | { | |||
2227 | Stream result = baseStream_; | |||
2228 | | |||
2229 | if (entry.IsCrypted == true) { | |||
2230 | result = CreateAndInitEncryptionStream(result, entry); | |||
2231 | } | |||
2232 | | |||
2233 | switch (entry.CompressionMethod) { | |||
2234 | case CompressionMethod.Stored: | |||
2235 | result = new UncompressedStream(result); | |||
2236 | break; | |||
2237 | | |||
2238 | case CompressionMethod.Deflated: | |||
2239 | var dos = new DeflaterOutputStream(result, new Deflater(9, true)); | |||
2240 | dos.IsStreamOwner = false; | |||
2241 | result = dos; | |||
2242 | break; | |||
2243 | | |||
2244 | default: | |||
2245 | throw new ZipException("Unknown compression method " + entry.CompressionMethod); | |||
2246 | } | |||
2247 | return result; | |||
2248 | } | |||
2249 | | |||
2250 | void AddEntry(ZipFile workFile, ZipUpdate update) | |||
2251 | { | |||
2252 | Stream source = null; | |||
2253 | | |||
2254 | if (update.Entry.IsFile) { | |||
2255 | source = update.GetSource(); | |||
2256 | | |||
2257 | if (source == null) { | |||
2258 | source = updateDataSource_.GetSource(update.Entry, update.Filename); | |||
2259 | } | |||
2260 | } | |||
2261 | | |||
2262 | if (source != null) { | |||
2263 | using (source) { | |||
2264 | long sourceStreamLength = source.Length; | |||
2265 | if (update.OutEntry.Size < 0) { | |||
2266 | update.OutEntry.Size = sourceStreamLength; | |||
2267 | } else { | |||
2268 | // Check for errant entries. | |||
2269 | if (update.OutEntry.Size != sourceStreamLength) { | |||
2270 | throw new ZipException("Entry size/stream size mismatch"); | |||
2271 | } | |||
2272 | } | |||
2273 | | |||
2274 | workFile.WriteLocalEntryHeader(update); | |||
2275 | | |||
2276 | long dataStart = workFile.baseStream_.Position; | |||
2277 | | |||
2278 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2279 | CopyBytes(update, output, source, sourceStreamLength, true); | |||
2280 | } | |||
2281 | | |||
2282 | long dataEnd = workFile.baseStream_.Position; | |||
2283 | update.OutEntry.CompressedSize = dataEnd - dataStart; | |||
2284 | | |||
2285 | if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) { | |||
2286 | var helper = new ZipHelperStream(workFile.baseStream_); | |||
2287 | helper.WriteDataDescriptor(update.OutEntry); | |||
2288 | } | |||
2289 | } | |||
2290 | } else { | |||
2291 | workFile.WriteLocalEntryHeader(update); | |||
2292 | update.OutEntry.CompressedSize = 0; | |||
2293 | } | |||
2294 | | |||
2295 | } | |||
2296 | | |||
2297 | void ModifyEntry(ZipFile workFile, ZipUpdate update) | |||
2298 | { | |||
2299 | workFile.WriteLocalEntryHeader(update); | |||
2300 | long dataStart = workFile.baseStream_.Position; | |||
2301 | | |||
2302 | // TODO: This is slow if the changes don't effect the data!! | |||
2303 | if (update.Entry.IsFile && (update.Filename != null)) { | |||
2304 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2305 | using (Stream source = this.GetInputStream(update.Entry)) { | |||
2306 | CopyBytes(update, output, source, source.Length, true); | |||
2307 | } | |||
2308 | } | |||
2309 | } | |||
2310 | | |||
2311 | long dataEnd = workFile.baseStream_.Position; | |||
2312 | update.Entry.CompressedSize = dataEnd - dataStart; | |||
2313 | } | |||
2314 | | |||
2315 | void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) | |||
2316 | { | |||
2317 | bool skipOver = false || update.Entry.Offset == destinationPosition; | |||
2318 | | |||
2319 | if (!skipOver) { | |||
2320 | baseStream_.Position = destinationPosition; | |||
2321 | workFile.WriteLocalEntryHeader(update); | |||
2322 | destinationPosition = baseStream_.Position; | |||
2323 | } | |||
2324 | | |||
2325 | long sourcePosition = 0; | |||
2326 | | |||
2327 | const int NameLengthOffset = 26; | |||
2328 | | |||
2329 | // TODO: Add base for SFX friendly handling | |||
2330 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2331 | | |||
2332 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2333 | | |||
2334 | // Clumsy way of handling retrieving the original name and extra data length for now. | |||
2335 | // TODO: Stop re-reading name and data length in CopyEntryDirect. | |||
2336 | uint nameLength = ReadLEUshort(); | |||
2337 | uint extraLength = ReadLEUshort(); | |||
2338 | | |||
2339 | sourcePosition = baseStream_.Position + nameLength + extraLength; | |||
2340 | | |||
2341 | if (skipOver) { | |||
2342 | if (update.OffsetBasedSize != -1) | |||
2343 | destinationPosition += update.OffsetBasedSize; | |||
2344 | else | |||
2345 | // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) ar | |||
2346 | // WinZip produces a warning on these entries: | |||
2347 | // "caution: value of lrec.csize (compressed size) changed from ..." | |||
2348 | destinationPosition += | |||
2349 | (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size | |||
2350 | update.Entry.CompressedSize + GetDescriptorSize(update); | |||
2351 | } else { | |||
2352 | if (update.Entry.CompressedSize > 0) { | |||
2353 | CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); | |||
2354 | } | |||
2355 | CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); | |||
2356 | } | |||
2357 | } | |||
2358 | | |||
2359 | void CopyEntry(ZipFile workFile, ZipUpdate update) | |||
2360 | { | |||
2361 | workFile.WriteLocalEntryHeader(update); | |||
2362 | | |||
2363 | if (update.Entry.CompressedSize > 0) { | |||
2364 | const int NameLengthOffset = 26; | |||
2365 | | |||
2366 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2367 | | |||
2368 | // TODO: This wont work for SFX files! | |||
2369 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2370 | | |||
2371 | uint nameLength = ReadLEUshort(); | |||
2372 | uint extraLength = ReadLEUshort(); | |||
2373 | | |||
2374 | baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); | |||
2375 | | |||
2376 | CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); | |||
2377 | } | |||
2378 | CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); | |||
2379 | } | |||
2380 | | |||
2381 | void Reopen(Stream source) | |||
2382 | { | |||
2383 | if (source == null) { | |||
2384 | throw new ZipException("Failed to reopen archive - no source"); | |||
2385 | } | |||
2386 | | |||
2387 | isNewArchive_ = false; | |||
2388 | baseStream_ = source; | |||
2389 | ReadEntries(); | |||
2390 | } | |||
2391 | | |||
2392 | void Reopen() | |||
2393 | { | |||
2394 | if (Name == null) { | |||
2395 | throw new InvalidOperationException("Name is not known cannot Reopen"); | |||
2396 | } | |||
2397 | | |||
2398 | Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); | |||
2399 | } | |||
2400 | | |||
2401 | void UpdateCommentOnly() | |||
2402 | { | |||
2403 | long baseLength = baseStream_.Length; | |||
2404 | | |||
2405 | ZipHelperStream updateFile = null; | |||
2406 | | |||
2407 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2408 | Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); | |||
2409 | updateFile = new ZipHelperStream(copyStream); | |||
2410 | updateFile.IsStreamOwner = true; | |||
2411 | | |||
2412 | baseStream_.Close(); | |||
2413 | baseStream_ = null; | |||
2414 | } else { | |||
2415 | if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2416 | // TODO: archiveStorage wasnt originally intended for this use. | |||
2417 | // Need to revisit this to tidy up handling as archive storage currently doesnt | |||
2418 | // handle the original stream well. | |||
2419 | // The problem is when using an existing zip archive with an in memory archive storage. | |||
2420 | // The open stream wont support writing but the memory storage should open the same file not an in memory one. | |||
2421 | | |||
2422 | // Need to tidy up the archive storage interface and contract basically. | |||
2423 | baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); | |||
2424 | updateFile = new ZipHelperStream(baseStream_); | |||
2425 | } else { | |||
2426 | baseStream_.Close(); | |||
2427 | baseStream_ = null; | |||
2428 | updateFile = new ZipHelperStream(Name); | |||
2429 | } | |||
2430 | } | |||
2431 | | |||
2432 | using (updateFile) { | |||
2433 | long locatedCentralDirOffset = | |||
2434 | updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2435 | baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2436 | if (locatedCentralDirOffset < 0) { | |||
2437 | throw new ZipException("Cannot find central directory"); | |||
2438 | } | |||
2439 | | |||
2440 | const int CentralHeaderCommentSizeOffset = 16; | |||
2441 | updateFile.Position += CentralHeaderCommentSizeOffset; | |||
2442 | | |||
2443 | byte[] rawComment = newComment_.RawComment; | |||
2444 | | |||
2445 | updateFile.WriteLEShort(rawComment.Length); | |||
2446 | updateFile.Write(rawComment, 0, rawComment.Length); | |||
2447 | updateFile.SetLength(updateFile.Position); | |||
2448 | } | |||
2449 | | |||
2450 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2451 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2452 | } else { | |||
2453 | ReadEntries(); | |||
2454 | } | |||
2455 | } | |||
2456 | | |||
2457 | /// <summary> | |||
2458 | /// Class used to sort updates. | |||
2459 | /// </summary> | |||
2460 | class UpdateComparer : IComparer | |||
2461 | { | |||
2462 | /// <summary> | |||
2463 | /// Compares two objects and returns a value indicating whether one is | |||
2464 | /// less than, equal to or greater than the other. | |||
2465 | /// </summary> | |||
2466 | /// <param name="x">First object to compare</param> | |||
2467 | /// <param name="y">Second object to compare.</param> | |||
2468 | /// <returns>Compare result.</returns> | |||
2469 | public int Compare( | |||
2470 | object x, | |||
2471 | object y) | |||
2472 | { | |||
2473 | var zx = x as ZipUpdate; | |||
2474 | var zy = y as ZipUpdate; | |||
2475 | | |||
2476 | int result; | |||
2477 | | |||
2478 | if (zx == null) { | |||
2479 | if (zy == null) { | |||
2480 | result = 0; | |||
2481 | } else { | |||
2482 | result = -1; | |||
2483 | } | |||
2484 | } else if (zy == null) { | |||
2485 | result = 1; | |||
2486 | } else { | |||
2487 | int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2488 | int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2489 | | |||
2490 | result = xCmdValue - yCmdValue; | |||
2491 | if (result == 0) { | |||
2492 | long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; | |||
2493 | if (offsetDiff < 0) { | |||
2494 | result = -1; | |||
2495 | } else if (offsetDiff == 0) { | |||
2496 | result = 0; | |||
2497 | } else { | |||
2498 | result = 1; | |||
2499 | } | |||
2500 | } | |||
2501 | } | |||
2502 | return result; | |||
2503 | } | |||
2504 | } | |||
2505 | | |||
2506 | void RunUpdates() | |||
2507 | { | |||
2508 | long sizeEntries = 0; | |||
2509 | long endOfStream = 0; | |||
2510 | bool directUpdate = false; | |||
2511 | long destinationPosition = 0; // NOT SFX friendly | |||
2512 | | |||
2513 | ZipFile workFile; | |||
2514 | | |||
2515 | if (IsNewArchive) { | |||
2516 | workFile = this; | |||
2517 | workFile.baseStream_.Position = 0; | |||
2518 | directUpdate = true; | |||
2519 | } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2520 | workFile = this; | |||
2521 | workFile.baseStream_.Position = 0; | |||
2522 | directUpdate = true; | |||
2523 | | |||
2524 | // Sort the updates by offset within copies/modifies, then adds. | |||
2525 | // This ensures that data required by copies will not be overwritten. | |||
2526 | updates_.Sort(new UpdateComparer()); | |||
2527 | } else { | |||
2528 | workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); | |||
2529 | workFile.UseZip64 = UseZip64; | |||
2530 | | |||
2531 | if (key != null) { | |||
2532 | workFile.key = (byte[])key.Clone(); | |||
2533 | } | |||
2534 | } | |||
2535 | | |||
2536 | try { | |||
2537 | foreach (ZipUpdate update in updates_) { | |||
2538 | if (update != null) { | |||
2539 | switch (update.Command) { | |||
2540 | case UpdateCommand.Copy: | |||
2541 | if (directUpdate) { | |||
2542 | CopyEntryDirect(workFile, update, ref destinationPosition); | |||
2543 | } else { | |||
2544 | CopyEntry(workFile, update); | |||
2545 | } | |||
2546 | break; | |||
2547 | | |||
2548 | case UpdateCommand.Modify: | |||
2549 | // TODO: Direct modifying of an entry will take some legwork. | |||
2550 | ModifyEntry(workFile, update); | |||
2551 | break; | |||
2552 | | |||
2553 | case UpdateCommand.Add: | |||
2554 | if (!IsNewArchive && directUpdate) { | |||
2555 | workFile.baseStream_.Position = destinationPosition; | |||
2556 | } | |||
2557 | | |||
2558 | AddEntry(workFile, update); | |||
2559 | | |||
2560 | if (directUpdate) { | |||
2561 | destinationPosition = workFile.baseStream_.Position; | |||
2562 | } | |||
2563 | break; | |||
2564 | } | |||
2565 | } | |||
2566 | } | |||
2567 | | |||
2568 | if (!IsNewArchive && directUpdate) { | |||
2569 | workFile.baseStream_.Position = destinationPosition; | |||
2570 | } | |||
2571 | | |||
2572 | long centralDirOffset = workFile.baseStream_.Position; | |||
2573 | | |||
2574 | foreach (ZipUpdate update in updates_) { | |||
2575 | if (update != null) { | |||
2576 | sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); | |||
2577 | } | |||
2578 | } | |||
2579 | | |||
2580 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
2581 | using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) { | |||
2582 | zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); | |||
2583 | } | |||
2584 | | |||
2585 | endOfStream = workFile.baseStream_.Position; | |||
2586 | | |||
2587 | // And now patch entries... | |||
2588 | foreach (ZipUpdate update in updates_) { | |||
2589 | if (update != null) { | |||
2590 | // If the size of the entry is zero leave the crc as 0 as well. | |||
2591 | // The calculated crc will be all bits on... | |||
2592 | if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { | |||
2593 | workFile.baseStream_.Position = update.CrcPatchOffset; | |||
2594 | workFile.WriteLEInt((int)update.OutEntry.Crc); | |||
2595 | } | |||
2596 | | |||
2597 | if (update.SizePatchOffset > 0) { | |||
2598 | workFile.baseStream_.Position = update.SizePatchOffset; | |||
2599 | if (update.OutEntry.LocalHeaderRequiresZip64) { | |||
2600 | workFile.WriteLeLong(update.OutEntry.Size); | |||
2601 | workFile.WriteLeLong(update.OutEntry.CompressedSize); | |||
2602 | } else { | |||
2603 | workFile.WriteLEInt((int)update.OutEntry.CompressedSize); | |||
2604 | workFile.WriteLEInt((int)update.OutEntry.Size); | |||
2605 | } | |||
2606 | } | |||
2607 | } | |||
2608 | } | |||
2609 | } catch { | |||
2610 | workFile.Close(); | |||
2611 | if (!directUpdate && (workFile.Name != null)) { | |||
2612 | File.Delete(workFile.Name); | |||
2613 | } | |||
2614 | throw; | |||
2615 | } | |||
2616 | | |||
2617 | if (directUpdate) { | |||
2618 | workFile.baseStream_.SetLength(endOfStream); | |||
2619 | workFile.baseStream_.Flush(); | |||
2620 | isNewArchive_ = false; | |||
2621 | ReadEntries(); | |||
2622 | } else { | |||
2623 | baseStream_.Close(); | |||
2624 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2625 | } | |||
2626 | } | |||
2627 | | |||
2628 | void CheckUpdating() | |||
2629 | { | |||
2630 | if (updates_ == null) { | |||
2631 | throw new InvalidOperationException("BeginUpdate has not been called"); | |||
2632 | } | |||
2633 | } | |||
2634 | | |||
2635 | #endregion | |||
2636 | | |||
2637 | #region ZipUpdate class | |||
2638 | /// <summary> | |||
2639 | /// Represents a pending update to a Zip file. | |||
2640 | /// </summary> | |||
2641 | class ZipUpdate | |||
2642 | { | |||
2643 | #region Constructors | |||
2644 | public ZipUpdate(string fileName, ZipEntry entry) | |||
2645 | { | |||
2646 | command_ = UpdateCommand.Add; | |||
2647 | entry_ = entry; | |||
2648 | filename_ = fileName; | |||
2649 | } | |||
2650 | | |||
2651 | [Obsolete] | |||
2652 | public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) | |||
2653 | { | |||
2654 | command_ = UpdateCommand.Add; | |||
2655 | entry_ = new ZipEntry(entryName); | |||
2656 | entry_.CompressionMethod = compressionMethod; | |||
2657 | filename_ = fileName; | |||
2658 | } | |||
2659 | | |||
2660 | [Obsolete] | |||
2661 | public ZipUpdate(string fileName, string entryName) | |||
2662 | : this(fileName, entryName, CompressionMethod.Deflated) | |||
2663 | { | |||
2664 | // Do nothing. | |||
2665 | } | |||
2666 | | |||
2667 | [Obsolete] | |||
2668 | public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
2669 | { | |||
2670 | command_ = UpdateCommand.Add; | |||
2671 | entry_ = new ZipEntry(entryName); | |||
2672 | entry_.CompressionMethod = compressionMethod; | |||
2673 | dataSource_ = dataSource; | |||
2674 | } | |||
2675 | | |||
2676 | public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) | |||
2677 | { | |||
2678 | command_ = UpdateCommand.Add; | |||
2679 | entry_ = entry; | |||
2680 | dataSource_ = dataSource; | |||
2681 | } | |||
2682 | | |||
2683 | public ZipUpdate(ZipEntry original, ZipEntry updated) | |||
2684 | { | |||
2685 | throw new ZipException("Modify not currently supported"); | |||
2686 | /* | |||
2687 | command_ = UpdateCommand.Modify; | |||
2688 | entry_ = ( ZipEntry )original.Clone(); | |||
2689 | outEntry_ = ( ZipEntry )updated.Clone(); | |||
2690 | */ | |||
2691 | } | |||
2692 | | |||
2693 | public ZipUpdate(UpdateCommand command, ZipEntry entry) | |||
2694 | { | |||
2695 | command_ = command; | |||
2696 | entry_ = (ZipEntry)entry.Clone(); | |||
2697 | } | |||
2698 | | |||
2699 | | |||
2700 | /// <summary> | |||
2701 | /// Copy an existing entry. | |||
2702 | /// </summary> | |||
2703 | /// <param name="entry">The existing entry to copy.</param> | |||
2704 | public ZipUpdate(ZipEntry entry) | |||
2705 | : this(UpdateCommand.Copy, entry) | |||
2706 | { | |||
2707 | // Do nothing. | |||
2708 | } | |||
2709 | #endregion | |||
2710 | | |||
2711 | /// <summary> | |||
2712 | /// Get the <see cref="ZipEntry"/> for this update. | |||
2713 | /// </summary> | |||
2714 | /// <remarks>This is the source or original entry.</remarks> | |||
2715 | public ZipEntry Entry { | |||
2716 | get { return entry_; } | |||
2717 | } | |||
2718 | | |||
2719 | /// <summary> | |||
2720 | /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. | |||
2721 | /// </summary> | |||
2722 | public ZipEntry OutEntry { | |||
2723 | get { | |||
2724 | if (outEntry_ == null) { | |||
2725 | outEntry_ = (ZipEntry)entry_.Clone(); | |||
2726 | } | |||
2727 | | |||
2728 | return outEntry_; | |||
2729 | } | |||
2730 | } | |||
2731 | | |||
2732 | /// <summary> | |||
2733 | /// Get the command for this update. | |||
2734 | /// </summary> | |||
2735 | public UpdateCommand Command { | |||
2736 | get { return command_; } | |||
2737 | } | |||
2738 | | |||
2739 | /// <summary> | |||
2740 | /// Get the filename if any for this update. Null if none exists. | |||
2741 | /// </summary> | |||
2742 | public string Filename { | |||
2743 | get { return filename_; } | |||
2744 | } | |||
2745 | | |||
2746 | /// <summary> | |||
2747 | /// Get/set the location of the size patch for this update. | |||
2748 | /// </summary> | |||
2749 | public long SizePatchOffset { | |||
2750 | get { return sizePatchOffset_; } | |||
2751 | set { sizePatchOffset_ = value; } | |||
2752 | } | |||
2753 | | |||
2754 | /// <summary> | |||
2755 | /// Get /set the location of the crc patch for this update. | |||
2756 | /// </summary> | |||
2757 | public long CrcPatchOffset { | |||
2758 | get { return crcPatchOffset_; } | |||
2759 | set { crcPatchOffset_ = value; } | |||
2760 | } | |||
2761 | | |||
2762 | /// <summary> | |||
2763 | /// Get/set the size calculated by offset. | |||
2764 | /// Specifically, the difference between this and next entry's starting offset. | |||
2765 | /// </summary> | |||
2766 | public long OffsetBasedSize { | |||
2767 | get { return _offsetBasedSize; } | |||
2768 | set { _offsetBasedSize = value; } | |||
2769 | } | |||
2770 | | |||
2771 | public Stream GetSource() | |||
2772 | { | |||
2773 | Stream result = null; | |||
2774 | if (dataSource_ != null) { | |||
2775 | result = dataSource_.GetSource(); | |||
2776 | } | |||
2777 | | |||
2778 | return result; | |||
2779 | } | |||
2780 | | |||
2781 | #region Instance Fields | |||
2782 | ZipEntry entry_; | |||
2783 | ZipEntry outEntry_; | |||
2784 | UpdateCommand command_; | |||
2785 | IStaticDataSource dataSource_; | |||
2786 | string filename_; | |||
2787 | long sizePatchOffset_ = -1; | |||
2788 | long crcPatchOffset_ = -1; | |||
2789 | long _offsetBasedSize = -1; | |||
2790 | #endregion | |||
2791 | } | |||
2792 | | |||
2793 | #endregion | |||
2794 | #endregion | |||
2795 | | |||
2796 | #region Disposing | |||
2797 | | |||
2798 | #region IDisposable Members | |||
2799 | void IDisposable.Dispose() | |||
2800 | { | |||
2801 | Close(); | |||
2802 | } | |||
2803 | #endregion | |||
2804 | | |||
2805 | void DisposeInternal(bool disposing) | |||
2806 | { | |||
2807 | if (!isDisposed_) { | |||
2808 | isDisposed_ = true; | |||
2809 | entries_ = new ZipEntry[0]; | |||
2810 | | |||
2811 | if (IsStreamOwner && (baseStream_ != null)) { | |||
2812 | lock (baseStream_) { | |||
2813 | baseStream_.Close(); | |||
2814 | } | |||
2815 | } | |||
2816 | | |||
2817 | PostUpdateCleanup(); | |||
2818 | } | |||
2819 | } | |||
2820 | | |||
2821 | /// <summary> | |||
2822 | /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. | |||
2823 | /// </summary> | |||
2824 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
2825 | /// false to release only unmanaged resources.</param> | |||
2826 | protected virtual void Dispose(bool disposing) | |||
2827 | { | |||
2828 | DisposeInternal(disposing); | |||
2829 | } | |||
2830 | | |||
2831 | #endregion | |||
2832 | | |||
2833 | #region Internal routines | |||
2834 | #region Reading | |||
2835 | /// <summary> | |||
2836 | /// Read an unsigned short in little endian byte order. | |||
2837 | /// </summary> | |||
2838 | /// <returns>Returns the value read.</returns> | |||
2839 | /// <exception cref="EndOfStreamException"> | |||
2840 | /// The stream ends prematurely | |||
2841 | /// </exception> | |||
2842 | ushort ReadLEUshort() | |||
2843 | { | |||
2844 | int data1 = baseStream_.ReadByte(); | |||
2845 | | |||
2846 | if (data1 < 0) { | |||
2847 | throw new EndOfStreamException("End of stream"); | |||
2848 | } | |||
2849 | | |||
2850 | int data2 = baseStream_.ReadByte(); | |||
2851 | | |||
2852 | if (data2 < 0) { | |||
2853 | throw new EndOfStreamException("End of stream"); | |||
2854 | } | |||
2855 | | |||
2856 | | |||
2857 | return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); | |||
2858 | } | |||
2859 | | |||
2860 | /// <summary> | |||
2861 | /// Read a uint in little endian byte order. | |||
2862 | /// </summary> | |||
2863 | /// <returns>Returns the value read.</returns> | |||
2864 | /// <exception cref="IOException"> | |||
2865 | /// An i/o error occurs. | |||
2866 | /// </exception> | |||
2867 | /// <exception cref="System.IO.EndOfStreamException"> | |||
2868 | /// The file ends prematurely | |||
2869 | /// </exception> | |||
2870 | uint ReadLEUint() | |||
2871 | { | |||
2872 | return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); | |||
2873 | } | |||
2874 | | |||
2875 | ulong ReadLEUlong() | |||
2876 | { | |||
2877 | return ReadLEUint() | ((ulong)ReadLEUint() << 32); | |||
2878 | } | |||
2879 | | |||
2880 | #endregion | |||
2881 | // NOTE this returns the offset of the first byte after the signature. | |||
2882 | long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
2883 | { | |||
2884 | using (ZipHelperStream les = new ZipHelperStream(baseStream_)) { | |||
2885 | return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); | |||
2886 | } | |||
2887 | } | |||
2888 | | |||
2889 | /// <summary> | |||
2890 | /// Search for and read the central directory of a zip file filling the entries array. | |||
2891 | /// </summary> | |||
2892 | /// <exception cref="System.IO.IOException"> | |||
2893 | /// An i/o error occurs. | |||
2894 | /// </exception> | |||
2895 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
2896 | /// The central directory is malformed or cannot be found | |||
2897 | /// </exception> | |||
2898 | void ReadEntries() | |||
2899 | { | |||
2900 | // Search for the End Of Central Directory. When a zip comment is | |||
2901 | // present the directory will start earlier | |||
2902 | // | |||
2903 | // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. | |||
2904 | // This should be compatible with both SFX and ZIP files but has only been tested for Zip files | |||
2905 | // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then | |||
2906 | // this could be invalid. | |||
2907 | // Could also speed this up by reading memory in larger blocks. | |||
2908 | | |||
2909 | if (baseStream_.CanSeek == false) { | |||
2910 | throw new ZipException("ZipFile stream must be seekable"); | |||
2911 | } | |||
2912 | | |||
2913 | long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2914 | baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2915 | | |||
2916 | if (locatedEndOfCentralDir < 0) { | |||
2917 | throw new ZipException("Cannot find central directory"); | |||
2918 | } | |||
2919 | | |||
2920 | // Read end of central directory record | |||
2921 | ushort thisDiskNumber = ReadLEUshort(); | |||
2922 | ushort startCentralDirDisk = ReadLEUshort(); | |||
2923 | ulong entriesForThisDisk = ReadLEUshort(); | |||
2924 | ulong entriesForWholeCentralDir = ReadLEUshort(); | |||
2925 | ulong centralDirSize = ReadLEUint(); | |||
2926 | long offsetOfCentralDir = ReadLEUint(); | |||
2927 | uint commentSize = ReadLEUshort(); | |||
2928 | | |||
2929 | if (commentSize > 0) { | |||
2930 | byte[] comment = new byte[commentSize]; | |||
2931 | | |||
2932 | StreamUtils.ReadFully(baseStream_, comment); | |||
2933 | comment_ = ZipConstants.ConvertToString(comment); | |||
2934 | } else { | |||
2935 | comment_ = string.Empty; | |||
2936 | } | |||
2937 | | |||
2938 | bool isZip64 = false; | |||
2939 | | |||
2940 | // Check if zip64 header information is required. | |||
2941 | if ((thisDiskNumber == 0xffff) || | |||
2942 | (startCentralDirDisk == 0xffff) || | |||
2943 | (entriesForThisDisk == 0xffff) || | |||
2944 | (entriesForWholeCentralDir == 0xffff) || | |||
2945 | (centralDirSize == 0xffffffff) || | |||
2946 | (offsetOfCentralDir == 0xffffffff)) { | |||
2947 | isZip64 = true; | |||
2948 | | |||
2949 | long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, | |||
2950 | if (offset < 0) { | |||
2951 | throw new ZipException("Cannot find Zip64 locator"); | |||
2952 | } | |||
2953 | | |||
2954 | // number of the disk with the start of the zip64 end of central directory 4 bytes | |||
2955 | // relative offset of the zip64 end of central directory record 8 bytes | |||
2956 | // total number of disks 4 bytes | |||
2957 | ReadLEUint(); // startDisk64 is not currently used | |||
2958 | ulong offset64 = ReadLEUlong(); | |||
2959 | uint totalDisks = ReadLEUint(); | |||
2960 | | |||
2961 | baseStream_.Position = (long)offset64; | |||
2962 | long sig64 = ReadLEUint(); | |||
2963 | | |||
2964 | if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { | |||
2965 | throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); | |||
2966 | } | |||
2967 | | |||
2968 | // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. | |||
2969 | ulong recordSize = ReadLEUlong(); | |||
2970 | int versionMadeBy = ReadLEUshort(); | |||
2971 | int versionToExtract = ReadLEUshort(); | |||
2972 | uint thisDisk = ReadLEUint(); | |||
2973 | uint centralDirDisk = ReadLEUint(); | |||
2974 | entriesForThisDisk = ReadLEUlong(); | |||
2975 | entriesForWholeCentralDir = ReadLEUlong(); | |||
2976 | centralDirSize = ReadLEUlong(); | |||
2977 | offsetOfCentralDir = (long)ReadLEUlong(); | |||
2978 | | |||
2979 | // NOTE: zip64 extensible data sector (variable size) is ignored. | |||
2980 | } | |||
2981 | | |||
2982 | entries_ = new ZipEntry[entriesForThisDisk]; | |||
2983 | | |||
2984 | // SFX/embedded support, find the offset of the first entry vis the start of the stream | |||
2985 | // This applies to Zip files that are appended to the end of an SFX stub. | |||
2986 | // Or are appended as a resource to an executable. | |||
2987 | // Zip files created by some archivers have the offsets altered to reflect the true offsets | |||
2988 | // and so dont require any adjustment here... | |||
2989 | // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? | |||
2990 | if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) { | |||
2991 | offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); | |||
2992 | if (offsetOfFirstEntry <= 0) { | |||
2993 | throw new ZipException("Invalid embedded zip archive"); | |||
2994 | } | |||
2995 | } | |||
2996 | | |||
2997 | baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); | |||
2998 | | |||
2999 | for (ulong i = 0; i < entriesForThisDisk; i++) { | |||
3000 | if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { | |||
3001 | throw new ZipException("Wrong Central Directory signature"); | |||
3002 | } | |||
3003 | | |||
3004 | int versionMadeBy = ReadLEUshort(); | |||
3005 | int versionToExtract = ReadLEUshort(); | |||
3006 | int bitFlags = ReadLEUshort(); | |||
3007 | int method = ReadLEUshort(); | |||
3008 | uint dostime = ReadLEUint(); | |||
3009 | uint crc = ReadLEUint(); | |||
3010 | var csize = (long)ReadLEUint(); | |||
3011 | var size = (long)ReadLEUint(); | |||
3012 | int nameLen = ReadLEUshort(); | |||
3013 | int extraLen = ReadLEUshort(); | |||
3014 | int commentLen = ReadLEUshort(); | |||
3015 | | |||
3016 | int diskStartNo = ReadLEUshort(); // Not currently used | |||
3017 | int internalAttributes = ReadLEUshort(); // Not currently used | |||
3018 | | |||
3019 | uint externalAttributes = ReadLEUint(); | |||
3020 | long offset = ReadLEUint(); | |||
3021 | | |||
3022 | byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; | |||
3023 | | |||
3024 | StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); | |||
3025 | string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); | |||
3026 | | |||
3027 | var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); | |||
3028 | entry.Crc = crc & 0xffffffffL; | |||
3029 | entry.Size = size & 0xffffffffL; | |||
3030 | entry.CompressedSize = csize & 0xffffffffL; | |||
3031 | entry.Flags = bitFlags; | |||
3032 | entry.DosTime = (uint)dostime; | |||
3033 | entry.ZipFileIndex = (long)i; | |||
3034 | entry.Offset = offset; | |||
3035 | entry.ExternalFileAttributes = (int)externalAttributes; | |||
3036 | | |||
3037 | if ((bitFlags & 8) == 0) { | |||
3038 | entry.CryptoCheckValue = (byte)(crc >> 24); | |||
3039 | } else { | |||
3040 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | |||
3041 | } | |||
3042 | | |||
3043 | if (extraLen > 0) { | |||
3044 | byte[] extra = new byte[extraLen]; | |||
3045 | StreamUtils.ReadFully(baseStream_, extra); | |||
3046 | entry.ExtraData = extra; | |||
3047 | } | |||
3048 | | |||
3049 | entry.ProcessExtraData(false); | |||
3050 | | |||
3051 | if (commentLen > 0) { | |||
3052 | StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); | |||
3053 | entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); | |||
3054 | } | |||
3055 | | |||
3056 | entries_[i] = entry; | |||
3057 | } | |||
3058 | } | |||
3059 | | |||
3060 | /// <summary> | |||
3061 | /// Locate the data for a given entry. | |||
3062 | /// </summary> | |||
3063 | /// <returns> | |||
3064 | /// The start offset of the data. | |||
3065 | /// </returns> | |||
3066 | /// <exception cref="System.IO.EndOfStreamException"> | |||
3067 | /// The stream ends prematurely | |||
3068 | /// </exception> | |||
3069 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
3070 | /// The local header signature is invalid, the entry and central header file name lengths are different | |||
3071 | /// or the local and entry compression methods dont match | |||
3072 | /// </exception> | |||
3073 | long LocateEntry(ZipEntry entry) | |||
3074 | { | |||
3075 | return TestLocalHeader(entry, HeaderTest.Extract); | |||
3076 | } | |||
3077 | | |||
3078 | Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) | |||
3079 | { | |||
3080 | CryptoStream result = null; | |||
3081 | | |||
3082 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3083 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3084 | var classicManaged = new PkzipClassicManaged(); | |||
3085 | | |||
3086 | OnKeysRequired(entry.Name); | |||
3087 | if (HaveKeys == false) { | |||
3088 | throw new ZipException("No password available for encrypted stream"); | |||
3089 | } | |||
3090 | | |||
3091 | result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); | |||
3092 | CheckClassicPassword(result, entry); | |||
3093 | } else { | |||
3094 | if (entry.Version == ZipConstants.VERSION_AES) { | |||
3095 | // | |||
3096 | OnKeysRequired(entry.Name); | |||
3097 | if (HaveKeys == false) { | |||
3098 | throw new ZipException("No password available for AES encrypted stream"); | |||
3099 | } | |||
3100 | int saltLen = entry.AESSaltLen; | |||
3101 | byte[] saltBytes = new byte[saltLen]; | |||
3102 | int saltIn = baseStream.Read(saltBytes, 0, saltLen); | |||
3103 | if (saltIn != saltLen) | |||
3104 | throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); | |||
3105 | // | |||
3106 | byte[] pwdVerifyRead = new byte[2]; | |||
3107 | baseStream.Read(pwdVerifyRead, 0, 2); | |||
3108 | int blockSize = entry.AESKeySize / 8; // bits to bytes | |||
3109 | | |||
3110 | var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); | |||
3111 | byte[] pwdVerifyCalc = decryptor.PwdVerifier; | |||
3112 | if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) | |||
3113 | throw new ZipException("Invalid password for AES"); | |||
3114 | result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); | |||
3115 | } else { | |||
3116 | throw new ZipException("Decryption method not supported"); | |||
3117 | } | |||
3118 | } | |||
3119 | | |||
3120 | return result; | |||
3121 | } | |||
3122 | | |||
3123 | Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) | |||
3124 | { | |||
3125 | CryptoStream result = null; | |||
3126 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3127 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3128 | var classicManaged = new PkzipClassicManaged(); | |||
3129 | | |||
3130 | OnKeysRequired(entry.Name); | |||
3131 | if (HaveKeys == false) { | |||
3132 | throw new ZipException("No password available for encrypted stream"); | |||
3133 | } | |||
3134 | | |||
3135 | // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream | |||
3136 | // which doesnt do this. | |||
3137 | result = new CryptoStream(new UncompressedStream(baseStream), | |||
3138 | classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); | |||
3139 | | |||
3140 | if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { | |||
3141 | WriteEncryptionHeader(result, entry.DosTime << 16); | |||
3142 | } else { | |||
3143 | WriteEncryptionHeader(result, entry.Crc); | |||
3144 | } | |||
3145 | } | |||
3146 | return result; | |||
3147 | } | |||
3148 | | |||
3149 | static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) | |||
3150 | { | |||
3151 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3152 | StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); | |||
3153 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | |||
3154 | throw new ZipException("Invalid password"); | |||
3155 | } | |||
3156 | } | |||
3157 | | |||
3158 | static void WriteEncryptionHeader(Stream stream, long crcValue) | |||
3159 | { | |||
3160 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3161 | var rnd = new Random(); | |||
3162 | rnd.NextBytes(cryptBuffer); | |||
3163 | cryptBuffer[11] = (byte)(crcValue >> 24); | |||
3164 | stream.Write(cryptBuffer, 0, cryptBuffer.Length); | |||
3165 | } | |||
3166 | | |||
3167 | #endregion | |||
3168 | | |||
3169 | #region Instance Fields | |||
3170 | bool isDisposed_; | |||
3171 | string name_; | |||
3172 | string comment_; | |||
3173 | string rawPassword_; | |||
3174 | Stream baseStream_; | |||
3175 | bool isStreamOwner; | |||
3176 | long offsetOfFirstEntry; | |||
3177 | ZipEntry[] entries_; | |||
3178 | byte[] key; | |||
3179 | bool isNewArchive_; | |||
3180 | | |||
3181 | // Default is dynamic which is not backwards compatible and can cause problems | |||
3182 | // with XP's built in compression which cant read Zip64 archives. | |||
3183 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
3184 | // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. | |||
3185 | UseZip64 useZip64_ = UseZip64.Dynamic; | |||
3186 | | |||
3187 | #region Zip Update Instance Fields | |||
3188 | ArrayList updates_; | |||
3189 | long updateCount_; // Count is managed manually as updates_ can contain nulls! | |||
3190 | Hashtable updateIndex_; | |||
3191 | IArchiveStorage archiveStorage_; | |||
3192 | IDynamicDataSource updateDataSource_; | |||
3193 | bool contentsEdited_; | |||
3194 | int bufferSize_ = DefaultBufferSize; | |||
3195 | byte[] copyBuffer_; | |||
3196 | ZipString newComment_; | |||
3197 | bool commentEdited_; | |||
3198 | IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); | |||
3199 | #endregion | |||
3200 | #endregion | |||
3201 | | |||
3202 | #region Support Classes | |||
3203 | /// <summary> | |||
3204 | /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. | |||
3205 | /// </summary> | |||
3206 | class ZipString | |||
3207 | { | |||
3208 | #region Constructors | |||
3209 | /// <summary> | |||
3210 | /// Initialise a <see cref="ZipString"/> with a string. | |||
3211 | /// </summary> | |||
3212 | /// <param name="comment">The textual string form.</param> | |||
3213 | public ZipString(string comment) | |||
3214 | { | |||
3215 | comment_ = comment; | |||
3216 | isSourceString_ = true; | |||
3217 | } | |||
3218 | | |||
3219 | /// <summary> | |||
3220 | /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. | |||
3221 | /// </summary> | |||
3222 | /// <param name="rawString"></param> | |||
3223 | public ZipString(byte[] rawString) | |||
3224 | { | |||
3225 | rawComment_ = rawString; | |||
3226 | } | |||
3227 | #endregion | |||
3228 | | |||
3229 | /// <summary> | |||
3230 | /// Get a value indicating the original source of data for this instance. | |||
3231 | /// True if the source was a string; false if the source was binary data. | |||
3232 | /// </summary> | |||
3233 | public bool IsSourceString { | |||
3234 | get { return isSourceString_; } | |||
3235 | } | |||
3236 | | |||
3237 | /// <summary> | |||
3238 | /// Get the length of the comment when represented as raw bytes. | |||
3239 | /// </summary> | |||
3240 | public int RawLength { | |||
3241 | get { | |||
3242 | MakeBytesAvailable(); | |||
3243 | return rawComment_.Length; | |||
3244 | } | |||
3245 | } | |||
3246 | | |||
3247 | /// <summary> | |||
3248 | /// Get the comment in its 'raw' form as plain bytes. | |||
3249 | /// </summary> | |||
3250 | public byte[] RawComment { | |||
3251 | get { | |||
3252 | MakeBytesAvailable(); | |||
3253 | return (byte[])rawComment_.Clone(); | |||
3254 | } | |||
3255 | } | |||
3256 | | |||
3257 | /// <summary> | |||
3258 | /// Reset the comment to its initial state. | |||
3259 | /// </summary> | |||
3260 | public void Reset() | |||
3261 | { | |||
3262 | if (isSourceString_) { | |||
3263 | rawComment_ = null; | |||
3264 | } else { | |||
3265 | comment_ = null; | |||
3266 | } | |||
3267 | } | |||
3268 | | |||
3269 | void MakeTextAvailable() | |||
3270 | { | |||
3271 | if (comment_ == null) { | |||
3272 | comment_ = ZipConstants.ConvertToString(rawComment_); | |||
3273 | } | |||
3274 | } | |||
3275 | | |||
3276 | void MakeBytesAvailable() | |||
3277 | { | |||
3278 | if (rawComment_ == null) { | |||
3279 | rawComment_ = ZipConstants.ConvertToArray(comment_); | |||
3280 | } | |||
3281 | } | |||
3282 | | |||
3283 | /// <summary> | |||
3284 | /// Implicit conversion of comment to a string. | |||
3285 | /// </summary> | |||
3286 | /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> | |||
3287 | /// <returns>The textual equivalent for the input value.</returns> | |||
3288 | static public implicit operator string(ZipString zipString) | |||
3289 | { | |||
3290 | zipString.MakeTextAvailable(); | |||
3291 | return zipString.comment_; | |||
3292 | } | |||
3293 | | |||
3294 | #region Instance Fields | |||
3295 | string comment_; | |||
3296 | byte[] rawComment_; | |||
3297 | bool isSourceString_; | |||
3298 | #endregion | |||
3299 | } | |||
3300 | | |||
3301 | /// <summary> | |||
3302 | /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> | |||
3303 | /// </summary> | |||
3304 | class ZipEntryEnumerator : IEnumerator | |||
3305 | { | |||
3306 | #region Constructors | |||
3307 | public ZipEntryEnumerator(ZipEntry[] entries) | |||
3308 | { | |||
3309 | array = entries; | |||
3310 | } | |||
3311 | | |||
3312 | #endregion | |||
3313 | #region IEnumerator Members | |||
3314 | public object Current { | |||
3315 | get { | |||
3316 | return array[index]; | |||
3317 | } | |||
3318 | } | |||
3319 | | |||
3320 | public void Reset() | |||
3321 | { | |||
3322 | index = -1; | |||
3323 | } | |||
3324 | | |||
3325 | public bool MoveNext() | |||
3326 | { | |||
3327 | return (++index < array.Length); | |||
3328 | } | |||
3329 | #endregion | |||
3330 | #region Instance Fields | |||
3331 | ZipEntry[] array; | |||
3332 | int index = -1; | |||
3333 | #endregion | |||
3334 | } | |||
3335 | | |||
3336 | /// <summary> | |||
3337 | /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data | |||
3338 | /// to and flush, but cannot read, seek or do anything else to. | |||
3339 | /// </summary> | |||
3340 | class UncompressedStream : Stream | |||
3341 | { | |||
3342 | #region Constructors | |||
3343 | public UncompressedStream(Stream baseStream) | |||
3344 | { | |||
3345 | baseStream_ = baseStream; | |||
3346 | } | |||
3347 | | |||
3348 | #endregion | |||
3349 | | |||
3350 | /// <summary> | |||
3351 | /// Close this stream instance. | |||
3352 | /// </summary> | |||
3353 | public override void Close() | |||
3354 | { | |||
3355 | // Do nothing | |||
3356 | } | |||
3357 | | |||
3358 | /// <summary> | |||
3359 | /// Gets a value indicating whether the current stream supports reading. | |||
3360 | /// </summary> | |||
3361 | public override bool CanRead { | |||
3362 | get { | |||
3363 | return false; | |||
3364 | } | |||
3365 | } | |||
3366 | | |||
3367 | /// <summary> | |||
3368 | /// Write any buffered data to underlying storage. | |||
3369 | /// </summary> | |||
3370 | public override void Flush() | |||
3371 | { | |||
3372 | baseStream_.Flush(); | |||
3373 | } | |||
3374 | | |||
3375 | /// <summary> | |||
3376 | /// Gets a value indicating whether the current stream supports writing. | |||
3377 | /// </summary> | |||
3378 | public override bool CanWrite { | |||
3379 | get { | |||
3380 | return baseStream_.CanWrite; | |||
3381 | } | |||
3382 | } | |||
3383 | | |||
3384 | /// <summary> | |||
3385 | /// Gets a value indicating whether the current stream supports seeking. | |||
3386 | /// </summary> | |||
3387 | public override bool CanSeek { | |||
3388 | get { | |||
3389 | return false; | |||
3390 | } | |||
3391 | } | |||
3392 | | |||
3393 | /// <summary> | |||
3394 | /// Get the length in bytes of the stream. | |||
3395 | /// </summary> | |||
3396 | public override long Length { | |||
3397 | get { | |||
3398 | return 0; | |||
3399 | } | |||
3400 | } | |||
3401 | | |||
3402 | /// <summary> | |||
3403 | /// Gets or sets the position within the current stream. | |||
3404 | /// </summary> | |||
3405 | public override long Position { | |||
3406 | get { | |||
3407 | return baseStream_.Position; | |||
3408 | } | |||
3409 | set { | |||
3410 | throw new NotImplementedException(); | |||
3411 | } | |||
3412 | } | |||
3413 | | |||
3414 | /// <summary> | |||
3415 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3416 | /// </summary> | |||
3417 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3418 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3419 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3420 | /// <returns> | |||
3421 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3422 | /// </returns> | |||
3423 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3424 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3425 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3426 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3427 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3428 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3429 | public override int Read(byte[] buffer, int offset, int count) | |||
3430 | { | |||
3431 | return 0; | |||
3432 | } | |||
3433 | | |||
3434 | /// <summary> | |||
3435 | /// Sets the position within the current stream. | |||
3436 | /// </summary> | |||
3437 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3438 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3439 | /// <returns> | |||
3440 | /// The new position within the current stream. | |||
3441 | /// </returns> | |||
3442 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3443 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3444 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3445 | public override long Seek(long offset, SeekOrigin origin) | |||
3446 | { | |||
3447 | return 0; | |||
3448 | } | |||
3449 | | |||
3450 | /// <summary> | |||
3451 | /// Sets the length of the current stream. | |||
3452 | /// </summary> | |||
3453 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3454 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3455 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3456 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3457 | public override void SetLength(long value) | |||
3458 | { | |||
3459 | } | |||
3460 | | |||
3461 | /// <summary> | |||
3462 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3463 | /// </summary> | |||
3464 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3465 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3466 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3467 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3468 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3469 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3470 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3471 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3472 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3473 | public override void Write(byte[] buffer, int offset, int count) | |||
3474 | { | |||
3475 | baseStream_.Write(buffer, offset, count); | |||
3476 | } | |||
3477 | | |||
3478 | readonly | |||
3479 | | |||
3480 | #region Instance Fields | |||
3481 | Stream baseStream_; | |||
3482 | #endregion | |||
3483 | } | |||
3484 | | |||
3485 | /// <summary> | |||
3486 | /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> | |||
3487 | /// whose data is only a part or subsection of a file. | |||
3488 | /// </summary> | |||
3489 | class PartialInputStream : Stream | |||
3490 | { | |||
3491 | #region Constructors | |||
3492 | /// <summary> | |||
3493 | /// Initialise a new instance of the <see cref="PartialInputStream"/> class. | |||
3494 | /// </summary> | |||
3495 | /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param> | |||
3496 | /// <param name="start">The start of the partial data.</param> | |||
3497 | /// <param name="length">The length of the partial data.</param> | |||
3498 | public PartialInputStream(ZipFile zipFile, long start, long length) | |||
3499 | { | |||
3500 | start_ = start; | |||
3501 | length_ = length; | |||
3502 | | |||
3503 | // Although this is the only time the zipfile is used | |||
3504 | // keeping a reference here prevents premature closure of | |||
3505 | // this zip file and thus the baseStream_. | |||
3506 | | |||
3507 | // Code like this will cause apparently random failures depending | |||
3508 | // on the size of the files and when garbage is collected. | |||
3509 | // | |||
3510 | // ZipFile z = new ZipFile (stream); | |||
3511 | // Stream reader = z.GetInputStream(0); | |||
3512 | // uses reader here.... | |||
3513 | zipFile_ = zipFile; | |||
3514 | baseStream_ = zipFile_.baseStream_; | |||
3515 | readPos_ = start; | |||
3516 | end_ = start + length; | |||
3517 | } | |||
3518 | #endregion | |||
3519 | | |||
3520 | /// <summary> | |||
3521 | /// Read a byte from this stream. | |||
3522 | /// </summary> | |||
3523 | /// <returns>Returns the byte read or -1 on end of stream.</returns> | |||
3524 | public override int ReadByte() | |||
3525 | { | |||
3526 | if (readPos_ >= end_) { | |||
3527 | // -1 is the correct value at end of stream. | |||
3528 | return -1; | |||
3529 | } | |||
3530 | | |||
3531 | lock (baseStream_) { | |||
3532 | baseStream_.Seek(readPos_++, SeekOrigin.Begin); | |||
3533 | return baseStream_.ReadByte(); | |||
3534 | } | |||
3535 | } | |||
3536 | | |||
3537 | /// <summary> | |||
3538 | /// Close this <see cref="PartialInputStream">partial input stream</see>. | |||
3539 | /// </summary> | |||
3540 | /// <remarks> | |||
3541 | /// The underlying stream is not closed. Close the parent ZipFile class to do that. | |||
3542 | /// </remarks> | |||
3543 | public override void Close() | |||
3544 | { | |||
3545 | // Do nothing at all! | |||
3546 | } | |||
3547 | | |||
3548 | /// <summary> | |||
3549 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3550 | /// </summary> | |||
3551 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3552 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3553 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3554 | /// <returns> | |||
3555 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3556 | /// </returns> | |||
3557 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3558 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3559 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3560 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3561 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3562 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3563 | public override int Read(byte[] buffer, int offset, int count) | |||
3564 | { | |||
3565 | lock (baseStream_) { | |||
3566 | if (count > end_ - readPos_) { | |||
3567 | count = (int)(end_ - readPos_); | |||
3568 | if (count == 0) { | |||
3569 | return 0; | |||
3570 | } | |||
3571 | } | |||
3572 | // Protect against Stream implementations that throw away their buffer on every Seek | |||
3573 | // (for example, Mono FileStream) | |||
3574 | if (baseStream_.Position != readPos_) { | |||
3575 | baseStream_.Seek(readPos_, SeekOrigin.Begin); | |||
3576 | } | |||
3577 | int readCount = baseStream_.Read(buffer, offset, count); | |||
3578 | if (readCount > 0) { | |||
3579 | readPos_ += readCount; | |||
3580 | } | |||
3581 | return readCount; | |||
3582 | } | |||
3583 | } | |||
3584 | | |||
3585 | /// <summary> | |||
3586 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3587 | /// </summary> | |||
3588 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3589 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3590 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3591 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3592 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3593 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3594 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3595 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3596 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3597 | public override void Write(byte[] buffer, int offset, int count) | |||
3598 | { | |||
3599 | throw new NotSupportedException(); | |||
3600 | } | |||
3601 | | |||
3602 | /// <summary> | |||
3603 | /// When overridden in a derived class, sets the length of the current stream. | |||
3604 | /// </summary> | |||
3605 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3606 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3607 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3608 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3609 | public override void SetLength(long value) | |||
3610 | { | |||
3611 | throw new NotSupportedException(); | |||
3612 | } | |||
3613 | | |||
3614 | /// <summary> | |||
3615 | /// When overridden in a derived class, sets the position within the current stream. | |||
3616 | /// </summary> | |||
3617 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3618 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3619 | /// <returns> | |||
3620 | /// The new position within the current stream. | |||
3621 | /// </returns> | |||
3622 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3623 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3624 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3625 | public override long Seek(long offset, SeekOrigin origin) | |||
3626 | { | |||
3627 | long newPos = readPos_; | |||
3628 | | |||
3629 | switch (origin) { | |||
3630 | case SeekOrigin.Begin: | |||
3631 | newPos = start_ + offset; | |||
3632 | break; | |||
3633 | | |||
3634 | case SeekOrigin.Current: | |||
3635 | newPos = readPos_ + offset; | |||
3636 | break; | |||
3637 | | |||
3638 | case SeekOrigin.End: | |||
3639 | newPos = end_ + offset; | |||
3640 | break; | |||
3641 | } | |||
3642 | | |||
3643 | if (newPos < start_) { | |||
3644 | throw new ArgumentException("Negative position is invalid"); | |||
3645 | } | |||
3646 | | |||
3647 | if (newPos >= end_) { | |||
3648 | throw new IOException("Cannot seek past end"); | |||
3649 | } | |||
3650 | readPos_ = newPos; | |||
3651 | return readPos_; | |||
3652 | } | |||
3653 | | |||
3654 | /// <summary> | |||
3655 | /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | |||
3656 | /// </summary> | |||
3657 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3658 | public override void Flush() | |||
3659 | { | |||
3660 | // Nothing to do. | |||
3661 | } | |||
3662 | | |||
3663 | /// <summary> | |||
3664 | /// Gets or sets the position within the current stream. | |||
3665 | /// </summary> | |||
3666 | /// <value></value> | |||
3667 | /// <returns>The current position within the stream.</returns> | |||
3668 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3669 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> | |||
3670 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3671 | public override long Position { | |||
3672 | get { return readPos_ - start_; } | |||
3673 | set { | |||
3674 | long newPos = start_ + value; | |||
3675 | | |||
3676 | if (newPos < start_) { | |||
3677 | throw new ArgumentException("Negative position is invalid"); | |||
3678 | } | |||
3679 | | |||
3680 | if (newPos >= end_) { | |||
3681 | throw new InvalidOperationException("Cannot seek past end"); | |||
3682 | } | |||
3683 | readPos_ = newPos; | |||
3684 | } | |||
3685 | } | |||
3686 | | |||
3687 | /// <summary> | |||
3688 | /// Gets the length in bytes of the stream. | |||
3689 | /// </summary> | |||
3690 | /// <value></value> | |||
3691 | /// <returns>A long value representing the length of the stream in bytes.</returns> | |||
3692 | /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </excep | |||
3693 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3694 | public override long Length { | |||
3695 | get { return length_; } | |||
3696 | } | |||
3697 | | |||
3698 | /// <summary> | |||
3699 | /// Gets a value indicating whether the current stream supports writing. | |||
3700 | /// </summary> | |||
3701 | /// <value>false</value> | |||
3702 | /// <returns>true if the stream supports writing; otherwise, false.</returns> | |||
3703 | public override bool CanWrite { | |||
3704 | get { return false; } | |||
3705 | } | |||
3706 | | |||
3707 | /// <summary> | |||
3708 | /// Gets a value indicating whether the current stream supports seeking. | |||
3709 | /// </summary> | |||
3710 | /// <value>true</value> | |||
3711 | /// <returns>true if the stream supports seeking; otherwise, false.</returns> | |||
3712 | public override bool CanSeek { | |||
3713 | get { return true; } | |||
3714 | } | |||
3715 | | |||
3716 | /// <summary> | |||
3717 | /// Gets a value indicating whether the current stream supports reading. | |||
3718 | /// </summary> | |||
3719 | /// <value>true.</value> | |||
3720 | /// <returns>true if the stream supports reading; otherwise, false.</returns> | |||
3721 | public override bool CanRead { | |||
3722 | get { return true; } | |||
3723 | } | |||
3724 | | |||
3725 | /// <summary> | |||
3726 | /// Gets a value that determines whether the current stream can time out. | |||
3727 | /// </summary> | |||
3728 | /// <value></value> | |||
3729 | /// <returns>A value that determines whether the current stream can time out.</returns> | |||
3730 | public override bool CanTimeout { | |||
3731 | get { return baseStream_.CanTimeout; } | |||
3732 | } | |||
3733 | #region Instance Fields | |||
3734 | ZipFile zipFile_; | |||
3735 | Stream baseStream_; | |||
3736 | long start_; | |||
3737 | long length_; | |||
3738 | long readPos_; | |||
3739 | long end_; | |||
3740 | #endregion | |||
3741 | } | |||
3742 | #endregion | |||
3743 | } | |||
3744 | | |||
3745 | #endregion | |||
3746 | | |||
3747 | #region DataSources | |||
3748 | /// <summary> | |||
3749 | /// Provides a static way to obtain a source of data for an entry. | |||
3750 | /// </summary> | |||
3751 | public interface IStaticDataSource | |||
3752 | { | |||
3753 | /// <summary> | |||
3754 | /// Get a source of data by creating a new stream. | |||
3755 | /// </summary> | |||
3756 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3757 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3758 | Stream GetSource(); | |||
3759 | } | |||
3760 | | |||
3761 | /// <summary> | |||
3762 | /// Represents a source of data that can dynamically provide | |||
3763 | /// multiple <see cref="Stream">data sources</see> based on the parameters passed. | |||
3764 | /// </summary> | |||
3765 | public interface IDynamicDataSource | |||
3766 | { | |||
3767 | /// <summary> | |||
3768 | /// Get a data source. | |||
3769 | /// </summary> | |||
3770 | /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> | |||
3771 | /// <param name="name">The name for data if known.</param> | |||
3772 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3773 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3774 | Stream GetSource(ZipEntry entry, string name); | |||
3775 | } | |||
3776 | | |||
3777 | /// <summary> | |||
3778 | /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. | |||
3779 | /// </summary> | |||
3780 | public class StaticDiskDataSource : IStaticDataSource | |||
3781 | { | |||
3782 | /// <summary> | |||
3783 | /// Initialise a new instnace of <see cref="StaticDiskDataSource"/> | |||
3784 | /// </summary> | |||
3785 | /// <param name="fileName">The name of the file to obtain data from.</param> | |||
3786 | public StaticDiskDataSource(string fileName) | |||
3787 | { | |||
3788 | fileName_ = fileName; | |||
3789 | } | |||
3790 | | |||
3791 | #region IDataSource Members | |||
3792 | | |||
3793 | /// <summary> | |||
3794 | /// Get a <see cref="Stream"/> providing data. | |||
3795 | /// </summary> | |||
3796 | /// <returns>Returns a <see cref="Stream"/> provising data.</returns> | |||
3797 | public Stream GetSource() | |||
3798 | { | |||
3799 | return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3800 | } | |||
3801 | | |||
3802 | readonly | |||
3803 | | |||
3804 | #endregion | |||
3805 | #region Instance Fields | |||
3806 | string fileName_; | |||
3807 | #endregion | |||
3808 | } | |||
3809 | | |||
3810 | | |||
3811 | /// <summary> | |||
3812 | /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. | |||
3813 | /// </summary> | |||
3814 | public class DynamicDiskDataSource : IDynamicDataSource | |||
3815 | { | |||
3816 | | |||
3817 | #region IDataSource Members | |||
3818 | /// <summary> | |||
3819 | /// Get a <see cref="Stream"/> providing data for an entry. | |||
3820 | /// </summary> | |||
3821 | /// <param name="entry">The entry to provide data for.</param> | |||
3822 | /// <param name="name">The file name for data if known.</param> | |||
3823 | /// <returns>Returns a stream providing data; or null if not available</returns> | |||
3824 | public Stream GetSource(ZipEntry entry, string name) | |||
3825 | { | |||
3826 | Stream result = null; | |||
3827 | | |||
3828 | if (name != null) { | |||
3829 | result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3830 | } | |||
3831 | | |||
3832 | return result; | |||
3833 | } | |||
3834 | | |||
3835 | #endregion | |||
3836 | } | |||
3837 | | |||
3838 | #endregion | |||
3839 | | |||
3840 | #region Archive Storage | |||
3841 | /// <summary> | |||
3842 | /// Defines facilities for data storage when updating Zip Archives. | |||
3843 | /// </summary> | |||
3844 | public interface IArchiveStorage | |||
3845 | { | |||
3846 | /// <summary> | |||
3847 | /// Get the <see cref="FileUpdateMode"/> to apply during updates. | |||
3848 | /// </summary> | |||
3849 | FileUpdateMode UpdateMode { get; } | |||
3850 | | |||
3851 | /// <summary> | |||
3852 | /// Get an empty <see cref="Stream"/> that can be used for temporary output. | |||
3853 | /// </summary> | |||
3854 | /// <returns>Returns a temporary output <see cref="Stream"/></returns> | |||
3855 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3856 | Stream GetTemporaryOutput(); | |||
3857 | | |||
3858 | /// <summary> | |||
3859 | /// Convert a temporary output stream to a final stream. | |||
3860 | /// </summary> | |||
3861 | /// <returns>The resulting final <see cref="Stream"/></returns> | |||
3862 | /// <seealso cref="GetTemporaryOutput"/> | |||
3863 | Stream ConvertTemporaryToFinal(); | |||
3864 | | |||
3865 | /// <summary> | |||
3866 | /// Make a temporary copy of the original stream. | |||
3867 | /// </summary> | |||
3868 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
3869 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3870 | Stream MakeTemporaryCopy(Stream stream); | |||
3871 | | |||
3872 | /// <summary> | |||
3873 | /// Return a stream suitable for performing direct updates on the original source. | |||
3874 | /// </summary> | |||
3875 | /// <param name="stream">The current stream.</param> | |||
3876 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3877 | /// <remarks>This may be the current stream passed.</remarks> | |||
3878 | Stream OpenForDirectUpdate(Stream stream); | |||
3879 | | |||
3880 | /// <summary> | |||
3881 | /// Dispose of this instance. | |||
3882 | /// </summary> | |||
3883 | void Dispose(); | |||
3884 | } | |||
3885 | | |||
3886 | /// <summary> | |||
3887 | /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. | |||
3888 | /// </summary> | |||
3889 | abstract public class BaseArchiveStorage : IArchiveStorage | |||
3890 | { | |||
3891 | #region Constructors | |||
3892 | /// <summary> | |||
3893 | /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. | |||
3894 | /// </summary> | |||
3895 | /// <param name="updateMode">The update mode.</param> | |||
| 48 | 3896 | protected BaseArchiveStorage(FileUpdateMode updateMode) | ||
3897 | { | |||
| 48 | 3898 | updateMode_ = updateMode; | ||
| 48 | 3899 | } | ||
3900 | #endregion | |||
3901 | | |||
3902 | #region IArchiveStorage Members | |||
3903 | | |||
3904 | /// <summary> | |||
3905 | /// Gets a temporary output <see cref="Stream"/> | |||
3906 | /// </summary> | |||
3907 | /// <returns>Returns the temporary output stream.</returns> | |||
3908 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3909 | public abstract Stream GetTemporaryOutput(); | |||
3910 | | |||
3911 | /// <summary> | |||
3912 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
3913 | /// </summary> | |||
3914 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
3915 | /// the final storage for the archive.</returns> | |||
3916 | /// <seealso cref="GetTemporaryOutput"/> | |||
3917 | public abstract Stream ConvertTemporaryToFinal(); | |||
3918 | | |||
3919 | /// <summary> | |||
3920 | /// Make a temporary copy of a <see cref="Stream"/>. | |||
3921 | /// </summary> | |||
3922 | /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> | |||
3923 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3924 | public abstract Stream MakeTemporaryCopy(Stream stream); | |||
3925 | | |||
3926 | /// <summary> | |||
3927 | /// Return a stream suitable for performing direct updates on the original source. | |||
3928 | /// </summary> | |||
3929 | /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> | |||
3930 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3931 | public abstract Stream OpenForDirectUpdate(Stream stream); | |||
3932 | | |||
3933 | /// <summary> | |||
3934 | /// Disposes this instance. | |||
3935 | /// </summary> | |||
3936 | public abstract void Dispose(); | |||
3937 | | |||
3938 | /// <summary> | |||
3939 | /// Gets the update mode applicable. | |||
3940 | /// </summary> | |||
3941 | /// <value>The update mode.</value> | |||
3942 | public FileUpdateMode UpdateMode { | |||
3943 | get { | |||
| 36 | 3944 | return updateMode_; | ||
3945 | } | |||
3946 | } | |||
3947 | | |||
3948 | #endregion | |||
3949 | | |||
3950 | #region Instance Fields | |||
3951 | FileUpdateMode updateMode_; | |||
3952 | #endregion | |||
3953 | } | |||
3954 | | |||
3955 | /// <summary> | |||
3956 | /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. | |||
3957 | /// </summary> | |||
3958 | public class DiskArchiveStorage : BaseArchiveStorage | |||
3959 | { | |||
3960 | #region Constructors | |||
3961 | /// <summary> | |||
3962 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3963 | /// </summary> | |||
3964 | /// <param name="file">The file.</param> | |||
3965 | /// <param name="updateMode">The update mode.</param> | |||
3966 | public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) | |||
3967 | : base(updateMode) | |||
3968 | { | |||
3969 | if (file.Name == null) { | |||
3970 | throw new ZipException("Cant handle non file archives"); | |||
3971 | } | |||
3972 | | |||
3973 | fileName_ = file.Name; | |||
3974 | } | |||
3975 | | |||
3976 | /// <summary> | |||
3977 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3978 | /// </summary> | |||
3979 | /// <param name="file">The file.</param> | |||
3980 | public DiskArchiveStorage(ZipFile file) | |||
3981 | : this(file, FileUpdateMode.Safe) | |||
3982 | { | |||
3983 | } | |||
3984 | #endregion | |||
3985 | | |||
3986 | #region IArchiveStorage Members | |||
3987 | | |||
3988 | /// <summary> | |||
3989 | /// Gets a temporary output <see cref="Stream"/> for performing updates on. | |||
3990 | /// </summary> | |||
3991 | /// <returns>Returns the temporary output stream.</returns> | |||
3992 | public override Stream GetTemporaryOutput() | |||
3993 | { | |||
3994 | if (temporaryName_ != null) { | |||
3995 | temporaryName_ = GetTempFileName(temporaryName_, true); | |||
3996 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
3997 | } else { | |||
3998 | // Determine where to place files based on internal strategy. | |||
3999 | // Currently this is always done in system temp directory. | |||
4000 | temporaryName_ = Path.GetTempFileName(); | |||
4001 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
4002 | } | |||
4003 | | |||
4004 | return temporaryStream_; | |||
4005 | } | |||
4006 | | |||
4007 | /// <summary> | |||
4008 | /// Converts a temporary <see cref="Stream"/> to its final form. | |||
4009 | /// </summary> | |||
4010 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4011 | /// the final storage for the archive.</returns> | |||
4012 | public override Stream ConvertTemporaryToFinal() | |||
4013 | { | |||
4014 | if (temporaryStream_ == null) { | |||
4015 | throw new ZipException("No temporary stream has been created"); | |||
4016 | } | |||
4017 | | |||
4018 | Stream result = null; | |||
4019 | | |||
4020 | string moveTempName = GetTempFileName(fileName_, false); | |||
4021 | bool newFileCreated = false; | |||
4022 | | |||
4023 | try { | |||
4024 | temporaryStream_.Close(); | |||
4025 | File.Move(fileName_, moveTempName); | |||
4026 | File.Move(temporaryName_, fileName_); | |||
4027 | newFileCreated = true; | |||
4028 | File.Delete(moveTempName); | |||
4029 | | |||
4030 | result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
4031 | } catch (Exception) { | |||
4032 | result = null; | |||
4033 | | |||
4034 | // Try to roll back changes... | |||
4035 | if (!newFileCreated) { | |||
4036 | File.Move(moveTempName, fileName_); | |||
4037 | File.Delete(temporaryName_); | |||
4038 | } | |||
4039 | | |||
4040 | throw; | |||
4041 | } | |||
4042 | | |||
4043 | return result; | |||
4044 | } | |||
4045 | | |||
4046 | /// <summary> | |||
4047 | /// Make a temporary copy of a stream. | |||
4048 | /// </summary> | |||
4049 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4050 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4051 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4052 | { | |||
4053 | stream.Close(); | |||
4054 | | |||
4055 | temporaryName_ = GetTempFileName(fileName_, true); | |||
4056 | File.Copy(fileName_, temporaryName_, true); | |||
4057 | | |||
4058 | temporaryStream_ = new FileStream(temporaryName_, | |||
4059 | FileMode.Open, | |||
4060 | FileAccess.ReadWrite); | |||
4061 | return temporaryStream_; | |||
4062 | } | |||
4063 | | |||
4064 | /// <summary> | |||
4065 | /// Return a stream suitable for performing direct updates on the original source. | |||
4066 | /// </summary> | |||
4067 | /// <param name="stream">The current stream.</param> | |||
4068 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4069 | /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks> | |||
4070 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4071 | { | |||
4072 | Stream result; | |||
4073 | if ((stream == null) || !stream.CanWrite) { | |||
4074 | if (stream != null) { | |||
4075 | stream.Close(); | |||
4076 | } | |||
4077 | | |||
4078 | result = new FileStream(fileName_, | |||
4079 | FileMode.Open, | |||
4080 | FileAccess.ReadWrite); | |||
4081 | } else { | |||
4082 | result = stream; | |||
4083 | } | |||
4084 | | |||
4085 | return result; | |||
4086 | } | |||
4087 | | |||
4088 | /// <summary> | |||
4089 | /// Disposes this instance. | |||
4090 | /// </summary> | |||
4091 | public override void Dispose() | |||
4092 | { | |||
4093 | if (temporaryStream_ != null) { | |||
4094 | temporaryStream_.Close(); | |||
4095 | } | |||
4096 | } | |||
4097 | | |||
4098 | #endregion | |||
4099 | | |||
4100 | #region Internal routines | |||
4101 | static string GetTempFileName(string original, bool makeTempFile) | |||
4102 | { | |||
4103 | string result = null; | |||
4104 | | |||
4105 | if (original == null) { | |||
4106 | result = Path.GetTempFileName(); | |||
4107 | } else { | |||
4108 | int counter = 0; | |||
4109 | int suffixSeed = DateTime.Now.Second; | |||
4110 | | |||
4111 | while (result == null) { | |||
4112 | counter += 1; | |||
4113 | string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); | |||
4114 | if (!File.Exists(newName)) { | |||
4115 | if (makeTempFile) { | |||
4116 | try { | |||
4117 | // Try and create the file. | |||
4118 | using (FileStream stream = File.Create(newName)) { | |||
4119 | } | |||
4120 | result = newName; | |||
4121 | } catch { | |||
4122 | suffixSeed = DateTime.Now.Second; | |||
4123 | } | |||
4124 | } else { | |||
4125 | result = newName; | |||
4126 | } | |||
4127 | } | |||
4128 | } | |||
4129 | } | |||
4130 | return result; | |||
4131 | } | |||
4132 | #endregion | |||
4133 | | |||
4134 | #region Instance Fields | |||
4135 | Stream temporaryStream_; | |||
4136 | string fileName_; | |||
4137 | string temporaryName_; | |||
4138 | #endregion | |||
4139 | } | |||
4140 | | |||
4141 | /// <summary> | |||
4142 | /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. | |||
4143 | /// </summary> | |||
4144 | public class MemoryArchiveStorage : BaseArchiveStorage | |||
4145 | { | |||
4146 | #region Constructors | |||
4147 | /// <summary> | |||
4148 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4149 | /// </summary> | |||
4150 | public MemoryArchiveStorage() | |||
4151 | : base(FileUpdateMode.Direct) | |||
4152 | { | |||
4153 | } | |||
4154 | | |||
4155 | /// <summary> | |||
4156 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4157 | /// </summary> | |||
4158 | /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> | |||
4159 | /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> | |||
4160 | public MemoryArchiveStorage(FileUpdateMode updateMode) | |||
4161 | : base(updateMode) | |||
4162 | { | |||
4163 | } | |||
4164 | | |||
4165 | #endregion | |||
4166 | | |||
4167 | #region Properties | |||
4168 | /// <summary> | |||
4169 | /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. | |||
4170 | /// </summary> | |||
4171 | public MemoryStream FinalStream { | |||
4172 | get { return finalStream_; } | |||
4173 | } | |||
4174 | | |||
4175 | #endregion | |||
4176 | | |||
4177 | #region IArchiveStorage Members | |||
4178 | | |||
4179 | /// <summary> | |||
4180 | /// Gets the temporary output <see cref="Stream"/> | |||
4181 | /// </summary> | |||
4182 | /// <returns>Returns the temporary output stream.</returns> | |||
4183 | public override Stream GetTemporaryOutput() | |||
4184 | { | |||
4185 | temporaryStream_ = new MemoryStream(); | |||
4186 | return temporaryStream_; | |||
4187 | } | |||
4188 | | |||
4189 | /// <summary> | |||
4190 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
4191 | /// </summary> | |||
4192 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4193 | /// the final storage for the archive.</returns> | |||
4194 | public override Stream ConvertTemporaryToFinal() | |||
4195 | { | |||
4196 | if (temporaryStream_ == null) { | |||
4197 | throw new ZipException("No temporary stream has been created"); | |||
4198 | } | |||
4199 | | |||
4200 | finalStream_ = new MemoryStream(temporaryStream_.ToArray()); | |||
4201 | return finalStream_; | |||
4202 | } | |||
4203 | | |||
4204 | /// <summary> | |||
4205 | /// Make a temporary copy of the original stream. | |||
4206 | /// </summary> | |||
4207 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4208 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4209 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4210 | { | |||
4211 | temporaryStream_ = new MemoryStream(); | |||
4212 | stream.Position = 0; | |||
4213 | StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); | |||
4214 | return temporaryStream_; | |||
4215 | } | |||
4216 | | |||
4217 | /// <summary> | |||
4218 | /// Return a stream suitable for performing direct updates on the original source. | |||
4219 | /// </summary> | |||
4220 | /// <param name="stream">The original source stream</param> | |||
4221 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4222 | /// <remarks>If the <paramref name="stream"/> passed is not null this is used; | |||
4223 | /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> | |||
4224 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4225 | { | |||
4226 | Stream result; | |||
4227 | if ((stream == null) || !stream.CanWrite) { | |||
4228 | | |||
4229 | result = new MemoryStream(); | |||
4230 | | |||
4231 | if (stream != null) { | |||
4232 | stream.Position = 0; | |||
4233 | StreamUtils.Copy(stream, result, new byte[4096]); | |||
4234 | | |||
4235 | stream.Close(); | |||
4236 | } | |||
4237 | } else { | |||
4238 | result = stream; | |||
4239 | } | |||
4240 | | |||
4241 | return result; | |||
4242 | } | |||
4243 | | |||
4244 | /// <summary> | |||
4245 | /// Disposes this instance. | |||
4246 | /// </summary> | |||
4247 | public override void Dispose() | |||
4248 | { | |||
4249 | if (temporaryStream_ != null) { | |||
4250 | temporaryStream_.Close(); | |||
4251 | } | |||
4252 | } | |||
4253 | | |||
4254 | #endregion | |||
4255 | | |||
4256 | #region Instance Fields | |||
4257 | MemoryStream temporaryStream_; | |||
4258 | MemoryStream finalStream_; | |||
4259 | #endregion | |||
4260 | } | |||
4261 | | |||
4262 | #endregion | |||
4263 | } |
| Class: | ICSharpCode.SharpZipLib.Core.CompletedFileHandler |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | |
| Covered lines: | 0 |
| Uncovered lines: | 0 |
| Coverable lines: | 0 |
| Total lines: | 0 |
| Line coverage: |
No files found. This usually happens if a file isn't covered by a test or the class does not contain any sequence points (e.g. a class that only contains auto properties).
+| Class: | ICSharpCode.SharpZipLib.Checksum.Crc32 |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Checksum\Crc32.cs |
| Covered lines: | 82 |
| Uncovered lines: | 0 |
| Coverable lines: | 82 |
| Total lines: | 189 |
| Line coverage: | 100% |
| Branch coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| ComputeCrc32(...) | 1 | 100 | 100 |
| .ctor() | 1 | 100 | 100 |
| Reset() | 1 | 100 | 100 |
| Update(...) | 1 | 100 | 100 |
| Update(...) | 2 | 100 | 100 |
| Update(...) | 7 | 100 | 100 |
| .cctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Checksum | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// CRC-32 with reversed data and unreversed output | |||
7 | /// </summary> | |||
8 | /// <remarks> | |||
9 | /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: | |||
10 | /// 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. | |||
11 | /// | |||
12 | /// Polynomials over GF(2) are represented in binary, one bit per coefficient, | |||
13 | /// with the lowest powers in the most significant bit. Then adding polynomials | |||
14 | /// is just exclusive-or, and multiplying a polynomial by x is a right shift by | |||
15 | /// one. If we call the above polynomial p, and represent a byte as the | |||
16 | /// polynomial q, also with the lowest power in the most significant bit (so the | |||
17 | /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, | |||
18 | /// where a mod b means the remainder after dividing a by b. | |||
19 | /// | |||
20 | /// This calculation is done using the shift-register method of multiplying and | |||
21 | /// taking the remainder. The register is initialized to zero, and for each | |||
22 | /// incoming bit, x^32 is added mod p to the register if the bit is a one (where | |||
23 | /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by | |||
24 | /// x (which is shifting right by one and adding x^32 mod p if the bit shifted | |||
25 | /// out is a one). We start with the highest power (least significant bit) of | |||
26 | /// q and repeat for all eight bits of q. | |||
27 | /// | |||
28 | /// The table is simply the CRC of all possible eight bit values. This is all | |||
29 | /// the information needed to generate CRC's on data a byte at a time for all | |||
30 | /// combinations of CRC register values and incoming bytes. | |||
31 | /// </remarks> | |||
32 | public sealed class Crc32 : IChecksum | |||
33 | { | |||
34 | #region Instance Fields | |||
| 1 | 35 | readonly static uint crcInit = 0xFFFFFFFF; | ||
| 1 | 36 | readonly static uint crcXor = 0xFFFFFFFF; | ||
37 | | |||
| 1 | 38 | readonly static uint[] crcTable = { | ||
| 1 | 39 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, | ||
| 1 | 40 | 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, | ||
| 1 | 41 | 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, | ||
| 1 | 42 | 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, | ||
| 1 | 43 | 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, | ||
| 1 | 44 | 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, | ||
| 1 | 45 | 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, | ||
| 1 | 46 | 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, | ||
| 1 | 47 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, | ||
| 1 | 48 | 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, | ||
| 1 | 49 | 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, | ||
| 1 | 50 | 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, | ||
| 1 | 51 | 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, | ||
| 1 | 52 | 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, | ||
| 1 | 53 | 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, | ||
| 1 | 54 | 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, | ||
| 1 | 55 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, | ||
| 1 | 56 | 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, | ||
| 1 | 57 | 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, | ||
| 1 | 58 | 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, | ||
| 1 | 59 | 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, | ||
| 1 | 60 | 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, | ||
| 1 | 61 | 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, | ||
| 1 | 62 | 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, | ||
| 1 | 63 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, | ||
| 1 | 64 | 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, | ||
| 1 | 65 | 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, | ||
| 1 | 66 | 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, | ||
| 1 | 67 | 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, | ||
| 1 | 68 | 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, | ||
| 1 | 69 | 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, | ||
| 1 | 70 | 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, | ||
| 1 | 71 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, | ||
| 1 | 72 | 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, | ||
| 1 | 73 | 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, | ||
| 1 | 74 | 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, | ||
| 1 | 75 | 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, | ||
| 1 | 76 | 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, | ||
| 1 | 77 | 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, | ||
| 1 | 78 | 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, | ||
| 1 | 79 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, | ||
| 1 | 80 | 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, | ||
| 1 | 81 | 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, | ||
| 1 | 82 | 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, | ||
| 1 | 83 | 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, | ||
| 1 | 84 | 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, | ||
| 1 | 85 | 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, | ||
| 1 | 86 | 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, | ||
| 1 | 87 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, | ||
| 1 | 88 | 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, | ||
| 1 | 89 | 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, | ||
| 1 | 90 | 0x2D02EF8D | ||
| 1 | 91 | }; | ||
92 | | |||
93 | /// <summary> | |||
94 | /// The CRC data checksum so far. | |||
95 | /// </summary> | |||
96 | uint checkValue; | |||
97 | #endregion | |||
98 | | |||
99 | internal static uint ComputeCrc32(uint oldCrc, byte bval) | |||
100 | { | |||
| 4516642 | 101 | return (uint)(Crc32.crcTable[(oldCrc ^ bval) & 0xFF] ^ (oldCrc >> 8)); | ||
102 | } | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Initialise a default instance of <see cref="Crc32"></see> | |||
106 | /// </summary> | |||
| 66290 | 107 | public Crc32() | ||
108 | { | |||
| 66290 | 109 | Reset(); | ||
| 66290 | 110 | } | ||
111 | | |||
112 | /// <summary> | |||
113 | /// Resets the CRC data checksum as if no update was ever called. | |||
114 | /// </summary> | |||
115 | public void Reset() | |||
116 | { | |||
| 66451 | 117 | checkValue = crcInit; | ||
| 66451 | 118 | } | ||
119 | | |||
120 | /// <summary> | |||
121 | /// Returns the CRC data checksum computed so far. | |||
122 | /// </summary> | |||
123 | /// <remarks>Reversed Out = false</remarks> | |||
124 | public long Value { | |||
125 | get { | |||
| 66251 | 126 | return (long)(checkValue ^ crcXor); | ||
127 | } | |||
128 | } | |||
129 | | |||
130 | /// <summary> | |||
131 | /// Updates the checksum with the int bval. | |||
132 | /// </summary> | |||
133 | /// <param name = "bval"> | |||
134 | /// the byte is taken as the lower 8 bits of bval | |||
135 | /// </param> | |||
136 | /// <remarks>Reversed Data = true</remarks> | |||
137 | public void Update(int bval) | |||
138 | { | |||
| 4528259 | 139 | checkValue = unchecked(crcTable[(checkValue ^ bval) & 0xFF] ^ (checkValue >> 8)); | ||
| 4528259 | 140 | } | ||
141 | | |||
142 | /// <summary> | |||
143 | /// Updates the CRC data checksum with the bytes taken from | |||
144 | /// a block of data. | |||
145 | /// </summary> | |||
146 | /// <param name="buffer">Contains the data to update the CRC with.</param> | |||
147 | public void Update(byte[] buffer) | |||
148 | { | |||
| 2 | 149 | if (buffer == null) { | ||
| 1 | 150 | throw new ArgumentNullException(nameof(buffer)); | ||
151 | } | |||
152 | | |||
| 1 | 153 | Update(buffer, 0, buffer.Length); | ||
| 1 | 154 | } | ||
155 | | |||
156 | /// <summary> | |||
157 | /// Update CRC data checksum based on a portion of a block of data | |||
158 | /// </summary> | |||
159 | /// <param name = "buffer">Contains the data to update the CRC with.</param> | |||
160 | /// <param name = "offset">The offset into the buffer where the data starts</param> | |||
161 | /// <param name = "count">The number of data bytes to update the CRC with.</param> | |||
162 | public void Update(byte[] buffer, int offset, int count) | |||
163 | { | |||
| 5155 | 164 | if (buffer == null) { | ||
| 1 | 165 | throw new ArgumentNullException(nameof(buffer)); | ||
166 | } | |||
167 | | |||
| 5154 | 168 | if (offset < 0) { | ||
| 1 | 169 | throw new ArgumentOutOfRangeException(nameof(offset), "cannot be less than zero"); | ||
170 | } | |||
171 | | |||
| 5153 | 172 | if (offset >= buffer.Length) { | ||
| 2 | 173 | throw new ArgumentOutOfRangeException(nameof(offset), "not a valid index into buffer"); | ||
174 | } | |||
175 | | |||
| 5151 | 176 | if (count < 0) { | ||
| 1 | 177 | throw new ArgumentOutOfRangeException(nameof(count), "cannot be less than zero"); | ||
178 | } | |||
179 | | |||
| 5150 | 180 | if (offset + count > buffer.Length) { | ||
| 1 | 181 | throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); | ||
182 | } | |||
183 | | |||
| 9066816 | 184 | for (int i = 0; i < count; ++i) { | ||
| 4528259 | 185 | Update(buffer[offset++]); | ||
186 | } | |||
| 5149 | 187 | } | ||
188 | } | |||
189 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.Deflater |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\Deflater.cs |
| Covered lines: | 69 |
| Uncovered lines: | 29 |
| Coverable lines: | 98 |
| Total lines: | 521 |
| Line coverage: | 70.4% |
| Branch coverage: | 68% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 4 | 92.31 | 71.43 |
| Reset() | 3 | 100 | 100 |
| Flush() | 1 | 100 | 100 |
| Finish() | 1 | 100 | 100 |
| SetInput(...) | 1 | 0 | 0 |
| SetInput(...) | 2 | 75 | 66.67 |
| SetLevel(...) | 5 | 88.89 | 77.78 |
| GetLevel() | 1 | 100 | 100 |
| SetStrategy(...) | 1 | 100 | 100 |
| Deflate(...) | 1 | 0 | 0 |
| Deflate(...) | 16 | 69.05 | 70.97 |
| SetDictionary(...) | 1 | 0 | 0 |
| SetDictionary(...) | 2 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// This is the Deflater class. The deflater class compresses input | |||
7 | /// with the deflate algorithm described in RFC 1951. It has several | |||
8 | /// compression levels and three different strategies described below. | |||
9 | /// | |||
10 | /// This class is <i>not</i> thread safe. This is inherent in the API, due | |||
11 | /// to the split of deflate and setInput. | |||
12 | /// | |||
13 | /// author of the original java version : Jochen Hoenicke | |||
14 | /// </summary> | |||
15 | public class Deflater | |||
16 | { | |||
17 | #region Deflater Documentation | |||
18 | /* | |||
19 | * The Deflater can do the following state transitions: | |||
20 | * | |||
21 | * (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---. | |||
22 | * / | (2) (5) | | |||
23 | * / v (5) | | |||
24 | * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3) | |||
25 | * \ | (3) | ,--------' | |||
26 | * | | | (3) / | |||
27 | * v v (5) v v | |||
28 | * (1) -> BUSY_STATE ----> FINISHING_STATE | |||
29 | * | (6) | |||
30 | * v | |||
31 | * FINISHED_STATE | |||
32 | * \_____________________________________/ | |||
33 | * | (7) | |||
34 | * v | |||
35 | * CLOSED_STATE | |||
36 | * | |||
37 | * (1) If we should produce a header we start in INIT_STATE, otherwise | |||
38 | * we start in BUSY_STATE. | |||
39 | * (2) A dictionary may be set only when we are in INIT_STATE, then | |||
40 | * we change the state as indicated. | |||
41 | * (3) Whether a dictionary is set or not, on the first call of deflate | |||
42 | * we change to BUSY_STATE. | |||
43 | * (4) -- intentionally left blank -- :) | |||
44 | * (5) FINISHING_STATE is entered, when flush() is called to indicate that | |||
45 | * there is no more INPUT. There are also states indicating, that | |||
46 | * the header wasn't written yet. | |||
47 | * (6) FINISHED_STATE is entered, when everything has been flushed to the | |||
48 | * internal pending output buffer. | |||
49 | * (7) At any time (7) | |||
50 | * | |||
51 | */ | |||
52 | #endregion | |||
53 | #region Public Constants | |||
54 | /// <summary> | |||
55 | /// The best and slowest compression level. This tries to find very | |||
56 | /// long and distant string repetitions. | |||
57 | /// </summary> | |||
58 | public const int BEST_COMPRESSION = 9; | |||
59 | | |||
60 | /// <summary> | |||
61 | /// The worst but fastest compression level. | |||
62 | /// </summary> | |||
63 | public const int BEST_SPEED = 1; | |||
64 | | |||
65 | /// <summary> | |||
66 | /// The default compression level. | |||
67 | /// </summary> | |||
68 | public const int DEFAULT_COMPRESSION = -1; | |||
69 | | |||
70 | /// <summary> | |||
71 | /// This level won't compress at all but output uncompressed blocks. | |||
72 | /// </summary> | |||
73 | public const int NO_COMPRESSION = 0; | |||
74 | | |||
75 | /// <summary> | |||
76 | /// The compression method. This is the only method supported so far. | |||
77 | /// There is no need to use this constant at all. | |||
78 | /// </summary> | |||
79 | public const int DEFLATED = 8; | |||
80 | #endregion | |||
81 | #region Local Constants | |||
82 | private const int IS_SETDICT = 0x01; | |||
83 | private const int IS_FLUSHING = 0x04; | |||
84 | private const int IS_FINISHING = 0x08; | |||
85 | | |||
86 | private const int INIT_STATE = 0x00; | |||
87 | private const int SETDICT_STATE = 0x01; | |||
88 | // private static int INIT_FINISHING_STATE = 0x08; | |||
89 | // private static int SETDICT_FINISHING_STATE = 0x09; | |||
90 | private const int BUSY_STATE = 0x10; | |||
91 | private const int FLUSHING_STATE = 0x14; | |||
92 | private const int FINISHING_STATE = 0x1c; | |||
93 | private const int FINISHED_STATE = 0x1e; | |||
94 | private const int CLOSED_STATE = 0x7f; | |||
95 | #endregion | |||
96 | #region Constructors | |||
97 | /// <summary> | |||
98 | /// Creates a new deflater with default compression level. | |||
99 | /// </summary> | |||
| 4 | 100 | public Deflater() : this(DEFAULT_COMPRESSION, false) | ||
101 | { | |||
102 | | |||
| 4 | 103 | } | ||
104 | | |||
105 | /// <summary> | |||
106 | /// Creates a new deflater with given compression level. | |||
107 | /// </summary> | |||
108 | /// <param name="level"> | |||
109 | /// the compression level, a value between NO_COMPRESSION | |||
110 | /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION. | |||
111 | /// </param> | |||
112 | /// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception> | |||
| 0 | 113 | public Deflater(int level) : this(level, false) | ||
114 | { | |||
115 | | |||
| 0 | 116 | } | ||
117 | | |||
118 | /// <summary> | |||
119 | /// Creates a new deflater with given compression level. | |||
120 | /// </summary> | |||
121 | /// <param name="level"> | |||
122 | /// the compression level, a value between NO_COMPRESSION | |||
123 | /// and BEST_COMPRESSION. | |||
124 | /// </param> | |||
125 | /// <param name="noZlibHeaderOrFooter"> | |||
126 | /// true, if we should suppress the Zlib/RFC1950 header at the | |||
127 | /// beginning and the adler checksum at the end of the output. This is | |||
128 | /// useful for the GZIP/PKZIP formats. | |||
129 | /// </param> | |||
130 | /// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception> | |||
| 273 | 131 | public Deflater(int level, bool noZlibHeaderOrFooter) | ||
132 | { | |||
| 273 | 133 | if (level == DEFAULT_COMPRESSION) { | ||
| 100 | 134 | level = 6; | ||
| 273 | 135 | } else if (level < NO_COMPRESSION || level > BEST_COMPRESSION) { | ||
| 0 | 136 | throw new ArgumentOutOfRangeException(nameof(level)); | ||
137 | } | |||
138 | | |||
| 273 | 139 | pending = new DeflaterPending(); | ||
| 273 | 140 | engine = new DeflaterEngine(pending); | ||
| 273 | 141 | this.noZlibHeaderOrFooter = noZlibHeaderOrFooter; | ||
| 273 | 142 | SetStrategy(DeflateStrategy.Default); | ||
| 273 | 143 | SetLevel(level); | ||
| 273 | 144 | Reset(); | ||
| 273 | 145 | } | ||
146 | #endregion | |||
147 | | |||
148 | /// <summary> | |||
149 | /// Resets the deflater. The deflater acts afterwards as if it was | |||
150 | /// just created with the same compression level and strategy as it | |||
151 | /// had before. | |||
152 | /// </summary> | |||
153 | public void Reset() | |||
154 | { | |||
| 392 | 155 | state = (noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE); | ||
| 392 | 156 | totalOut = 0; | ||
| 392 | 157 | pending.Reset(); | ||
| 392 | 158 | engine.Reset(); | ||
| 392 | 159 | } | ||
160 | | |||
161 | /// <summary> | |||
162 | /// Gets the current adler checksum of the data that was processed so far. | |||
163 | /// </summary> | |||
164 | public int Adler { | |||
165 | get { | |||
| 0 | 166 | return engine.Adler; | ||
167 | } | |||
168 | } | |||
169 | | |||
170 | /// <summary> | |||
171 | /// Gets the number of input bytes processed so far. | |||
172 | /// </summary> | |||
173 | public long TotalIn { | |||
174 | get { | |||
| 9 | 175 | return engine.TotalIn; | ||
176 | } | |||
177 | } | |||
178 | | |||
179 | /// <summary> | |||
180 | /// Gets the number of output bytes so far. | |||
181 | /// </summary> | |||
182 | public long TotalOut { | |||
183 | get { | |||
| 119 | 184 | return totalOut; | ||
185 | } | |||
186 | } | |||
187 | | |||
188 | /// <summary> | |||
189 | /// Flushes the current input block. Further calls to deflate() will | |||
190 | /// produce enough output to inflate everything in the current input | |||
191 | /// block. This is not part of Sun's JDK so I have made it package | |||
192 | /// private. It is used by DeflaterOutputStream to implement | |||
193 | /// flush(). | |||
194 | /// </summary> | |||
195 | public void Flush() | |||
196 | { | |||
| 30 | 197 | state |= IS_FLUSHING; | ||
| 30 | 198 | } | ||
199 | | |||
200 | /// <summary> | |||
201 | /// Finishes the deflater with the current input block. It is an error | |||
202 | /// to give more input after this method was called. This method must | |||
203 | /// be called to force all bytes to be flushed. | |||
204 | /// </summary> | |||
205 | public void Finish() | |||
206 | { | |||
| 325 | 207 | state |= (IS_FLUSHING | IS_FINISHING); | ||
| 325 | 208 | } | ||
209 | | |||
210 | /// <summary> | |||
211 | /// Returns true if the stream was finished and no more output bytes | |||
212 | /// are available. | |||
213 | /// </summary> | |||
214 | public bool IsFinished { | |||
215 | get { | |||
| 1898 | 216 | return (state == FINISHED_STATE) && pending.IsFlushed; | ||
217 | } | |||
218 | } | |||
219 | | |||
220 | /// <summary> | |||
221 | /// Returns true, if the input buffer is empty. | |||
222 | /// You should then call setInput(). | |||
223 | /// NOTE: This method can also return true when the stream | |||
224 | /// was finished. | |||
225 | /// </summary> | |||
226 | public bool IsNeedingInput { | |||
227 | get { | |||
| 16258 | 228 | return engine.NeedsInput(); | ||
229 | } | |||
230 | } | |||
231 | | |||
232 | /// <summary> | |||
233 | /// Sets the data which should be compressed next. This should be only | |||
234 | /// called when needsInput indicates that more input is needed. | |||
235 | /// If you call setInput when needsInput() returns false, the | |||
236 | /// previous input that is still pending will be thrown away. | |||
237 | /// The given byte array should not be changed, before needsInput() returns | |||
238 | /// true again. | |||
239 | /// This call is equivalent to <code>setInput(input, 0, input.length)</code>. | |||
240 | /// </summary> | |||
241 | /// <param name="input"> | |||
242 | /// the buffer containing the input data. | |||
243 | /// </param> | |||
244 | /// <exception cref="System.InvalidOperationException"> | |||
245 | /// if the buffer was finished() or ended(). | |||
246 | /// </exception> | |||
247 | public void SetInput(byte[] input) | |||
248 | { | |||
| 0 | 249 | SetInput(input, 0, input.Length); | ||
| 0 | 250 | } | ||
251 | | |||
252 | /// <summary> | |||
253 | /// Sets the data which should be compressed next. This should be | |||
254 | /// only called when needsInput indicates that more input is needed. | |||
255 | /// The given byte array should not be changed, before needsInput() returns | |||
256 | /// true again. | |||
257 | /// </summary> | |||
258 | /// <param name="input"> | |||
259 | /// the buffer containing the input data. | |||
260 | /// </param> | |||
261 | /// <param name="offset"> | |||
262 | /// the start of the data. | |||
263 | /// </param> | |||
264 | /// <param name="count"> | |||
265 | /// the number of data bytes of input. | |||
266 | /// </param> | |||
267 | /// <exception cref="System.InvalidOperationException"> | |||
268 | /// if the buffer was Finish()ed or if previous input is still pending. | |||
269 | /// </exception> | |||
270 | public void SetInput(byte[] input, int offset, int count) | |||
271 | { | |||
| 4479 | 272 | if ((state & IS_FINISHING) != 0) { | ||
| 0 | 273 | throw new InvalidOperationException("Finish() already called"); | ||
274 | } | |||
| 4479 | 275 | engine.SetInput(input, offset, count); | ||
| 4479 | 276 | } | ||
277 | | |||
278 | /// <summary> | |||
279 | /// Sets the compression level. There is no guarantee of the exact | |||
280 | /// position of the change, but if you call this when needsInput is | |||
281 | /// true the change of compression level will occur somewhere near | |||
282 | /// before the end of the so far given input. | |||
283 | /// </summary> | |||
284 | /// <param name="level"> | |||
285 | /// the new compression level. | |||
286 | /// </param> | |||
287 | public void SetLevel(int level) | |||
288 | { | |||
| 447 | 289 | if (level == DEFAULT_COMPRESSION) { | ||
| 59 | 290 | level = 6; | ||
| 447 | 291 | } else if (level < NO_COMPRESSION || level > BEST_COMPRESSION) { | ||
| 0 | 292 | throw new ArgumentOutOfRangeException(nameof(level)); | ||
293 | } | |||
294 | | |||
| 447 | 295 | if (this.level != level) { | ||
| 327 | 296 | this.level = level; | ||
| 327 | 297 | engine.SetLevel(level); | ||
298 | } | |||
| 447 | 299 | } | ||
300 | | |||
301 | /// <summary> | |||
302 | /// Get current compression level | |||
303 | /// </summary> | |||
304 | /// <returns>Returns the current compression level</returns> | |||
305 | public int GetLevel() | |||
306 | { | |||
| 3 | 307 | return level; | ||
308 | } | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Sets the compression strategy. Strategy is one of | |||
312 | /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact | |||
313 | /// position where the strategy is changed, the same as for | |||
314 | /// SetLevel() applies. | |||
315 | /// </summary> | |||
316 | /// <param name="strategy"> | |||
317 | /// The new compression strategy. | |||
318 | /// </param> | |||
319 | public void SetStrategy(DeflateStrategy strategy) | |||
320 | { | |||
| 273 | 321 | engine.Strategy = strategy; | ||
| 273 | 322 | } | ||
323 | | |||
324 | /// <summary> | |||
325 | /// Deflates the current input block with to the given array. | |||
326 | /// </summary> | |||
327 | /// <param name="output"> | |||
328 | /// The buffer where compressed data is stored | |||
329 | /// </param> | |||
330 | /// <returns> | |||
331 | /// The number of compressed bytes added to the output, or 0 if either | |||
332 | /// IsNeedingInput() or IsFinished returns true or length is zero. | |||
333 | /// </returns> | |||
334 | public int Deflate(byte[] output) | |||
335 | { | |||
| 0 | 336 | return Deflate(output, 0, output.Length); | ||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Deflates the current input block to the given array. | |||
341 | /// </summary> | |||
342 | /// <param name="output"> | |||
343 | /// Buffer to store the compressed data. | |||
344 | /// </param> | |||
345 | /// <param name="offset"> | |||
346 | /// Offset into the output array. | |||
347 | /// </param> | |||
348 | /// <param name="length"> | |||
349 | /// The maximum number of bytes that may be stored. | |||
350 | /// </param> | |||
351 | /// <returns> | |||
352 | /// The number of compressed bytes added to the output, or 0 if either | |||
353 | /// needsInput() or finished() returns true or length is zero. | |||
354 | /// </returns> | |||
355 | /// <exception cref="System.InvalidOperationException"> | |||
356 | /// If Finish() was previously called. | |||
357 | /// </exception> | |||
358 | /// <exception cref="System.ArgumentOutOfRangeException"> | |||
359 | /// If offset or length don't match the array length. | |||
360 | /// </exception> | |||
361 | public int Deflate(byte[] output, int offset, int length) | |||
362 | { | |||
| 12717 | 363 | int origLength = length; | ||
364 | | |||
| 12717 | 365 | if (state == CLOSED_STATE) { | ||
| 0 | 366 | throw new InvalidOperationException("Deflater closed"); | ||
367 | } | |||
368 | | |||
| 12717 | 369 | if (state < BUSY_STATE) { | ||
370 | // output header | |||
| 14 | 371 | int header = (DEFLATED + | ||
| 14 | 372 | ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8; | ||
| 14 | 373 | int level_flags = (level - 1) >> 1; | ||
| 14 | 374 | if (level_flags < 0 || level_flags > 3) { | ||
| 2 | 375 | level_flags = 3; | ||
376 | } | |||
| 14 | 377 | header |= level_flags << 6; | ||
| 14 | 378 | if ((state & IS_SETDICT) != 0) { | ||
379 | // Dictionary was set | |||
| 0 | 380 | header |= DeflaterConstants.PRESET_DICT; | ||
381 | } | |||
| 14 | 382 | header += 31 - (header % 31); | ||
383 | | |||
| 14 | 384 | pending.WriteShortMSB(header); | ||
| 14 | 385 | if ((state & IS_SETDICT) != 0) { | ||
| 0 | 386 | int chksum = engine.Adler; | ||
| 0 | 387 | engine.ResetAdler(); | ||
| 0 | 388 | pending.WriteShortMSB(chksum >> 16); | ||
| 0 | 389 | pending.WriteShortMSB(chksum & 0xffff); | ||
390 | } | |||
391 | | |||
| 14 | 392 | state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING)); | ||
393 | } | |||
394 | | |||
395 | for (;;) { | |||
| 13248 | 396 | int count = pending.Flush(output, offset, length); | ||
| 13248 | 397 | offset += count; | ||
| 13248 | 398 | totalOut += count; | ||
| 13248 | 399 | length -= count; | ||
400 | | |||
| 13248 | 401 | if (length == 0 || state == FINISHED_STATE) { | ||
402 | break; | |||
403 | } | |||
404 | | |||
| 4878 | 405 | if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0)) { | ||
| 4652 | 406 | switch (state) { | ||
407 | case BUSY_STATE: | |||
408 | // We need more input now | |||
| 4347 | 409 | return origLength - length; | ||
410 | case FLUSHING_STATE: | |||
| 0 | 411 | if (level != NO_COMPRESSION) { | ||
412 | /* We have to supply some lookahead. 8 bit lookahead | |||
413 | * is needed by the zlib inflater, and we must fill | |||
414 | * the next byte, so that all bits are flushed. | |||
415 | */ | |||
| 0 | 416 | int neededbits = 8 + ((-pending.BitCount) & 7); | ||
| 0 | 417 | while (neededbits > 0) { | ||
418 | /* write a static tree block consisting solely of | |||
419 | * an EOF: | |||
420 | */ | |||
| 0 | 421 | pending.WriteBits(2, 10); | ||
| 0 | 422 | neededbits -= 10; | ||
423 | } | |||
424 | } | |||
| 0 | 425 | state = BUSY_STATE; | ||
| 0 | 426 | break; | ||
427 | case FINISHING_STATE: | |||
| 305 | 428 | pending.AlignToByte(); | ||
429 | | |||
430 | // Compressed data is complete. Write footer information if required. | |||
| 305 | 431 | if (!noZlibHeaderOrFooter) { | ||
| 14 | 432 | int adler = engine.Adler; | ||
| 14 | 433 | pending.WriteShortMSB(adler >> 16); | ||
| 14 | 434 | pending.WriteShortMSB(adler & 0xffff); | ||
435 | } | |||
| 305 | 436 | state = FINISHED_STATE; | ||
| 305 | 437 | break; | ||
438 | } | |||
439 | } | |||
440 | } | |||
| 8370 | 441 | return origLength - length; | ||
442 | } | |||
443 | | |||
444 | /// <summary> | |||
445 | /// Sets the dictionary which should be used in the deflate process. | |||
446 | /// This call is equivalent to <code>setDictionary(dict, 0, dict.Length)</code>. | |||
447 | /// </summary> | |||
448 | /// <param name="dictionary"> | |||
449 | /// the dictionary. | |||
450 | /// </param> | |||
451 | /// <exception cref="System.InvalidOperationException"> | |||
452 | /// if SetInput () or Deflate () were already called or another dictionary was already set. | |||
453 | /// </exception> | |||
454 | public void SetDictionary(byte[] dictionary) | |||
455 | { | |||
| 0 | 456 | SetDictionary(dictionary, 0, dictionary.Length); | ||
| 0 | 457 | } | ||
458 | | |||
459 | /// <summary> | |||
460 | /// Sets the dictionary which should be used in the deflate process. | |||
461 | /// The dictionary is a byte array containing strings that are | |||
462 | /// likely to occur in the data which should be compressed. The | |||
463 | /// dictionary is not stored in the compressed output, only a | |||
464 | /// checksum. To decompress the output you need to supply the same | |||
465 | /// dictionary again. | |||
466 | /// </summary> | |||
467 | /// <param name="dictionary"> | |||
468 | /// The dictionary data | |||
469 | /// </param> | |||
470 | /// <param name="index"> | |||
471 | /// The index where dictionary information commences. | |||
472 | /// </param> | |||
473 | /// <param name="count"> | |||
474 | /// The number of bytes in the dictionary. | |||
475 | /// </param> | |||
476 | /// <exception cref="System.InvalidOperationException"> | |||
477 | /// If SetInput () or Deflate() were already called or another dictionary was already set. | |||
478 | /// </exception> | |||
479 | public void SetDictionary(byte[] dictionary, int index, int count) | |||
480 | { | |||
| 0 | 481 | if (state != INIT_STATE) { | ||
| 0 | 482 | throw new InvalidOperationException(); | ||
483 | } | |||
484 | | |||
| 0 | 485 | state = SETDICT_STATE; | ||
| 0 | 486 | engine.SetDictionary(dictionary, index, count); | ||
| 0 | 487 | } | ||
488 | | |||
489 | #region Instance Fields | |||
490 | /// <summary> | |||
491 | /// Compression level. | |||
492 | /// </summary> | |||
493 | int level; | |||
494 | | |||
495 | /// <summary> | |||
496 | /// If true no Zlib/RFC1950 headers or footers are generated | |||
497 | /// </summary> | |||
498 | bool noZlibHeaderOrFooter; | |||
499 | | |||
500 | /// <summary> | |||
501 | /// The current state. | |||
502 | /// </summary> | |||
503 | int state; | |||
504 | | |||
505 | /// <summary> | |||
506 | /// The total bytes of output written. | |||
507 | /// </summary> | |||
508 | long totalOut; | |||
509 | | |||
510 | /// <summary> | |||
511 | /// The pending output. | |||
512 | /// </summary> | |||
513 | DeflaterPending pending; | |||
514 | | |||
515 | /// <summary> | |||
516 | /// The deflater engine. | |||
517 | /// </summary> | |||
518 | DeflaterEngine engine; | |||
519 | #endregion | |||
520 | } | |||
521 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.DeflaterConstants |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\DeflaterConstants.cs |
| Covered lines: | 6 |
| Uncovered lines: | 0 |
| Coverable lines: | 6 |
| Total lines: | 146 |
| Line coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .cctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// This class contains constants used for deflation. | |||
7 | /// </summary> | |||
8 | public static class DeflaterConstants | |||
9 | { | |||
10 | /// <summary> | |||
11 | /// Set to true to enable debugging | |||
12 | /// </summary> | |||
13 | public const bool DEBUGGING = false; | |||
14 | | |||
15 | /// <summary> | |||
16 | /// Written to Zip file to identify a stored block | |||
17 | /// </summary> | |||
18 | public const int STORED_BLOCK = 0; | |||
19 | | |||
20 | /// <summary> | |||
21 | /// Identifies static tree in Zip file | |||
22 | /// </summary> | |||
23 | public const int STATIC_TREES = 1; | |||
24 | | |||
25 | /// <summary> | |||
26 | /// Identifies dynamic tree in Zip file | |||
27 | /// </summary> | |||
28 | public const int DYN_TREES = 2; | |||
29 | | |||
30 | /// <summary> | |||
31 | /// Header flag indicating a preset dictionary for deflation | |||
32 | /// </summary> | |||
33 | public const int PRESET_DICT = 0x20; | |||
34 | | |||
35 | /// <summary> | |||
36 | /// Sets internal buffer sizes for Huffman encoding | |||
37 | /// </summary> | |||
38 | public const int DEFAULT_MEM_LEVEL = 8; | |||
39 | | |||
40 | /// <summary> | |||
41 | /// Internal compression engine constant | |||
42 | /// </summary> | |||
43 | public const int MAX_MATCH = 258; | |||
44 | | |||
45 | /// <summary> | |||
46 | /// Internal compression engine constant | |||
47 | /// </summary> | |||
48 | public const int MIN_MATCH = 3; | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Internal compression engine constant | |||
52 | /// </summary> | |||
53 | public const int MAX_WBITS = 15; | |||
54 | | |||
55 | /// <summary> | |||
56 | /// Internal compression engine constant | |||
57 | /// </summary> | |||
58 | public const int WSIZE = 1 << MAX_WBITS; | |||
59 | | |||
60 | /// <summary> | |||
61 | /// Internal compression engine constant | |||
62 | /// </summary> | |||
63 | public const int WMASK = WSIZE - 1; | |||
64 | | |||
65 | /// <summary> | |||
66 | /// Internal compression engine constant | |||
67 | /// </summary> | |||
68 | public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; | |||
69 | | |||
70 | /// <summary> | |||
71 | /// Internal compression engine constant | |||
72 | /// </summary> | |||
73 | public const int HASH_SIZE = 1 << HASH_BITS; | |||
74 | | |||
75 | /// <summary> | |||
76 | /// Internal compression engine constant | |||
77 | /// </summary> | |||
78 | public const int HASH_MASK = HASH_SIZE - 1; | |||
79 | | |||
80 | /// <summary> | |||
81 | /// Internal compression engine constant | |||
82 | /// </summary> | |||
83 | public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; | |||
84 | | |||
85 | /// <summary> | |||
86 | /// Internal compression engine constant | |||
87 | /// </summary> | |||
88 | public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; | |||
89 | | |||
90 | /// <summary> | |||
91 | /// Internal compression engine constant | |||
92 | /// </summary> | |||
93 | public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; | |||
94 | | |||
95 | /// <summary> | |||
96 | /// Internal compression engine constant | |||
97 | /// </summary> | |||
98 | public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); | |||
99 | | |||
100 | /// <summary> | |||
101 | /// Internal compression engine constant | |||
102 | /// </summary> | |||
| 1 | 103 | public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); | ||
104 | | |||
105 | /// <summary> | |||
106 | /// Internal compression engine constant | |||
107 | /// </summary> | |||
108 | public const int DEFLATE_STORED = 0; | |||
109 | | |||
110 | /// <summary> | |||
111 | /// Internal compression engine constant | |||
112 | /// </summary> | |||
113 | public const int DEFLATE_FAST = 1; | |||
114 | | |||
115 | /// <summary> | |||
116 | /// Internal compression engine constant | |||
117 | /// </summary> | |||
118 | public const int DEFLATE_SLOW = 2; | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Internal compression engine constant | |||
122 | /// </summary> | |||
| 1 | 123 | public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; | ||
124 | | |||
125 | /// <summary> | |||
126 | /// Internal compression engine constant | |||
127 | /// </summary> | |||
| 1 | 128 | public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 }; | ||
129 | | |||
130 | /// <summary> | |||
131 | /// Internal compression engine constant | |||
132 | /// </summary> | |||
| 1 | 133 | public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 }; | ||
134 | | |||
135 | /// <summary> | |||
136 | /// Internal compression engine constant | |||
137 | /// </summary> | |||
| 1 | 138 | public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 }; | ||
139 | | |||
140 | /// <summary> | |||
141 | /// Internal compression engine constant | |||
142 | /// </summary> | |||
| 1 | 143 | public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; | ||
144 | | |||
145 | } | |||
146 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.DeflaterEngine |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\DeflaterEngine.cs |
| Covered lines: | 238 |
| Uncovered lines: | 37 |
| Coverable lines: | 275 |
| Total lines: | 812 |
| Line coverage: | 86.5% |
| Branch coverage: | 84.4% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| Deflate(...) | 6 | 91.67 | 85.71 |
| SetInput(...) | 7 | 66.67 | 53.85 |
| NeedsInput() | 1 | 100 | 100 |
| SetDictionary(...) | 4 | 0 | 0 |
| Reset() | 3 | 100 | 100 |
| ResetAdler() | 1 | 0 | 0 |
| SetLevel(...) | 11 | 65.38 | 63.16 |
| FillWindow() | 6 | 100 | 100 |
| UpdateHash() | 1 | 100 | 100 |
| InsertString() | 1 | 100 | 100 |
| SlideWindow() | 7 | 100 | 100 |
| FindLongestMatch(...) | 20 | 100 | 94.87 |
| DeflateStored(...) | 8 | 100 | 100 |
| DeflateFast(...) | 18 | 93.94 | 87.88 |
| DeflateSlow(...) | 25 | 100 | 93.33 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using ICSharpCode.SharpZipLib.Checksum; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// Strategies for deflater | |||
8 | /// </summary> | |||
9 | public enum DeflateStrategy | |||
10 | { | |||
11 | /// <summary> | |||
12 | /// The default strategy | |||
13 | /// </summary> | |||
14 | Default = 0, | |||
15 | | |||
16 | /// <summary> | |||
17 | /// This strategy will only allow longer string repetitions. It is | |||
18 | /// useful for random data with a small character set. | |||
19 | /// </summary> | |||
20 | Filtered = 1, | |||
21 | | |||
22 | | |||
23 | /// <summary> | |||
24 | /// This strategy will not look for string repetitions at all. It | |||
25 | /// only encodes with Huffman trees (which means, that more common | |||
26 | /// characters get a smaller encoding. | |||
27 | /// </summary> | |||
28 | HuffmanOnly = 2 | |||
29 | } | |||
30 | | |||
31 | // DEFLATE ALGORITHM: | |||
32 | // | |||
33 | // The uncompressed stream is inserted into the window array. When | |||
34 | // the window array is full the first half is thrown away and the | |||
35 | // second half is copied to the beginning. | |||
36 | // | |||
37 | // The head array is a hash table. Three characters build a hash value | |||
38 | // and they the value points to the corresponding index in window of | |||
39 | // the last string with this hash. The prev array implements a | |||
40 | // linked list of matches with the same hash: prev[index & WMASK] points | |||
41 | // to the previous index with the same hash. | |||
42 | // | |||
43 | | |||
44 | | |||
45 | /// <summary> | |||
46 | /// Low level compression engine for deflate algorithm which uses a 32K sliding window | |||
47 | /// with secondary compression from Huffman/Shannon-Fano codes. | |||
48 | /// </summary> | |||
49 | public class DeflaterEngine | |||
50 | { | |||
51 | #region Constants | |||
52 | const int TooFar = 4096; | |||
53 | #endregion | |||
54 | | |||
55 | #region Constructors | |||
56 | /// <summary> | |||
57 | /// Construct instance with pending buffer | |||
58 | /// </summary> | |||
59 | /// <param name="pending"> | |||
60 | /// Pending buffer to use | |||
61 | /// </param>> | |||
| 273 | 62 | public DeflaterEngine(DeflaterPending pending) | ||
63 | { | |||
| 273 | 64 | this.pending = pending; | ||
| 273 | 65 | huffman = new DeflaterHuffman(pending); | ||
| 273 | 66 | adler = new Adler32(); | ||
67 | | |||
| 273 | 68 | window = new byte[2 * DeflaterConstants.WSIZE]; | ||
| 273 | 69 | head = new short[DeflaterConstants.HASH_SIZE]; | ||
| 273 | 70 | prev = new short[DeflaterConstants.WSIZE]; | ||
71 | | |||
72 | // We start at index 1, to avoid an implementation deficiency, that | |||
73 | // we cannot build a repeat pattern at index 0. | |||
| 273 | 74 | blockStart = strstart = 1; | ||
| 273 | 75 | } | ||
76 | | |||
77 | #endregion | |||
78 | | |||
79 | /// <summary> | |||
80 | /// Deflate drives actual compression of data | |||
81 | /// </summary> | |||
82 | /// <param name="flush">True to flush input buffers</param> | |||
83 | /// <param name="finish">Finish deflation with the current input.</param> | |||
84 | /// <returns>Returns true if progress has been made.</returns> | |||
85 | public bool Deflate(bool flush, bool finish) | |||
86 | { | |||
87 | bool progress; | |||
88 | do { | |||
| 9140 | 89 | FillWindow(); | ||
| 9140 | 90 | bool canFlush = flush && (inputOff == inputEnd); | ||
91 | | |||
92 | #if DebugDeflation | |||
93 | if (DeflaterConstants.DEBUGGING) { | |||
94 | Console.WriteLine("window: [" + blockStart + "," + strstart + "," | |||
95 | + lookahead + "], " + compressionFunction + "," + canFlush); | |||
96 | } | |||
97 | #endif | |||
| 9140 | 98 | switch (compressionFunction) { | ||
99 | case DeflaterConstants.DEFLATE_STORED: | |||
| 1373 | 100 | progress = DeflateStored(canFlush, finish); | ||
| 1373 | 101 | break; | ||
102 | case DeflaterConstants.DEFLATE_FAST: | |||
| 3236 | 103 | progress = DeflateFast(canFlush, finish); | ||
| 3236 | 104 | break; | ||
105 | case DeflaterConstants.DEFLATE_SLOW: | |||
| 4531 | 106 | progress = DeflateSlow(canFlush, finish); | ||
| 4531 | 107 | break; | ||
108 | default: | |||
| 0 | 109 | throw new InvalidOperationException("unknown compressionFunction"); | ||
110 | } | |||
| 9140 | 111 | } while (pending.IsFlushed && progress); // repeat while we have no pending output and progress was made | ||
| 4878 | 112 | return progress; | ||
113 | } | |||
114 | | |||
115 | /// <summary> | |||
116 | /// Sets input data to be deflated. Should only be called when <code>NeedsInput()</code> | |||
117 | /// returns true | |||
118 | /// </summary> | |||
119 | /// <param name="buffer">The buffer containing input data.</param> | |||
120 | /// <param name="offset">The offset of the first byte of data.</param> | |||
121 | /// <param name="count">The number of bytes of data to use as input.</param> | |||
122 | public void SetInput(byte[] buffer, int offset, int count) | |||
123 | { | |||
| 4479 | 124 | if (buffer == null) { | ||
| 0 | 125 | throw new ArgumentNullException(nameof(buffer)); | ||
126 | } | |||
127 | | |||
| 4479 | 128 | if (offset < 0) { | ||
| 0 | 129 | throw new ArgumentOutOfRangeException(nameof(offset)); | ||
130 | } | |||
131 | | |||
| 4479 | 132 | if (count < 0) { | ||
| 0 | 133 | throw new ArgumentOutOfRangeException(nameof(count)); | ||
134 | } | |||
135 | | |||
| 4479 | 136 | if (inputOff < inputEnd) { | ||
| 0 | 137 | throw new InvalidOperationException("Old input was not completely processed"); | ||
138 | } | |||
139 | | |||
| 4479 | 140 | int end = offset + count; | ||
141 | | |||
142 | /* We want to throw an ArrayIndexOutOfBoundsException early. The | |||
143 | * check is very tricky: it also handles integer wrap around. | |||
144 | */ | |||
| 4479 | 145 | if ((offset > end) || (end > buffer.Length)) { | ||
| 0 | 146 | throw new ArgumentOutOfRangeException(nameof(count)); | ||
147 | } | |||
148 | | |||
| 4479 | 149 | inputBuf = buffer; | ||
| 4479 | 150 | inputOff = offset; | ||
| 4479 | 151 | inputEnd = end; | ||
| 4479 | 152 | } | ||
153 | | |||
154 | /// <summary> | |||
155 | /// Determines if more <see cref="SetInput">input</see> is needed. | |||
156 | /// </summary> | |||
157 | /// <returns>Return true if input is needed via <see cref="SetInput">SetInput</see></returns> | |||
158 | public bool NeedsInput() | |||
159 | { | |||
| 16258 | 160 | return (inputEnd == inputOff); | ||
161 | } | |||
162 | | |||
163 | /// <summary> | |||
164 | /// Set compression dictionary | |||
165 | /// </summary> | |||
166 | /// <param name="buffer">The buffer containing the dictionary data</param> | |||
167 | /// <param name="offset">The offset in the buffer for the first byte of data</param> | |||
168 | /// <param name="length">The length of the dictionary data.</param> | |||
169 | public void SetDictionary(byte[] buffer, int offset, int length) | |||
170 | { | |||
171 | #if DebugDeflation | |||
172 | if (DeflaterConstants.DEBUGGING && (strstart != 1) ) | |||
173 | { | |||
174 | throw new InvalidOperationException("strstart not 1"); | |||
175 | } | |||
176 | #endif | |||
| 0 | 177 | adler.Update(buffer, offset, length); | ||
| 0 | 178 | if (length < DeflaterConstants.MIN_MATCH) { | ||
| 0 | 179 | return; | ||
180 | } | |||
181 | | |||
| 0 | 182 | if (length > DeflaterConstants.MAX_DIST) { | ||
| 0 | 183 | offset += length - DeflaterConstants.MAX_DIST; | ||
| 0 | 184 | length = DeflaterConstants.MAX_DIST; | ||
185 | } | |||
186 | | |||
| 0 | 187 | System.Array.Copy(buffer, offset, window, strstart, length); | ||
188 | | |||
| 0 | 189 | UpdateHash(); | ||
| 0 | 190 | --length; | ||
| 0 | 191 | while (--length > 0) { | ||
| 0 | 192 | InsertString(); | ||
| 0 | 193 | strstart++; | ||
194 | } | |||
| 0 | 195 | strstart += 2; | ||
| 0 | 196 | blockStart = strstart; | ||
| 0 | 197 | } | ||
198 | | |||
199 | /// <summary> | |||
200 | /// Reset internal state | |||
201 | /// </summary> | |||
202 | public void Reset() | |||
203 | { | |||
| 392 | 204 | huffman.Reset(); | ||
| 392 | 205 | adler.Reset(); | ||
| 392 | 206 | blockStart = strstart = 1; | ||
| 392 | 207 | lookahead = 0; | ||
| 392 | 208 | totalIn = 0; | ||
| 392 | 209 | prevAvailable = false; | ||
| 392 | 210 | matchLen = DeflaterConstants.MIN_MATCH - 1; | ||
211 | | |||
| 25690896 | 212 | for (int i = 0; i < DeflaterConstants.HASH_SIZE; i++) { | ||
| 12845056 | 213 | head[i] = 0; | ||
214 | } | |||
215 | | |||
| 25690896 | 216 | for (int i = 0; i < DeflaterConstants.WSIZE; i++) { | ||
| 12845056 | 217 | prev[i] = 0; | ||
218 | } | |||
| 392 | 219 | } | ||
220 | | |||
221 | /// <summary> | |||
222 | /// Reset Adler checksum | |||
223 | /// </summary> | |||
224 | public void ResetAdler() | |||
225 | { | |||
| 0 | 226 | adler.Reset(); | ||
| 0 | 227 | } | ||
228 | | |||
229 | /// <summary> | |||
230 | /// Get current value of Adler checksum | |||
231 | /// </summary> | |||
232 | public int Adler { | |||
233 | get { | |||
| 14 | 234 | return unchecked((int)adler.Value); | ||
235 | } | |||
236 | } | |||
237 | | |||
238 | /// <summary> | |||
239 | /// Total data processed | |||
240 | /// </summary> | |||
241 | public long TotalIn { | |||
242 | get { | |||
| 9 | 243 | return totalIn; | ||
244 | } | |||
245 | } | |||
246 | | |||
247 | /// <summary> | |||
248 | /// Get/set the <see cref="DeflateStrategy">deflate strategy</see> | |||
249 | /// </summary> | |||
250 | public DeflateStrategy Strategy { | |||
251 | get { | |||
| 0 | 252 | return strategy; | ||
253 | } | |||
254 | set { | |||
| 273 | 255 | strategy = value; | ||
| 273 | 256 | } | ||
257 | } | |||
258 | | |||
259 | /// <summary> | |||
260 | /// Set the deflate level (0-9) | |||
261 | /// </summary> | |||
262 | /// <param name="level">The value to set the level to.</param> | |||
263 | public void SetLevel(int level) | |||
264 | { | |||
| 327 | 265 | if ((level < 0) || (level > 9)) { | ||
| 0 | 266 | throw new ArgumentOutOfRangeException(nameof(level)); | ||
267 | } | |||
268 | | |||
| 327 | 269 | goodLength = DeflaterConstants.GOOD_LENGTH[level]; | ||
| 327 | 270 | max_lazy = DeflaterConstants.MAX_LAZY[level]; | ||
| 327 | 271 | niceLength = DeflaterConstants.NICE_LENGTH[level]; | ||
| 327 | 272 | max_chain = DeflaterConstants.MAX_CHAIN[level]; | ||
273 | | |||
| 327 | 274 | if (DeflaterConstants.COMPR_FUNC[level] != compressionFunction) { | ||
275 | | |||
276 | #if DebugDeflation | |||
277 | if (DeflaterConstants.DEBUGGING) { | |||
278 | Console.WriteLine("Change from " + compressionFunction + " to " | |||
279 | + DeflaterConstants.COMPR_FUNC[level]); | |||
280 | } | |||
281 | #endif | |||
| 309 | 282 | switch (compressionFunction) { | ||
283 | case DeflaterConstants.DEFLATE_STORED: | |||
| 272 | 284 | if (strstart > blockStart) { | ||
| 0 | 285 | huffman.FlushStoredBlock(window, blockStart, | ||
| 0 | 286 | strstart - blockStart, false); | ||
| 0 | 287 | blockStart = strstart; | ||
288 | } | |||
| 272 | 289 | UpdateHash(); | ||
| 272 | 290 | break; | ||
291 | | |||
292 | case DeflaterConstants.DEFLATE_FAST: | |||
| 6 | 293 | if (strstart > blockStart) { | ||
| 0 | 294 | huffman.FlushBlock(window, blockStart, strstart - blockStart, | ||
| 0 | 295 | false); | ||
| 0 | 296 | blockStart = strstart; | ||
297 | } | |||
| 0 | 298 | break; | ||
299 | | |||
300 | case DeflaterConstants.DEFLATE_SLOW: | |||
| 31 | 301 | if (prevAvailable) { | ||
| 0 | 302 | huffman.TallyLit(window[strstart - 1] & 0xff); | ||
303 | } | |||
| 31 | 304 | if (strstart > blockStart) { | ||
| 0 | 305 | huffman.FlushBlock(window, blockStart, strstart - blockStart, false); | ||
| 0 | 306 | blockStart = strstart; | ||
307 | } | |||
| 31 | 308 | prevAvailable = false; | ||
| 31 | 309 | matchLen = DeflaterConstants.MIN_MATCH - 1; | ||
310 | break; | |||
311 | } | |||
| 309 | 312 | compressionFunction = DeflaterConstants.COMPR_FUNC[level]; | ||
313 | } | |||
| 327 | 314 | } | ||
315 | | |||
316 | /// <summary> | |||
317 | /// Fill the window | |||
318 | /// </summary> | |||
319 | public void FillWindow() | |||
320 | { | |||
321 | /* If the window is almost full and there is insufficient lookahead, | |||
322 | * move the upper half to the lower one to make room in the upper half. | |||
323 | */ | |||
| 9140 | 324 | if (strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST) { | ||
| 20 | 325 | SlideWindow(); | ||
326 | } | |||
327 | | |||
328 | /* If there is not enough lookahead, but still some input left, | |||
329 | * read in the input | |||
330 | */ | |||
| 13659 | 331 | while (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) { | ||
| 4519 | 332 | int more = 2 * DeflaterConstants.WSIZE - lookahead - strstart; | ||
333 | | |||
| 4519 | 334 | if (more > inputEnd - inputOff) { | ||
| 4479 | 335 | more = inputEnd - inputOff; | ||
336 | } | |||
337 | | |||
| 4519 | 338 | System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more); | ||
| 4519 | 339 | adler.Update(inputBuf, inputOff, more); | ||
340 | | |||
| 4519 | 341 | inputOff += more; | ||
| 4519 | 342 | totalIn += more; | ||
| 4519 | 343 | lookahead += more; | ||
344 | } | |||
345 | | |||
| 9140 | 346 | if (lookahead >= DeflaterConstants.MIN_MATCH) { | ||
| 8381 | 347 | UpdateHash(); | ||
348 | } | |||
| 9140 | 349 | } | ||
350 | | |||
351 | void UpdateHash() | |||
352 | { | |||
353 | /* | |||
354 | if (DEBUGGING) { | |||
355 | Console.WriteLine("updateHash: "+strstart); | |||
356 | } | |||
357 | */ | |||
| 8653 | 358 | ins_h = (window[strstart] << DeflaterConstants.HASH_SHIFT) ^ window[strstart + 1]; | ||
| 8653 | 359 | } | ||
360 | | |||
361 | /// <summary> | |||
362 | /// Inserts the current string in the head hash and returns the previous | |||
363 | /// value for this hash. | |||
364 | /// </summary> | |||
365 | /// <returns>The previous hash value</returns> | |||
366 | int InsertString() | |||
367 | { | |||
368 | short match; | |||
| 3625693 | 369 | int hash = ((ins_h << DeflaterConstants.HASH_SHIFT) ^ window[strstart + (DeflaterConstants.MIN_MATCH - 1)]) & Defl | ||
370 | | |||
371 | #if DebugDeflation | |||
372 | if (DeflaterConstants.DEBUGGING) | |||
373 | { | |||
374 | if (hash != (((window[strstart] << (2*HASH_SHIFT)) ^ | |||
375 | (window[strstart + 1] << HASH_SHIFT) ^ | |||
376 | (window[strstart + 2])) & HASH_MASK)) { | |||
377 | throw new SharpZipBaseException("hash inconsistent: " + hash + "/" | |||
378 | +window[strstart] + "," | |||
379 | +window[strstart + 1] + "," | |||
380 | +window[strstart + 2] + "," + HASH_SHIFT); | |||
381 | } | |||
382 | } | |||
383 | #endif | |||
| 3625693 | 384 | prev[strstart & DeflaterConstants.WMASK] = match = head[hash]; | ||
| 3625693 | 385 | head[hash] = unchecked((short)strstart); | ||
| 3625693 | 386 | ins_h = hash; | ||
| 3625693 | 387 | return match & 0xffff; | ||
388 | } | |||
389 | | |||
390 | void SlideWindow() | |||
391 | { | |||
| 40 | 392 | Array.Copy(window, DeflaterConstants.WSIZE, window, 0, DeflaterConstants.WSIZE); | ||
| 40 | 393 | matchStart -= DeflaterConstants.WSIZE; | ||
| 40 | 394 | strstart -= DeflaterConstants.WSIZE; | ||
| 40 | 395 | blockStart -= DeflaterConstants.WSIZE; | ||
396 | | |||
397 | // Slide the hash table (could be avoided with 32 bit values | |||
398 | // at the expense of memory usage). | |||
| 2621520 | 399 | for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i) { | ||
| 1310720 | 400 | int m = head[i] & 0xffff; | ||
| 1310720 | 401 | head[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); | ||
402 | } | |||
403 | | |||
404 | // Slide the prev table. | |||
| 2621520 | 405 | for (int i = 0; i < DeflaterConstants.WSIZE; i++) { | ||
| 1310720 | 406 | int m = prev[i] & 0xffff; | ||
| 1310720 | 407 | prev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); | ||
408 | } | |||
| 40 | 409 | } | ||
410 | | |||
411 | /// <summary> | |||
412 | /// Find the best (longest) string in the window matching the | |||
413 | /// string starting at strstart. | |||
414 | /// | |||
415 | /// Preconditions: | |||
416 | /// <code> | |||
417 | /// strstart + DeflaterConstants.MAX_MATCH <= window.length.</code> | |||
418 | /// </summary> | |||
419 | /// <param name="curMatch"></param> | |||
420 | /// <returns>True if a match greater than the minimum length is found</returns> | |||
421 | bool FindLongestMatch(int curMatch) | |||
422 | { | |||
| 1801927 | 423 | int chainLength = this.max_chain; | ||
| 1801927 | 424 | int niceLength = this.niceLength; | ||
| 1801927 | 425 | short[] prev = this.prev; | ||
| 1801927 | 426 | int scan = this.strstart; | ||
427 | int match; | |||
| 1801927 | 428 | int best_end = this.strstart + matchLen; | ||
| 1801927 | 429 | int best_len = Math.Max(matchLen, DeflaterConstants.MIN_MATCH - 1); | ||
430 | | |||
| 1801927 | 431 | int limit = Math.Max(strstart - DeflaterConstants.MAX_DIST, 0); | ||
432 | | |||
| 1801927 | 433 | int strend = strstart + DeflaterConstants.MAX_MATCH - 1; | ||
| 1801927 | 434 | byte scan_end1 = window[best_end - 1]; | ||
| 1801927 | 435 | byte scan_end = window[best_end]; | ||
436 | | |||
437 | // Do not waste too much time if we already have a good match: | |||
| 1801927 | 438 | if (best_len >= this.goodLength) { | ||
| 66 | 439 | chainLength >>= 2; | ||
440 | } | |||
441 | | |||
442 | /* Do not look for matches beyond the end of the input. This is necessary | |||
443 | * to make deflate deterministic. | |||
444 | */ | |||
| 1801927 | 445 | if (niceLength > lookahead) { | ||
| 3257 | 446 | niceLength = lookahead; | ||
447 | } | |||
448 | | |||
449 | #if DebugDeflation | |||
450 | | |||
451 | if (DeflaterConstants.DEBUGGING && (strstart > 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD)) | |||
452 | { | |||
453 | throw new InvalidOperationException("need lookahead"); | |||
454 | } | |||
455 | #endif | |||
456 | | |||
457 | do { | |||
458 | | |||
459 | #if DebugDeflation | |||
460 | | |||
461 | if (DeflaterConstants.DEBUGGING && (curMatch >= strstart) ) | |||
462 | { | |||
463 | throw new InvalidOperationException("no future"); | |||
464 | } | |||
465 | #endif | |||
| 2700341 | 466 | if (window[curMatch + best_len] != scan_end || | ||
| 2700341 | 467 | window[curMatch + best_len - 1] != scan_end1 || | ||
| 2700341 | 468 | window[curMatch] != window[scan] || | ||
| 2700341 | 469 | window[curMatch + 1] != window[scan + 1]) { | ||
470 | continue; | |||
471 | } | |||
472 | | |||
| 5274 | 473 | match = curMatch + 2; | ||
| 5274 | 474 | scan += 2; | ||
475 | | |||
476 | /* We check for insufficient lookahead only every 8th comparison; | |||
477 | * the 256th check will be made at strstart + 258. | |||
478 | */ | |||
| 9174 | 479 | while ( | ||
| 9174 | 480 | window[++scan] == window[++match] && | ||
| 9174 | 481 | window[++scan] == window[++match] && | ||
| 9174 | 482 | window[++scan] == window[++match] && | ||
| 9174 | 483 | window[++scan] == window[++match] && | ||
| 9174 | 484 | window[++scan] == window[++match] && | ||
| 9174 | 485 | window[++scan] == window[++match] && | ||
| 9174 | 486 | window[++scan] == window[++match] && | ||
| 9174 | 487 | window[++scan] == window[++match] && | ||
| 9174 | 488 | (scan < strend)) { | ||
489 | // Do nothing | |||
490 | } | |||
491 | | |||
| 5274 | 492 | if (scan > best_end) { | ||
493 | #if DebugDeflation | |||
494 | if (DeflaterConstants.DEBUGGING && (ins_h == 0) ) | |||
495 | Console.Error.WriteLine("Found match: " + curMatch + "-" + (scan - strstart)); | |||
496 | #endif | |||
| 5212 | 497 | matchStart = curMatch; | ||
| 5212 | 498 | best_end = scan; | ||
| 5212 | 499 | best_len = scan - strstart; | ||
500 | | |||
| 5212 | 501 | if (best_len >= niceLength) { | ||
502 | break; | |||
503 | } | |||
504 | | |||
| 5136 | 505 | scan_end1 = window[best_end - 1]; | ||
| 5136 | 506 | scan_end = window[best_end]; | ||
507 | } | |||
| 5198 | 508 | scan = strstart; | ||
| 2700265 | 509 | } while ((curMatch = (prev[curMatch & DeflaterConstants.WMASK] & 0xffff)) > limit && --chainLength != 0); | ||
510 | | |||
| 1801927 | 511 | matchLen = Math.Min(best_len, lookahead); | ||
| 1801927 | 512 | return matchLen >= DeflaterConstants.MIN_MATCH; | ||
513 | } | |||
514 | | |||
515 | bool DeflateStored(bool flush, bool finish) | |||
516 | { | |||
| 1373 | 517 | if (!flush && (lookahead == 0)) { | ||
| 676 | 518 | return false; | ||
519 | } | |||
520 | | |||
| 697 | 521 | strstart += lookahead; | ||
| 697 | 522 | lookahead = 0; | ||
523 | | |||
| 697 | 524 | int storedLength = strstart - blockStart; | ||
525 | | |||
| 697 | 526 | if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full | ||
| 697 | 527 | (blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out | ||
| 697 | 528 | flush) { | ||
| 21 | 529 | bool lastBlock = finish; | ||
| 21 | 530 | if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) { | ||
| 2 | 531 | storedLength = DeflaterConstants.MAX_BLOCK_SIZE; | ||
| 2 | 532 | lastBlock = false; | ||
533 | } | |||
534 | | |||
535 | #if DebugDeflation | |||
536 | if (DeflaterConstants.DEBUGGING) | |||
537 | { | |||
538 | Console.WriteLine("storedBlock[" + storedLength + "," + lastBlock + "]"); | |||
539 | } | |||
540 | #endif | |||
541 | | |||
| 21 | 542 | huffman.FlushStoredBlock(window, blockStart, storedLength, lastBlock); | ||
| 21 | 543 | blockStart += storedLength; | ||
| 21 | 544 | return !lastBlock; | ||
545 | } | |||
| 676 | 546 | return true; | ||
547 | } | |||
548 | | |||
549 | bool DeflateFast(bool flush, bool finish) | |||
550 | { | |||
| 3236 | 551 | if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) { | ||
| 1526 | 552 | return false; | ||
553 | } | |||
554 | | |||
| 1597843 | 555 | while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) { | ||
| 1596259 | 556 | if (lookahead == 0) { | ||
557 | // We are flushing everything | |||
| 30 | 558 | huffman.FlushBlock(window, blockStart, strstart - blockStart, finish); | ||
| 30 | 559 | blockStart = strstart; | ||
| 30 | 560 | return false; | ||
561 | } | |||
562 | | |||
| 1596229 | 563 | if (strstart > 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD) { | ||
564 | /* slide window, as FindLongestMatch needs this. | |||
565 | * This should only happen when flushing and the window | |||
566 | * is almost full. | |||
567 | */ | |||
| 0 | 568 | SlideWindow(); | ||
569 | } | |||
570 | | |||
571 | int hashHead; | |||
| 1596229 | 572 | if (lookahead >= DeflaterConstants.MIN_MATCH && | ||
| 1596229 | 573 | (hashHead = InsertString()) != 0 && | ||
| 1596229 | 574 | strategy != DeflateStrategy.HuffmanOnly && | ||
| 1596229 | 575 | strstart - hashHead <= DeflaterConstants.MAX_DIST && | ||
| 1596229 | 576 | FindLongestMatch(hashHead)) { | ||
577 | // longestMatch sets matchStart and matchLen | |||
578 | #if DebugDeflation | |||
579 | if (DeflaterConstants.DEBUGGING) | |||
580 | { | |||
581 | for (int i = 0 ; i < matchLen; i++) { | |||
582 | if (window[strstart + i] != window[matchStart + i]) { | |||
583 | throw new SharpZipBaseException("Match failure"); | |||
584 | } | |||
585 | } | |||
586 | } | |||
587 | #endif | |||
588 | | |||
| 2203 | 589 | bool full = huffman.TallyDist(strstart - matchStart, matchLen); | ||
590 | | |||
| 2203 | 591 | lookahead -= matchLen; | ||
| 2203 | 592 | if (matchLen <= max_lazy && lookahead >= DeflaterConstants.MIN_MATCH) { | ||
| 6608 | 593 | while (--matchLen > 0) { | ||
| 4408 | 594 | ++strstart; | ||
| 4408 | 595 | InsertString(); | ||
596 | } | |||
| 2200 | 597 | ++strstart; | ||
| 2200 | 598 | } else { | ||
| 3 | 599 | strstart += matchLen; | ||
| 3 | 600 | if (lookahead >= DeflaterConstants.MIN_MATCH - 1) { | ||
| 0 | 601 | UpdateHash(); | ||
602 | } | |||
603 | } | |||
| 2203 | 604 | matchLen = DeflaterConstants.MIN_MATCH - 1; | ||
| 2203 | 605 | if (!full) { | ||
| 2203 | 606 | continue; | ||
607 | } | |||
608 | } else { | |||
609 | // No match found | |||
| 1594026 | 610 | huffman.TallyLit(window[strstart] & 0xff); | ||
| 1594026 | 611 | ++strstart; | ||
| 1594026 | 612 | --lookahead; | ||
613 | } | |||
614 | | |||
| 1594026 | 615 | if (huffman.IsFull()) { | ||
| 96 | 616 | bool lastBlock = finish && (lookahead == 0); | ||
| 96 | 617 | huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock); | ||
| 96 | 618 | blockStart = strstart; | ||
| 96 | 619 | return !lastBlock; | ||
620 | } | |||
621 | } | |||
| 1584 | 622 | return true; | ||
623 | } | |||
624 | | |||
625 | bool DeflateSlow(bool flush, bool finish) | |||
626 | { | |||
| 4531 | 627 | if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) { | ||
| 2145 | 628 | return false; | ||
629 | } | |||
630 | | |||
| 2011632 | 631 | while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) { | ||
| 2009630 | 632 | if (lookahead == 0) { | ||
| 264 | 633 | if (prevAvailable) { | ||
| 205 | 634 | huffman.TallyLit(window[strstart - 1] & 0xff); | ||
635 | } | |||
| 264 | 636 | prevAvailable = false; | ||
637 | | |||
638 | // We are flushing everything | |||
639 | #if DebugDeflation | |||
640 | if (DeflaterConstants.DEBUGGING && !flush) | |||
641 | { | |||
642 | throw new SharpZipBaseException("Not flushing, but no lookahead"); | |||
643 | } | |||
644 | #endif | |||
| 264 | 645 | huffman.FlushBlock(window, blockStart, strstart - blockStart, | ||
| 264 | 646 | finish); | ||
| 264 | 647 | blockStart = strstart; | ||
| 264 | 648 | return false; | ||
649 | } | |||
650 | | |||
| 2009366 | 651 | if (strstart >= 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD) { | ||
652 | /* slide window, as FindLongestMatch needs this. | |||
653 | * This should only happen when flushing and the window | |||
654 | * is almost full. | |||
655 | */ | |||
| 20 | 656 | SlideWindow(); | ||
657 | } | |||
658 | | |||
| 2009366 | 659 | int prevMatch = matchStart; | ||
| 2009366 | 660 | int prevLen = matchLen; | ||
| 2009366 | 661 | if (lookahead >= DeflaterConstants.MIN_MATCH) { | ||
662 | | |||
| 2008972 | 663 | int hashHead = InsertString(); | ||
664 | | |||
| 2008972 | 665 | if (strategy != DeflateStrategy.HuffmanOnly && | ||
| 2008972 | 666 | hashHead != 0 && | ||
| 2008972 | 667 | strstart - hashHead <= DeflaterConstants.MAX_DIST && | ||
| 2008972 | 668 | FindLongestMatch(hashHead)) { | ||
669 | | |||
670 | // longestMatch sets matchStart and matchLen | |||
671 | | |||
672 | // Discard match if too small and too far away | |||
| 3376 | 673 | if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == DeflaterConstants.MIN_MATCH && st | ||
| 2383 | 674 | matchLen = DeflaterConstants.MIN_MATCH - 1; | ||
675 | } | |||
676 | } | |||
677 | } | |||
678 | | |||
679 | // previous match was better | |||
| 2009366 | 680 | if ((prevLen >= DeflaterConstants.MIN_MATCH) && (matchLen <= prevLen)) { | ||
681 | #if DebugDeflation | |||
682 | if (DeflaterConstants.DEBUGGING) | |||
683 | { | |||
684 | for (int i = 0 ; i < matchLen; i++) { | |||
685 | if (window[strstart-1+i] != window[prevMatch + i]) | |||
686 | throw new SharpZipBaseException(); | |||
687 | } | |||
688 | } | |||
689 | #endif | |||
| 625 | 690 | huffman.TallyDist(strstart - 1 - prevMatch, prevLen); | ||
| 625 | 691 | prevLen -= 2; | ||
692 | do { | |||
| 16197 | 693 | strstart++; | ||
| 16197 | 694 | lookahead--; | ||
| 16197 | 695 | if (lookahead >= DeflaterConstants.MIN_MATCH) { | ||
| 16135 | 696 | InsertString(); | ||
697 | } | |||
| 16197 | 698 | } while (--prevLen > 0); | ||
699 | | |||
| 625 | 700 | strstart++; | ||
| 625 | 701 | lookahead--; | ||
| 625 | 702 | prevAvailable = false; | ||
| 625 | 703 | matchLen = DeflaterConstants.MIN_MATCH - 1; | ||
| 625 | 704 | } else { | ||
| 2008741 | 705 | if (prevAvailable) { | ||
| 2007911 | 706 | huffman.TallyLit(window[strstart - 1] & 0xff); | ||
707 | } | |||
| 2008741 | 708 | prevAvailable = true; | ||
| 2008741 | 709 | strstart++; | ||
| 2008741 | 710 | lookahead--; | ||
711 | } | |||
712 | | |||
| 2009366 | 713 | if (huffman.IsFull()) { | ||
| 120 | 714 | int len = strstart - blockStart; | ||
| 120 | 715 | if (prevAvailable) { | ||
| 120 | 716 | len--; | ||
717 | } | |||
| 120 | 718 | bool lastBlock = (finish && (lookahead == 0) && !prevAvailable); | ||
| 120 | 719 | huffman.FlushBlock(window, blockStart, len, lastBlock); | ||
| 120 | 720 | blockStart += len; | ||
| 120 | 721 | return !lastBlock; | ||
722 | } | |||
723 | } | |||
| 2002 | 724 | return true; | ||
725 | } | |||
726 | | |||
727 | #region Instance Fields | |||
728 | | |||
729 | // Hash index of string to be inserted | |||
730 | int ins_h; | |||
731 | | |||
732 | /// <summary> | |||
733 | /// Hashtable, hashing three characters to an index for window, so | |||
734 | /// that window[index]..window[index+2] have this hash code. | |||
735 | /// Note that the array should really be unsigned short, so you need | |||
736 | /// to and the values with 0xffff. | |||
737 | /// </summary> | |||
738 | short[] head; | |||
739 | | |||
740 | /// <summary> | |||
741 | /// <code>prev[index & WMASK]</code> points to the previous index that has the | |||
742 | /// same hash code as the string starting at index. This way | |||
743 | /// entries with the same hash code are in a linked list. | |||
744 | /// Note that the array should really be unsigned short, so you need | |||
745 | /// to and the values with 0xffff. | |||
746 | /// </summary> | |||
747 | short[] prev; | |||
748 | | |||
749 | int matchStart; | |||
750 | // Length of best match | |||
751 | int matchLen; | |||
752 | // Set if previous match exists | |||
753 | bool prevAvailable; | |||
754 | int blockStart; | |||
755 | | |||
756 | /// <summary> | |||
757 | /// Points to the current character in the window. | |||
758 | /// </summary> | |||
759 | int strstart; | |||
760 | | |||
761 | /// <summary> | |||
762 | /// lookahead is the number of characters starting at strstart in | |||
763 | /// window that are valid. | |||
764 | /// So window[strstart] until window[strstart+lookahead-1] are valid | |||
765 | /// characters. | |||
766 | /// </summary> | |||
767 | int lookahead; | |||
768 | | |||
769 | /// <summary> | |||
770 | /// This array contains the part of the uncompressed stream that | |||
771 | /// is of relevance. The current character is indexed by strstart. | |||
772 | /// </summary> | |||
773 | byte[] window; | |||
774 | | |||
775 | DeflateStrategy strategy; | |||
776 | int max_chain, max_lazy, niceLength, goodLength; | |||
777 | | |||
778 | /// <summary> | |||
779 | /// The current compression function. | |||
780 | /// </summary> | |||
781 | int compressionFunction; | |||
782 | | |||
783 | /// <summary> | |||
784 | /// The input data for compression. | |||
785 | /// </summary> | |||
786 | byte[] inputBuf; | |||
787 | | |||
788 | /// <summary> | |||
789 | /// The total bytes of input read. | |||
790 | /// </summary> | |||
791 | long totalIn; | |||
792 | | |||
793 | /// <summary> | |||
794 | /// The offset into inputBuf, where input data starts. | |||
795 | /// </summary> | |||
796 | int inputOff; | |||
797 | | |||
798 | /// <summary> | |||
799 | /// The end offset of the input data. | |||
800 | /// </summary> | |||
801 | int inputEnd; | |||
802 | | |||
803 | DeflaterPending pending; | |||
804 | DeflaterHuffman huffman; | |||
805 | | |||
806 | /// <summary> | |||
807 | /// The adler checksum | |||
808 | /// </summary> | |||
809 | Adler32 adler; | |||
810 | #endregion | |||
811 | } | |||
812 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.DeflaterHuffman |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\DeflaterHuffman.cs |
| Covered lines: | 349 |
| Uncovered lines: | 9 |
| Coverable lines: | 358 |
| Total lines: | 865 |
| Line coverage: | 97.4% |
| Branch coverage: | 91.1% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .cctor() | 6 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| Reset() | 1 | 100 | 100 |
| SendAllTrees(...) | 2 | 100 | 100 |
| CompressBlock() | 6 | 100 | 100 |
| FlushStoredBlock(...) | 3 | 100 | 100 |
| FlushBlock(...) | 13 | 100 | 85.71 |
| IsFull() | 1 | 100 | 100 |
| TallyLit(...) | 1 | 100 | 100 |
| TallyDist(...) | 4 | 100 | 100 |
| BitReverse(...) | 1 | 100 | 100 |
| Lcode(...) | 3 | 100 | 100 |
| Dcode(...) | 2 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| Reset() | 2 | 100 | 100 |
| WriteSymbol(...) | 1 | 100 | 100 |
| CheckEmpty() | 3 | 0 | 0 |
| SetStaticCodes(...) | 1 | 100 | 100 |
| BuildCodes() | 4 | 100 | 100 |
| BuildTree() | 21 | 98.51 | 97.44 |
| GetEncodedLength() | 2 | 100 | 100 |
| CalcBLFreq(...) | 10 | 100 | 100 |
| WriteTree(...) | 11 | 91.18 | 85.71 |
| BuildLength(...) | 13 | 100 | 84 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// This is the DeflaterHuffman class. | |||
7 | /// | |||
8 | /// This class is <i>not</i> thread safe. This is inherent in the API, due | |||
9 | /// to the split of Deflate and SetInput. | |||
10 | /// | |||
11 | /// author of the original java version : Jochen Hoenicke | |||
12 | /// </summary> | |||
13 | public class DeflaterHuffman | |||
14 | { | |||
15 | const int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); | |||
16 | const int LITERAL_NUM = 286; | |||
17 | | |||
18 | // Number of distance codes | |||
19 | const int DIST_NUM = 30; | |||
20 | // Number of codes used to transfer bit lengths | |||
21 | const int BITLEN_NUM = 19; | |||
22 | | |||
23 | // repeat previous bit length 3-6 times (2 bits of repeat count) | |||
24 | const int REP_3_6 = 16; | |||
25 | // repeat a zero length 3-10 times (3 bits of repeat count) | |||
26 | const int REP_3_10 = 17; | |||
27 | // repeat a zero length 11-138 times (7 bits of repeat count) | |||
28 | const int REP_11_138 = 18; | |||
29 | | |||
30 | const int EOF_SYMBOL = 256; | |||
31 | | |||
32 | // The lengths of the bit length codes are sent in order of decreasing | |||
33 | // probability, to avoid transmitting the lengths for unused bit length codes. | |||
| 1 | 34 | static readonly int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; | ||
35 | | |||
| 1 | 36 | static readonly byte[] bit4Reverse = { | ||
| 1 | 37 | 0, | ||
| 1 | 38 | 8, | ||
| 1 | 39 | 4, | ||
| 1 | 40 | 12, | ||
| 1 | 41 | 2, | ||
| 1 | 42 | 10, | ||
| 1 | 43 | 6, | ||
| 1 | 44 | 14, | ||
| 1 | 45 | 1, | ||
| 1 | 46 | 9, | ||
| 1 | 47 | 5, | ||
| 1 | 48 | 13, | ||
| 1 | 49 | 3, | ||
| 1 | 50 | 11, | ||
| 1 | 51 | 7, | ||
| 1 | 52 | 15 | ||
| 1 | 53 | }; | ||
54 | | |||
55 | static short[] staticLCodes; | |||
56 | static byte[] staticLLength; | |||
57 | static short[] staticDCodes; | |||
58 | static byte[] staticDLength; | |||
59 | | |||
60 | class Tree | |||
61 | { | |||
62 | #region Instance Fields | |||
63 | public short[] freqs; | |||
64 | | |||
65 | public byte[] length; | |||
66 | | |||
67 | public int minNumCodes; | |||
68 | | |||
69 | public int numCodes; | |||
70 | | |||
71 | short[] codes; | |||
72 | readonly int[] bl_counts; | |||
73 | readonly int maxLength; | |||
74 | DeflaterHuffman dh; | |||
75 | #endregion | |||
76 | | |||
77 | #region Constructors | |||
| 819 | 78 | public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength) | ||
79 | { | |||
| 819 | 80 | this.dh = dh; | ||
| 819 | 81 | this.minNumCodes = minCodes; | ||
| 819 | 82 | this.maxLength = maxLength; | ||
| 819 | 83 | freqs = new short[elems]; | ||
| 819 | 84 | bl_counts = new int[maxLength]; | ||
| 819 | 85 | } | ||
86 | | |||
87 | #endregion | |||
88 | | |||
89 | /// <summary> | |||
90 | /// Resets the internal state of the tree | |||
91 | /// </summary> | |||
92 | public void Reset() | |||
93 | { | |||
| 623948 | 94 | for (int i = 0; i < freqs.Length; i++) { | ||
| 309205 | 95 | freqs[i] = 0; | ||
96 | } | |||
| 2769 | 97 | codes = null; | ||
| 2769 | 98 | length = null; | ||
| 2769 | 99 | } | ||
100 | | |||
101 | public void WriteSymbol(int code) | |||
102 | { | |||
103 | // if (DeflaterConstants.DEBUGGING) { | |||
104 | // freqs[code]--; | |||
105 | // // Console.Write("writeSymbol("+freqs.length+","+code+"): "); | |||
106 | // } | |||
| 8298 | 107 | dh.pending.WriteBits(codes[code] & 0xffff, length[code]); | ||
| 8298 | 108 | } | ||
109 | | |||
110 | /// <summary> | |||
111 | /// Check that all frequencies are zero | |||
112 | /// </summary> | |||
113 | /// <exception cref="SharpZipBaseException"> | |||
114 | /// At least one frequency is non-zero | |||
115 | /// </exception> | |||
116 | public void CheckEmpty() | |||
117 | { | |||
| 0 | 118 | bool empty = true; | ||
| 0 | 119 | for (int i = 0; i < freqs.Length; i++) { | ||
| 0 | 120 | empty &= freqs[i] == 0; | ||
121 | } | |||
122 | | |||
| 0 | 123 | if (!empty) { | ||
| 0 | 124 | throw new SharpZipBaseException("!Empty"); | ||
125 | } | |||
| 0 | 126 | } | ||
127 | | |||
128 | /// <summary> | |||
129 | /// Set static codes and length | |||
130 | /// </summary> | |||
131 | /// <param name="staticCodes">new codes</param> | |||
132 | /// <param name="staticLengths">length for new codes</param> | |||
133 | public void SetStaticCodes(short[] staticCodes, byte[] staticLengths) | |||
134 | { | |||
| 474 | 135 | codes = staticCodes; | ||
| 474 | 136 | length = staticLengths; | ||
| 474 | 137 | } | ||
138 | | |||
139 | /// <summary> | |||
140 | /// Build dynamic codes and lengths | |||
141 | /// </summary> | |||
142 | public void BuildCodes() | |||
143 | { | |||
| 3 | 144 | int numSymbols = freqs.Length; | ||
| 3 | 145 | int[] nextCode = new int[maxLength]; | ||
| 3 | 146 | int code = 0; | ||
147 | | |||
| 3 | 148 | codes = new short[freqs.Length]; | ||
149 | | |||
150 | // if (DeflaterConstants.DEBUGGING) { | |||
151 | // //Console.WriteLine("buildCodes: "+freqs.Length); | |||
152 | // } | |||
153 | | |||
| 80 | 154 | for (int bits = 0; bits < maxLength; bits++) { | ||
| 37 | 155 | nextCode[bits] = code; | ||
| 37 | 156 | code += bl_counts[bits] << (15 - bits); | ||
157 | | |||
158 | // if (DeflaterConstants.DEBUGGING) { | |||
159 | // //Console.WriteLine("bits: " + ( bits + 1) + " count: " + bl_counts[bits] | |||
160 | // +" nextCode: "+code); | |||
161 | // } | |||
162 | } | |||
163 | | |||
164 | #if DebugDeflation | |||
165 | if ( DeflaterConstants.DEBUGGING && (code != 65536) ) | |||
166 | { | |||
167 | throw new SharpZipBaseException("Inconsistent bl_counts!"); | |||
168 | } | |||
169 | #endif | |||
| 576 | 170 | for (int i = 0; i < numCodes; i++) { | ||
| 285 | 171 | int bits = length[i]; | ||
| 285 | 172 | if (bits > 0) { | ||
173 | | |||
174 | // if (DeflaterConstants.DEBUGGING) { | |||
175 | // //Console.WriteLine("codes["+i+"] = rev(" + nextCode[bits-1]+"), | |||
176 | // +bits); | |||
177 | // } | |||
178 | | |||
| 38 | 179 | codes[i] = BitReverse(nextCode[bits - 1]); | ||
| 38 | 180 | nextCode[bits - 1] += 1 << (16 - bits); | ||
181 | } | |||
182 | } | |||
| 3 | 183 | } | ||
184 | | |||
185 | public void BuildTree() | |||
186 | { | |||
| 1530 | 187 | int numSymbols = freqs.Length; | ||
188 | | |||
189 | /* heap is a priority queue, sorted by frequency, least frequent | |||
190 | * nodes first. The heap is a binary tree, with the property, that | |||
191 | * the parent node is smaller than both child nodes. This assures | |||
192 | * that the smallest node is the first parent. | |||
193 | * | |||
194 | * The binary tree is encoded in an array: 0 is root node and | |||
195 | * the nodes 2*n+1, 2*n+2 are the child nodes of node n. | |||
196 | */ | |||
| 1530 | 197 | int[] heap = new int[numSymbols]; | ||
| 1530 | 198 | int heapLen = 0; | ||
| 1530 | 199 | int maxCode = 0; | ||
| 344760 | 200 | for (int n = 0; n < numSymbols; n++) { | ||
| 170850 | 201 | int freq = freqs[n]; | ||
| 170850 | 202 | if (freq != 0) { | ||
203 | // Insert n into heap | |||
| 81797 | 204 | int pos = heapLen++; | ||
205 | int ppos; | |||
| 161994 | 206 | while (pos > 0 && freqs[heap[ppos = (pos - 1) / 2]] > freq) { | ||
| 80197 | 207 | heap[pos] = heap[ppos]; | ||
| 80197 | 208 | pos = ppos; | ||
209 | } | |||
| 81797 | 210 | heap[pos] = n; | ||
211 | | |||
| 81797 | 212 | maxCode = n; | ||
213 | } | |||
214 | } | |||
215 | | |||
216 | /* We could encode a single literal with 0 bits but then we | |||
217 | * don't see the literals. Therefore we force at least two | |||
218 | * literals to avoid this case. We don't care about order in | |||
219 | * this case, both literals get a 1 bit code. | |||
220 | */ | |||
| 2077 | 221 | while (heapLen < 2) { | ||
| 547 | 222 | int node = maxCode < 2 ? ++maxCode : 0; | ||
| 547 | 223 | heap[heapLen++] = node; | ||
224 | } | |||
225 | | |||
| 1530 | 226 | numCodes = Math.Max(maxCode + 1, minNumCodes); | ||
227 | | |||
| 1530 | 228 | int numLeafs = heapLen; | ||
| 1530 | 229 | int[] childs = new int[4 * heapLen - 2]; | ||
| 1530 | 230 | int[] values = new int[2 * heapLen - 1]; | ||
| 1530 | 231 | int numNodes = numLeafs; | ||
| 167748 | 232 | for (int i = 0; i < heapLen; i++) { | ||
| 82344 | 233 | int node = heap[i]; | ||
| 82344 | 234 | childs[2 * i] = node; | ||
| 82344 | 235 | childs[2 * i + 1] = -1; | ||
| 82344 | 236 | values[i] = freqs[node] << 8; | ||
| 82344 | 237 | heap[i] = i; | ||
238 | } | |||
239 | | |||
240 | /* Construct the Huffman tree by repeatedly combining the least two | |||
241 | * frequent nodes. | |||
242 | */ | |||
243 | do { | |||
| 80814 | 244 | int first = heap[0]; | ||
| 80814 | 245 | int last = heap[--heapLen]; | ||
246 | | |||
247 | // Propagate the hole to the leafs of the heap | |||
| 80814 | 248 | int ppos = 0; | ||
| 80814 | 249 | int path = 1; | ||
250 | | |||
| 507013 | 251 | while (path < heapLen) { | ||
| 426199 | 252 | if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]]) { | ||
| 191359 | 253 | path++; | ||
254 | } | |||
255 | | |||
| 426199 | 256 | heap[ppos] = heap[path]; | ||
| 426199 | 257 | ppos = path; | ||
| 426199 | 258 | path = path * 2 + 1; | ||
259 | } | |||
260 | | |||
261 | /* Now propagate the last element down along path. Normally | |||
262 | * it shouldn't go too deep. | |||
263 | */ | |||
| 80814 | 264 | int lastVal = values[last]; | ||
| 103741 | 265 | while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal) { | ||
| 22927 | 266 | heap[path] = heap[ppos]; | ||
267 | } | |||
| 80814 | 268 | heap[path] = last; | ||
269 | | |||
270 | | |||
| 80814 | 271 | int second = heap[0]; | ||
272 | | |||
273 | // Create a new node father of first and second | |||
| 80814 | 274 | last = numNodes++; | ||
| 80814 | 275 | childs[2 * last] = first; | ||
| 80814 | 276 | childs[2 * last + 1] = second; | ||
| 80814 | 277 | int mindepth = Math.Min(values[first] & 0xff, values[second] & 0xff); | ||
| 80814 | 278 | values[last] = lastVal = values[first] + values[second] - mindepth + 1; | ||
279 | | |||
280 | // Again, propagate the hole to the leafs | |||
| 80814 | 281 | ppos = 0; | ||
| 80814 | 282 | path = 1; | ||
283 | | |||
| 507819 | 284 | while (path < heapLen) { | ||
| 427005 | 285 | if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]]) { | ||
| 193052 | 286 | path++; | ||
287 | } | |||
288 | | |||
| 427005 | 289 | heap[ppos] = heap[path]; | ||
| 427005 | 290 | ppos = path; | ||
| 427005 | 291 | path = ppos * 2 + 1; | ||
292 | } | |||
293 | | |||
294 | // Now propagate the new element down along path | |||
| 86039 | 295 | while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal) { | ||
| 5225 | 296 | heap[path] = heap[ppos]; | ||
297 | } | |||
| 80814 | 298 | heap[path] = last; | ||
| 80814 | 299 | } while (heapLen > 1); | ||
300 | | |||
| 1530 | 301 | if (heap[0] != childs.Length / 2 - 1) { | ||
| 0 | 302 | throw new SharpZipBaseException("Heap invariant violated"); | ||
303 | } | |||
304 | | |||
| 1530 | 305 | BuildLength(childs); | ||
| 1530 | 306 | } | ||
307 | | |||
308 | /// <summary> | |||
309 | /// Get encoded length | |||
310 | /// </summary> | |||
311 | /// <returns>Encoded length, the sum of frequencies * lengths</returns> | |||
312 | public int GetEncodedLength() | |||
313 | { | |||
| 1530 | 314 | int len = 0; | ||
| 344760 | 315 | for (int i = 0; i < freqs.Length; i++) { | ||
| 170850 | 316 | len += freqs[i] * length[i]; | ||
317 | } | |||
| 1530 | 318 | return len; | ||
319 | } | |||
320 | | |||
321 | /// <summary> | |||
322 | /// Scan a literal or distance tree to determine the frequencies of the codes | |||
323 | /// in the bit length tree. | |||
324 | /// </summary> | |||
325 | public void CalcBLFreq(Tree blTree) | |||
326 | { | |||
327 | int max_count; /* max repeat count */ | |||
328 | int min_count; /* min repeat count */ | |||
329 | int count; /* repeat count of the current code */ | |||
| 1020 | 330 | int curlen = -1; /* length of current code */ | ||
331 | | |||
| 1020 | 332 | int i = 0; | ||
| 26206 | 333 | while (i < numCodes) { | ||
| 25186 | 334 | count = 1; | ||
| 25186 | 335 | int nextlen = length[i]; | ||
| 25186 | 336 | if (nextlen == 0) { | ||
| 2966 | 337 | max_count = 138; | ||
| 2966 | 338 | min_count = 3; | ||
| 2966 | 339 | } else { | ||
| 22220 | 340 | max_count = 6; | ||
| 22220 | 341 | min_count = 3; | ||
| 22220 | 342 | if (curlen != nextlen) { | ||
| 12628 | 343 | blTree.freqs[nextlen]++; | ||
| 12628 | 344 | count = 0; | ||
345 | } | |||
346 | } | |||
| 25186 | 347 | curlen = nextlen; | ||
| 25186 | 348 | i++; | ||
349 | | |||
| 129433 | 350 | while (i < numCodes && curlen == length[i]) { | ||
| 114222 | 351 | i++; | ||
| 114222 | 352 | if (++count >= max_count) { | ||
353 | break; | |||
354 | } | |||
355 | } | |||
356 | | |||
| 25186 | 357 | if (count < min_count) { | ||
| 13100 | 358 | blTree.freqs[curlen] += (short)count; | ||
| 25186 | 359 | } else if (curlen != 0) { | ||
| 10711 | 360 | blTree.freqs[REP_3_6]++; | ||
| 12086 | 361 | } else if (count <= 10) { | ||
| 319 | 362 | blTree.freqs[REP_3_10]++; | ||
| 319 | 363 | } else { | ||
| 1056 | 364 | blTree.freqs[REP_11_138]++; | ||
365 | } | |||
366 | } | |||
| 1020 | 367 | } | ||
368 | | |||
369 | /// <summary> | |||
370 | /// Write tree values | |||
371 | /// </summary> | |||
372 | /// <param name="blTree">Tree to write</param> | |||
373 | public void WriteTree(Tree blTree) | |||
374 | { | |||
375 | int max_count; // max repeat count | |||
376 | int min_count; // min repeat count | |||
377 | int count; // repeat count of the current code | |||
| 2 | 378 | int curlen = -1; // length of current code | ||
379 | | |||
| 2 | 380 | int i = 0; | ||
| 42 | 381 | while (i < numCodes) { | ||
| 40 | 382 | count = 1; | ||
| 40 | 383 | int nextlen = length[i]; | ||
| 40 | 384 | if (nextlen == 0) { | ||
| 14 | 385 | max_count = 138; | ||
| 14 | 386 | min_count = 3; | ||
| 14 | 387 | } else { | ||
| 26 | 388 | max_count = 6; | ||
| 26 | 389 | min_count = 3; | ||
| 26 | 390 | if (curlen != nextlen) { | ||
| 26 | 391 | blTree.WriteSymbol(nextlen); | ||
| 26 | 392 | count = 0; | ||
393 | } | |||
394 | } | |||
| 40 | 395 | curlen = nextlen; | ||
| 40 | 396 | i++; | ||
397 | | |||
| 266 | 398 | while (i < numCodes && curlen == length[i]) { | ||
| 226 | 399 | i++; | ||
| 226 | 400 | if (++count >= max_count) { | ||
401 | break; | |||
402 | } | |||
403 | } | |||
404 | | |||
| 40 | 405 | if (count < min_count) { | ||
| 41 | 406 | while (count-- > 0) { | ||
| 10 | 407 | blTree.WriteSymbol(curlen); | ||
408 | } | |||
| 40 | 409 | } else if (curlen != 0) { | ||
| 0 | 410 | blTree.WriteSymbol(REP_3_6); | ||
| 0 | 411 | dh.pending.WriteBits(count - 3, 2); | ||
| 9 | 412 | } else if (count <= 10) { | ||
| 4 | 413 | blTree.WriteSymbol(REP_3_10); | ||
| 4 | 414 | dh.pending.WriteBits(count - 3, 3); | ||
| 4 | 415 | } else { | ||
| 5 | 416 | blTree.WriteSymbol(REP_11_138); | ||
| 5 | 417 | dh.pending.WriteBits(count - 11, 7); | ||
418 | } | |||
419 | } | |||
| 2 | 420 | } | ||
421 | | |||
422 | void BuildLength(int[] childs) | |||
423 | { | |||
| 1530 | 424 | this.length = new byte[freqs.Length]; | ||
| 1530 | 425 | int numNodes = childs.Length / 2; | ||
| 1530 | 426 | int numLeafs = (numNodes + 1) / 2; | ||
| 1530 | 427 | int overflow = 0; | ||
428 | | |||
| 40800 | 429 | for (int i = 0; i < maxLength; i++) { | ||
| 18870 | 430 | bl_counts[i] = 0; | ||
431 | } | |||
432 | | |||
433 | // First calculate optimal bit lengths | |||
| 1530 | 434 | int[] lengths = new int[numNodes]; | ||
| 1530 | 435 | lengths[numNodes - 1] = 0; | ||
436 | | |||
| 329376 | 437 | for (int i = numNodes - 1; i >= 0; i--) { | ||
| 163158 | 438 | if (childs[2 * i + 1] != -1) { | ||
| 80814 | 439 | int bitLength = lengths[i] + 1; | ||
| 80814 | 440 | if (bitLength > maxLength) { | ||
| 2 | 441 | bitLength = maxLength; | ||
| 2 | 442 | overflow++; | ||
443 | } | |||
| 80814 | 444 | lengths[childs[2 * i]] = lengths[childs[2 * i + 1]] = bitLength; | ||
| 80814 | 445 | } else { | ||
446 | // A leaf node | |||
| 82344 | 447 | int bitLength = lengths[i]; | ||
| 82344 | 448 | bl_counts[bitLength - 1]++; | ||
| 82344 | 449 | this.length[childs[2 * i]] = (byte)lengths[i]; | ||
450 | } | |||
451 | } | |||
452 | | |||
453 | // if (DeflaterConstants.DEBUGGING) { | |||
454 | // //Console.WriteLine("Tree "+freqs.Length+" lengths:"); | |||
455 | // for (int i=0; i < numLeafs; i++) { | |||
456 | // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] | |||
457 | // + " len: "+length[childs[2*i]]); | |||
458 | // } | |||
459 | // } | |||
460 | | |||
| 1530 | 461 | if (overflow == 0) { | ||
| 1528 | 462 | return; | ||
463 | } | |||
464 | | |||
| 2 | 465 | int incrBitLen = maxLength - 1; | ||
466 | do { | |||
467 | // Find the first bit length which could increase: | |||
| 2 | 468 | while (bl_counts[--incrBitLen] == 0) { | ||
469 | } | |||
470 | | |||
471 | // Move this node one down and remove a corresponding | |||
472 | // number of overflow nodes. | |||
473 | do { | |||
| 2 | 474 | bl_counts[incrBitLen]--; | ||
| 2 | 475 | bl_counts[++incrBitLen]++; | ||
| 2 | 476 | overflow -= 1 << (maxLength - 1 - incrBitLen); | ||
| 2 | 477 | } while (overflow > 0 && incrBitLen < maxLength - 1); | ||
| 2 | 478 | } while (overflow > 0); | ||
479 | | |||
480 | /* We may have overshot above. Move some nodes from maxLength to | |||
481 | * maxLength-1 in that case. | |||
482 | */ | |||
| 2 | 483 | bl_counts[maxLength - 1] += overflow; | ||
| 2 | 484 | bl_counts[maxLength - 2] -= overflow; | ||
485 | | |||
486 | /* Now recompute all bit lengths, scanning in increasing | |||
487 | * frequency. It is simpler to reconstruct all lengths instead of | |||
488 | * fixing only the wrong ones. This idea is taken from 'ar' | |||
489 | * written by Haruhiko Okumura. | |||
490 | * | |||
491 | * The nodes were inserted with decreasing frequency into the childs | |||
492 | * array. | |||
493 | */ | |||
| 2 | 494 | int nodePtr = 2 * numLeafs; | ||
| 32 | 495 | for (int bits = maxLength; bits != 0; bits--) { | ||
| 14 | 496 | int n = bl_counts[bits - 1]; | ||
| 44 | 497 | while (n > 0) { | ||
| 30 | 498 | int childPtr = 2 * childs[nodePtr++]; | ||
| 30 | 499 | if (childs[childPtr + 1] == -1) { | ||
500 | // We found another leaf | |||
| 18 | 501 | length[childs[childPtr]] = (byte)bits; | ||
| 18 | 502 | n--; | ||
503 | } | |||
504 | } | |||
505 | } | |||
506 | // if (DeflaterConstants.DEBUGGING) { | |||
507 | // //Console.WriteLine("*** After overflow elimination. ***"); | |||
508 | // for (int i=0; i < numLeafs; i++) { | |||
509 | // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] | |||
510 | // + " len: "+length[childs[2*i]]); | |||
511 | // } | |||
512 | // } | |||
| 2 | 513 | } | ||
514 | | |||
515 | } | |||
516 | | |||
517 | #region Instance Fields | |||
518 | /// <summary> | |||
519 | /// Pending buffer to use | |||
520 | /// </summary> | |||
521 | public DeflaterPending pending; | |||
522 | | |||
523 | Tree literalTree; | |||
524 | Tree distTree; | |||
525 | Tree blTree; | |||
526 | | |||
527 | // Buffer for distances | |||
528 | short[] d_buf; | |||
529 | byte[] l_buf; | |||
530 | int last_lit; | |||
531 | int extra_bits; | |||
532 | #endregion | |||
533 | | |||
534 | static DeflaterHuffman() | |||
535 | { | |||
536 | // See RFC 1951 3.2.6 | |||
537 | // Literal codes | |||
| 1 | 538 | staticLCodes = new short[LITERAL_NUM]; | ||
| 1 | 539 | staticLLength = new byte[LITERAL_NUM]; | ||
540 | | |||
| 1 | 541 | int i = 0; | ||
| 145 | 542 | while (i < 144) { | ||
| 144 | 543 | staticLCodes[i] = BitReverse((0x030 + i) << 8); | ||
| 144 | 544 | staticLLength[i++] = 8; | ||
545 | } | |||
546 | | |||
| 113 | 547 | while (i < 256) { | ||
| 112 | 548 | staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7); | ||
| 112 | 549 | staticLLength[i++] = 9; | ||
550 | } | |||
551 | | |||
| 25 | 552 | while (i < 280) { | ||
| 24 | 553 | staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9); | ||
| 24 | 554 | staticLLength[i++] = 7; | ||
555 | } | |||
556 | | |||
| 7 | 557 | while (i < LITERAL_NUM) { | ||
| 6 | 558 | staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8); | ||
| 6 | 559 | staticLLength[i++] = 8; | ||
560 | } | |||
561 | | |||
562 | // Distance codes | |||
| 1 | 563 | staticDCodes = new short[DIST_NUM]; | ||
| 1 | 564 | staticDLength = new byte[DIST_NUM]; | ||
| 62 | 565 | for (i = 0; i < DIST_NUM; i++) { | ||
| 30 | 566 | staticDCodes[i] = BitReverse(i << 11); | ||
| 30 | 567 | staticDLength[i] = 5; | ||
568 | } | |||
| 1 | 569 | } | ||
570 | | |||
571 | /// <summary> | |||
572 | /// Construct instance with pending buffer | |||
573 | /// </summary> | |||
574 | /// <param name="pending">Pending buffer to use</param> | |||
| 273 | 575 | public DeflaterHuffman(DeflaterPending pending) | ||
576 | { | |||
| 273 | 577 | this.pending = pending; | ||
578 | | |||
| 273 | 579 | literalTree = new Tree(this, LITERAL_NUM, 257, 15); | ||
| 273 | 580 | distTree = new Tree(this, DIST_NUM, 1, 15); | ||
| 273 | 581 | blTree = new Tree(this, BITLEN_NUM, 4, 7); | ||
582 | | |||
| 273 | 583 | d_buf = new short[BUFSIZE]; | ||
| 273 | 584 | l_buf = new byte[BUFSIZE]; | ||
| 273 | 585 | } | ||
586 | | |||
587 | /// <summary> | |||
588 | /// Reset internal state | |||
589 | /// </summary> | |||
590 | public void Reset() | |||
591 | { | |||
| 923 | 592 | last_lit = 0; | ||
| 923 | 593 | extra_bits = 0; | ||
| 923 | 594 | literalTree.Reset(); | ||
| 923 | 595 | distTree.Reset(); | ||
| 923 | 596 | blTree.Reset(); | ||
| 923 | 597 | } | ||
598 | | |||
599 | /// <summary> | |||
600 | /// Write all trees to pending buffer | |||
601 | /// </summary> | |||
602 | /// <param name="blTreeCodes">The number/rank of treecodes to send.</param> | |||
603 | public void SendAllTrees(int blTreeCodes) | |||
604 | { | |||
| 1 | 605 | blTree.BuildCodes(); | ||
| 1 | 606 | literalTree.BuildCodes(); | ||
| 1 | 607 | distTree.BuildCodes(); | ||
| 1 | 608 | pending.WriteBits(literalTree.numCodes - 257, 5); | ||
| 1 | 609 | pending.WriteBits(distTree.numCodes - 1, 5); | ||
| 1 | 610 | pending.WriteBits(blTreeCodes - 4, 4); | ||
| 38 | 611 | for (int rank = 0; rank < blTreeCodes; rank++) { | ||
| 18 | 612 | pending.WriteBits(blTree.length[BL_ORDER[rank]], 3); | ||
613 | } | |||
| 1 | 614 | literalTree.WriteTree(blTree); | ||
| 1 | 615 | distTree.WriteTree(blTree); | ||
616 | | |||
617 | #if DebugDeflation | |||
618 | if (DeflaterConstants.DEBUGGING) { | |||
619 | blTree.CheckEmpty(); | |||
620 | } | |||
621 | #endif | |||
| 1 | 622 | } | ||
623 | | |||
624 | /// <summary> | |||
625 | /// Compress current buffer writing data to pending buffer | |||
626 | /// </summary> | |||
627 | public void CompressBlock() | |||
628 | { | |||
| 16324 | 629 | for (int i = 0; i < last_lit; i++) { | ||
| 7924 | 630 | int litlen = l_buf[i] & 0xff; | ||
| 7924 | 631 | int dist = d_buf[i]; | ||
| 7924 | 632 | if (dist-- != 0) { | ||
633 | // if (DeflaterConstants.DEBUGGING) { | |||
634 | // Console.Write("["+(dist+1)+","+(litlen+3)+"]: "); | |||
635 | // } | |||
636 | | |||
| 91 | 637 | int lc = Lcode(litlen); | ||
| 91 | 638 | literalTree.WriteSymbol(lc); | ||
639 | | |||
| 91 | 640 | int bits = (lc - 261) / 4; | ||
| 91 | 641 | if (bits > 0 && bits <= 5) { | ||
| 25 | 642 | pending.WriteBits(litlen & ((1 << bits) - 1), bits); | ||
643 | } | |||
644 | | |||
| 91 | 645 | int dc = Dcode(dist); | ||
| 91 | 646 | distTree.WriteSymbol(dc); | ||
647 | | |||
| 91 | 648 | bits = dc / 2 - 1; | ||
| 91 | 649 | if (bits > 0) { | ||
| 70 | 650 | pending.WriteBits(dist & ((1 << bits) - 1), bits); | ||
651 | } | |||
| 70 | 652 | } else { | ||
653 | // if (DeflaterConstants.DEBUGGING) { | |||
654 | // if (litlen > 32 && litlen < 127) { | |||
655 | // Console.Write("("+(char)litlen+"): "); | |||
656 | // } else { | |||
657 | // Console.Write("{"+litlen+"}: "); | |||
658 | // } | |||
659 | // } | |||
| 7833 | 660 | literalTree.WriteSymbol(litlen); | ||
661 | } | |||
662 | } | |||
663 | | |||
664 | #if DebugDeflation | |||
665 | if (DeflaterConstants.DEBUGGING) { | |||
666 | Console.Write("EOF: "); | |||
667 | } | |||
668 | #endif | |||
| 238 | 669 | literalTree.WriteSymbol(EOF_SYMBOL); | ||
670 | | |||
671 | #if DebugDeflation | |||
672 | if (DeflaterConstants.DEBUGGING) { | |||
673 | literalTree.CheckEmpty(); | |||
674 | distTree.CheckEmpty(); | |||
675 | } | |||
676 | #endif | |||
| 238 | 677 | } | ||
678 | | |||
679 | /// <summary> | |||
680 | /// Flush block to output with no compression | |||
681 | /// </summary> | |||
682 | /// <param name="stored">Data to write</param> | |||
683 | /// <param name="storedOffset">Index of first byte to write</param> | |||
684 | /// <param name="storedLength">Count of bytes to write</param> | |||
685 | /// <param name="lastBlock">True if this is the last block</param> | |||
686 | public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) | |||
687 | { | |||
688 | #if DebugDeflation | |||
689 | // if (DeflaterConstants.DEBUGGING) { | |||
690 | // //Console.WriteLine("Flushing stored block "+ storedLength); | |||
691 | // } | |||
692 | #endif | |||
| 293 | 693 | pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); | ||
| 293 | 694 | pending.AlignToByte(); | ||
| 293 | 695 | pending.WriteShort(storedLength); | ||
| 293 | 696 | pending.WriteShort(~storedLength); | ||
| 293 | 697 | pending.WriteBlock(stored, storedOffset, storedLength); | ||
| 293 | 698 | Reset(); | ||
| 293 | 699 | } | ||
700 | | |||
701 | /// <summary> | |||
702 | /// Flush block to output with compression | |||
703 | /// </summary> | |||
704 | /// <param name="stored">Data to flush</param> | |||
705 | /// <param name="storedOffset">Index of first byte to flush</param> | |||
706 | /// <param name="storedLength">Count of bytes to flush</param> | |||
707 | /// <param name="lastBlock">True if this is the last block</param> | |||
708 | public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) | |||
709 | { | |||
| 510 | 710 | literalTree.freqs[EOF_SYMBOL]++; | ||
711 | | |||
712 | // Build trees | |||
| 510 | 713 | literalTree.BuildTree(); | ||
| 510 | 714 | distTree.BuildTree(); | ||
715 | | |||
716 | // Calculate bitlen frequency | |||
| 510 | 717 | literalTree.CalcBLFreq(blTree); | ||
| 510 | 718 | distTree.CalcBLFreq(blTree); | ||
719 | | |||
720 | // Build bitlen tree | |||
| 510 | 721 | blTree.BuildTree(); | ||
722 | | |||
| 510 | 723 | int blTreeCodes = 4; | ||
| 3556 | 724 | for (int i = 18; i > blTreeCodes; i--) { | ||
| 1268 | 725 | if (blTree.length[BL_ORDER[i]] > 0) { | ||
| 510 | 726 | blTreeCodes = i + 1; | ||
727 | } | |||
728 | } | |||
| 510 | 729 | int opt_len = 14 + blTreeCodes * 3 + blTree.GetEncodedLength() + | ||
| 510 | 730 | literalTree.GetEncodedLength() + distTree.GetEncodedLength() + | ||
| 510 | 731 | extra_bits; | ||
732 | | |||
| 510 | 733 | int static_len = extra_bits; | ||
| 292740 | 734 | for (int i = 0; i < LITERAL_NUM; i++) { | ||
| 145860 | 735 | static_len += literalTree.freqs[i] * staticLLength[i]; | ||
736 | } | |||
| 31620 | 737 | for (int i = 0; i < DIST_NUM; i++) { | ||
| 15300 | 738 | static_len += distTree.freqs[i] * staticDLength[i]; | ||
739 | } | |||
| 510 | 740 | if (opt_len >= static_len) { | ||
741 | // Force static trees | |||
| 270 | 742 | opt_len = static_len; | ||
743 | } | |||
744 | | |||
| 510 | 745 | if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) { | ||
746 | // Store Block | |||
747 | | |||
748 | // if (DeflaterConstants.DEBUGGING) { | |||
749 | // //Console.WriteLine("Storing, since " + storedLength + " < " + opt_len | |||
750 | // + " <= " + static_len); | |||
751 | // } | |||
| 272 | 752 | FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); | ||
| 510 | 753 | } else if (opt_len == static_len) { | ||
754 | // Encode with static tree | |||
| 237 | 755 | pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); | ||
| 237 | 756 | literalTree.SetStaticCodes(staticLCodes, staticLLength); | ||
| 237 | 757 | distTree.SetStaticCodes(staticDCodes, staticDLength); | ||
| 237 | 758 | CompressBlock(); | ||
| 237 | 759 | Reset(); | ||
| 237 | 760 | } else { | ||
761 | // Encode with dynamic tree | |||
| 1 | 762 | pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); | ||
| 1 | 763 | SendAllTrees(blTreeCodes); | ||
| 1 | 764 | CompressBlock(); | ||
| 1 | 765 | Reset(); | ||
766 | } | |||
| 1 | 767 | } | ||
768 | | |||
769 | /// <summary> | |||
770 | /// Get value indicating if internal buffer is full | |||
771 | /// </summary> | |||
772 | /// <returns>true if buffer is full</returns> | |||
773 | public bool IsFull() | |||
774 | { | |||
| 7208362 | 775 | return last_lit >= BUFSIZE; | ||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Add literal to buffer | |||
780 | /// </summary> | |||
781 | /// <param name="literal">Literal value to add to buffer.</param> | |||
782 | /// <returns>Value indicating internal buffer is full</returns> | |||
783 | public bool TallyLit(int literal) | |||
784 | { | |||
785 | // if (DeflaterConstants.DEBUGGING) { | |||
786 | // if (lit > 32 && lit < 127) { | |||
787 | // //Console.WriteLine("("+(char)lit+")"); | |||
788 | // } else { | |||
789 | // //Console.WriteLine("{"+lit+"}"); | |||
790 | // } | |||
791 | // } | |||
| 3602142 | 792 | d_buf[last_lit] = 0; | ||
| 3602142 | 793 | l_buf[last_lit++] = (byte)literal; | ||
| 3602142 | 794 | literalTree.freqs[literal]++; | ||
| 3602142 | 795 | return IsFull(); | ||
796 | } | |||
797 | | |||
798 | /// <summary> | |||
799 | /// Add distance code and length to literal and distance trees | |||
800 | /// </summary> | |||
801 | /// <param name="distance">Distance code</param> | |||
802 | /// <param name="length">Length</param> | |||
803 | /// <returns>Value indicating if internal buffer is full</returns> | |||
804 | public bool TallyDist(int distance, int length) | |||
805 | { | |||
806 | // if (DeflaterConstants.DEBUGGING) { | |||
807 | // //Console.WriteLine("[" + distance + "," + length + "]"); | |||
808 | // } | |||
809 | | |||
| 2828 | 810 | d_buf[last_lit] = (short)distance; | ||
| 2828 | 811 | l_buf[last_lit++] = (byte)(length - 3); | ||
812 | | |||
| 2828 | 813 | int lc = Lcode(length - 3); | ||
| 2828 | 814 | literalTree.freqs[lc]++; | ||
| 2828 | 815 | if (lc >= 265 && lc < 285) { | ||
| 25 | 816 | extra_bits += (lc - 261) / 4; | ||
817 | } | |||
818 | | |||
| 2828 | 819 | int dc = Dcode(distance - 1); | ||
| 2828 | 820 | distTree.freqs[dc]++; | ||
| 2828 | 821 | if (dc >= 4) { | ||
| 2807 | 822 | extra_bits += dc / 2 - 1; | ||
823 | } | |||
| 2828 | 824 | return IsFull(); | ||
825 | } | |||
826 | | |||
827 | | |||
828 | /// <summary> | |||
829 | /// Reverse the bits of a 16 bit value. | |||
830 | /// </summary> | |||
831 | /// <param name="toReverse">Value to reverse bits</param> | |||
832 | /// <returns>Value with bits reversed</returns> | |||
833 | public static short BitReverse(int toReverse) | |||
834 | { | |||
| 712 | 835 | return (short)(bit4Reverse[toReverse & 0xF] << 12 | | ||
| 712 | 836 | bit4Reverse[(toReverse >> 4) & 0xF] << 8 | | ||
| 712 | 837 | bit4Reverse[(toReverse >> 8) & 0xF] << 4 | | ||
| 712 | 838 | bit4Reverse[toReverse >> 12]); | ||
839 | } | |||
840 | | |||
841 | static int Lcode(int length) | |||
842 | { | |||
| 2919 | 843 | if (length == 255) { | ||
| 82 | 844 | return 285; | ||
845 | } | |||
846 | | |||
| 2837 | 847 | int code = 257; | ||
| 3067 | 848 | while (length >= 8) { | ||
| 230 | 849 | code += 4; | ||
| 230 | 850 | length >>= 1; | ||
851 | } | |||
| 2837 | 852 | return code + length; | ||
853 | } | |||
854 | | |||
855 | static int Dcode(int distance) | |||
856 | { | |||
| 2919 | 857 | int code = 0; | ||
| 34328 | 858 | while (distance >= 4) { | ||
| 31409 | 859 | code += 2; | ||
| 31409 | 860 | distance >>= 1; | ||
861 | } | |||
| 2919 | 862 | return code + distance; | ||
863 | } | |||
864 | } | |||
865 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\Streams\DeflaterOutputStream.cs |
| Covered lines: | 76 |
| Uncovered lines: | 28 |
| Coverable lines: | 104 |
| Total lines: | 476 |
| Line coverage: | 73% |
| Branch coverage: | 71.4% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 5 | 71.43 | 55.56 |
| Finish() | 7 | 87.5 | 76.92 |
| EncryptBlock(...) | 1 | 100 | 100 |
| InitializePassword(...) | 1 | 100 | 100 |
| InitializeAESPassword(...) | 2 | 0 | 0 |
| Deflate() | 5 | 88.89 | 88.89 |
| Seek(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| ReadByte() | 1 | 0 | 0 |
| Read(...) | 1 | 0 | 0 |
| BeginRead(...) | 1 | 0 | 0 |
| BeginWrite(...) | 1 | 0 | 0 |
| Flush() | 1 | 100 | 100 |
| Close() | 4 | 100 | 100 |
| GetAuthCodeIfAES() | 2 | 66.67 | 66.67 |
| WriteByte(...) | 1 | 100 | 100 |
| Write(...) | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Security.Cryptography; | |||
4 | using ICSharpCode.SharpZipLib.Encryption; | |||
5 | | |||
6 | namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams | |||
7 | { | |||
8 | /// <summary> | |||
9 | /// A special stream deflating or compressing the bytes that are | |||
10 | /// written to it. It uses a Deflater to perform actual deflating.<br/> | |||
11 | /// Authors of the original java version : Tom Tromey, Jochen Hoenicke | |||
12 | /// </summary> | |||
13 | public class DeflaterOutputStream : Stream | |||
14 | { | |||
15 | #region Constructors | |||
16 | /// <summary> | |||
17 | /// Creates a new DeflaterOutputStream with a default Deflater and default buffer size. | |||
18 | /// </summary> | |||
19 | /// <param name="baseOutputStream"> | |||
20 | /// the output stream where deflated output should be written. | |||
21 | /// </param> | |||
22 | public DeflaterOutputStream(Stream baseOutputStream) | |||
| 4 | 23 | : this(baseOutputStream, new Deflater(), 512) | ||
24 | { | |||
| 4 | 25 | } | ||
26 | | |||
27 | /// <summary> | |||
28 | /// Creates a new DeflaterOutputStream with the given Deflater and | |||
29 | /// default buffer size. | |||
30 | /// </summary> | |||
31 | /// <param name="baseOutputStream"> | |||
32 | /// the output stream where deflated output should be written. | |||
33 | /// </param> | |||
34 | /// <param name="deflater"> | |||
35 | /// the underlying deflater. | |||
36 | /// </param> | |||
37 | public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater) | |||
| 260 | 38 | : this(baseOutputStream, deflater, 512) | ||
39 | { | |||
| 260 | 40 | } | ||
41 | | |||
42 | /// <summary> | |||
43 | /// Creates a new DeflaterOutputStream with the given Deflater and | |||
44 | /// buffer size. | |||
45 | /// </summary> | |||
46 | /// <param name="baseOutputStream"> | |||
47 | /// The output stream where deflated output is written. | |||
48 | /// </param> | |||
49 | /// <param name="deflater"> | |||
50 | /// The underlying deflater to use | |||
51 | /// </param> | |||
52 | /// <param name="bufferSize"> | |||
53 | /// The buffer size in bytes to use when deflating (minimum value 512) | |||
54 | /// </param> | |||
55 | /// <exception cref="ArgumentOutOfRangeException"> | |||
56 | /// bufsize is less than or equal to zero. | |||
57 | /// </exception> | |||
58 | /// <exception cref="ArgumentException"> | |||
59 | /// baseOutputStream does not support writing | |||
60 | /// </exception> | |||
61 | /// <exception cref="ArgumentNullException"> | |||
62 | /// deflater instance is null | |||
63 | /// </exception> | |||
| 273 | 64 | public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufferSize) | ||
65 | { | |||
| 273 | 66 | if (baseOutputStream == null) { | ||
| 0 | 67 | throw new ArgumentNullException(nameof(baseOutputStream)); | ||
68 | } | |||
69 | | |||
| 273 | 70 | if (baseOutputStream.CanWrite == false) { | ||
| 0 | 71 | throw new ArgumentException("Must support writing", nameof(baseOutputStream)); | ||
72 | } | |||
73 | | |||
| 273 | 74 | if (deflater == null) { | ||
| 0 | 75 | throw new ArgumentNullException(nameof(deflater)); | ||
76 | } | |||
77 | | |||
| 273 | 78 | if (bufferSize < 512) { | ||
| 0 | 79 | throw new ArgumentOutOfRangeException(nameof(bufferSize)); | ||
80 | } | |||
81 | | |||
| 273 | 82 | baseOutputStream_ = baseOutputStream; | ||
| 273 | 83 | buffer_ = new byte[bufferSize]; | ||
| 273 | 84 | deflater_ = deflater; | ||
| 273 | 85 | } | ||
86 | #endregion | |||
87 | | |||
88 | #region Public API | |||
89 | /// <summary> | |||
90 | /// Finishes the stream by calling finish() on the deflater. | |||
91 | /// </summary> | |||
92 | /// <exception cref="SharpZipBaseException"> | |||
93 | /// Not all input is deflated | |||
94 | /// </exception> | |||
95 | public virtual void Finish() | |||
96 | { | |||
| 325 | 97 | deflater_.Finish(); | ||
| 1573 | 98 | while (!deflater_.IsFinished) { | ||
| 1248 | 99 | int len = deflater_.Deflate(buffer_, 0, buffer_.Length); | ||
| 1248 | 100 | if (len <= 0) { | ||
101 | break; | |||
102 | } | |||
103 | | |||
| 1248 | 104 | if (cryptoTransform_ != null) { | ||
| 196 | 105 | EncryptBlock(buffer_, 0, len); | ||
106 | } | |||
107 | | |||
| 1248 | 108 | baseOutputStream_.Write(buffer_, 0, len); | ||
109 | } | |||
110 | | |||
| 325 | 111 | if (!deflater_.IsFinished) { | ||
| 0 | 112 | throw new SharpZipBaseException("Can't deflate all input?"); | ||
113 | } | |||
114 | | |||
| 325 | 115 | baseOutputStream_.Flush(); | ||
116 | | |||
| 325 | 117 | if (cryptoTransform_ != null) { | ||
| 31 | 118 | if (cryptoTransform_ is ZipAESTransform) { | ||
| 0 | 119 | AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); | ||
120 | } | |||
| 31 | 121 | cryptoTransform_.Dispose(); | ||
| 31 | 122 | cryptoTransform_ = null; | ||
123 | } | |||
| 325 | 124 | } | ||
125 | | |||
126 | /// <summary> | |||
127 | /// Get/set flag indicating ownership of the underlying stream. | |||
128 | /// When the flag is true <see cref="Close"></see> will close the underlying stream also. | |||
129 | /// </summary> | |||
130 | public bool IsStreamOwner { | |||
| 11 | 131 | get { return isStreamOwner_; } | ||
| 386 | 132 | set { isStreamOwner_ = value; } | ||
133 | } | |||
134 | | |||
135 | /// <summary> | |||
136 | /// Allows client to determine if an entry can be patched after its added | |||
137 | /// </summary> | |||
138 | public bool CanPatchEntries { | |||
139 | get { | |||
| 141 | 140 | return baseOutputStream_.CanSeek; | ||
141 | } | |||
142 | } | |||
143 | | |||
144 | #endregion | |||
145 | | |||
146 | #region Encryption | |||
147 | | |||
148 | string password; | |||
149 | | |||
150 | ICryptoTransform cryptoTransform_; | |||
151 | | |||
152 | /// <summary> | |||
153 | /// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream. | |||
154 | /// </summary> | |||
155 | protected byte[] AESAuthCode; | |||
156 | | |||
157 | /// <summary> | |||
158 | /// Get/set the password used for encryption. | |||
159 | /// </summary> | |||
160 | /// <remarks>When set to null or if the password is empty no encryption is performed</remarks> | |||
161 | public string Password { | |||
162 | get { | |||
| 361 | 163 | return password; | ||
164 | } | |||
165 | set { | |||
| 69 | 166 | if ((value != null) && (value.Length == 0)) { | ||
| 0 | 167 | password = null; | ||
| 0 | 168 | } else { | ||
| 69 | 169 | password = value; | ||
170 | } | |||
| 69 | 171 | } | ||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Encrypt a block of data | |||
176 | /// </summary> | |||
177 | /// <param name="buffer"> | |||
178 | /// Data to encrypt. NOTE the original contents of the buffer are lost | |||
179 | /// </param> | |||
180 | /// <param name="offset"> | |||
181 | /// Offset of first byte in buffer to encrypt | |||
182 | /// </param> | |||
183 | /// <param name="length"> | |||
184 | /// Number of bytes in buffer to encrypt | |||
185 | /// </param> | |||
186 | protected void EncryptBlock(byte[] buffer, int offset, int length) | |||
187 | { | |||
| 2305 | 188 | cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0); | ||
| 2305 | 189 | } | ||
190 | | |||
191 | /// <summary> | |||
192 | /// Initializes encryption keys based on given <paramref name="password"/>. | |||
193 | /// </summary> | |||
194 | /// <param name="password">The password.</param> | |||
195 | protected void InitializePassword(string password) | |||
196 | { | |||
| 33 | 197 | var pkManaged = new PkzipClassicManaged(); | ||
| 33 | 198 | byte[] key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(password)); | ||
| 33 | 199 | cryptoTransform_ = pkManaged.CreateEncryptor(key, null); | ||
| 33 | 200 | } | ||
201 | | |||
202 | /// <summary> | |||
203 | /// Initializes encryption keys based on given password. | |||
204 | /// </summary> | |||
205 | protected void InitializeAESPassword(ZipEntry entry, string rawPassword, | |||
206 | out byte[] salt, out byte[] pwdVerifier) | |||
207 | { | |||
| 0 | 208 | salt = new byte[entry.AESSaltLen]; | ||
209 | // Salt needs to be cryptographically random, and unique per file | |||
| 0 | 210 | if (_aesRnd == null) | ||
| 0 | 211 | _aesRnd = new RNGCryptoServiceProvider(); | ||
| 0 | 212 | _aesRnd.GetBytes(salt); | ||
| 0 | 213 | int blockSize = entry.AESKeySize / 8; // bits to bytes | ||
214 | | |||
| 0 | 215 | cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true); | ||
| 0 | 216 | pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier; | ||
| 0 | 217 | } | ||
218 | | |||
219 | #endregion | |||
220 | | |||
221 | #region Deflation Support | |||
222 | /// <summary> | |||
223 | /// Deflates everything in the input buffers. This will call | |||
224 | /// <code>def.deflate()</code> until all bytes from the input buffers | |||
225 | /// are processed. | |||
226 | /// </summary> | |||
227 | protected void Deflate() | |||
228 | { | |||
| 11749 | 229 | while (!deflater_.IsNeedingInput) { | ||
| 11469 | 230 | int deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length); | ||
231 | | |||
| 11469 | 232 | if (deflateCount <= 0) { | ||
233 | break; | |||
234 | } | |||
| 7240 | 235 | if (cryptoTransform_ != null) { | ||
| 1977 | 236 | EncryptBlock(buffer_, 0, deflateCount); | ||
237 | } | |||
238 | | |||
| 7240 | 239 | baseOutputStream_.Write(buffer_, 0, deflateCount); | ||
240 | } | |||
241 | | |||
| 4509 | 242 | if (!deflater_.IsNeedingInput) { | ||
| 0 | 243 | throw new SharpZipBaseException("DeflaterOutputStream can't deflate all input?"); | ||
244 | } | |||
| 4509 | 245 | } | ||
246 | #endregion | |||
247 | | |||
248 | #region Stream Overrides | |||
249 | /// <summary> | |||
250 | /// Gets value indicating stream can be read from | |||
251 | /// </summary> | |||
252 | public override bool CanRead { | |||
253 | get { | |||
| 0 | 254 | return false; | ||
255 | } | |||
256 | } | |||
257 | | |||
258 | /// <summary> | |||
259 | /// Gets a value indicating if seeking is supported for this stream | |||
260 | /// This property always returns false | |||
261 | /// </summary> | |||
262 | public override bool CanSeek { | |||
263 | get { | |||
| 2 | 264 | return false; | ||
265 | } | |||
266 | } | |||
267 | | |||
268 | /// <summary> | |||
269 | /// Get value indicating if this stream supports writing | |||
270 | /// </summary> | |||
271 | public override bool CanWrite { | |||
272 | get { | |||
| 5 | 273 | return baseOutputStream_.CanWrite; | ||
274 | } | |||
275 | } | |||
276 | | |||
277 | /// <summary> | |||
278 | /// Get current length of stream | |||
279 | /// </summary> | |||
280 | public override long Length { | |||
281 | get { | |||
| 0 | 282 | return baseOutputStream_.Length; | ||
283 | } | |||
284 | } | |||
285 | | |||
286 | /// <summary> | |||
287 | /// Gets the current position within the stream. | |||
288 | /// </summary> | |||
289 | /// <exception cref="NotSupportedException">Any attempt to set position</exception> | |||
290 | public override long Position { | |||
291 | get { | |||
| 0 | 292 | return baseOutputStream_.Position; | ||
293 | } | |||
294 | set { | |||
| 0 | 295 | throw new NotSupportedException("Position property not supported"); | ||
296 | } | |||
297 | } | |||
298 | | |||
299 | /// <summary> | |||
300 | /// Sets the current position of this stream to the given value. Not supported by this class! | |||
301 | /// </summary> | |||
302 | /// <param name="offset">The offset relative to the <paramref name="origin"/> to seek.</param> | |||
303 | /// <param name="origin">The <see cref="SeekOrigin"/> to seek from.</param> | |||
304 | /// <returns>The new position in the stream.</returns> | |||
305 | /// <exception cref="NotSupportedException">Any access</exception> | |||
306 | public override long Seek(long offset, SeekOrigin origin) | |||
307 | { | |||
| 0 | 308 | throw new NotSupportedException("DeflaterOutputStream Seek not supported"); | ||
309 | } | |||
310 | | |||
311 | /// <summary> | |||
312 | /// Sets the length of this stream to the given value. Not supported by this class! | |||
313 | /// </summary> | |||
314 | /// <param name="value">The new stream length.</param> | |||
315 | /// <exception cref="NotSupportedException">Any access</exception> | |||
316 | public override void SetLength(long value) | |||
317 | { | |||
| 0 | 318 | throw new NotSupportedException("DeflaterOutputStream SetLength not supported"); | ||
319 | } | |||
320 | | |||
321 | /// <summary> | |||
322 | /// Read a byte from stream advancing position by one | |||
323 | /// </summary> | |||
324 | /// <returns>The byte read cast to an int. THe value is -1 if at the end of the stream.</returns> | |||
325 | /// <exception cref="NotSupportedException">Any access</exception> | |||
326 | public override int ReadByte() | |||
327 | { | |||
| 0 | 328 | throw new NotSupportedException("DeflaterOutputStream ReadByte not supported"); | ||
329 | } | |||
330 | | |||
331 | /// <summary> | |||
332 | /// Read a block of bytes from stream | |||
333 | /// </summary> | |||
334 | /// <param name="buffer">The buffer to store read data in.</param> | |||
335 | /// <param name="offset">The offset to start storing at.</param> | |||
336 | /// <param name="count">The maximum number of bytes to read.</param> | |||
337 | /// <returns>The actual number of bytes read. Zero if end of stream is detected.</returns> | |||
338 | /// <exception cref="NotSupportedException">Any access</exception> | |||
339 | public override int Read(byte[] buffer, int offset, int count) | |||
340 | { | |||
| 0 | 341 | throw new NotSupportedException("DeflaterOutputStream Read not supported"); | ||
342 | } | |||
343 | | |||
344 | /// <summary> | |||
345 | /// Asynchronous reads are not supported a NotSupportedException is always thrown | |||
346 | /// </summary> | |||
347 | /// <param name="buffer">The buffer to read into.</param> | |||
348 | /// <param name="offset">The offset to start storing data at.</param> | |||
349 | /// <param name="count">The number of bytes to read</param> | |||
350 | /// <param name="callback">The async callback to use.</param> | |||
351 | /// <param name="state">The state to use.</param> | |||
352 | /// <returns>Returns an <see cref="IAsyncResult"/></returns> | |||
353 | /// <exception cref="NotSupportedException">Any access</exception> | |||
354 | public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) | |||
355 | { | |||
| 0 | 356 | throw new NotSupportedException("DeflaterOutputStream BeginRead not currently supported"); | ||
357 | } | |||
358 | | |||
359 | /// <summary> | |||
360 | /// Asynchronous writes arent supported, a NotSupportedException is always thrown | |||
361 | /// </summary> | |||
362 | /// <param name="buffer">The buffer to write.</param> | |||
363 | /// <param name="offset">The offset to begin writing at.</param> | |||
364 | /// <param name="count">The number of bytes to write.</param> | |||
365 | /// <param name="callback">The <see cref="AsyncCallback"/> to use.</param> | |||
366 | /// <param name="state">The state object.</param> | |||
367 | /// <returns>Returns an IAsyncResult.</returns> | |||
368 | /// <exception cref="NotSupportedException">Any access</exception> | |||
369 | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) | |||
370 | { | |||
| 0 | 371 | throw new NotSupportedException("BeginWrite is not supported"); | ||
372 | } | |||
373 | | |||
374 | /// <summary> | |||
375 | /// Flushes the stream by calling <see cref="DeflaterOutputStream.Flush">Flush</see> on the deflater and then | |||
376 | /// on the underlying stream. This ensures that all bytes are flushed. | |||
377 | /// </summary> | |||
378 | public override void Flush() | |||
379 | { | |||
| 30 | 380 | deflater_.Flush(); | ||
| 30 | 381 | Deflate(); | ||
| 30 | 382 | baseOutputStream_.Flush(); | ||
| 30 | 383 | } | ||
384 | | |||
385 | /// <summary> | |||
386 | /// Calls <see cref="Finish"/> and closes the underlying | |||
387 | /// stream when <see cref="IsStreamOwner"></see> is true. | |||
388 | /// </summary> | |||
389 | public override void Close() | |||
390 | { | |||
| 267 | 391 | if (!isClosed_) { | ||
| 259 | 392 | isClosed_ = true; | ||
393 | | |||
394 | try { | |||
| 259 | 395 | Finish(); | ||
| 258 | 396 | if (cryptoTransform_ != null) { | ||
| 1 | 397 | GetAuthCodeIfAES(); | ||
| 1 | 398 | cryptoTransform_.Dispose(); | ||
| 1 | 399 | cryptoTransform_ = null; | ||
400 | } | |||
| 258 | 401 | } finally { | ||
| 259 | 402 | if (isStreamOwner_) { | ||
| 68 | 403 | baseOutputStream_.Close(); | ||
404 | } | |||
| 259 | 405 | } | ||
406 | } | |||
| 266 | 407 | } | ||
408 | | |||
409 | private void GetAuthCodeIfAES() | |||
410 | { | |||
| 1 | 411 | if (cryptoTransform_ is ZipAESTransform) { | ||
| 0 | 412 | AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); | ||
413 | } | |||
| 1 | 414 | } | ||
415 | | |||
416 | /// <summary> | |||
417 | /// Writes a single byte to the compressed output stream. | |||
418 | /// </summary> | |||
419 | /// <param name="value"> | |||
420 | /// The byte value. | |||
421 | /// </param> | |||
422 | public override void WriteByte(byte value) | |||
423 | { | |||
| 24 | 424 | byte[] b = new byte[1]; | ||
| 24 | 425 | b[0] = value; | ||
| 24 | 426 | Write(b, 0, 1); | ||
| 22 | 427 | } | ||
428 | | |||
429 | /// <summary> | |||
430 | /// Writes bytes from an array to the compressed stream. | |||
431 | /// </summary> | |||
432 | /// <param name="buffer"> | |||
433 | /// The byte array | |||
434 | /// </param> | |||
435 | /// <param name="offset"> | |||
436 | /// The offset into the byte array where to start. | |||
437 | /// </param> | |||
438 | /// <param name="count"> | |||
439 | /// The number of bytes to write. | |||
440 | /// </param> | |||
441 | public override void Write(byte[] buffer, int offset, int count) | |||
442 | { | |||
| 4479 | 443 | deflater_.SetInput(buffer, offset, count); | ||
| 4479 | 444 | Deflate(); | ||
| 4479 | 445 | } | ||
446 | #endregion | |||
447 | | |||
448 | #region Instance Fields | |||
449 | /// <summary> | |||
450 | /// This buffer is used temporarily to retrieve the bytes from the | |||
451 | /// deflater and write them to the underlying output stream. | |||
452 | /// </summary> | |||
453 | byte[] buffer_; | |||
454 | | |||
455 | /// <summary> | |||
456 | /// The deflater which is used to deflate the stream. | |||
457 | /// </summary> | |||
458 | protected Deflater deflater_; | |||
459 | | |||
460 | /// <summary> | |||
461 | /// Base stream the deflater depends on. | |||
462 | /// </summary> | |||
463 | protected Stream baseOutputStream_; | |||
464 | | |||
465 | bool isClosed_; | |||
466 | | |||
| 273 | 467 | bool isStreamOwner_ = true; | ||
468 | #endregion | |||
469 | | |||
470 | #region Static Fields | |||
471 | | |||
472 | // Static to help ensure that multiple files within a zip will get different random salt | |||
473 | private static RNGCryptoServiceProvider _aesRnd; | |||
474 | #endregion | |||
475 | } | |||
476 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.DeflaterPending |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\DeflaterPending.cs |
| Covered lines: | 2 |
| Uncovered lines: | 0 |
| Coverable lines: | 2 |
| Total lines: | 17 |
| Line coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
2 | { | |||
3 | /// <summary> | |||
4 | /// This class stores the pending output of the Deflater. | |||
5 | /// | |||
6 | /// author of the original java version : Jochen Hoenicke | |||
7 | /// </summary> | |||
8 | public class DeflaterPending : PendingBuffer | |||
9 | { | |||
10 | /// <summary> | |||
11 | /// Construct instance with default buffer size | |||
12 | /// </summary> | |||
| 273 | 13 | public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE) | ||
14 | { | |||
| 273 | 15 | } | ||
16 | } | |||
17 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.DescriptorData |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipHelperStream.cs |
| Covered lines: | 6 |
| Uncovered lines: | 0 |
| Coverable lines: | 6 |
| Total lines: | 560 |
| Line coverage: | 100% |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Text; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// Holds data pertinent to a data descriptor. | |||
9 | /// </summary> | |||
10 | public class DescriptorData | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Get /set the compressed size of data. | |||
14 | /// </summary> | |||
15 | public long CompressedSize { | |||
| 13 | 16 | get { return compressedSize; } | ||
| 26 | 17 | set { compressedSize = value; } | ||
18 | } | |||
19 | | |||
20 | /// <summary> | |||
21 | /// Get / set the uncompressed size of data | |||
22 | /// </summary> | |||
23 | public long Size { | |||
| 13 | 24 | get { return size; } | ||
| 26 | 25 | set { size = value; } | ||
26 | } | |||
27 | | |||
28 | /// <summary> | |||
29 | /// Get /set the crc value. | |||
30 | /// </summary> | |||
31 | public long Crc { | |||
| 13 | 32 | get { return crc; } | ||
| 26 | 33 | set { crc = (value & 0xffffffff); } | ||
34 | } | |||
35 | | |||
36 | #region Instance Fields | |||
37 | long size; | |||
38 | long compressedSize; | |||
39 | long crc; | |||
40 | #endregion | |||
41 | } | |||
42 | | |||
43 | class EntryPatchData | |||
44 | { | |||
45 | public long SizePatchOffset { | |||
46 | get { return sizePatchOffset_; } | |||
47 | set { sizePatchOffset_ = value; } | |||
48 | } | |||
49 | | |||
50 | public long CrcPatchOffset { | |||
51 | get { return crcPatchOffset_; } | |||
52 | set { crcPatchOffset_ = value; } | |||
53 | } | |||
54 | | |||
55 | #region Instance Fields | |||
56 | long sizePatchOffset_; | |||
57 | long crcPatchOffset_; | |||
58 | #endregion | |||
59 | } | |||
60 | | |||
61 | /// <summary> | |||
62 | /// This class assists with writing/reading from Zip files. | |||
63 | /// </summary> | |||
64 | internal class ZipHelperStream : Stream | |||
65 | { | |||
66 | #region Constructors | |||
67 | /// <summary> | |||
68 | /// Initialise an instance of this class. | |||
69 | /// </summary> | |||
70 | /// <param name="name">The name of the file to open.</param> | |||
71 | public ZipHelperStream(string name) | |||
72 | { | |||
73 | stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite); | |||
74 | isOwner_ = true; | |||
75 | } | |||
76 | | |||
77 | /// <summary> | |||
78 | /// Initialise a new instance of <see cref="ZipHelperStream"/>. | |||
79 | /// </summary> | |||
80 | /// <param name="stream">The stream to use.</param> | |||
81 | public ZipHelperStream(Stream stream) | |||
82 | { | |||
83 | stream_ = stream; | |||
84 | } | |||
85 | #endregion | |||
86 | | |||
87 | /// <summary> | |||
88 | /// Get / set a value indicating wether the the underlying stream is owned or not. | |||
89 | /// </summary> | |||
90 | /// <remarks>If the stream is owned it is closed when this instance is closed.</remarks> | |||
91 | public bool IsStreamOwner { | |||
92 | get { return isOwner_; } | |||
93 | set { isOwner_ = value; } | |||
94 | } | |||
95 | | |||
96 | #region Base Stream Methods | |||
97 | public override bool CanRead { | |||
98 | get { return stream_.CanRead; } | |||
99 | } | |||
100 | | |||
101 | public override bool CanSeek { | |||
102 | get { return stream_.CanSeek; } | |||
103 | } | |||
104 | | |||
105 | public override bool CanTimeout { | |||
106 | get { return stream_.CanTimeout; } | |||
107 | } | |||
108 | | |||
109 | public override long Length { | |||
110 | get { return stream_.Length; } | |||
111 | } | |||
112 | | |||
113 | public override long Position { | |||
114 | get { return stream_.Position; } | |||
115 | set { stream_.Position = value; } | |||
116 | } | |||
117 | | |||
118 | public override bool CanWrite { | |||
119 | get { return stream_.CanWrite; } | |||
120 | } | |||
121 | | |||
122 | public override void Flush() | |||
123 | { | |||
124 | stream_.Flush(); | |||
125 | } | |||
126 | | |||
127 | public override long Seek(long offset, SeekOrigin origin) | |||
128 | { | |||
129 | return stream_.Seek(offset, origin); | |||
130 | } | |||
131 | | |||
132 | public override void SetLength(long value) | |||
133 | { | |||
134 | stream_.SetLength(value); | |||
135 | } | |||
136 | | |||
137 | public override int Read(byte[] buffer, int offset, int count) | |||
138 | { | |||
139 | return stream_.Read(buffer, offset, count); | |||
140 | } | |||
141 | | |||
142 | public override void Write(byte[] buffer, int offset, int count) | |||
143 | { | |||
144 | stream_.Write(buffer, offset, count); | |||
145 | } | |||
146 | | |||
147 | /// <summary> | |||
148 | /// Close the stream. | |||
149 | /// </summary> | |||
150 | /// <remarks> | |||
151 | /// The underlying stream is closed only if <see cref="IsStreamOwner"/> is true. | |||
152 | /// </remarks> | |||
153 | override public void Close() | |||
154 | { | |||
155 | Stream toClose = stream_; | |||
156 | stream_ = null; | |||
157 | if (isOwner_ && (toClose != null)) { | |||
158 | isOwner_ = false; | |||
159 | toClose.Close(); | |||
160 | } | |||
161 | } | |||
162 | #endregion | |||
163 | | |||
164 | // Write the local file header | |||
165 | // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage | |||
166 | void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData) | |||
167 | { | |||
168 | CompressionMethod method = entry.CompressionMethod; | |||
169 | bool headerInfoAvailable = true; // How to get this? | |||
170 | bool patchEntryHeader = false; | |||
171 | | |||
172 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
173 | | |||
174 | WriteLEShort(entry.Version); | |||
175 | WriteLEShort(entry.Flags); | |||
176 | WriteLEShort((byte)method); | |||
177 | WriteLEInt((int)entry.DosTime); | |||
178 | | |||
179 | if (headerInfoAvailable == true) { | |||
180 | WriteLEInt((int)entry.Crc); | |||
181 | if (entry.LocalHeaderRequiresZip64) { | |||
182 | WriteLEInt(-1); | |||
183 | WriteLEInt(-1); | |||
184 | } else { | |||
185 | WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.Compressed | |||
186 | WriteLEInt((int)entry.Size); | |||
187 | } | |||
188 | } else { | |||
189 | if (patchData != null) { | |||
190 | patchData.CrcPatchOffset = stream_.Position; | |||
191 | } | |||
192 | WriteLEInt(0); // Crc | |||
193 | | |||
194 | if (patchData != null) { | |||
195 | patchData.SizePatchOffset = stream_.Position; | |||
196 | } | |||
197 | | |||
198 | // For local header both sizes appear in Zip64 Extended Information | |||
199 | if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { | |||
200 | WriteLEInt(-1); | |||
201 | WriteLEInt(-1); | |||
202 | } else { | |||
203 | WriteLEInt(0); // Compressed size | |||
204 | WriteLEInt(0); // Uncompressed size | |||
205 | } | |||
206 | } | |||
207 | | |||
208 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
209 | | |||
210 | if (name.Length > 0xFFFF) { | |||
211 | throw new ZipException("Entry name too long."); | |||
212 | } | |||
213 | | |||
214 | var ed = new ZipExtraData(entry.ExtraData); | |||
215 | | |||
216 | if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) { | |||
217 | ed.StartNewEntry(); | |||
218 | if (headerInfoAvailable) { | |||
219 | ed.AddLeLong(entry.Size); | |||
220 | ed.AddLeLong(entry.CompressedSize); | |||
221 | } else { | |||
222 | ed.AddLeLong(-1); | |||
223 | ed.AddLeLong(-1); | |||
224 | } | |||
225 | ed.AddNewEntry(1); | |||
226 | | |||
227 | if (!ed.Find(1)) { | |||
228 | throw new ZipException("Internal error cant find extra data"); | |||
229 | } | |||
230 | | |||
231 | if (patchData != null) { | |||
232 | patchData.SizePatchOffset = ed.CurrentReadIndex; | |||
233 | } | |||
234 | } else { | |||
235 | ed.Delete(1); | |||
236 | } | |||
237 | | |||
238 | byte[] extra = ed.GetEntryData(); | |||
239 | | |||
240 | WriteLEShort(name.Length); | |||
241 | WriteLEShort(extra.Length); | |||
242 | | |||
243 | if (name.Length > 0) { | |||
244 | stream_.Write(name, 0, name.Length); | |||
245 | } | |||
246 | | |||
247 | if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { | |||
248 | patchData.SizePatchOffset += stream_.Position; | |||
249 | } | |||
250 | | |||
251 | if (extra.Length > 0) { | |||
252 | stream_.Write(extra, 0, extra.Length); | |||
253 | } | |||
254 | } | |||
255 | | |||
256 | /// <summary> | |||
257 | /// Locates a block with the desired <paramref name="signature"/>. | |||
258 | /// </summary> | |||
259 | /// <param name="signature">The signature to find.</param> | |||
260 | /// <param name="endLocation">Location, marking the end of block.</param> | |||
261 | /// <param name="minimumBlockSize">Minimum size of the block.</param> | |||
262 | /// <param name="maximumVariableData">The maximum variable data.</param> | |||
263 | /// <returns>Eeturns the offset of the first byte after the signature; -1 if not found</returns> | |||
264 | public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
265 | { | |||
266 | long pos = endLocation - minimumBlockSize; | |||
267 | if (pos < 0) { | |||
268 | return -1; | |||
269 | } | |||
270 | | |||
271 | long giveUpMarker = Math.Max(pos - maximumVariableData, 0); | |||
272 | | |||
273 | // TODO: This loop could be optimised for speed. | |||
274 | do { | |||
275 | if (pos < giveUpMarker) { | |||
276 | return -1; | |||
277 | } | |||
278 | Seek(pos--, SeekOrigin.Begin); | |||
279 | } while (ReadLEInt() != signature); | |||
280 | | |||
281 | return Position; | |||
282 | } | |||
283 | | |||
284 | /// <summary> | |||
285 | /// Write Zip64 end of central directory records (File header and locator). | |||
286 | /// </summary> | |||
287 | /// <param name="noOfEntries">The number of entries in the central directory.</param> | |||
288 | /// <param name="sizeEntries">The size of entries in the central directory.</param> | |||
289 | /// <param name="centralDirOffset">The offset of the dentral directory.</param> | |||
290 | public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset) | |||
291 | { | |||
292 | long centralSignatureOffset = stream_.Position; | |||
293 | WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature); | |||
294 | WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12) | |||
295 | WriteLEShort(ZipConstants.VersionMadeBy); // Version made by | |||
296 | WriteLEShort(ZipConstants.VersionZip64); // Version to extract | |||
297 | WriteLEInt(0); // Number of this disk | |||
298 | WriteLEInt(0); // number of the disk with the start of the central directory | |||
299 | WriteLELong(noOfEntries); // No of entries on this disk | |||
300 | WriteLELong(noOfEntries); // Total No of entries in central directory | |||
301 | WriteLELong(sizeEntries); // Size of the central directory | |||
302 | WriteLELong(centralDirOffset); // offset of start of central directory | |||
303 | // zip64 extensible data sector not catered for here (variable size) | |||
304 | | |||
305 | // Write the Zip64 end of central directory locator | |||
306 | WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature); | |||
307 | | |||
308 | // no of the disk with the start of the zip64 end of central directory | |||
309 | WriteLEInt(0); | |||
310 | | |||
311 | // relative offset of the zip64 end of central directory record | |||
312 | WriteLELong(centralSignatureOffset); | |||
313 | | |||
314 | // total number of disks | |||
315 | WriteLEInt(1); | |||
316 | } | |||
317 | | |||
318 | /// <summary> | |||
319 | /// Write the required records to end the central directory. | |||
320 | /// </summary> | |||
321 | /// <param name="noOfEntries">The number of entries in the directory.</param> | |||
322 | /// <param name="sizeEntries">The size of the entries in the directory.</param> | |||
323 | /// <param name="startOfCentralDirectory">The start of the central directory.</param> | |||
324 | /// <param name="comment">The archive comment. (This can be null).</param> | |||
325 | public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries, | |||
326 | long startOfCentralDirectory, byte[] comment) | |||
327 | { | |||
328 | | |||
329 | if ((noOfEntries >= 0xffff) || | |||
330 | (startOfCentralDirectory >= 0xffffffff) || | |||
331 | (sizeEntries >= 0xffffffff)) { | |||
332 | WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory); | |||
333 | } | |||
334 | | |||
335 | WriteLEInt(ZipConstants.EndOfCentralDirectorySignature); | |||
336 | | |||
337 | // TODO: ZipFile Multi disk handling not done | |||
338 | WriteLEShort(0); // number of this disk | |||
339 | WriteLEShort(0); // no of disk with start of central dir | |||
340 | | |||
341 | | |||
342 | // Number of entries | |||
343 | if (noOfEntries >= 0xffff) { | |||
344 | WriteLEUshort(0xffff); // Zip64 marker | |||
345 | WriteLEUshort(0xffff); | |||
346 | } else { | |||
347 | WriteLEShort((short)noOfEntries); // entries in central dir for this disk | |||
348 | WriteLEShort((short)noOfEntries); // total entries in central directory | |||
349 | } | |||
350 | | |||
351 | // Size of the central directory | |||
352 | if (sizeEntries >= 0xffffffff) { | |||
353 | WriteLEUint(0xffffffff); // Zip64 marker | |||
354 | } else { | |||
355 | WriteLEInt((int)sizeEntries); | |||
356 | } | |||
357 | | |||
358 | | |||
359 | // offset of start of central directory | |||
360 | if (startOfCentralDirectory >= 0xffffffff) { | |||
361 | WriteLEUint(0xffffffff); // Zip64 marker | |||
362 | } else { | |||
363 | WriteLEInt((int)startOfCentralDirectory); | |||
364 | } | |||
365 | | |||
366 | int commentLength = (comment != null) ? comment.Length : 0; | |||
367 | | |||
368 | if (commentLength > 0xffff) { | |||
369 | throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength)); | |||
370 | } | |||
371 | | |||
372 | WriteLEShort(commentLength); | |||
373 | | |||
374 | if (commentLength > 0) { | |||
375 | Write(comment, 0, comment.Length); | |||
376 | } | |||
377 | } | |||
378 | | |||
379 | #region LE value reading/writing | |||
380 | /// <summary> | |||
381 | /// Read an unsigned short in little endian byte order. | |||
382 | /// </summary> | |||
383 | /// <returns>Returns the value read.</returns> | |||
384 | /// <exception cref="IOException"> | |||
385 | /// An i/o error occurs. | |||
386 | /// </exception> | |||
387 | /// <exception cref="EndOfStreamException"> | |||
388 | /// The file ends prematurely | |||
389 | /// </exception> | |||
390 | public int ReadLEShort() | |||
391 | { | |||
392 | int byteValue1 = stream_.ReadByte(); | |||
393 | | |||
394 | if (byteValue1 < 0) { | |||
395 | throw new EndOfStreamException(); | |||
396 | } | |||
397 | | |||
398 | int byteValue2 = stream_.ReadByte(); | |||
399 | if (byteValue2 < 0) { | |||
400 | throw new EndOfStreamException(); | |||
401 | } | |||
402 | | |||
403 | return byteValue1 | (byteValue2 << 8); | |||
404 | } | |||
405 | | |||
406 | /// <summary> | |||
407 | /// Read an int in little endian byte order. | |||
408 | /// </summary> | |||
409 | /// <returns>Returns the value read.</returns> | |||
410 | /// <exception cref="IOException"> | |||
411 | /// An i/o error occurs. | |||
412 | /// </exception> | |||
413 | /// <exception cref="System.IO.EndOfStreamException"> | |||
414 | /// The file ends prematurely | |||
415 | /// </exception> | |||
416 | public int ReadLEInt() | |||
417 | { | |||
418 | return ReadLEShort() | (ReadLEShort() << 16); | |||
419 | } | |||
420 | | |||
421 | /// <summary> | |||
422 | /// Read a long in little endian byte order. | |||
423 | /// </summary> | |||
424 | /// <returns>The value read.</returns> | |||
425 | public long ReadLELong() | |||
426 | { | |||
427 | return (uint)ReadLEInt() | ((long)ReadLEInt() << 32); | |||
428 | } | |||
429 | | |||
430 | /// <summary> | |||
431 | /// Write an unsigned short in little endian byte order. | |||
432 | /// </summary> | |||
433 | /// <param name="value">The value to write.</param> | |||
434 | public void WriteLEShort(int value) | |||
435 | { | |||
436 | stream_.WriteByte((byte)(value & 0xff)); | |||
437 | stream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
438 | } | |||
439 | | |||
440 | /// <summary> | |||
441 | /// Write a ushort in little endian byte order. | |||
442 | /// </summary> | |||
443 | /// <param name="value">The value to write.</param> | |||
444 | public void WriteLEUshort(ushort value) | |||
445 | { | |||
446 | stream_.WriteByte((byte)(value & 0xff)); | |||
447 | stream_.WriteByte((byte)(value >> 8)); | |||
448 | } | |||
449 | | |||
450 | /// <summary> | |||
451 | /// Write an int in little endian byte order. | |||
452 | /// </summary> | |||
453 | /// <param name="value">The value to write.</param> | |||
454 | public void WriteLEInt(int value) | |||
455 | { | |||
456 | WriteLEShort(value); | |||
457 | WriteLEShort(value >> 16); | |||
458 | } | |||
459 | | |||
460 | /// <summary> | |||
461 | /// Write a uint in little endian byte order. | |||
462 | /// </summary> | |||
463 | /// <param name="value">The value to write.</param> | |||
464 | public void WriteLEUint(uint value) | |||
465 | { | |||
466 | WriteLEUshort((ushort)(value & 0xffff)); | |||
467 | WriteLEUshort((ushort)(value >> 16)); | |||
468 | } | |||
469 | | |||
470 | /// <summary> | |||
471 | /// Write a long in little endian byte order. | |||
472 | /// </summary> | |||
473 | /// <param name="value">The value to write.</param> | |||
474 | public void WriteLELong(long value) | |||
475 | { | |||
476 | WriteLEInt((int)value); | |||
477 | WriteLEInt((int)(value >> 32)); | |||
478 | } | |||
479 | | |||
480 | /// <summary> | |||
481 | /// Write a ulong in little endian byte order. | |||
482 | /// </summary> | |||
483 | /// <param name="value">The value to write.</param> | |||
484 | public void WriteLEUlong(ulong value) | |||
485 | { | |||
486 | WriteLEUint((uint)(value & 0xffffffff)); | |||
487 | WriteLEUint((uint)(value >> 32)); | |||
488 | } | |||
489 | | |||
490 | #endregion | |||
491 | | |||
492 | /// <summary> | |||
493 | /// Write a data descriptor. | |||
494 | /// </summary> | |||
495 | /// <param name="entry">The entry to write a descriptor for.</param> | |||
496 | /// <returns>Returns the number of descriptor bytes written.</returns> | |||
497 | public int WriteDataDescriptor(ZipEntry entry) | |||
498 | { | |||
499 | if (entry == null) { | |||
500 | throw new ArgumentNullException(nameof(entry)); | |||
501 | } | |||
502 | | |||
503 | int result = 0; | |||
504 | | |||
505 | // Add data descriptor if flagged as required | |||
506 | if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
507 | // The signature is not PKZIP originally but is now described as optional | |||
508 | // in the PKZIP Appnote documenting trhe format. | |||
509 | WriteLEInt(ZipConstants.DataDescriptorSignature); | |||
510 | WriteLEInt(unchecked((int)(entry.Crc))); | |||
511 | | |||
512 | result += 8; | |||
513 | | |||
514 | if (entry.LocalHeaderRequiresZip64) { | |||
515 | WriteLELong(entry.CompressedSize); | |||
516 | WriteLELong(entry.Size); | |||
517 | result += 16; | |||
518 | } else { | |||
519 | WriteLEInt((int)entry.CompressedSize); | |||
520 | WriteLEInt((int)entry.Size); | |||
521 | result += 8; | |||
522 | } | |||
523 | } | |||
524 | | |||
525 | return result; | |||
526 | } | |||
527 | | |||
528 | /// <summary> | |||
529 | /// Read data descriptor at the end of compressed data. | |||
530 | /// </summary> | |||
531 | /// <param name="zip64">if set to <c>true</c> [zip64].</param> | |||
532 | /// <param name="data">The data to fill in.</param> | |||
533 | /// <returns>Returns the number of bytes read in the descriptor.</returns> | |||
534 | public void ReadDataDescriptor(bool zip64, DescriptorData data) | |||
535 | { | |||
536 | int intValue = ReadLEInt(); | |||
537 | | |||
538 | // In theory this may not be a descriptor according to PKZIP appnote. | |||
539 | // In practise its always there. | |||
540 | if (intValue != ZipConstants.DataDescriptorSignature) { | |||
541 | throw new ZipException("Data descriptor signature not found"); | |||
542 | } | |||
543 | | |||
544 | data.Crc = ReadLEInt(); | |||
545 | | |||
546 | if (zip64) { | |||
547 | data.CompressedSize = ReadLELong(); | |||
548 | data.Size = ReadLELong(); | |||
549 | } else { | |||
550 | data.CompressedSize = ReadLEInt(); | |||
551 | data.Size = ReadLEInt(); | |||
552 | } | |||
553 | } | |||
554 | | |||
555 | #region Instance Fields | |||
556 | bool isOwner_; | |||
557 | Stream stream_; | |||
558 | #endregion | |||
559 | } | |||
560 | } |
| Class: | ICSharpCode.SharpZipLib.Core.DirectoryEventArgs |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\FileSystemScanner.cs |
| Covered lines: | 0 |
| Uncovered lines: | 4 |
| Coverable lines: | 4 |
| Total lines: | 475 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Core | |||
4 | { | |||
5 | #region EventArgs | |||
6 | /// <summary> | |||
7 | /// Event arguments for scanning. | |||
8 | /// </summary> | |||
9 | public class ScanEventArgs : EventArgs | |||
10 | { | |||
11 | #region Constructors | |||
12 | /// <summary> | |||
13 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
14 | /// </summary> | |||
15 | /// <param name="name">The file or directory name.</param> | |||
16 | public ScanEventArgs(string name) | |||
17 | { | |||
18 | name_ = name; | |||
19 | } | |||
20 | #endregion | |||
21 | | |||
22 | /// <summary> | |||
23 | /// The file or directory name for this event. | |||
24 | /// </summary> | |||
25 | public string Name { | |||
26 | get { return name_; } | |||
27 | } | |||
28 | | |||
29 | /// <summary> | |||
30 | /// Get set a value indicating if scanning should continue or not. | |||
31 | /// </summary> | |||
32 | public bool ContinueRunning { | |||
33 | get { return continueRunning_; } | |||
34 | set { continueRunning_ = value; } | |||
35 | } | |||
36 | | |||
37 | #region Instance Fields | |||
38 | string name_; | |||
39 | bool continueRunning_ = true; | |||
40 | #endregion | |||
41 | } | |||
42 | | |||
43 | /// <summary> | |||
44 | /// Event arguments during processing of a single file or directory. | |||
45 | /// </summary> | |||
46 | public class ProgressEventArgs : EventArgs | |||
47 | { | |||
48 | #region Constructors | |||
49 | /// <summary> | |||
50 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
51 | /// </summary> | |||
52 | /// <param name="name">The file or directory name if known.</param> | |||
53 | /// <param name="processed">The number of bytes processed so far</param> | |||
54 | /// <param name="target">The total number of bytes to process, 0 if not known</param> | |||
55 | public ProgressEventArgs(string name, long processed, long target) | |||
56 | { | |||
57 | name_ = name; | |||
58 | processed_ = processed; | |||
59 | target_ = target; | |||
60 | } | |||
61 | #endregion | |||
62 | | |||
63 | /// <summary> | |||
64 | /// The name for this event if known. | |||
65 | /// </summary> | |||
66 | public string Name { | |||
67 | get { return name_; } | |||
68 | } | |||
69 | | |||
70 | /// <summary> | |||
71 | /// Get set a value indicating wether scanning should continue or not. | |||
72 | /// </summary> | |||
73 | public bool ContinueRunning { | |||
74 | get { return continueRunning_; } | |||
75 | set { continueRunning_ = value; } | |||
76 | } | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Get a percentage representing how much of the <see cref="Target"></see> has been processed | |||
80 | /// </summary> | |||
81 | /// <value>0.0 to 100.0 percent; 0 if target is not known.</value> | |||
82 | public float PercentComplete { | |||
83 | get { | |||
84 | float result; | |||
85 | if (target_ <= 0) { | |||
86 | result = 0; | |||
87 | } else { | |||
88 | result = ((float)processed_ / (float)target_) * 100.0f; | |||
89 | } | |||
90 | return result; | |||
91 | } | |||
92 | } | |||
93 | | |||
94 | /// <summary> | |||
95 | /// The number of bytes processed so far | |||
96 | /// </summary> | |||
97 | public long Processed { | |||
98 | get { return processed_; } | |||
99 | } | |||
100 | | |||
101 | /// <summary> | |||
102 | /// The number of bytes to process. | |||
103 | /// </summary> | |||
104 | /// <remarks>Target may be 0 or negative if the value isnt known.</remarks> | |||
105 | public long Target { | |||
106 | get { return target_; } | |||
107 | } | |||
108 | | |||
109 | #region Instance Fields | |||
110 | string name_; | |||
111 | long processed_; | |||
112 | long target_; | |||
113 | bool continueRunning_ = true; | |||
114 | #endregion | |||
115 | } | |||
116 | | |||
117 | /// <summary> | |||
118 | /// Event arguments for directories. | |||
119 | /// </summary> | |||
120 | public class DirectoryEventArgs : ScanEventArgs | |||
121 | { | |||
122 | #region Constructors | |||
123 | /// <summary> | |||
124 | /// Initialize an instance of <see cref="DirectoryEventArgs"></see>. | |||
125 | /// </summary> | |||
126 | /// <param name="name">The name for this directory.</param> | |||
127 | /// <param name="hasMatchingFiles">Flag value indicating if any matching files are contained in this directory.</par | |||
128 | public DirectoryEventArgs(string name, bool hasMatchingFiles) | |||
| 0 | 129 | : base(name) | ||
130 | { | |||
| 0 | 131 | hasMatchingFiles_ = hasMatchingFiles; | ||
| 0 | 132 | } | ||
133 | #endregion | |||
134 | | |||
135 | /// <summary> | |||
136 | /// Get a value indicating if the directory contains any matching files or not. | |||
137 | /// </summary> | |||
138 | public bool HasMatchingFiles { | |||
| 0 | 139 | get { return hasMatchingFiles_; } | ||
140 | } | |||
141 | | |||
142 | readonly | |||
143 | | |||
144 | #region Instance Fields | |||
145 | bool hasMatchingFiles_; | |||
146 | #endregion | |||
147 | } | |||
148 | | |||
149 | /// <summary> | |||
150 | /// Arguments passed when scan failures are detected. | |||
151 | /// </summary> | |||
152 | public class ScanFailureEventArgs : EventArgs | |||
153 | { | |||
154 | #region Constructors | |||
155 | /// <summary> | |||
156 | /// Initialise a new instance of <see cref="ScanFailureEventArgs"></see> | |||
157 | /// </summary> | |||
158 | /// <param name="name">The name to apply.</param> | |||
159 | /// <param name="e">The exception to use.</param> | |||
160 | public ScanFailureEventArgs(string name, Exception e) | |||
161 | { | |||
162 | name_ = name; | |||
163 | exception_ = e; | |||
164 | continueRunning_ = true; | |||
165 | } | |||
166 | #endregion | |||
167 | | |||
168 | /// <summary> | |||
169 | /// The applicable name. | |||
170 | /// </summary> | |||
171 | public string Name { | |||
172 | get { return name_; } | |||
173 | } | |||
174 | | |||
175 | /// <summary> | |||
176 | /// The applicable exception. | |||
177 | /// </summary> | |||
178 | public Exception Exception { | |||
179 | get { return exception_; } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Get / set a value indicating wether scanning should continue. | |||
184 | /// </summary> | |||
185 | public bool ContinueRunning { | |||
186 | get { return continueRunning_; } | |||
187 | set { continueRunning_ = value; } | |||
188 | } | |||
189 | | |||
190 | #region Instance Fields | |||
191 | string name_; | |||
192 | Exception exception_; | |||
193 | bool continueRunning_; | |||
194 | #endregion | |||
195 | } | |||
196 | | |||
197 | #endregion | |||
198 | | |||
199 | #region Delegates | |||
200 | /// <summary> | |||
201 | /// Delegate invoked before starting to process a file. | |||
202 | /// </summary> | |||
203 | /// <param name="sender">The source of the event</param> | |||
204 | /// <param name="e">The event arguments.</param> | |||
205 | public delegate void ProcessFileHandler(object sender, ScanEventArgs e); | |||
206 | | |||
207 | /// <summary> | |||
208 | /// Delegate invoked during processing of a file or directory | |||
209 | /// </summary> | |||
210 | /// <param name="sender">The source of the event</param> | |||
211 | /// <param name="e">The event arguments.</param> | |||
212 | public delegate void ProgressHandler(object sender, ProgressEventArgs e); | |||
213 | | |||
214 | /// <summary> | |||
215 | /// Delegate invoked when a file has been completely processed. | |||
216 | /// </summary> | |||
217 | /// <param name="sender">The source of the event</param> | |||
218 | /// <param name="e">The event arguments.</param> | |||
219 | public delegate void CompletedFileHandler(object sender, ScanEventArgs e); | |||
220 | | |||
221 | /// <summary> | |||
222 | /// Delegate invoked when a directory failure is detected. | |||
223 | /// </summary> | |||
224 | /// <param name="sender">The source of the event</param> | |||
225 | /// <param name="e">The event arguments.</param> | |||
226 | public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e); | |||
227 | | |||
228 | /// <summary> | |||
229 | /// Delegate invoked when a file failure is detected. | |||
230 | /// </summary> | |||
231 | /// <param name="sender">The source of the event</param> | |||
232 | /// <param name="e">The event arguments.</param> | |||
233 | public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e); | |||
234 | #endregion | |||
235 | | |||
236 | /// <summary> | |||
237 | /// FileSystemScanner provides facilities scanning of files and directories. | |||
238 | /// </summary> | |||
239 | public class FileSystemScanner | |||
240 | { | |||
241 | #region Constructors | |||
242 | /// <summary> | |||
243 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
244 | /// </summary> | |||
245 | /// <param name="filter">The <see cref="PathFilter">file filter</see> to apply when scanning.</param> | |||
246 | public FileSystemScanner(string filter) | |||
247 | { | |||
248 | fileFilter_ = new PathFilter(filter); | |||
249 | } | |||
250 | | |||
251 | /// <summary> | |||
252 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
253 | /// </summary> | |||
254 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
255 | /// <param name="directoryFilter">The <see cref="PathFilter"> directory filter</see> to apply.</param> | |||
256 | public FileSystemScanner(string fileFilter, string directoryFilter) | |||
257 | { | |||
258 | fileFilter_ = new PathFilter(fileFilter); | |||
259 | directoryFilter_ = new PathFilter(directoryFilter); | |||
260 | } | |||
261 | | |||
262 | /// <summary> | |||
263 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
264 | /// </summary> | |||
265 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
266 | public FileSystemScanner(IScanFilter fileFilter) | |||
267 | { | |||
268 | fileFilter_ = fileFilter; | |||
269 | } | |||
270 | | |||
271 | /// <summary> | |||
272 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
273 | /// </summary> | |||
274 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
275 | /// <param name="directoryFilter">The directory <see cref="IScanFilter">filter</see> to apply.</param> | |||
276 | public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter) | |||
277 | { | |||
278 | fileFilter_ = fileFilter; | |||
279 | directoryFilter_ = directoryFilter; | |||
280 | } | |||
281 | #endregion | |||
282 | | |||
283 | #region Delegates | |||
284 | /// <summary> | |||
285 | /// Delegate to invoke when a directory is processed. | |||
286 | /// </summary> | |||
287 | public event EventHandler<DirectoryEventArgs> ProcessDirectory; | |||
288 | | |||
289 | /// <summary> | |||
290 | /// Delegate to invoke when a file is processed. | |||
291 | /// </summary> | |||
292 | public ProcessFileHandler ProcessFile; | |||
293 | | |||
294 | /// <summary> | |||
295 | /// Delegate to invoke when processing for a file has finished. | |||
296 | /// </summary> | |||
297 | public CompletedFileHandler CompletedFile; | |||
298 | | |||
299 | /// <summary> | |||
300 | /// Delegate to invoke when a directory failure is detected. | |||
301 | /// </summary> | |||
302 | public DirectoryFailureHandler DirectoryFailure; | |||
303 | | |||
304 | /// <summary> | |||
305 | /// Delegate to invoke when a file failure is detected. | |||
306 | /// </summary> | |||
307 | public FileFailureHandler FileFailure; | |||
308 | #endregion | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Raise the DirectoryFailure event. | |||
312 | /// </summary> | |||
313 | /// <param name="directory">The directory name.</param> | |||
314 | /// <param name="e">The exception detected.</param> | |||
315 | bool OnDirectoryFailure(string directory, Exception e) | |||
316 | { | |||
317 | DirectoryFailureHandler handler = DirectoryFailure; | |||
318 | bool result = (handler != null); | |||
319 | if (result) { | |||
320 | var args = new ScanFailureEventArgs(directory, e); | |||
321 | handler(this, args); | |||
322 | alive_ = args.ContinueRunning; | |||
323 | } | |||
324 | return result; | |||
325 | } | |||
326 | | |||
327 | /// <summary> | |||
328 | /// Raise the FileFailure event. | |||
329 | /// </summary> | |||
330 | /// <param name="file">The file name.</param> | |||
331 | /// <param name="e">The exception detected.</param> | |||
332 | bool OnFileFailure(string file, Exception e) | |||
333 | { | |||
334 | FileFailureHandler handler = FileFailure; | |||
335 | | |||
336 | bool result = (handler != null); | |||
337 | | |||
338 | if (result) { | |||
339 | var args = new ScanFailureEventArgs(file, e); | |||
340 | FileFailure(this, args); | |||
341 | alive_ = args.ContinueRunning; | |||
342 | } | |||
343 | return result; | |||
344 | } | |||
345 | | |||
346 | /// <summary> | |||
347 | /// Raise the ProcessFile event. | |||
348 | /// </summary> | |||
349 | /// <param name="file">The file name.</param> | |||
350 | void OnProcessFile(string file) | |||
351 | { | |||
352 | ProcessFileHandler handler = ProcessFile; | |||
353 | | |||
354 | if (handler != null) { | |||
355 | var args = new ScanEventArgs(file); | |||
356 | handler(this, args); | |||
357 | alive_ = args.ContinueRunning; | |||
358 | } | |||
359 | } | |||
360 | | |||
361 | /// <summary> | |||
362 | /// Raise the complete file event | |||
363 | /// </summary> | |||
364 | /// <param name="file">The file name</param> | |||
365 | void OnCompleteFile(string file) | |||
366 | { | |||
367 | CompletedFileHandler handler = CompletedFile; | |||
368 | | |||
369 | if (handler != null) { | |||
370 | var args = new ScanEventArgs(file); | |||
371 | handler(this, args); | |||
372 | alive_ = args.ContinueRunning; | |||
373 | } | |||
374 | } | |||
375 | | |||
376 | /// <summary> | |||
377 | /// Raise the ProcessDirectory event. | |||
378 | /// </summary> | |||
379 | /// <param name="directory">The directory name.</param> | |||
380 | /// <param name="hasMatchingFiles">Flag indicating if the directory has matching files.</param> | |||
381 | void OnProcessDirectory(string directory, bool hasMatchingFiles) | |||
382 | { | |||
383 | EventHandler<DirectoryEventArgs> handler = ProcessDirectory; | |||
384 | | |||
385 | if (handler != null) { | |||
386 | var args = new DirectoryEventArgs(directory, hasMatchingFiles); | |||
387 | handler(this, args); | |||
388 | alive_ = args.ContinueRunning; | |||
389 | } | |||
390 | } | |||
391 | | |||
392 | /// <summary> | |||
393 | /// Scan a directory. | |||
394 | /// </summary> | |||
395 | /// <param name="directory">The base directory to scan.</param> | |||
396 | /// <param name="recurse">True to recurse subdirectories, false to scan a single directory.</param> | |||
397 | public void Scan(string directory, bool recurse) | |||
398 | { | |||
399 | alive_ = true; | |||
400 | ScanDir(directory, recurse); | |||
401 | } | |||
402 | | |||
403 | void ScanDir(string directory, bool recurse) | |||
404 | { | |||
405 | | |||
406 | try { | |||
407 | string[] names = System.IO.Directory.GetFiles(directory); | |||
408 | bool hasMatch = false; | |||
409 | for (int fileIndex = 0; fileIndex < names.Length; ++fileIndex) { | |||
410 | if (!fileFilter_.IsMatch(names[fileIndex])) { | |||
411 | names[fileIndex] = null; | |||
412 | } else { | |||
413 | hasMatch = true; | |||
414 | } | |||
415 | } | |||
416 | | |||
417 | OnProcessDirectory(directory, hasMatch); | |||
418 | | |||
419 | if (alive_ && hasMatch) { | |||
420 | foreach (string fileName in names) { | |||
421 | try { | |||
422 | if (fileName != null) { | |||
423 | OnProcessFile(fileName); | |||
424 | if (!alive_) { | |||
425 | break; | |||
426 | } | |||
427 | } | |||
428 | } catch (Exception e) { | |||
429 | if (!OnFileFailure(fileName, e)) { | |||
430 | throw; | |||
431 | } | |||
432 | } | |||
433 | } | |||
434 | } | |||
435 | } catch (Exception e) { | |||
436 | if (!OnDirectoryFailure(directory, e)) { | |||
437 | throw; | |||
438 | } | |||
439 | } | |||
440 | | |||
441 | if (alive_ && recurse) { | |||
442 | try { | |||
443 | string[] names = System.IO.Directory.GetDirectories(directory); | |||
444 | foreach (string fulldir in names) { | |||
445 | if ((directoryFilter_ == null) || (directoryFilter_.IsMatch(fulldir))) { | |||
446 | ScanDir(fulldir, true); | |||
447 | if (!alive_) { | |||
448 | break; | |||
449 | } | |||
450 | } | |||
451 | } | |||
452 | } catch (Exception e) { | |||
453 | if (!OnDirectoryFailure(directory, e)) { | |||
454 | throw; | |||
455 | } | |||
456 | } | |||
457 | } | |||
458 | } | |||
459 | | |||
460 | #region Instance Fields | |||
461 | /// <summary> | |||
462 | /// The file filter currently in use. | |||
463 | /// </summary> | |||
464 | IScanFilter fileFilter_; | |||
465 | /// <summary> | |||
466 | /// The directory filter currently in use. | |||
467 | /// </summary> | |||
468 | IScanFilter directoryFilter_; | |||
469 | /// <summary> | |||
470 | /// Flag indicating if scanning should continue running. | |||
471 | /// </summary> | |||
472 | bool alive_; | |||
473 | #endregion | |||
474 | } | |||
475 | } |
| Class: | ICSharpCode.SharpZipLib.Core.DirectoryFailureHandler |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | |
| Covered lines: | 0 |
| Uncovered lines: | 0 |
| Coverable lines: | 0 |
| Total lines: | 0 |
| Line coverage: |
No files found. This usually happens if a file isn't covered by a test or the class does not contain any sequence points (e.g. a class that only contains auto properties).
+| Class: | ICSharpCode.SharpZipLib.Zip.DiskArchiveStorage |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs |
| Covered lines: | 55 |
| Uncovered lines: | 15 |
| Coverable lines: | 70 |
| Total lines: | 4263 |
| Line coverage: | 78.5% |
| Branch coverage: | 62.5% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 2 | 80 | 66.67 |
| .ctor(...) | 1 | 100 | 100 |
| GetTemporaryOutput() | 2 | 57.14 | 66.67 |
| ConvertTemporaryToFinal() | 3 | 63.16 | 40 |
| MakeTemporaryCopy(...) | 1 | 100 | 100 |
| OpenForDirectUpdate(...) | 4 | 85.71 | 57.14 |
| Dispose() | 2 | 100 | 100 |
| GetTempFileName(...) | 6 | 75 | 88.89 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.IO; | |||
4 | using System.Text; | |||
5 | using System.Globalization; | |||
6 | using System.Security.Cryptography; | |||
7 | using ICSharpCode.SharpZipLib.Encryption; | |||
8 | using ICSharpCode.SharpZipLib.Core; | |||
9 | using ICSharpCode.SharpZipLib.Checksum; | |||
10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
11 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
12 | | |||
13 | namespace ICSharpCode.SharpZipLib.Zip | |||
14 | { | |||
15 | #region Keys Required Event Args | |||
16 | /// <summary> | |||
17 | /// Arguments used with KeysRequiredEvent | |||
18 | /// </summary> | |||
19 | public class KeysRequiredEventArgs : EventArgs | |||
20 | { | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
24 | /// </summary> | |||
25 | /// <param name="name">The name of the file for which keys are required.</param> | |||
26 | public KeysRequiredEventArgs(string name) | |||
27 | { | |||
28 | fileName = name; | |||
29 | } | |||
30 | | |||
31 | /// <summary> | |||
32 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
33 | /// </summary> | |||
34 | /// <param name="name">The name of the file for which keys are required.</param> | |||
35 | /// <param name="keyValue">The current key value.</param> | |||
36 | public KeysRequiredEventArgs(string name, byte[] keyValue) | |||
37 | { | |||
38 | fileName = name; | |||
39 | key = keyValue; | |||
40 | } | |||
41 | | |||
42 | #endregion | |||
43 | #region Properties | |||
44 | /// <summary> | |||
45 | /// Gets the name of the file for which keys are required. | |||
46 | /// </summary> | |||
47 | public string FileName { | |||
48 | get { return fileName; } | |||
49 | } | |||
50 | | |||
51 | /// <summary> | |||
52 | /// Gets or sets the key value | |||
53 | /// </summary> | |||
54 | public byte[] Key { | |||
55 | get { return key; } | |||
56 | set { key = value; } | |||
57 | } | |||
58 | #endregion | |||
59 | | |||
60 | #region Instance Fields | |||
61 | string fileName; | |||
62 | byte[] key; | |||
63 | #endregion | |||
64 | } | |||
65 | #endregion | |||
66 | | |||
67 | #region Test Definitions | |||
68 | /// <summary> | |||
69 | /// The strategy to apply to testing. | |||
70 | /// </summary> | |||
71 | public enum TestStrategy | |||
72 | { | |||
73 | /// <summary> | |||
74 | /// Find the first error only. | |||
75 | /// </summary> | |||
76 | FindFirstError, | |||
77 | /// <summary> | |||
78 | /// Find all possible errors. | |||
79 | /// </summary> | |||
80 | FindAllErrors, | |||
81 | } | |||
82 | | |||
83 | /// <summary> | |||
84 | /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. | |||
85 | /// </summary> | |||
86 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
87 | public enum TestOperation | |||
88 | { | |||
89 | /// <summary> | |||
90 | /// Setting up testing. | |||
91 | /// </summary> | |||
92 | Initialising, | |||
93 | | |||
94 | /// <summary> | |||
95 | /// Testing an individual entries header | |||
96 | /// </summary> | |||
97 | EntryHeader, | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Testing an individual entries data | |||
101 | /// </summary> | |||
102 | EntryData, | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Testing an individual entry has completed. | |||
106 | /// </summary> | |||
107 | EntryComplete, | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Running miscellaneous tests | |||
111 | /// </summary> | |||
112 | MiscellaneousTests, | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Testing is complete | |||
116 | /// </summary> | |||
117 | Complete, | |||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. | |||
122 | /// </summary> | |||
123 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
124 | public class TestStatus | |||
125 | { | |||
126 | #region Constructors | |||
127 | /// <summary> | |||
128 | /// Initialise a new instance of <see cref="TestStatus"/> | |||
129 | /// </summary> | |||
130 | /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> | |||
131 | public TestStatus(ZipFile file) | |||
132 | { | |||
133 | file_ = file; | |||
134 | } | |||
135 | #endregion | |||
136 | | |||
137 | #region Properties | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Get the current <see cref="TestOperation"/> in progress. | |||
141 | /// </summary> | |||
142 | public TestOperation Operation { | |||
143 | get { return operation_; } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get the <see cref="ZipFile"/> this status is applicable to. | |||
148 | /// </summary> | |||
149 | public ZipFile File { | |||
150 | get { return file_; } | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Get the current/last entry tested. | |||
155 | /// </summary> | |||
156 | public ZipEntry Entry { | |||
157 | get { return entry_; } | |||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Get the number of errors detected so far. | |||
162 | /// </summary> | |||
163 | public int ErrorCount { | |||
164 | get { return errorCount_; } | |||
165 | } | |||
166 | | |||
167 | /// <summary> | |||
168 | /// Get the number of bytes tested so far for the current entry. | |||
169 | /// </summary> | |||
170 | public long BytesTested { | |||
171 | get { return bytesTested_; } | |||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Get a value indicating wether the last entry test was valid. | |||
176 | /// </summary> | |||
177 | public bool EntryValid { | |||
178 | get { return entryValid_; } | |||
179 | } | |||
180 | #endregion | |||
181 | | |||
182 | #region Internal API | |||
183 | internal void AddError() | |||
184 | { | |||
185 | errorCount_++; | |||
186 | entryValid_ = false; | |||
187 | } | |||
188 | | |||
189 | internal void SetOperation(TestOperation operation) | |||
190 | { | |||
191 | operation_ = operation; | |||
192 | } | |||
193 | | |||
194 | internal void SetEntry(ZipEntry entry) | |||
195 | { | |||
196 | entry_ = entry; | |||
197 | entryValid_ = true; | |||
198 | bytesTested_ = 0; | |||
199 | } | |||
200 | | |||
201 | internal void SetBytesTested(long value) | |||
202 | { | |||
203 | bytesTested_ = value; | |||
204 | } | |||
205 | #endregion | |||
206 | | |||
207 | #region Instance Fields | |||
208 | ZipFile file_; | |||
209 | ZipEntry entry_; | |||
210 | bool entryValid_; | |||
211 | int errorCount_; | |||
212 | long bytesTested_; | |||
213 | TestOperation operation_; | |||
214 | #endregion | |||
215 | } | |||
216 | | |||
217 | /// <summary> | |||
218 | /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if | |||
219 | /// </summary> | |||
220 | /// <remarks>If the message is non-null an error has occured. If the message is null | |||
221 | /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> | |||
222 | public delegate void ZipTestResultHandler(TestStatus status, string message); | |||
223 | #endregion | |||
224 | | |||
225 | #region Update Definitions | |||
226 | /// <summary> | |||
227 | /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. | |||
228 | /// </summary> | |||
229 | public enum FileUpdateMode | |||
230 | { | |||
231 | /// <summary> | |||
232 | /// Perform all updates on temporary files ensuring that the original file is saved. | |||
233 | /// </summary> | |||
234 | Safe, | |||
235 | /// <summary> | |||
236 | /// Update the archive directly, which is faster but less safe. | |||
237 | /// </summary> | |||
238 | Direct, | |||
239 | } | |||
240 | #endregion | |||
241 | | |||
242 | #region ZipFile Class | |||
243 | /// <summary> | |||
244 | /// This class represents a Zip archive. You can ask for the contained | |||
245 | /// entries, or get an input stream for a file entry. The entry is | |||
246 | /// automatically decompressed. | |||
247 | /// | |||
248 | /// You can also update the archive adding or deleting entries. | |||
249 | /// | |||
250 | /// This class is thread safe for input: You can open input streams for arbitrary | |||
251 | /// entries in different threads. | |||
252 | /// <br/> | |||
253 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
254 | /// </summary> | |||
255 | /// <example> | |||
256 | /// <code> | |||
257 | /// using System; | |||
258 | /// using System.Text; | |||
259 | /// using System.Collections; | |||
260 | /// using System.IO; | |||
261 | /// | |||
262 | /// using ICSharpCode.SharpZipLib.Zip; | |||
263 | /// | |||
264 | /// class MainClass | |||
265 | /// { | |||
266 | /// static public void Main(string[] args) | |||
267 | /// { | |||
268 | /// using (ZipFile zFile = new ZipFile(args[0])) { | |||
269 | /// Console.WriteLine("Listing of : " + zFile.Name); | |||
270 | /// Console.WriteLine(""); | |||
271 | /// Console.WriteLine("Raw Size Size Date Time Name"); | |||
272 | /// Console.WriteLine("-------- -------- -------- ------ ---------"); | |||
273 | /// foreach (ZipEntry e in zFile) { | |||
274 | /// if ( e.IsFile ) { | |||
275 | /// DateTime d = e.DateTime; | |||
276 | /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, | |||
277 | /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), | |||
278 | /// e.Name); | |||
279 | /// } | |||
280 | /// } | |||
281 | /// } | |||
282 | /// } | |||
283 | /// } | |||
284 | /// </code> | |||
285 | /// </example> | |||
286 | public class ZipFile : IEnumerable, IDisposable | |||
287 | { | |||
288 | #region KeyHandling | |||
289 | | |||
290 | /// <summary> | |||
291 | /// Delegate for handling keys/password setting during compresion/decompression. | |||
292 | /// </summary> | |||
293 | public delegate void KeysRequiredEventHandler( | |||
294 | object sender, | |||
295 | KeysRequiredEventArgs e | |||
296 | ); | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Event handler for handling encryption keys. | |||
300 | /// </summary> | |||
301 | public KeysRequiredEventHandler KeysRequired; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Handles getting of encryption keys when required. | |||
305 | /// </summary> | |||
306 | /// <param name="fileName">The file for which encryption keys are required.</param> | |||
307 | void OnKeysRequired(string fileName) | |||
308 | { | |||
309 | if (KeysRequired != null) { | |||
310 | var krea = new KeysRequiredEventArgs(fileName, key); | |||
311 | KeysRequired(this, krea); | |||
312 | key = krea.Key; | |||
313 | } | |||
314 | } | |||
315 | | |||
316 | /// <summary> | |||
317 | /// Get/set the encryption key value. | |||
318 | /// </summary> | |||
319 | byte[] Key { | |||
320 | get { return key; } | |||
321 | set { key = value; } | |||
322 | } | |||
323 | | |||
324 | /// <summary> | |||
325 | /// Password to be used for encrypting/decrypting files. | |||
326 | /// </summary> | |||
327 | /// <remarks>Set to null if no password is required.</remarks> | |||
328 | public string Password { | |||
329 | set { | |||
330 | if (string.IsNullOrEmpty(value)) { | |||
331 | key = null; | |||
332 | } else { | |||
333 | rawPassword_ = value; | |||
334 | key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); | |||
335 | } | |||
336 | } | |||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Get a value indicating wether encryption keys are currently available. | |||
341 | /// </summary> | |||
342 | bool HaveKeys { | |||
343 | get { return key != null; } | |||
344 | } | |||
345 | #endregion | |||
346 | | |||
347 | #region Constructors | |||
348 | /// <summary> | |||
349 | /// Opens a Zip file with the given name for reading. | |||
350 | /// </summary> | |||
351 | /// <param name="name">The name of the file to open.</param> | |||
352 | /// <exception cref="ArgumentNullException">The argument supplied is null.</exception> | |||
353 | /// <exception cref="IOException"> | |||
354 | /// An i/o error occurs | |||
355 | /// </exception> | |||
356 | /// <exception cref="ZipException"> | |||
357 | /// The file doesn't contain a valid zip archive. | |||
358 | /// </exception> | |||
359 | public ZipFile(string name) | |||
360 | { | |||
361 | if (name == null) { | |||
362 | throw new ArgumentNullException(nameof(name)); | |||
363 | } | |||
364 | | |||
365 | name_ = name; | |||
366 | | |||
367 | baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
368 | isStreamOwner = true; | |||
369 | | |||
370 | try { | |||
371 | ReadEntries(); | |||
372 | } catch { | |||
373 | DisposeInternal(true); | |||
374 | throw; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Opens a Zip file reading the given <see cref="FileStream"/>. | |||
380 | /// </summary> | |||
381 | /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> | |||
382 | /// <exception cref="ArgumentNullException">The supplied argument is null.</exception> | |||
383 | /// <exception cref="IOException"> | |||
384 | /// An i/o error occurs. | |||
385 | /// </exception> | |||
386 | /// <exception cref="ZipException"> | |||
387 | /// The file doesn't contain a valid zip archive. | |||
388 | /// </exception> | |||
389 | public ZipFile(FileStream file) | |||
390 | { | |||
391 | if (file == null) { | |||
392 | throw new ArgumentNullException(nameof(file)); | |||
393 | } | |||
394 | | |||
395 | if (!file.CanSeek) { | |||
396 | throw new ArgumentException("Stream is not seekable", nameof(file)); | |||
397 | } | |||
398 | | |||
399 | baseStream_ = file; | |||
400 | name_ = file.Name; | |||
401 | isStreamOwner = true; | |||
402 | | |||
403 | try { | |||
404 | ReadEntries(); | |||
405 | } catch { | |||
406 | DisposeInternal(true); | |||
407 | throw; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Opens a Zip file reading the given <see cref="Stream"/>. | |||
413 | /// </summary> | |||
414 | /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> | |||
415 | /// <exception cref="IOException"> | |||
416 | /// An i/o error occurs | |||
417 | /// </exception> | |||
418 | /// <exception cref="ZipException"> | |||
419 | /// The stream doesn't contain a valid zip archive.<br/> | |||
420 | /// </exception> | |||
421 | /// <exception cref="ArgumentException"> | |||
422 | /// The <see cref="Stream">stream</see> doesnt support seeking. | |||
423 | /// </exception> | |||
424 | /// <exception cref="ArgumentNullException"> | |||
425 | /// The <see cref="Stream">stream</see> argument is null. | |||
426 | /// </exception> | |||
427 | public ZipFile(Stream stream) | |||
428 | { | |||
429 | if (stream == null) { | |||
430 | throw new ArgumentNullException(nameof(stream)); | |||
431 | } | |||
432 | | |||
433 | if (!stream.CanSeek) { | |||
434 | throw new ArgumentException("Stream is not seekable", nameof(stream)); | |||
435 | } | |||
436 | | |||
437 | baseStream_ = stream; | |||
438 | isStreamOwner = true; | |||
439 | | |||
440 | if (baseStream_.Length > 0) { | |||
441 | try { | |||
442 | ReadEntries(); | |||
443 | } catch { | |||
444 | DisposeInternal(true); | |||
445 | throw; | |||
446 | } | |||
447 | } else { | |||
448 | entries_ = new ZipEntry[0]; | |||
449 | isNewArchive_ = true; | |||
450 | } | |||
451 | } | |||
452 | | |||
453 | /// <summary> | |||
454 | /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. | |||
455 | /// </summary> | |||
456 | internal ZipFile() | |||
457 | { | |||
458 | entries_ = new ZipEntry[0]; | |||
459 | isNewArchive_ = true; | |||
460 | } | |||
461 | | |||
462 | #endregion | |||
463 | | |||
464 | #region Destructors and Closing | |||
465 | /// <summary> | |||
466 | /// Finalize this instance. | |||
467 | /// </summary> | |||
468 | ~ZipFile() | |||
469 | { | |||
470 | Dispose(false); | |||
471 | } | |||
472 | | |||
473 | /// <summary> | |||
474 | /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying | |||
475 | /// Once closed, no further instance methods should be called. | |||
476 | /// </summary> | |||
477 | /// <exception cref="System.IO.IOException"> | |||
478 | /// An i/o error occurs. | |||
479 | /// </exception> | |||
480 | public void Close() | |||
481 | { | |||
482 | DisposeInternal(true); | |||
483 | GC.SuppressFinalize(this); | |||
484 | } | |||
485 | | |||
486 | #endregion | |||
487 | | |||
488 | #region Creators | |||
489 | /// <summary> | |||
490 | /// Create a new <see cref="ZipFile"/> whose data will be stored in a file. | |||
491 | /// </summary> | |||
492 | /// <param name="fileName">The name of the archive to create.</param> | |||
493 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
494 | /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception> | |||
495 | public static ZipFile Create(string fileName) | |||
496 | { | |||
497 | if (fileName == null) { | |||
498 | throw new ArgumentNullException(nameof(fileName)); | |||
499 | } | |||
500 | | |||
501 | FileStream fs = File.Create(fileName); | |||
502 | | |||
503 | var result = new ZipFile(); | |||
504 | result.name_ = fileName; | |||
505 | result.baseStream_ = fs; | |||
506 | result.isStreamOwner = true; | |||
507 | return result; | |||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. | |||
512 | /// </summary> | |||
513 | /// <param name="outStream">The stream providing data storage.</param> | |||
514 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
515 | /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception> | |||
516 | /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception> | |||
517 | public static ZipFile Create(Stream outStream) | |||
518 | { | |||
519 | if (outStream == null) { | |||
520 | throw new ArgumentNullException(nameof(outStream)); | |||
521 | } | |||
522 | | |||
523 | if (!outStream.CanWrite) { | |||
524 | throw new ArgumentException("Stream is not writeable", nameof(outStream)); | |||
525 | } | |||
526 | | |||
527 | if (!outStream.CanSeek) { | |||
528 | throw new ArgumentException("Stream is not seekable", nameof(outStream)); | |||
529 | } | |||
530 | | |||
531 | var result = new ZipFile(); | |||
532 | result.baseStream_ = outStream; | |||
533 | return result; | |||
534 | } | |||
535 | | |||
536 | #endregion | |||
537 | | |||
538 | #region Properties | |||
539 | /// <summary> | |||
540 | /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. | |||
541 | /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. | |||
542 | /// </summary> | |||
543 | /// <remarks> | |||
544 | /// The default value is true in all cases. | |||
545 | /// </remarks> | |||
546 | public bool IsStreamOwner { | |||
547 | get { return isStreamOwner; } | |||
548 | set { isStreamOwner = value; } | |||
549 | } | |||
550 | | |||
551 | /// <summary> | |||
552 | /// Get a value indicating wether | |||
553 | /// this archive is embedded in another file or not. | |||
554 | /// </summary> | |||
555 | public bool IsEmbeddedArchive { | |||
556 | // Not strictly correct in all circumstances currently | |||
557 | get { return offsetOfFirstEntry > 0; } | |||
558 | } | |||
559 | | |||
560 | /// <summary> | |||
561 | /// Get a value indicating that this archive is a new one. | |||
562 | /// </summary> | |||
563 | public bool IsNewArchive { | |||
564 | get { return isNewArchive_; } | |||
565 | } | |||
566 | | |||
567 | /// <summary> | |||
568 | /// Gets the comment for the zip file. | |||
569 | /// </summary> | |||
570 | public string ZipFileComment { | |||
571 | get { return comment_; } | |||
572 | } | |||
573 | | |||
574 | /// <summary> | |||
575 | /// Gets the name of this zip file. | |||
576 | /// </summary> | |||
577 | public string Name { | |||
578 | get { return name_; } | |||
579 | } | |||
580 | | |||
581 | /// <summary> | |||
582 | /// Gets the number of entries in this zip file. | |||
583 | /// </summary> | |||
584 | /// <exception cref="InvalidOperationException"> | |||
585 | /// The Zip file has been closed. | |||
586 | /// </exception> | |||
587 | [Obsolete("Use the Count property instead")] | |||
588 | public int Size { | |||
589 | get { | |||
590 | return entries_.Length; | |||
591 | } | |||
592 | } | |||
593 | | |||
594 | /// <summary> | |||
595 | /// Get the number of entries contained in this <see cref="ZipFile"/>. | |||
596 | /// </summary> | |||
597 | public long Count { | |||
598 | get { | |||
599 | return entries_.Length; | |||
600 | } | |||
601 | } | |||
602 | | |||
603 | /// <summary> | |||
604 | /// Indexer property for ZipEntries | |||
605 | /// </summary> | |||
606 | [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] | |||
607 | public ZipEntry this[int index] { | |||
608 | get { | |||
609 | return (ZipEntry)entries_[index].Clone(); | |||
610 | } | |||
611 | } | |||
612 | | |||
613 | #endregion | |||
614 | | |||
615 | #region Input Handling | |||
616 | /// <summary> | |||
617 | /// Gets an enumerator for the Zip entries in this Zip file. | |||
618 | /// </summary> | |||
619 | /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> | |||
620 | /// <exception cref="ObjectDisposedException"> | |||
621 | /// The Zip file has been closed. | |||
622 | /// </exception> | |||
623 | public IEnumerator GetEnumerator() | |||
624 | { | |||
625 | if (isDisposed_) { | |||
626 | throw new ObjectDisposedException("ZipFile"); | |||
627 | } | |||
628 | | |||
629 | return new ZipEntryEnumerator(entries_); | |||
630 | } | |||
631 | | |||
632 | /// <summary> | |||
633 | /// Return the index of the entry with a matching name | |||
634 | /// </summary> | |||
635 | /// <param name="name">Entry name to find</param> | |||
636 | /// <param name="ignoreCase">If true the comparison is case insensitive</param> | |||
637 | /// <returns>The index position of the matching entry or -1 if not found</returns> | |||
638 | /// <exception cref="ObjectDisposedException"> | |||
639 | /// The Zip file has been closed. | |||
640 | /// </exception> | |||
641 | public int FindEntry(string name, bool ignoreCase) | |||
642 | { | |||
643 | if (isDisposed_) { | |||
644 | throw new ObjectDisposedException("ZipFile"); | |||
645 | } | |||
646 | | |||
647 | // TODO: This will be slow as the next ice age for huge archives! | |||
648 | for (int i = 0; i < entries_.Length; i++) { | |||
649 | if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) { | |||
650 | return i; | |||
651 | } | |||
652 | } | |||
653 | return -1; | |||
654 | } | |||
655 | | |||
656 | /// <summary> | |||
657 | /// Searches for a zip entry in this archive with the given name. | |||
658 | /// String comparisons are case insensitive | |||
659 | /// </summary> | |||
660 | /// <param name="name"> | |||
661 | /// The name to find. May contain directory components separated by slashes ('/'). | |||
662 | /// </param> | |||
663 | /// <returns> | |||
664 | /// A clone of the zip entry, or null if no entry with that name exists. | |||
665 | /// </returns> | |||
666 | /// <exception cref="ObjectDisposedException"> | |||
667 | /// The Zip file has been closed. | |||
668 | /// </exception> | |||
669 | public ZipEntry GetEntry(string name) | |||
670 | { | |||
671 | if (isDisposed_) { | |||
672 | throw new ObjectDisposedException("ZipFile"); | |||
673 | } | |||
674 | | |||
675 | int index = FindEntry(name, true); | |||
676 | return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; | |||
677 | } | |||
678 | | |||
679 | /// <summary> | |||
680 | /// Gets an input stream for reading the given zip entry data in an uncompressed form. | |||
681 | /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). | |||
682 | /// </summary> | |||
683 | /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> | |||
684 | /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> | |||
685 | /// <exception cref="ObjectDisposedException"> | |||
686 | /// The ZipFile has already been closed | |||
687 | /// </exception> | |||
688 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
689 | /// The compression method for the entry is unknown | |||
690 | /// </exception> | |||
691 | /// <exception cref="IndexOutOfRangeException"> | |||
692 | /// The entry is not found in the ZipFile | |||
693 | /// </exception> | |||
694 | public Stream GetInputStream(ZipEntry entry) | |||
695 | { | |||
696 | if (entry == null) { | |||
697 | throw new ArgumentNullException(nameof(entry)); | |||
698 | } | |||
699 | | |||
700 | if (isDisposed_) { | |||
701 | throw new ObjectDisposedException("ZipFile"); | |||
702 | } | |||
703 | | |||
704 | long index = entry.ZipFileIndex; | |||
705 | if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { | |||
706 | index = FindEntry(entry.Name, true); | |||
707 | if (index < 0) { | |||
708 | throw new ZipException("Entry cannot be found"); | |||
709 | } | |||
710 | } | |||
711 | return GetInputStream(index); | |||
712 | } | |||
713 | | |||
714 | /// <summary> | |||
715 | /// Creates an input stream reading a zip entry | |||
716 | /// </summary> | |||
717 | /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> | |||
718 | /// <returns> | |||
719 | /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> | |||
720 | /// </returns> | |||
721 | /// <exception cref="ObjectDisposedException"> | |||
722 | /// The ZipFile has already been closed | |||
723 | /// </exception> | |||
724 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
725 | /// The compression method for the entry is unknown | |||
726 | /// </exception> | |||
727 | /// <exception cref="IndexOutOfRangeException"> | |||
728 | /// The entry is not found in the ZipFile | |||
729 | /// </exception> | |||
730 | public Stream GetInputStream(long entryIndex) | |||
731 | { | |||
732 | if (isDisposed_) { | |||
733 | throw new ObjectDisposedException("ZipFile"); | |||
734 | } | |||
735 | | |||
736 | long start = LocateEntry(entries_[entryIndex]); | |||
737 | CompressionMethod method = entries_[entryIndex].CompressionMethod; | |||
738 | Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); | |||
739 | | |||
740 | if (entries_[entryIndex].IsCrypted == true) { | |||
741 | result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); | |||
742 | if (result == null) { | |||
743 | throw new ZipException("Unable to decrypt this entry"); | |||
744 | } | |||
745 | } | |||
746 | | |||
747 | switch (method) { | |||
748 | case CompressionMethod.Stored: | |||
749 | // read as is. | |||
750 | break; | |||
751 | | |||
752 | case CompressionMethod.Deflated: | |||
753 | // No need to worry about ownership and closing as underlying stream close does nothing. | |||
754 | result = new InflaterInputStream(result, new Inflater(true)); | |||
755 | break; | |||
756 | | |||
757 | default: | |||
758 | throw new ZipException("Unsupported compression method " + method); | |||
759 | } | |||
760 | | |||
761 | return result; | |||
762 | } | |||
763 | | |||
764 | #endregion | |||
765 | | |||
766 | #region Archive Testing | |||
767 | /// <summary> | |||
768 | /// Test an archive for integrity/validity | |||
769 | /// </summary> | |||
770 | /// <param name="testData">Perform low level data Crc check</param> | |||
771 | /// <returns>true if all tests pass, false otherwise</returns> | |||
772 | /// <remarks>Testing will terminate on the first error found.</remarks> | |||
773 | public bool TestArchive(bool testData) | |||
774 | { | |||
775 | return TestArchive(testData, TestStrategy.FindFirstError, null); | |||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Test an archive for integrity/validity | |||
780 | /// </summary> | |||
781 | /// <param name="testData">Perform low level data Crc check</param> | |||
782 | /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> | |||
783 | /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> | |||
784 | /// <returns>true if all tests pass, false otherwise</returns> | |||
785 | /// <exception cref="ObjectDisposedException">The object has already been closed.</exception> | |||
786 | public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) | |||
787 | { | |||
788 | if (isDisposed_) { | |||
789 | throw new ObjectDisposedException("ZipFile"); | |||
790 | } | |||
791 | | |||
792 | var status = new TestStatus(this); | |||
793 | | |||
794 | if (resultHandler != null) { | |||
795 | resultHandler(status, null); | |||
796 | } | |||
797 | | |||
798 | HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; | |||
799 | | |||
800 | bool testing = true; | |||
801 | | |||
802 | try { | |||
803 | int entryIndex = 0; | |||
804 | | |||
805 | while (testing && (entryIndex < Count)) { | |||
806 | if (resultHandler != null) { | |||
807 | status.SetEntry(this[entryIndex]); | |||
808 | status.SetOperation(TestOperation.EntryHeader); | |||
809 | resultHandler(status, null); | |||
810 | } | |||
811 | | |||
812 | try { | |||
813 | TestLocalHeader(this[entryIndex], test); | |||
814 | } catch (ZipException ex) { | |||
815 | status.AddError(); | |||
816 | | |||
817 | if (resultHandler != null) { | |||
818 | resultHandler(status, | |||
819 | string.Format("Exception during test - '{0}'", ex.Message)); | |||
820 | } | |||
821 | | |||
822 | testing &= strategy != TestStrategy.FindFirstError; | |||
823 | } | |||
824 | | |||
825 | if (testing && testData && this[entryIndex].IsFile) { | |||
826 | if (resultHandler != null) { | |||
827 | status.SetOperation(TestOperation.EntryData); | |||
828 | resultHandler(status, null); | |||
829 | } | |||
830 | | |||
831 | var crc = new Crc32(); | |||
832 | | |||
833 | using (Stream entryStream = this.GetInputStream(this[entryIndex])) { | |||
834 | | |||
835 | byte[] buffer = new byte[4096]; | |||
836 | long totalBytes = 0; | |||
837 | int bytesRead; | |||
838 | while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { | |||
839 | crc.Update(buffer, 0, bytesRead); | |||
840 | | |||
841 | if (resultHandler != null) { | |||
842 | totalBytes += bytesRead; | |||
843 | status.SetBytesTested(totalBytes); | |||
844 | resultHandler(status, null); | |||
845 | } | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | if (this[entryIndex].Crc != crc.Value) { | |||
850 | status.AddError(); | |||
851 | | |||
852 | if (resultHandler != null) { | |||
853 | resultHandler(status, "CRC mismatch"); | |||
854 | } | |||
855 | | |||
856 | testing &= strategy != TestStrategy.FindFirstError; | |||
857 | } | |||
858 | | |||
859 | if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
860 | var helper = new ZipHelperStream(baseStream_); | |||
861 | var data = new DescriptorData(); | |||
862 | helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); | |||
863 | if (this[entryIndex].Crc != data.Crc) { | |||
864 | status.AddError(); | |||
865 | } | |||
866 | | |||
867 | if (this[entryIndex].CompressedSize != data.CompressedSize) { | |||
868 | status.AddError(); | |||
869 | } | |||
870 | | |||
871 | if (this[entryIndex].Size != data.Size) { | |||
872 | status.AddError(); | |||
873 | } | |||
874 | } | |||
875 | } | |||
876 | | |||
877 | if (resultHandler != null) { | |||
878 | status.SetOperation(TestOperation.EntryComplete); | |||
879 | resultHandler(status, null); | |||
880 | } | |||
881 | | |||
882 | entryIndex += 1; | |||
883 | } | |||
884 | | |||
885 | if (resultHandler != null) { | |||
886 | status.SetOperation(TestOperation.MiscellaneousTests); | |||
887 | resultHandler(status, null); | |||
888 | } | |||
889 | | |||
890 | // TODO: the 'Corrina Johns' test where local headers are missing from | |||
891 | // the central directory. They are therefore invisible to many archivers. | |||
892 | } catch (Exception ex) { | |||
893 | status.AddError(); | |||
894 | | |||
895 | if (resultHandler != null) { | |||
896 | resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); | |||
897 | } | |||
898 | } | |||
899 | | |||
900 | if (resultHandler != null) { | |||
901 | status.SetOperation(TestOperation.Complete); | |||
902 | status.SetEntry(null); | |||
903 | resultHandler(status, null); | |||
904 | } | |||
905 | | |||
906 | return (status.ErrorCount == 0); | |||
907 | } | |||
908 | | |||
909 | [Flags] | |||
910 | enum HeaderTest | |||
911 | { | |||
912 | Extract = 0x01, // Check that this header represents an entry whose data can be extracted | |||
913 | Header = 0x02, // Check that this header contents are valid | |||
914 | } | |||
915 | | |||
916 | /// <summary> | |||
917 | /// Test a local header against that provided from the central directory | |||
918 | /// </summary> | |||
919 | /// <param name="entry"> | |||
920 | /// The entry to test against | |||
921 | /// </param> | |||
922 | /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> | |||
923 | /// <returns>The offset of the entries data in the file</returns> | |||
924 | long TestLocalHeader(ZipEntry entry, HeaderTest tests) | |||
925 | { | |||
926 | lock (baseStream_) { | |||
927 | bool testHeader = (tests & HeaderTest.Header) != 0; | |||
928 | bool testData = (tests & HeaderTest.Extract) != 0; | |||
929 | | |||
930 | baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); | |||
931 | if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { | |||
932 | throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset) | |||
933 | } | |||
934 | | |||
935 | var extractVersion = (short)(ReadLEUshort() & 0x00ff); | |||
936 | var localFlags = (short)ReadLEUshort(); | |||
937 | var compressionMethod = (short)ReadLEUshort(); | |||
938 | var fileTime = (short)ReadLEUshort(); | |||
939 | var fileDate = (short)ReadLEUshort(); | |||
940 | uint crcValue = ReadLEUint(); | |||
941 | long compressedSize = ReadLEUint(); | |||
942 | long size = ReadLEUint(); | |||
943 | int storedNameLength = ReadLEUshort(); | |||
944 | int extraDataLength = ReadLEUshort(); | |||
945 | | |||
946 | byte[] nameData = new byte[storedNameLength]; | |||
947 | StreamUtils.ReadFully(baseStream_, nameData); | |||
948 | | |||
949 | byte[] extraData = new byte[extraDataLength]; | |||
950 | StreamUtils.ReadFully(baseStream_, extraData); | |||
951 | | |||
952 | var localExtraData = new ZipExtraData(extraData); | |||
953 | | |||
954 | // Extra data / zip64 checks | |||
955 | if (localExtraData.Find(1)) { | |||
956 | // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 | |||
957 | // and size or compressedSize = MaxValue, due to rogue creators. | |||
958 | | |||
959 | size = localExtraData.ReadLong(); | |||
960 | compressedSize = localExtraData.ReadLong(); | |||
961 | | |||
962 | if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
963 | // These may be valid if patched later | |||
964 | if ((size != -1) && (size != entry.Size)) { | |||
965 | throw new ZipException("Size invalid for descriptor"); | |||
966 | } | |||
967 | | |||
968 | if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) { | |||
969 | throw new ZipException("Compressed size invalid for descriptor"); | |||
970 | } | |||
971 | } | |||
972 | } else { | |||
973 | // No zip64 extra data but entry requires it. | |||
974 | if ((extractVersion >= ZipConstants.VersionZip64) && | |||
975 | (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) { | |||
976 | throw new ZipException("Required Zip64 extended information missing"); | |||
977 | } | |||
978 | } | |||
979 | | |||
980 | if (testData) { | |||
981 | if (entry.IsFile) { | |||
982 | if (!entry.IsCompressionMethodSupported()) { | |||
983 | throw new ZipException("Compression method not supported"); | |||
984 | } | |||
985 | | |||
986 | if ((extractVersion > ZipConstants.VersionMadeBy) | |||
987 | || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { | |||
988 | throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract | |||
989 | } | |||
990 | | |||
991 | if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance | |||
992 | throw new ZipException("The library does not support the zip version required to extract this entry"); | |||
993 | } | |||
994 | } | |||
995 | } | |||
996 | | |||
997 | if (testHeader) { | |||
998 | if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. | |||
999 | (extractVersion != 10) && | |||
1000 | (extractVersion != 11) && | |||
1001 | (extractVersion != 20) && | |||
1002 | (extractVersion != 21) && | |||
1003 | (extractVersion != 25) && | |||
1004 | (extractVersion != 27) && | |||
1005 | (extractVersion != 45) && | |||
1006 | (extractVersion != 46) && | |||
1007 | (extractVersion != 50) && | |||
1008 | (extractVersion != 51) && | |||
1009 | (extractVersion != 52) && | |||
1010 | (extractVersion != 61) && | |||
1011 | (extractVersion != 62) && | |||
1012 | (extractVersion != 63) | |||
1013 | ) { | |||
1014 | throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersi | |||
1015 | } | |||
1016 | | |||
1017 | // Local entry flags dont have reserved bit set on. | |||
1018 | if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R | |||
1019 | throw new ZipException("Reserved bit flags cannot be set."); | |||
1020 | } | |||
1021 | | |||
1022 | // Encryption requires extract version >= 20 | |||
1023 | if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { | |||
1024 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0}) | |||
1025 | } | |||
1026 | | |||
1027 | // Strong encryption requires encryption flag to be set and extract version >= 50. | |||
1028 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1029 | if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) { | |||
1030 | throw new ZipException("Strong encryption flag set but encryption flag is not set"); | |||
1031 | } | |||
1032 | | |||
1033 | if (extractVersion < 50) { | |||
1034 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0 | |||
1035 | } | |||
1036 | } | |||
1037 | | |||
1038 | // Patched entries require extract version >= 27 | |||
1039 | if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { | |||
1040 | throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); | |||
1041 | } | |||
1042 | | |||
1043 | // Central header flags match local entry flags. | |||
1044 | if (localFlags != entry.Flags) { | |||
1045 | throw new ZipException("Central header/local header flags mismatch"); | |||
1046 | } | |||
1047 | | |||
1048 | // Central header compression method matches local entry | |||
1049 | if (entry.CompressionMethod != (CompressionMethod)compressionMethod) { | |||
1050 | throw new ZipException("Central header/local header compression method mismatch"); | |||
1051 | } | |||
1052 | | |||
1053 | if (entry.Version != extractVersion) { | |||
1054 | throw new ZipException("Extract version mismatch"); | |||
1055 | } | |||
1056 | | |||
1057 | // Strong encryption and extract version match | |||
1058 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1059 | if (extractVersion < 62) { | |||
1060 | throw new ZipException("Strong encryption flag set but version not high enough"); | |||
1061 | } | |||
1062 | } | |||
1063 | | |||
1064 | if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) { | |||
1065 | if ((fileTime != 0) || (fileDate != 0)) { | |||
1066 | throw new ZipException("Header masked set but date/time values non-zero"); | |||
1067 | } | |||
1068 | } | |||
1069 | | |||
1070 | if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) { | |||
1071 | if (crcValue != (uint)entry.Crc) { | |||
1072 | throw new ZipException("Central header/local header crc mismatch"); | |||
1073 | } | |||
1074 | } | |||
1075 | | |||
1076 | // Crc valid for empty entry. | |||
1077 | // This will also apply to streamed entries where size isnt known and the header cant be patched | |||
1078 | if ((size == 0) && (compressedSize == 0)) { | |||
1079 | if (crcValue != 0) { | |||
1080 | throw new ZipException("Invalid CRC for empty entry"); | |||
1081 | } | |||
1082 | } | |||
1083 | | |||
1084 | // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS str | |||
1085 | // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably | |||
1086 | if (entry.Name.Length > storedNameLength) { | |||
1087 | throw new ZipException("File name length mismatch"); | |||
1088 | } | |||
1089 | | |||
1090 | // Name data has already been read convert it and compare. | |||
1091 | string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); | |||
1092 | | |||
1093 | // Central directory and local entry name match | |||
1094 | if (localName != entry.Name) { | |||
1095 | throw new ZipException("Central header and local header file name mismatch"); | |||
1096 | } | |||
1097 | | |||
1098 | // Directories have zero actual size but can have compressed size | |||
1099 | if (entry.IsDirectory) { | |||
1100 | if (size > 0) { | |||
1101 | throw new ZipException("Directory cannot have size"); | |||
1102 | } | |||
1103 | | |||
1104 | // There may be other cases where the compressed size can be greater than this? | |||
1105 | // If so until details are known we will be strict. | |||
1106 | if (entry.IsCrypted) { | |||
1107 | if (compressedSize > ZipConstants.CryptoHeaderSize + 2) { | |||
1108 | throw new ZipException("Directory compressed size invalid"); | |||
1109 | } | |||
1110 | } else if (compressedSize > 2) { | |||
1111 | // When not compressed the directory size can validly be 2 bytes | |||
1112 | // if the true size wasnt known when data was originally being written. | |||
1113 | // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes | |||
1114 | throw new ZipException("Directory compressed size invalid"); | |||
1115 | } | |||
1116 | } | |||
1117 | | |||
1118 | if (!ZipNameTransform.IsValidName(localName, true)) { | |||
1119 | throw new ZipException("Name is invalid"); | |||
1120 | } | |||
1121 | } | |||
1122 | | |||
1123 | // Tests that apply to both data and header. | |||
1124 | | |||
1125 | // Size can be verified only if it is known in the local header. | |||
1126 | // it will always be known in the central header. | |||
1127 | if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || | |||
1128 | ((size > 0 || compressedSize > 0) && entry.Size > 0)) { | |||
1129 | | |||
1130 | if ((size != 0) | |||
1131 | && (size != entry.Size)) { | |||
1132 | throw new ZipException( | |||
1133 | string.Format("Size mismatch between central header({0}) and local header({1})", | |||
1134 | entry.Size, size)); | |||
1135 | } | |||
1136 | | |||
1137 | if ((compressedSize != 0) | |||
1138 | && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) { | |||
1139 | throw new ZipException( | |||
1140 | string.Format("Compressed size mismatch between central header({0}) and local header({1})", | |||
1141 | entry.CompressedSize, compressedSize)); | |||
1142 | } | |||
1143 | } | |||
1144 | | |||
1145 | int extraLength = storedNameLength + extraDataLength; | |||
1146 | return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; | |||
1147 | } | |||
1148 | } | |||
1149 | | |||
1150 | #endregion | |||
1151 | | |||
1152 | #region Updating | |||
1153 | | |||
1154 | const int DefaultBufferSize = 4096; | |||
1155 | | |||
1156 | /// <summary> | |||
1157 | /// The kind of update to apply. | |||
1158 | /// </summary> | |||
1159 | enum UpdateCommand | |||
1160 | { | |||
1161 | Copy, // Copy original file contents. | |||
1162 | Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. | |||
1163 | Add, // Add a new file to the archive. | |||
1164 | } | |||
1165 | | |||
1166 | #region Properties | |||
1167 | /// <summary> | |||
1168 | /// Get / set the <see cref="INameTransform"/> to apply to names when updating. | |||
1169 | /// </summary> | |||
1170 | public INameTransform NameTransform { | |||
1171 | get { | |||
1172 | return updateEntryFactory_.NameTransform; | |||
1173 | } | |||
1174 | | |||
1175 | set { | |||
1176 | updateEntryFactory_.NameTransform = value; | |||
1177 | } | |||
1178 | } | |||
1179 | | |||
1180 | /// <summary> | |||
1181 | /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values | |||
1182 | /// during updates. | |||
1183 | /// </summary> | |||
1184 | public IEntryFactory EntryFactory { | |||
1185 | get { | |||
1186 | return updateEntryFactory_; | |||
1187 | } | |||
1188 | | |||
1189 | set { | |||
1190 | if (value == null) { | |||
1191 | updateEntryFactory_ = new ZipEntryFactory(); | |||
1192 | } else { | |||
1193 | updateEntryFactory_ = value; | |||
1194 | } | |||
1195 | } | |||
1196 | } | |||
1197 | | |||
1198 | /// <summary> | |||
1199 | /// Get /set the buffer size to be used when updating this zip file. | |||
1200 | /// </summary> | |||
1201 | public int BufferSize { | |||
1202 | get { return bufferSize_; } | |||
1203 | set { | |||
1204 | if (value < 1024) { | |||
1205 | throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); | |||
1206 | } | |||
1207 | | |||
1208 | if (bufferSize_ != value) { | |||
1209 | bufferSize_ = value; | |||
1210 | copyBuffer_ = null; | |||
1211 | } | |||
1212 | } | |||
1213 | } | |||
1214 | | |||
1215 | /// <summary> | |||
1216 | /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. | |||
1217 | /// </summary> | |||
1218 | public bool IsUpdating { | |||
1219 | get { return updates_ != null; } | |||
1220 | } | |||
1221 | | |||
1222 | /// <summary> | |||
1223 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
1224 | /// </summary> | |||
1225 | public UseZip64 UseZip64 { | |||
1226 | get { return useZip64_; } | |||
1227 | set { useZip64_ = value; } | |||
1228 | } | |||
1229 | | |||
1230 | #endregion | |||
1231 | | |||
1232 | #region Immediate updating | |||
1233 | // TBD: Direct form of updating | |||
1234 | // | |||
1235 | // public void Update(IEntryMatcher deleteMatcher) | |||
1236 | // { | |||
1237 | // } | |||
1238 | // | |||
1239 | // public void Update(IScanner addScanner) | |||
1240 | // { | |||
1241 | // } | |||
1242 | #endregion | |||
1243 | | |||
1244 | #region Deferred Updating | |||
1245 | /// <summary> | |||
1246 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1247 | /// </summary> | |||
1248 | /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</p | |||
1249 | /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param | |||
1250 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1251 | /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception> | |||
1252 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1253 | public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) | |||
1254 | { | |||
1255 | if (archiveStorage == null) { | |||
1256 | throw new ArgumentNullException(nameof(archiveStorage)); | |||
1257 | } | |||
1258 | | |||
1259 | if (dataSource == null) { | |||
1260 | throw new ArgumentNullException(nameof(dataSource)); | |||
1261 | } | |||
1262 | | |||
1263 | if (isDisposed_) { | |||
1264 | throw new ObjectDisposedException("ZipFile"); | |||
1265 | } | |||
1266 | | |||
1267 | if (IsEmbeddedArchive) { | |||
1268 | throw new ZipException("Cannot update embedded/SFX archives"); | |||
1269 | } | |||
1270 | | |||
1271 | archiveStorage_ = archiveStorage; | |||
1272 | updateDataSource_ = dataSource; | |||
1273 | | |||
1274 | // NOTE: the baseStream_ may not currently support writing or seeking. | |||
1275 | | |||
1276 | updateIndex_ = new Hashtable(); | |||
1277 | | |||
1278 | updates_ = new ArrayList(entries_.Length); | |||
1279 | foreach (ZipEntry entry in entries_) { | |||
1280 | int index = updates_.Add(new ZipUpdate(entry)); | |||
1281 | updateIndex_.Add(entry.Name, index); | |||
1282 | } | |||
1283 | | |||
1284 | // We must sort by offset before using offset's calculated sizes | |||
1285 | updates_.Sort(new UpdateComparer()); | |||
1286 | | |||
1287 | int idx = 0; | |||
1288 | foreach (ZipUpdate update in updates_) { | |||
1289 | //If last entry, there is no next entry offset to use | |||
1290 | if (idx == updates_.Count - 1) | |||
1291 | break; | |||
1292 | | |||
1293 | update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; | |||
1294 | idx++; | |||
1295 | } | |||
1296 | updateCount_ = updates_.Count; | |||
1297 | | |||
1298 | contentsEdited_ = false; | |||
1299 | commentEdited_ = false; | |||
1300 | newComment_ = null; | |||
1301 | } | |||
1302 | | |||
1303 | /// <summary> | |||
1304 | /// Begin updating to this <see cref="ZipFile"/> archive. | |||
1305 | /// </summary> | |||
1306 | /// <param name="archiveStorage">The storage to use during the update.</param> | |||
1307 | public void BeginUpdate(IArchiveStorage archiveStorage) | |||
1308 | { | |||
1309 | BeginUpdate(archiveStorage, new DynamicDiskDataSource()); | |||
1310 | } | |||
1311 | | |||
1312 | /// <summary> | |||
1313 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1314 | /// </summary> | |||
1315 | /// <seealso cref="BeginUpdate(IArchiveStorage)"/> | |||
1316 | /// <seealso cref="CommitUpdate"></seealso> | |||
1317 | /// <seealso cref="AbortUpdate"></seealso> | |||
1318 | public void BeginUpdate() | |||
1319 | { | |||
1320 | if (Name == null) { | |||
1321 | BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); | |||
1322 | } else { | |||
1323 | BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); | |||
1324 | } | |||
1325 | } | |||
1326 | | |||
1327 | /// <summary> | |||
1328 | /// Commit current updates, updating this archive. | |||
1329 | /// </summary> | |||
1330 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1331 | /// <seealso cref="AbortUpdate"></seealso> | |||
1332 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1333 | public void CommitUpdate() | |||
1334 | { | |||
1335 | if (isDisposed_) { | |||
1336 | throw new ObjectDisposedException("ZipFile"); | |||
1337 | } | |||
1338 | | |||
1339 | CheckUpdating(); | |||
1340 | | |||
1341 | try { | |||
1342 | updateIndex_.Clear(); | |||
1343 | updateIndex_ = null; | |||
1344 | | |||
1345 | if (contentsEdited_) { | |||
1346 | RunUpdates(); | |||
1347 | } else if (commentEdited_) { | |||
1348 | UpdateCommentOnly(); | |||
1349 | } else { | |||
1350 | // Create an empty archive if none existed originally. | |||
1351 | if (entries_.Length == 0) { | |||
1352 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
1353 | using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) { | |||
1354 | zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); | |||
1355 | } | |||
1356 | } | |||
1357 | } | |||
1358 | | |||
1359 | } finally { | |||
1360 | PostUpdateCleanup(); | |||
1361 | } | |||
1362 | } | |||
1363 | | |||
1364 | /// <summary> | |||
1365 | /// Abort updating leaving the archive unchanged. | |||
1366 | /// </summary> | |||
1367 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1368 | /// <seealso cref="CommitUpdate"></seealso> | |||
1369 | public void AbortUpdate() | |||
1370 | { | |||
1371 | PostUpdateCleanup(); | |||
1372 | } | |||
1373 | | |||
1374 | /// <summary> | |||
1375 | /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. | |||
1376 | /// </summary> | |||
1377 | /// <param name="comment">The comment to record.</param> | |||
1378 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1379 | public void SetComment(string comment) | |||
1380 | { | |||
1381 | if (isDisposed_) { | |||
1382 | throw new ObjectDisposedException("ZipFile"); | |||
1383 | } | |||
1384 | | |||
1385 | CheckUpdating(); | |||
1386 | | |||
1387 | newComment_ = new ZipString(comment); | |||
1388 | | |||
1389 | if (newComment_.RawLength > 0xffff) { | |||
1390 | newComment_ = null; | |||
1391 | throw new ZipException("Comment length exceeds maximum - 65535"); | |||
1392 | } | |||
1393 | | |||
1394 | // We dont take account of the original and current comment appearing to be the same | |||
1395 | // as encoding may be different. | |||
1396 | commentEdited_ = true; | |||
1397 | } | |||
1398 | | |||
1399 | #endregion | |||
1400 | | |||
1401 | #region Adding Entries | |||
1402 | | |||
1403 | void AddUpdate(ZipUpdate update) | |||
1404 | { | |||
1405 | contentsEdited_ = true; | |||
1406 | | |||
1407 | int index = FindExistingUpdate(update.Entry.Name); | |||
1408 | | |||
1409 | if (index >= 0) { | |||
1410 | if (updates_[index] == null) { | |||
1411 | updateCount_ += 1; | |||
1412 | } | |||
1413 | | |||
1414 | // Direct replacement is faster than delete and add. | |||
1415 | updates_[index] = update; | |||
1416 | } else { | |||
1417 | index = updates_.Add(update); | |||
1418 | updateCount_ += 1; | |||
1419 | updateIndex_.Add(update.Entry.Name, index); | |||
1420 | } | |||
1421 | } | |||
1422 | | |||
1423 | /// <summary> | |||
1424 | /// Add a new entry to the archive. | |||
1425 | /// </summary> | |||
1426 | /// <param name="fileName">The name of the file to add.</param> | |||
1427 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1428 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> | |||
1429 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1430 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1431 | /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception> | |||
1432 | public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) | |||
1433 | { | |||
1434 | if (fileName == null) { | |||
1435 | throw new ArgumentNullException(nameof(fileName)); | |||
1436 | } | |||
1437 | | |||
1438 | if (isDisposed_) { | |||
1439 | throw new ObjectDisposedException("ZipFile"); | |||
1440 | } | |||
1441 | | |||
1442 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1443 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1444 | } | |||
1445 | | |||
1446 | CheckUpdating(); | |||
1447 | contentsEdited_ = true; | |||
1448 | | |||
1449 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1450 | entry.IsUnicodeText = useUnicodeText; | |||
1451 | entry.CompressionMethod = compressionMethod; | |||
1452 | | |||
1453 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1454 | } | |||
1455 | | |||
1456 | /// <summary> | |||
1457 | /// Add a new entry to the archive. | |||
1458 | /// </summary> | |||
1459 | /// <param name="fileName">The name of the file to add.</param> | |||
1460 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1461 | /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception> | |||
1462 | /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception> | |||
1463 | public void Add(string fileName, CompressionMethod compressionMethod) | |||
1464 | { | |||
1465 | if (fileName == null) { | |||
1466 | throw new ArgumentNullException(nameof(fileName)); | |||
1467 | } | |||
1468 | | |||
1469 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1470 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1471 | } | |||
1472 | | |||
1473 | CheckUpdating(); | |||
1474 | contentsEdited_ = true; | |||
1475 | | |||
1476 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1477 | entry.CompressionMethod = compressionMethod; | |||
1478 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1479 | } | |||
1480 | | |||
1481 | /// <summary> | |||
1482 | /// Add a file to the archive. | |||
1483 | /// </summary> | |||
1484 | /// <param name="fileName">The name of the file to add.</param> | |||
1485 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1486 | public void Add(string fileName) | |||
1487 | { | |||
1488 | if (fileName == null) { | |||
1489 | throw new ArgumentNullException(nameof(fileName)); | |||
1490 | } | |||
1491 | | |||
1492 | CheckUpdating(); | |||
1493 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); | |||
1494 | } | |||
1495 | | |||
1496 | /// <summary> | |||
1497 | /// Add a file to the archive. | |||
1498 | /// </summary> | |||
1499 | /// <param name="fileName">The name of the file to add.</param> | |||
1500 | /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param> | |||
1501 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1502 | public void Add(string fileName, string entryName) | |||
1503 | { | |||
1504 | if (fileName == null) { | |||
1505 | throw new ArgumentNullException(nameof(fileName)); | |||
1506 | } | |||
1507 | | |||
1508 | if (entryName == null) { | |||
1509 | throw new ArgumentNullException(nameof(entryName)); | |||
1510 | } | |||
1511 | | |||
1512 | CheckUpdating(); | |||
1513 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); | |||
1514 | } | |||
1515 | | |||
1516 | | |||
1517 | /// <summary> | |||
1518 | /// Add a file entry with data. | |||
1519 | /// </summary> | |||
1520 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1521 | /// <param name="entryName">The name to give to the entry.</param> | |||
1522 | public void Add(IStaticDataSource dataSource, string entryName) | |||
1523 | { | |||
1524 | if (dataSource == null) { | |||
1525 | throw new ArgumentNullException(nameof(dataSource)); | |||
1526 | } | |||
1527 | | |||
1528 | if (entryName == null) { | |||
1529 | throw new ArgumentNullException(nameof(entryName)); | |||
1530 | } | |||
1531 | | |||
1532 | CheckUpdating(); | |||
1533 | AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); | |||
1534 | } | |||
1535 | | |||
1536 | /// <summary> | |||
1537 | /// Add a file entry with data. | |||
1538 | /// </summary> | |||
1539 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1540 | /// <param name="entryName">The name to give to the entry.</param> | |||
1541 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1542 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
1543 | { | |||
1544 | if (dataSource == null) { | |||
1545 | throw new ArgumentNullException(nameof(dataSource)); | |||
1546 | } | |||
1547 | | |||
1548 | if (entryName == null) { | |||
1549 | throw new ArgumentNullException(nameof(entryName)); | |||
1550 | } | |||
1551 | | |||
1552 | CheckUpdating(); | |||
1553 | | |||
1554 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1555 | entry.CompressionMethod = compressionMethod; | |||
1556 | | |||
1557 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1558 | } | |||
1559 | | |||
1560 | /// <summary> | |||
1561 | /// Add a file entry with data. | |||
1562 | /// </summary> | |||
1563 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1564 | /// <param name="entryName">The name to give to the entry.</param> | |||
1565 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1566 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> | |||
1567 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicode | |||
1568 | { | |||
1569 | if (dataSource == null) { | |||
1570 | throw new ArgumentNullException(nameof(dataSource)); | |||
1571 | } | |||
1572 | | |||
1573 | if (entryName == null) { | |||
1574 | throw new ArgumentNullException(nameof(entryName)); | |||
1575 | } | |||
1576 | | |||
1577 | CheckUpdating(); | |||
1578 | | |||
1579 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1580 | entry.IsUnicodeText = useUnicodeText; | |||
1581 | entry.CompressionMethod = compressionMethod; | |||
1582 | | |||
1583 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1584 | } | |||
1585 | | |||
1586 | /// <summary> | |||
1587 | /// Add a <see cref="ZipEntry"/> that contains no data. | |||
1588 | /// </summary> | |||
1589 | /// <param name="entry">The entry to add.</param> | |||
1590 | /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> | |||
1591 | public void Add(ZipEntry entry) | |||
1592 | { | |||
1593 | if (entry == null) { | |||
1594 | throw new ArgumentNullException(nameof(entry)); | |||
1595 | } | |||
1596 | | |||
1597 | CheckUpdating(); | |||
1598 | | |||
1599 | if ((entry.Size != 0) || (entry.CompressedSize != 0)) { | |||
1600 | throw new ZipException("Entry cannot have any data"); | |||
1601 | } | |||
1602 | | |||
1603 | AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); | |||
1604 | } | |||
1605 | | |||
1606 | /// <summary> | |||
1607 | /// Add a directory entry to the archive. | |||
1608 | /// </summary> | |||
1609 | /// <param name="directoryName">The directory to add.</param> | |||
1610 | public void AddDirectory(string directoryName) | |||
1611 | { | |||
1612 | if (directoryName == null) { | |||
1613 | throw new ArgumentNullException(nameof(directoryName)); | |||
1614 | } | |||
1615 | | |||
1616 | CheckUpdating(); | |||
1617 | | |||
1618 | ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); | |||
1619 | AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); | |||
1620 | } | |||
1621 | | |||
1622 | #endregion | |||
1623 | | |||
1624 | #region Modifying Entries | |||
1625 | /* Modify not yet ready for public consumption. | |||
1626 | Direct modification of an entry should not overwrite original data before its read. | |||
1627 | Safe mode is trivial in this sense. | |||
1628 | public void Modify(ZipEntry original, ZipEntry updated) | |||
1629 | { | |||
1630 | if ( original == null ) { | |||
1631 | throw new ArgumentNullException("original"); | |||
1632 | } | |||
1633 | | |||
1634 | if ( updated == null ) { | |||
1635 | throw new ArgumentNullException("updated"); | |||
1636 | } | |||
1637 | | |||
1638 | CheckUpdating(); | |||
1639 | contentsEdited_ = true; | |||
1640 | updates_.Add(new ZipUpdate(original, updated)); | |||
1641 | } | |||
1642 | */ | |||
1643 | #endregion | |||
1644 | | |||
1645 | #region Deleting Entries | |||
1646 | /// <summary> | |||
1647 | /// Delete an entry by name | |||
1648 | /// </summary> | |||
1649 | /// <param name="fileName">The filename to delete</param> | |||
1650 | /// <returns>True if the entry was found and deleted; false otherwise.</returns> | |||
1651 | public bool Delete(string fileName) | |||
1652 | { | |||
1653 | if (fileName == null) { | |||
1654 | throw new ArgumentNullException(nameof(fileName)); | |||
1655 | } | |||
1656 | | |||
1657 | CheckUpdating(); | |||
1658 | | |||
1659 | bool result = false; | |||
1660 | int index = FindExistingUpdate(fileName); | |||
1661 | if ((index >= 0) && (updates_[index] != null)) { | |||
1662 | result = true; | |||
1663 | contentsEdited_ = true; | |||
1664 | updates_[index] = null; | |||
1665 | updateCount_ -= 1; | |||
1666 | } else { | |||
1667 | throw new ZipException("Cannot find entry to delete"); | |||
1668 | } | |||
1669 | return result; | |||
1670 | } | |||
1671 | | |||
1672 | /// <summary> | |||
1673 | /// Delete a <see cref="ZipEntry"/> from the archive. | |||
1674 | /// </summary> | |||
1675 | /// <param name="entry">The entry to delete.</param> | |||
1676 | public void Delete(ZipEntry entry) | |||
1677 | { | |||
1678 | if (entry == null) { | |||
1679 | throw new ArgumentNullException(nameof(entry)); | |||
1680 | } | |||
1681 | | |||
1682 | CheckUpdating(); | |||
1683 | | |||
1684 | int index = FindExistingUpdate(entry); | |||
1685 | if (index >= 0) { | |||
1686 | contentsEdited_ = true; | |||
1687 | updates_[index] = null; | |||
1688 | updateCount_ -= 1; | |||
1689 | } else { | |||
1690 | throw new ZipException("Cannot find entry to delete"); | |||
1691 | } | |||
1692 | } | |||
1693 | | |||
1694 | #endregion | |||
1695 | | |||
1696 | #region Update Support | |||
1697 | | |||
1698 | #region Writing Values/Headers | |||
1699 | void WriteLEShort(int value) | |||
1700 | { | |||
1701 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1702 | baseStream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
1703 | } | |||
1704 | | |||
1705 | /// <summary> | |||
1706 | /// Write an unsigned short in little endian byte order. | |||
1707 | /// </summary> | |||
1708 | void WriteLEUshort(ushort value) | |||
1709 | { | |||
1710 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1711 | baseStream_.WriteByte((byte)(value >> 8)); | |||
1712 | } | |||
1713 | | |||
1714 | /// <summary> | |||
1715 | /// Write an int in little endian byte order. | |||
1716 | /// </summary> | |||
1717 | void WriteLEInt(int value) | |||
1718 | { | |||
1719 | WriteLEShort(value & 0xffff); | |||
1720 | WriteLEShort(value >> 16); | |||
1721 | } | |||
1722 | | |||
1723 | /// <summary> | |||
1724 | /// Write an unsigned int in little endian byte order. | |||
1725 | /// </summary> | |||
1726 | void WriteLEUint(uint value) | |||
1727 | { | |||
1728 | WriteLEUshort((ushort)(value & 0xffff)); | |||
1729 | WriteLEUshort((ushort)(value >> 16)); | |||
1730 | } | |||
1731 | | |||
1732 | /// <summary> | |||
1733 | /// Write a long in little endian byte order. | |||
1734 | /// </summary> | |||
1735 | void WriteLeLong(long value) | |||
1736 | { | |||
1737 | WriteLEInt((int)(value & 0xffffffff)); | |||
1738 | WriteLEInt((int)(value >> 32)); | |||
1739 | } | |||
1740 | | |||
1741 | void WriteLEUlong(ulong value) | |||
1742 | { | |||
1743 | WriteLEUint((uint)(value & 0xffffffff)); | |||
1744 | WriteLEUint((uint)(value >> 32)); | |||
1745 | } | |||
1746 | | |||
1747 | void WriteLocalEntryHeader(ZipUpdate update) | |||
1748 | { | |||
1749 | ZipEntry entry = update.OutEntry; | |||
1750 | | |||
1751 | // TODO: Local offset will require adjusting for multi-disk zip files. | |||
1752 | entry.Offset = baseStream_.Position; | |||
1753 | | |||
1754 | // TODO: Need to clear any entry flags that dont make sense or throw an exception here. | |||
1755 | if (update.Command != UpdateCommand.Copy) { | |||
1756 | if (entry.CompressionMethod == CompressionMethod.Deflated) { | |||
1757 | if (entry.Size == 0) { | |||
1758 | // No need to compress - no data. | |||
1759 | entry.CompressedSize = entry.Size; | |||
1760 | entry.Crc = 0; | |||
1761 | entry.CompressionMethod = CompressionMethod.Stored; | |||
1762 | } | |||
1763 | } else if (entry.CompressionMethod == CompressionMethod.Stored) { | |||
1764 | entry.Flags &= ~(int)GeneralBitFlags.Descriptor; | |||
1765 | } | |||
1766 | | |||
1767 | if (HaveKeys) { | |||
1768 | entry.IsCrypted = true; | |||
1769 | if (entry.Crc < 0) { | |||
1770 | entry.Flags |= (int)GeneralBitFlags.Descriptor; | |||
1771 | } | |||
1772 | } else { | |||
1773 | entry.IsCrypted = false; | |||
1774 | } | |||
1775 | | |||
1776 | switch (useZip64_) { | |||
1777 | case UseZip64.Dynamic: | |||
1778 | if (entry.Size < 0) { | |||
1779 | entry.ForceZip64(); | |||
1780 | } | |||
1781 | break; | |||
1782 | | |||
1783 | case UseZip64.On: | |||
1784 | entry.ForceZip64(); | |||
1785 | break; | |||
1786 | | |||
1787 | case UseZip64.Off: | |||
1788 | // Do nothing. The entry itself may be using Zip64 independantly. | |||
1789 | break; | |||
1790 | } | |||
1791 | } | |||
1792 | | |||
1793 | // Write the local file header | |||
1794 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
1795 | | |||
1796 | WriteLEShort(entry.Version); | |||
1797 | WriteLEShort(entry.Flags); | |||
1798 | | |||
1799 | WriteLEShort((byte)entry.CompressionMethod); | |||
1800 | WriteLEInt((int)entry.DosTime); | |||
1801 | | |||
1802 | if (!entry.HasCrc) { | |||
1803 | // Note patch address for updating CRC later. | |||
1804 | update.CrcPatchOffset = baseStream_.Position; | |||
1805 | WriteLEInt((int)0); | |||
1806 | } else { | |||
1807 | WriteLEInt(unchecked((int)entry.Crc)); | |||
1808 | } | |||
1809 | | |||
1810 | if (entry.LocalHeaderRequiresZip64) { | |||
1811 | WriteLEInt(-1); | |||
1812 | WriteLEInt(-1); | |||
1813 | } else { | |||
1814 | if ((entry.CompressedSize < 0) || (entry.Size < 0)) { | |||
1815 | update.SizePatchOffset = baseStream_.Position; | |||
1816 | } | |||
1817 | | |||
1818 | WriteLEInt((int)entry.CompressedSize); | |||
1819 | WriteLEInt((int)entry.Size); | |||
1820 | } | |||
1821 | | |||
1822 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1823 | | |||
1824 | if (name.Length > 0xFFFF) { | |||
1825 | throw new ZipException("Entry name too long."); | |||
1826 | } | |||
1827 | | |||
1828 | var ed = new ZipExtraData(entry.ExtraData); | |||
1829 | | |||
1830 | if (entry.LocalHeaderRequiresZip64) { | |||
1831 | ed.StartNewEntry(); | |||
1832 | | |||
1833 | // Local entry header always includes size and compressed size. | |||
1834 | // NOTE the order of these fields is reversed when compared to the normal headers! | |||
1835 | ed.AddLeLong(entry.Size); | |||
1836 | ed.AddLeLong(entry.CompressedSize); | |||
1837 | ed.AddNewEntry(1); | |||
1838 | } else { | |||
1839 | ed.Delete(1); | |||
1840 | } | |||
1841 | | |||
1842 | entry.ExtraData = ed.GetEntryData(); | |||
1843 | | |||
1844 | WriteLEShort(name.Length); | |||
1845 | WriteLEShort(entry.ExtraData.Length); | |||
1846 | | |||
1847 | if (name.Length > 0) { | |||
1848 | baseStream_.Write(name, 0, name.Length); | |||
1849 | } | |||
1850 | | |||
1851 | if (entry.LocalHeaderRequiresZip64) { | |||
1852 | if (!ed.Find(1)) { | |||
1853 | throw new ZipException("Internal error cannot find extra data"); | |||
1854 | } | |||
1855 | | |||
1856 | update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; | |||
1857 | } | |||
1858 | | |||
1859 | if (entry.ExtraData.Length > 0) { | |||
1860 | baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); | |||
1861 | } | |||
1862 | } | |||
1863 | | |||
1864 | int WriteCentralDirectoryHeader(ZipEntry entry) | |||
1865 | { | |||
1866 | if (entry.CompressedSize < 0) { | |||
1867 | throw new ZipException("Attempt to write central directory entry with unknown csize"); | |||
1868 | } | |||
1869 | | |||
1870 | if (entry.Size < 0) { | |||
1871 | throw new ZipException("Attempt to write central directory entry with unknown size"); | |||
1872 | } | |||
1873 | | |||
1874 | if (entry.Crc < 0) { | |||
1875 | throw new ZipException("Attempt to write central directory entry with unknown crc"); | |||
1876 | } | |||
1877 | | |||
1878 | // Write the central file header | |||
1879 | WriteLEInt(ZipConstants.CentralHeaderSignature); | |||
1880 | | |||
1881 | // Version made by | |||
1882 | WriteLEShort(ZipConstants.VersionMadeBy); | |||
1883 | | |||
1884 | // Version required to extract | |||
1885 | WriteLEShort(entry.Version); | |||
1886 | | |||
1887 | WriteLEShort(entry.Flags); | |||
1888 | | |||
1889 | unchecked { | |||
1890 | WriteLEShort((byte)entry.CompressionMethod); | |||
1891 | WriteLEInt((int)entry.DosTime); | |||
1892 | WriteLEInt((int)entry.Crc); | |||
1893 | } | |||
1894 | | |||
1895 | if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { | |||
1896 | WriteLEInt(-1); | |||
1897 | } else { | |||
1898 | WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); | |||
1899 | } | |||
1900 | | |||
1901 | if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { | |||
1902 | WriteLEInt(-1); | |||
1903 | } else { | |||
1904 | WriteLEInt((int)entry.Size); | |||
1905 | } | |||
1906 | | |||
1907 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1908 | | |||
1909 | if (name.Length > 0xFFFF) { | |||
1910 | throw new ZipException("Entry name is too long."); | |||
1911 | } | |||
1912 | | |||
1913 | WriteLEShort(name.Length); | |||
1914 | | |||
1915 | // Central header extra data is different to local header version so regenerate. | |||
1916 | var ed = new ZipExtraData(entry.ExtraData); | |||
1917 | | |||
1918 | if (entry.CentralHeaderRequiresZip64) { | |||
1919 | ed.StartNewEntry(); | |||
1920 | | |||
1921 | if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1922 | ed.AddLeLong(entry.Size); | |||
1923 | } | |||
1924 | | |||
1925 | if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1926 | ed.AddLeLong(entry.CompressedSize); | |||
1927 | } | |||
1928 | | |||
1929 | if (entry.Offset >= 0xffffffff) { | |||
1930 | ed.AddLeLong(entry.Offset); | |||
1931 | } | |||
1932 | | |||
1933 | // Number of disk on which this file starts isnt supported and is never written here. | |||
1934 | ed.AddNewEntry(1); | |||
1935 | } else { | |||
1936 | // Should have already be done when local header was added. | |||
1937 | ed.Delete(1); | |||
1938 | } | |||
1939 | | |||
1940 | byte[] centralExtraData = ed.GetEntryData(); | |||
1941 | | |||
1942 | WriteLEShort(centralExtraData.Length); | |||
1943 | WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); | |||
1944 | | |||
1945 | WriteLEShort(0); // disk number | |||
1946 | WriteLEShort(0); // internal file attributes | |||
1947 | | |||
1948 | // External file attributes... | |||
1949 | if (entry.ExternalFileAttributes != -1) { | |||
1950 | WriteLEInt(entry.ExternalFileAttributes); | |||
1951 | } else { | |||
1952 | if (entry.IsDirectory) { | |||
1953 | WriteLEUint(16); | |||
1954 | } else { | |||
1955 | WriteLEUint(0); | |||
1956 | } | |||
1957 | } | |||
1958 | | |||
1959 | if (entry.Offset >= 0xffffffff) { | |||
1960 | WriteLEUint(0xffffffff); | |||
1961 | } else { | |||
1962 | WriteLEUint((uint)(int)entry.Offset); | |||
1963 | } | |||
1964 | | |||
1965 | if (name.Length > 0) { | |||
1966 | baseStream_.Write(name, 0, name.Length); | |||
1967 | } | |||
1968 | | |||
1969 | if (centralExtraData.Length > 0) { | |||
1970 | baseStream_.Write(centralExtraData, 0, centralExtraData.Length); | |||
1971 | } | |||
1972 | | |||
1973 | byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; | |||
1974 | | |||
1975 | if (rawComment.Length > 0) { | |||
1976 | baseStream_.Write(rawComment, 0, rawComment.Length); | |||
1977 | } | |||
1978 | | |||
1979 | return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; | |||
1980 | } | |||
1981 | #endregion | |||
1982 | | |||
1983 | void PostUpdateCleanup() | |||
1984 | { | |||
1985 | updateDataSource_ = null; | |||
1986 | updates_ = null; | |||
1987 | updateIndex_ = null; | |||
1988 | | |||
1989 | if (archiveStorage_ != null) { | |||
1990 | archiveStorage_.Dispose(); | |||
1991 | archiveStorage_ = null; | |||
1992 | } | |||
1993 | } | |||
1994 | | |||
1995 | string GetTransformedFileName(string name) | |||
1996 | { | |||
1997 | INameTransform transform = NameTransform; | |||
1998 | return (transform != null) ? | |||
1999 | transform.TransformFile(name) : | |||
2000 | name; | |||
2001 | } | |||
2002 | | |||
2003 | string GetTransformedDirectoryName(string name) | |||
2004 | { | |||
2005 | INameTransform transform = NameTransform; | |||
2006 | return (transform != null) ? | |||
2007 | transform.TransformDirectory(name) : | |||
2008 | name; | |||
2009 | } | |||
2010 | | |||
2011 | /// <summary> | |||
2012 | /// Get a raw memory buffer. | |||
2013 | /// </summary> | |||
2014 | /// <returns>Returns a raw memory buffer.</returns> | |||
2015 | byte[] GetBuffer() | |||
2016 | { | |||
2017 | if (copyBuffer_ == null) { | |||
2018 | copyBuffer_ = new byte[bufferSize_]; | |||
2019 | } | |||
2020 | return copyBuffer_; | |||
2021 | } | |||
2022 | | |||
2023 | void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) | |||
2024 | { | |||
2025 | int bytesToCopy = GetDescriptorSize(update); | |||
2026 | | |||
2027 | if (bytesToCopy > 0) { | |||
2028 | byte[] buffer = GetBuffer(); | |||
2029 | | |||
2030 | while (bytesToCopy > 0) { | |||
2031 | int readSize = Math.Min(buffer.Length, bytesToCopy); | |||
2032 | | |||
2033 | int bytesRead = source.Read(buffer, 0, readSize); | |||
2034 | if (bytesRead > 0) { | |||
2035 | dest.Write(buffer, 0, bytesRead); | |||
2036 | bytesToCopy -= bytesRead; | |||
2037 | } else { | |||
2038 | throw new ZipException("Unxpected end of stream"); | |||
2039 | } | |||
2040 | } | |||
2041 | } | |||
2042 | } | |||
2043 | | |||
2044 | void CopyBytes(ZipUpdate update, Stream destination, Stream source, | |||
2045 | long bytesToCopy, bool updateCrc) | |||
2046 | { | |||
2047 | if (destination == source) { | |||
2048 | throw new InvalidOperationException("Destination and source are the same"); | |||
2049 | } | |||
2050 | | |||
2051 | // NOTE: Compressed size is updated elsewhere. | |||
2052 | var crc = new Crc32(); | |||
2053 | byte[] buffer = GetBuffer(); | |||
2054 | | |||
2055 | long targetBytes = bytesToCopy; | |||
2056 | long totalBytesRead = 0; | |||
2057 | | |||
2058 | int bytesRead; | |||
2059 | do { | |||
2060 | int readSize = buffer.Length; | |||
2061 | | |||
2062 | if (bytesToCopy < readSize) { | |||
2063 | readSize = (int)bytesToCopy; | |||
2064 | } | |||
2065 | | |||
2066 | bytesRead = source.Read(buffer, 0, readSize); | |||
2067 | if (bytesRead > 0) { | |||
2068 | if (updateCrc) { | |||
2069 | crc.Update(buffer, 0, bytesRead); | |||
2070 | } | |||
2071 | destination.Write(buffer, 0, bytesRead); | |||
2072 | bytesToCopy -= bytesRead; | |||
2073 | totalBytesRead += bytesRead; | |||
2074 | } | |||
2075 | } | |||
2076 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2077 | | |||
2078 | if (totalBytesRead != targetBytes) { | |||
2079 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2080 | } | |||
2081 | | |||
2082 | if (updateCrc) { | |||
2083 | update.OutEntry.Crc = crc.Value; | |||
2084 | } | |||
2085 | } | |||
2086 | | |||
2087 | /// <summary> | |||
2088 | /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. | |||
2089 | /// </summary> | |||
2090 | /// <param name="update">The update to get the size for.</param> | |||
2091 | /// <returns>The descriptor size, zero if there isnt one.</returns> | |||
2092 | int GetDescriptorSize(ZipUpdate update) | |||
2093 | { | |||
2094 | int result = 0; | |||
2095 | if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
2096 | result = ZipConstants.DataDescriptorSize - 4; | |||
2097 | if (update.Entry.LocalHeaderRequiresZip64) { | |||
2098 | result = ZipConstants.Zip64DataDescriptorSize - 4; | |||
2099 | } | |||
2100 | } | |||
2101 | return result; | |||
2102 | } | |||
2103 | | |||
2104 | void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) | |||
2105 | { | |||
2106 | int bytesToCopy = GetDescriptorSize(update); | |||
2107 | | |||
2108 | while (bytesToCopy > 0) { | |||
2109 | var readSize = (int)bytesToCopy; | |||
2110 | byte[] buffer = GetBuffer(); | |||
2111 | | |||
2112 | stream.Position = sourcePosition; | |||
2113 | int bytesRead = stream.Read(buffer, 0, readSize); | |||
2114 | if (bytesRead > 0) { | |||
2115 | stream.Position = destinationPosition; | |||
2116 | stream.Write(buffer, 0, bytesRead); | |||
2117 | bytesToCopy -= bytesRead; | |||
2118 | destinationPosition += bytesRead; | |||
2119 | sourcePosition += bytesRead; | |||
2120 | } else { | |||
2121 | throw new ZipException("Unxpected end of stream"); | |||
2122 | } | |||
2123 | } | |||
2124 | } | |||
2125 | | |||
2126 | void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou | |||
2127 | { | |||
2128 | long bytesToCopy = update.Entry.CompressedSize; | |||
2129 | | |||
2130 | // NOTE: Compressed size is updated elsewhere. | |||
2131 | var crc = new Crc32(); | |||
2132 | byte[] buffer = GetBuffer(); | |||
2133 | | |||
2134 | long targetBytes = bytesToCopy; | |||
2135 | long totalBytesRead = 0; | |||
2136 | | |||
2137 | int bytesRead; | |||
2138 | do { | |||
2139 | int readSize = buffer.Length; | |||
2140 | | |||
2141 | if (bytesToCopy < readSize) { | |||
2142 | readSize = (int)bytesToCopy; | |||
2143 | } | |||
2144 | | |||
2145 | stream.Position = sourcePosition; | |||
2146 | bytesRead = stream.Read(buffer, 0, readSize); | |||
2147 | if (bytesRead > 0) { | |||
2148 | if (updateCrc) { | |||
2149 | crc.Update(buffer, 0, bytesRead); | |||
2150 | } | |||
2151 | stream.Position = destinationPosition; | |||
2152 | stream.Write(buffer, 0, bytesRead); | |||
2153 | | |||
2154 | destinationPosition += bytesRead; | |||
2155 | sourcePosition += bytesRead; | |||
2156 | bytesToCopy -= bytesRead; | |||
2157 | totalBytesRead += bytesRead; | |||
2158 | } | |||
2159 | } | |||
2160 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2161 | | |||
2162 | if (totalBytesRead != targetBytes) { | |||
2163 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2164 | } | |||
2165 | | |||
2166 | if (updateCrc) { | |||
2167 | update.OutEntry.Crc = crc.Value; | |||
2168 | } | |||
2169 | } | |||
2170 | | |||
2171 | int FindExistingUpdate(ZipEntry entry) | |||
2172 | { | |||
2173 | int result = -1; | |||
2174 | string convertedName = GetTransformedFileName(entry.Name); | |||
2175 | | |||
2176 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2177 | result = (int)updateIndex_[convertedName]; | |||
2178 | } | |||
2179 | /* | |||
2180 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2181 | // for CF? | |||
2182 | for (int index = 0; index < updates_.Count; ++index) | |||
2183 | { | |||
2184 | ZipUpdate zu = ( ZipUpdate )updates_[index]; | |||
2185 | if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && | |||
2186 | (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { | |||
2187 | result = index; | |||
2188 | break; | |||
2189 | } | |||
2190 | } | |||
2191 | */ | |||
2192 | return result; | |||
2193 | } | |||
2194 | | |||
2195 | int FindExistingUpdate(string fileName) | |||
2196 | { | |||
2197 | int result = -1; | |||
2198 | | |||
2199 | string convertedName = GetTransformedFileName(fileName); | |||
2200 | | |||
2201 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2202 | result = (int)updateIndex_[convertedName]; | |||
2203 | } | |||
2204 | | |||
2205 | /* | |||
2206 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2207 | // for CF? | |||
2208 | for ( int index = 0; index < updates_.Count; ++index ) { | |||
2209 | if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, | |||
2210 | true, CultureInfo.InvariantCulture) == 0 ) { | |||
2211 | result = index; | |||
2212 | break; | |||
2213 | } | |||
2214 | } | |||
2215 | */ | |||
2216 | | |||
2217 | return result; | |||
2218 | } | |||
2219 | | |||
2220 | /// <summary> | |||
2221 | /// Get an output stream for the specified <see cref="ZipEntry"/> | |||
2222 | /// </summary> | |||
2223 | /// <param name="entry">The entry to get an output stream for.</param> | |||
2224 | /// <returns>The output stream obtained for the entry.</returns> | |||
2225 | Stream GetOutputStream(ZipEntry entry) | |||
2226 | { | |||
2227 | Stream result = baseStream_; | |||
2228 | | |||
2229 | if (entry.IsCrypted == true) { | |||
2230 | result = CreateAndInitEncryptionStream(result, entry); | |||
2231 | } | |||
2232 | | |||
2233 | switch (entry.CompressionMethod) { | |||
2234 | case CompressionMethod.Stored: | |||
2235 | result = new UncompressedStream(result); | |||
2236 | break; | |||
2237 | | |||
2238 | case CompressionMethod.Deflated: | |||
2239 | var dos = new DeflaterOutputStream(result, new Deflater(9, true)); | |||
2240 | dos.IsStreamOwner = false; | |||
2241 | result = dos; | |||
2242 | break; | |||
2243 | | |||
2244 | default: | |||
2245 | throw new ZipException("Unknown compression method " + entry.CompressionMethod); | |||
2246 | } | |||
2247 | return result; | |||
2248 | } | |||
2249 | | |||
2250 | void AddEntry(ZipFile workFile, ZipUpdate update) | |||
2251 | { | |||
2252 | Stream source = null; | |||
2253 | | |||
2254 | if (update.Entry.IsFile) { | |||
2255 | source = update.GetSource(); | |||
2256 | | |||
2257 | if (source == null) { | |||
2258 | source = updateDataSource_.GetSource(update.Entry, update.Filename); | |||
2259 | } | |||
2260 | } | |||
2261 | | |||
2262 | if (source != null) { | |||
2263 | using (source) { | |||
2264 | long sourceStreamLength = source.Length; | |||
2265 | if (update.OutEntry.Size < 0) { | |||
2266 | update.OutEntry.Size = sourceStreamLength; | |||
2267 | } else { | |||
2268 | // Check for errant entries. | |||
2269 | if (update.OutEntry.Size != sourceStreamLength) { | |||
2270 | throw new ZipException("Entry size/stream size mismatch"); | |||
2271 | } | |||
2272 | } | |||
2273 | | |||
2274 | workFile.WriteLocalEntryHeader(update); | |||
2275 | | |||
2276 | long dataStart = workFile.baseStream_.Position; | |||
2277 | | |||
2278 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2279 | CopyBytes(update, output, source, sourceStreamLength, true); | |||
2280 | } | |||
2281 | | |||
2282 | long dataEnd = workFile.baseStream_.Position; | |||
2283 | update.OutEntry.CompressedSize = dataEnd - dataStart; | |||
2284 | | |||
2285 | if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) { | |||
2286 | var helper = new ZipHelperStream(workFile.baseStream_); | |||
2287 | helper.WriteDataDescriptor(update.OutEntry); | |||
2288 | } | |||
2289 | } | |||
2290 | } else { | |||
2291 | workFile.WriteLocalEntryHeader(update); | |||
2292 | update.OutEntry.CompressedSize = 0; | |||
2293 | } | |||
2294 | | |||
2295 | } | |||
2296 | | |||
2297 | void ModifyEntry(ZipFile workFile, ZipUpdate update) | |||
2298 | { | |||
2299 | workFile.WriteLocalEntryHeader(update); | |||
2300 | long dataStart = workFile.baseStream_.Position; | |||
2301 | | |||
2302 | // TODO: This is slow if the changes don't effect the data!! | |||
2303 | if (update.Entry.IsFile && (update.Filename != null)) { | |||
2304 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2305 | using (Stream source = this.GetInputStream(update.Entry)) { | |||
2306 | CopyBytes(update, output, source, source.Length, true); | |||
2307 | } | |||
2308 | } | |||
2309 | } | |||
2310 | | |||
2311 | long dataEnd = workFile.baseStream_.Position; | |||
2312 | update.Entry.CompressedSize = dataEnd - dataStart; | |||
2313 | } | |||
2314 | | |||
2315 | void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) | |||
2316 | { | |||
2317 | bool skipOver = false || update.Entry.Offset == destinationPosition; | |||
2318 | | |||
2319 | if (!skipOver) { | |||
2320 | baseStream_.Position = destinationPosition; | |||
2321 | workFile.WriteLocalEntryHeader(update); | |||
2322 | destinationPosition = baseStream_.Position; | |||
2323 | } | |||
2324 | | |||
2325 | long sourcePosition = 0; | |||
2326 | | |||
2327 | const int NameLengthOffset = 26; | |||
2328 | | |||
2329 | // TODO: Add base for SFX friendly handling | |||
2330 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2331 | | |||
2332 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2333 | | |||
2334 | // Clumsy way of handling retrieving the original name and extra data length for now. | |||
2335 | // TODO: Stop re-reading name and data length in CopyEntryDirect. | |||
2336 | uint nameLength = ReadLEUshort(); | |||
2337 | uint extraLength = ReadLEUshort(); | |||
2338 | | |||
2339 | sourcePosition = baseStream_.Position + nameLength + extraLength; | |||
2340 | | |||
2341 | if (skipOver) { | |||
2342 | if (update.OffsetBasedSize != -1) | |||
2343 | destinationPosition += update.OffsetBasedSize; | |||
2344 | else | |||
2345 | // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) ar | |||
2346 | // WinZip produces a warning on these entries: | |||
2347 | // "caution: value of lrec.csize (compressed size) changed from ..." | |||
2348 | destinationPosition += | |||
2349 | (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size | |||
2350 | update.Entry.CompressedSize + GetDescriptorSize(update); | |||
2351 | } else { | |||
2352 | if (update.Entry.CompressedSize > 0) { | |||
2353 | CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); | |||
2354 | } | |||
2355 | CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); | |||
2356 | } | |||
2357 | } | |||
2358 | | |||
2359 | void CopyEntry(ZipFile workFile, ZipUpdate update) | |||
2360 | { | |||
2361 | workFile.WriteLocalEntryHeader(update); | |||
2362 | | |||
2363 | if (update.Entry.CompressedSize > 0) { | |||
2364 | const int NameLengthOffset = 26; | |||
2365 | | |||
2366 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2367 | | |||
2368 | // TODO: This wont work for SFX files! | |||
2369 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2370 | | |||
2371 | uint nameLength = ReadLEUshort(); | |||
2372 | uint extraLength = ReadLEUshort(); | |||
2373 | | |||
2374 | baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); | |||
2375 | | |||
2376 | CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); | |||
2377 | } | |||
2378 | CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); | |||
2379 | } | |||
2380 | | |||
2381 | void Reopen(Stream source) | |||
2382 | { | |||
2383 | if (source == null) { | |||
2384 | throw new ZipException("Failed to reopen archive - no source"); | |||
2385 | } | |||
2386 | | |||
2387 | isNewArchive_ = false; | |||
2388 | baseStream_ = source; | |||
2389 | ReadEntries(); | |||
2390 | } | |||
2391 | | |||
2392 | void Reopen() | |||
2393 | { | |||
2394 | if (Name == null) { | |||
2395 | throw new InvalidOperationException("Name is not known cannot Reopen"); | |||
2396 | } | |||
2397 | | |||
2398 | Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); | |||
2399 | } | |||
2400 | | |||
2401 | void UpdateCommentOnly() | |||
2402 | { | |||
2403 | long baseLength = baseStream_.Length; | |||
2404 | | |||
2405 | ZipHelperStream updateFile = null; | |||
2406 | | |||
2407 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2408 | Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); | |||
2409 | updateFile = new ZipHelperStream(copyStream); | |||
2410 | updateFile.IsStreamOwner = true; | |||
2411 | | |||
2412 | baseStream_.Close(); | |||
2413 | baseStream_ = null; | |||
2414 | } else { | |||
2415 | if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2416 | // TODO: archiveStorage wasnt originally intended for this use. | |||
2417 | // Need to revisit this to tidy up handling as archive storage currently doesnt | |||
2418 | // handle the original stream well. | |||
2419 | // The problem is when using an existing zip archive with an in memory archive storage. | |||
2420 | // The open stream wont support writing but the memory storage should open the same file not an in memory one. | |||
2421 | | |||
2422 | // Need to tidy up the archive storage interface and contract basically. | |||
2423 | baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); | |||
2424 | updateFile = new ZipHelperStream(baseStream_); | |||
2425 | } else { | |||
2426 | baseStream_.Close(); | |||
2427 | baseStream_ = null; | |||
2428 | updateFile = new ZipHelperStream(Name); | |||
2429 | } | |||
2430 | } | |||
2431 | | |||
2432 | using (updateFile) { | |||
2433 | long locatedCentralDirOffset = | |||
2434 | updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2435 | baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2436 | if (locatedCentralDirOffset < 0) { | |||
2437 | throw new ZipException("Cannot find central directory"); | |||
2438 | } | |||
2439 | | |||
2440 | const int CentralHeaderCommentSizeOffset = 16; | |||
2441 | updateFile.Position += CentralHeaderCommentSizeOffset; | |||
2442 | | |||
2443 | byte[] rawComment = newComment_.RawComment; | |||
2444 | | |||
2445 | updateFile.WriteLEShort(rawComment.Length); | |||
2446 | updateFile.Write(rawComment, 0, rawComment.Length); | |||
2447 | updateFile.SetLength(updateFile.Position); | |||
2448 | } | |||
2449 | | |||
2450 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2451 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2452 | } else { | |||
2453 | ReadEntries(); | |||
2454 | } | |||
2455 | } | |||
2456 | | |||
2457 | /// <summary> | |||
2458 | /// Class used to sort updates. | |||
2459 | /// </summary> | |||
2460 | class UpdateComparer : IComparer | |||
2461 | { | |||
2462 | /// <summary> | |||
2463 | /// Compares two objects and returns a value indicating whether one is | |||
2464 | /// less than, equal to or greater than the other. | |||
2465 | /// </summary> | |||
2466 | /// <param name="x">First object to compare</param> | |||
2467 | /// <param name="y">Second object to compare.</param> | |||
2468 | /// <returns>Compare result.</returns> | |||
2469 | public int Compare( | |||
2470 | object x, | |||
2471 | object y) | |||
2472 | { | |||
2473 | var zx = x as ZipUpdate; | |||
2474 | var zy = y as ZipUpdate; | |||
2475 | | |||
2476 | int result; | |||
2477 | | |||
2478 | if (zx == null) { | |||
2479 | if (zy == null) { | |||
2480 | result = 0; | |||
2481 | } else { | |||
2482 | result = -1; | |||
2483 | } | |||
2484 | } else if (zy == null) { | |||
2485 | result = 1; | |||
2486 | } else { | |||
2487 | int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2488 | int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2489 | | |||
2490 | result = xCmdValue - yCmdValue; | |||
2491 | if (result == 0) { | |||
2492 | long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; | |||
2493 | if (offsetDiff < 0) { | |||
2494 | result = -1; | |||
2495 | } else if (offsetDiff == 0) { | |||
2496 | result = 0; | |||
2497 | } else { | |||
2498 | result = 1; | |||
2499 | } | |||
2500 | } | |||
2501 | } | |||
2502 | return result; | |||
2503 | } | |||
2504 | } | |||
2505 | | |||
2506 | void RunUpdates() | |||
2507 | { | |||
2508 | long sizeEntries = 0; | |||
2509 | long endOfStream = 0; | |||
2510 | bool directUpdate = false; | |||
2511 | long destinationPosition = 0; // NOT SFX friendly | |||
2512 | | |||
2513 | ZipFile workFile; | |||
2514 | | |||
2515 | if (IsNewArchive) { | |||
2516 | workFile = this; | |||
2517 | workFile.baseStream_.Position = 0; | |||
2518 | directUpdate = true; | |||
2519 | } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2520 | workFile = this; | |||
2521 | workFile.baseStream_.Position = 0; | |||
2522 | directUpdate = true; | |||
2523 | | |||
2524 | // Sort the updates by offset within copies/modifies, then adds. | |||
2525 | // This ensures that data required by copies will not be overwritten. | |||
2526 | updates_.Sort(new UpdateComparer()); | |||
2527 | } else { | |||
2528 | workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); | |||
2529 | workFile.UseZip64 = UseZip64; | |||
2530 | | |||
2531 | if (key != null) { | |||
2532 | workFile.key = (byte[])key.Clone(); | |||
2533 | } | |||
2534 | } | |||
2535 | | |||
2536 | try { | |||
2537 | foreach (ZipUpdate update in updates_) { | |||
2538 | if (update != null) { | |||
2539 | switch (update.Command) { | |||
2540 | case UpdateCommand.Copy: | |||
2541 | if (directUpdate) { | |||
2542 | CopyEntryDirect(workFile, update, ref destinationPosition); | |||
2543 | } else { | |||
2544 | CopyEntry(workFile, update); | |||
2545 | } | |||
2546 | break; | |||
2547 | | |||
2548 | case UpdateCommand.Modify: | |||
2549 | // TODO: Direct modifying of an entry will take some legwork. | |||
2550 | ModifyEntry(workFile, update); | |||
2551 | break; | |||
2552 | | |||
2553 | case UpdateCommand.Add: | |||
2554 | if (!IsNewArchive && directUpdate) { | |||
2555 | workFile.baseStream_.Position = destinationPosition; | |||
2556 | } | |||
2557 | | |||
2558 | AddEntry(workFile, update); | |||
2559 | | |||
2560 | if (directUpdate) { | |||
2561 | destinationPosition = workFile.baseStream_.Position; | |||
2562 | } | |||
2563 | break; | |||
2564 | } | |||
2565 | } | |||
2566 | } | |||
2567 | | |||
2568 | if (!IsNewArchive && directUpdate) { | |||
2569 | workFile.baseStream_.Position = destinationPosition; | |||
2570 | } | |||
2571 | | |||
2572 | long centralDirOffset = workFile.baseStream_.Position; | |||
2573 | | |||
2574 | foreach (ZipUpdate update in updates_) { | |||
2575 | if (update != null) { | |||
2576 | sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); | |||
2577 | } | |||
2578 | } | |||
2579 | | |||
2580 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
2581 | using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) { | |||
2582 | zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); | |||
2583 | } | |||
2584 | | |||
2585 | endOfStream = workFile.baseStream_.Position; | |||
2586 | | |||
2587 | // And now patch entries... | |||
2588 | foreach (ZipUpdate update in updates_) { | |||
2589 | if (update != null) { | |||
2590 | // If the size of the entry is zero leave the crc as 0 as well. | |||
2591 | // The calculated crc will be all bits on... | |||
2592 | if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { | |||
2593 | workFile.baseStream_.Position = update.CrcPatchOffset; | |||
2594 | workFile.WriteLEInt((int)update.OutEntry.Crc); | |||
2595 | } | |||
2596 | | |||
2597 | if (update.SizePatchOffset > 0) { | |||
2598 | workFile.baseStream_.Position = update.SizePatchOffset; | |||
2599 | if (update.OutEntry.LocalHeaderRequiresZip64) { | |||
2600 | workFile.WriteLeLong(update.OutEntry.Size); | |||
2601 | workFile.WriteLeLong(update.OutEntry.CompressedSize); | |||
2602 | } else { | |||
2603 | workFile.WriteLEInt((int)update.OutEntry.CompressedSize); | |||
2604 | workFile.WriteLEInt((int)update.OutEntry.Size); | |||
2605 | } | |||
2606 | } | |||
2607 | } | |||
2608 | } | |||
2609 | } catch { | |||
2610 | workFile.Close(); | |||
2611 | if (!directUpdate && (workFile.Name != null)) { | |||
2612 | File.Delete(workFile.Name); | |||
2613 | } | |||
2614 | throw; | |||
2615 | } | |||
2616 | | |||
2617 | if (directUpdate) { | |||
2618 | workFile.baseStream_.SetLength(endOfStream); | |||
2619 | workFile.baseStream_.Flush(); | |||
2620 | isNewArchive_ = false; | |||
2621 | ReadEntries(); | |||
2622 | } else { | |||
2623 | baseStream_.Close(); | |||
2624 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2625 | } | |||
2626 | } | |||
2627 | | |||
2628 | void CheckUpdating() | |||
2629 | { | |||
2630 | if (updates_ == null) { | |||
2631 | throw new InvalidOperationException("BeginUpdate has not been called"); | |||
2632 | } | |||
2633 | } | |||
2634 | | |||
2635 | #endregion | |||
2636 | | |||
2637 | #region ZipUpdate class | |||
2638 | /// <summary> | |||
2639 | /// Represents a pending update to a Zip file. | |||
2640 | /// </summary> | |||
2641 | class ZipUpdate | |||
2642 | { | |||
2643 | #region Constructors | |||
2644 | public ZipUpdate(string fileName, ZipEntry entry) | |||
2645 | { | |||
2646 | command_ = UpdateCommand.Add; | |||
2647 | entry_ = entry; | |||
2648 | filename_ = fileName; | |||
2649 | } | |||
2650 | | |||
2651 | [Obsolete] | |||
2652 | public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) | |||
2653 | { | |||
2654 | command_ = UpdateCommand.Add; | |||
2655 | entry_ = new ZipEntry(entryName); | |||
2656 | entry_.CompressionMethod = compressionMethod; | |||
2657 | filename_ = fileName; | |||
2658 | } | |||
2659 | | |||
2660 | [Obsolete] | |||
2661 | public ZipUpdate(string fileName, string entryName) | |||
2662 | : this(fileName, entryName, CompressionMethod.Deflated) | |||
2663 | { | |||
2664 | // Do nothing. | |||
2665 | } | |||
2666 | | |||
2667 | [Obsolete] | |||
2668 | public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
2669 | { | |||
2670 | command_ = UpdateCommand.Add; | |||
2671 | entry_ = new ZipEntry(entryName); | |||
2672 | entry_.CompressionMethod = compressionMethod; | |||
2673 | dataSource_ = dataSource; | |||
2674 | } | |||
2675 | | |||
2676 | public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) | |||
2677 | { | |||
2678 | command_ = UpdateCommand.Add; | |||
2679 | entry_ = entry; | |||
2680 | dataSource_ = dataSource; | |||
2681 | } | |||
2682 | | |||
2683 | public ZipUpdate(ZipEntry original, ZipEntry updated) | |||
2684 | { | |||
2685 | throw new ZipException("Modify not currently supported"); | |||
2686 | /* | |||
2687 | command_ = UpdateCommand.Modify; | |||
2688 | entry_ = ( ZipEntry )original.Clone(); | |||
2689 | outEntry_ = ( ZipEntry )updated.Clone(); | |||
2690 | */ | |||
2691 | } | |||
2692 | | |||
2693 | public ZipUpdate(UpdateCommand command, ZipEntry entry) | |||
2694 | { | |||
2695 | command_ = command; | |||
2696 | entry_ = (ZipEntry)entry.Clone(); | |||
2697 | } | |||
2698 | | |||
2699 | | |||
2700 | /// <summary> | |||
2701 | /// Copy an existing entry. | |||
2702 | /// </summary> | |||
2703 | /// <param name="entry">The existing entry to copy.</param> | |||
2704 | public ZipUpdate(ZipEntry entry) | |||
2705 | : this(UpdateCommand.Copy, entry) | |||
2706 | { | |||
2707 | // Do nothing. | |||
2708 | } | |||
2709 | #endregion | |||
2710 | | |||
2711 | /// <summary> | |||
2712 | /// Get the <see cref="ZipEntry"/> for this update. | |||
2713 | /// </summary> | |||
2714 | /// <remarks>This is the source or original entry.</remarks> | |||
2715 | public ZipEntry Entry { | |||
2716 | get { return entry_; } | |||
2717 | } | |||
2718 | | |||
2719 | /// <summary> | |||
2720 | /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. | |||
2721 | /// </summary> | |||
2722 | public ZipEntry OutEntry { | |||
2723 | get { | |||
2724 | if (outEntry_ == null) { | |||
2725 | outEntry_ = (ZipEntry)entry_.Clone(); | |||
2726 | } | |||
2727 | | |||
2728 | return outEntry_; | |||
2729 | } | |||
2730 | } | |||
2731 | | |||
2732 | /// <summary> | |||
2733 | /// Get the command for this update. | |||
2734 | /// </summary> | |||
2735 | public UpdateCommand Command { | |||
2736 | get { return command_; } | |||
2737 | } | |||
2738 | | |||
2739 | /// <summary> | |||
2740 | /// Get the filename if any for this update. Null if none exists. | |||
2741 | /// </summary> | |||
2742 | public string Filename { | |||
2743 | get { return filename_; } | |||
2744 | } | |||
2745 | | |||
2746 | /// <summary> | |||
2747 | /// Get/set the location of the size patch for this update. | |||
2748 | /// </summary> | |||
2749 | public long SizePatchOffset { | |||
2750 | get { return sizePatchOffset_; } | |||
2751 | set { sizePatchOffset_ = value; } | |||
2752 | } | |||
2753 | | |||
2754 | /// <summary> | |||
2755 | /// Get /set the location of the crc patch for this update. | |||
2756 | /// </summary> | |||
2757 | public long CrcPatchOffset { | |||
2758 | get { return crcPatchOffset_; } | |||
2759 | set { crcPatchOffset_ = value; } | |||
2760 | } | |||
2761 | | |||
2762 | /// <summary> | |||
2763 | /// Get/set the size calculated by offset. | |||
2764 | /// Specifically, the difference between this and next entry's starting offset. | |||
2765 | /// </summary> | |||
2766 | public long OffsetBasedSize { | |||
2767 | get { return _offsetBasedSize; } | |||
2768 | set { _offsetBasedSize = value; } | |||
2769 | } | |||
2770 | | |||
2771 | public Stream GetSource() | |||
2772 | { | |||
2773 | Stream result = null; | |||
2774 | if (dataSource_ != null) { | |||
2775 | result = dataSource_.GetSource(); | |||
2776 | } | |||
2777 | | |||
2778 | return result; | |||
2779 | } | |||
2780 | | |||
2781 | #region Instance Fields | |||
2782 | ZipEntry entry_; | |||
2783 | ZipEntry outEntry_; | |||
2784 | UpdateCommand command_; | |||
2785 | IStaticDataSource dataSource_; | |||
2786 | string filename_; | |||
2787 | long sizePatchOffset_ = -1; | |||
2788 | long crcPatchOffset_ = -1; | |||
2789 | long _offsetBasedSize = -1; | |||
2790 | #endregion | |||
2791 | } | |||
2792 | | |||
2793 | #endregion | |||
2794 | #endregion | |||
2795 | | |||
2796 | #region Disposing | |||
2797 | | |||
2798 | #region IDisposable Members | |||
2799 | void IDisposable.Dispose() | |||
2800 | { | |||
2801 | Close(); | |||
2802 | } | |||
2803 | #endregion | |||
2804 | | |||
2805 | void DisposeInternal(bool disposing) | |||
2806 | { | |||
2807 | if (!isDisposed_) { | |||
2808 | isDisposed_ = true; | |||
2809 | entries_ = new ZipEntry[0]; | |||
2810 | | |||
2811 | if (IsStreamOwner && (baseStream_ != null)) { | |||
2812 | lock (baseStream_) { | |||
2813 | baseStream_.Close(); | |||
2814 | } | |||
2815 | } | |||
2816 | | |||
2817 | PostUpdateCleanup(); | |||
2818 | } | |||
2819 | } | |||
2820 | | |||
2821 | /// <summary> | |||
2822 | /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. | |||
2823 | /// </summary> | |||
2824 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
2825 | /// false to release only unmanaged resources.</param> | |||
2826 | protected virtual void Dispose(bool disposing) | |||
2827 | { | |||
2828 | DisposeInternal(disposing); | |||
2829 | } | |||
2830 | | |||
2831 | #endregion | |||
2832 | | |||
2833 | #region Internal routines | |||
2834 | #region Reading | |||
2835 | /// <summary> | |||
2836 | /// Read an unsigned short in little endian byte order. | |||
2837 | /// </summary> | |||
2838 | /// <returns>Returns the value read.</returns> | |||
2839 | /// <exception cref="EndOfStreamException"> | |||
2840 | /// The stream ends prematurely | |||
2841 | /// </exception> | |||
2842 | ushort ReadLEUshort() | |||
2843 | { | |||
2844 | int data1 = baseStream_.ReadByte(); | |||
2845 | | |||
2846 | if (data1 < 0) { | |||
2847 | throw new EndOfStreamException("End of stream"); | |||
2848 | } | |||
2849 | | |||
2850 | int data2 = baseStream_.ReadByte(); | |||
2851 | | |||
2852 | if (data2 < 0) { | |||
2853 | throw new EndOfStreamException("End of stream"); | |||
2854 | } | |||
2855 | | |||
2856 | | |||
2857 | return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); | |||
2858 | } | |||
2859 | | |||
2860 | /// <summary> | |||
2861 | /// Read a uint in little endian byte order. | |||
2862 | /// </summary> | |||
2863 | /// <returns>Returns the value read.</returns> | |||
2864 | /// <exception cref="IOException"> | |||
2865 | /// An i/o error occurs. | |||
2866 | /// </exception> | |||
2867 | /// <exception cref="System.IO.EndOfStreamException"> | |||
2868 | /// The file ends prematurely | |||
2869 | /// </exception> | |||
2870 | uint ReadLEUint() | |||
2871 | { | |||
2872 | return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); | |||
2873 | } | |||
2874 | | |||
2875 | ulong ReadLEUlong() | |||
2876 | { | |||
2877 | return ReadLEUint() | ((ulong)ReadLEUint() << 32); | |||
2878 | } | |||
2879 | | |||
2880 | #endregion | |||
2881 | // NOTE this returns the offset of the first byte after the signature. | |||
2882 | long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
2883 | { | |||
2884 | using (ZipHelperStream les = new ZipHelperStream(baseStream_)) { | |||
2885 | return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); | |||
2886 | } | |||
2887 | } | |||
2888 | | |||
2889 | /// <summary> | |||
2890 | /// Search for and read the central directory of a zip file filling the entries array. | |||
2891 | /// </summary> | |||
2892 | /// <exception cref="System.IO.IOException"> | |||
2893 | /// An i/o error occurs. | |||
2894 | /// </exception> | |||
2895 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
2896 | /// The central directory is malformed or cannot be found | |||
2897 | /// </exception> | |||
2898 | void ReadEntries() | |||
2899 | { | |||
2900 | // Search for the End Of Central Directory. When a zip comment is | |||
2901 | // present the directory will start earlier | |||
2902 | // | |||
2903 | // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. | |||
2904 | // This should be compatible with both SFX and ZIP files but has only been tested for Zip files | |||
2905 | // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then | |||
2906 | // this could be invalid. | |||
2907 | // Could also speed this up by reading memory in larger blocks. | |||
2908 | | |||
2909 | if (baseStream_.CanSeek == false) { | |||
2910 | throw new ZipException("ZipFile stream must be seekable"); | |||
2911 | } | |||
2912 | | |||
2913 | long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2914 | baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2915 | | |||
2916 | if (locatedEndOfCentralDir < 0) { | |||
2917 | throw new ZipException("Cannot find central directory"); | |||
2918 | } | |||
2919 | | |||
2920 | // Read end of central directory record | |||
2921 | ushort thisDiskNumber = ReadLEUshort(); | |||
2922 | ushort startCentralDirDisk = ReadLEUshort(); | |||
2923 | ulong entriesForThisDisk = ReadLEUshort(); | |||
2924 | ulong entriesForWholeCentralDir = ReadLEUshort(); | |||
2925 | ulong centralDirSize = ReadLEUint(); | |||
2926 | long offsetOfCentralDir = ReadLEUint(); | |||
2927 | uint commentSize = ReadLEUshort(); | |||
2928 | | |||
2929 | if (commentSize > 0) { | |||
2930 | byte[] comment = new byte[commentSize]; | |||
2931 | | |||
2932 | StreamUtils.ReadFully(baseStream_, comment); | |||
2933 | comment_ = ZipConstants.ConvertToString(comment); | |||
2934 | } else { | |||
2935 | comment_ = string.Empty; | |||
2936 | } | |||
2937 | | |||
2938 | bool isZip64 = false; | |||
2939 | | |||
2940 | // Check if zip64 header information is required. | |||
2941 | if ((thisDiskNumber == 0xffff) || | |||
2942 | (startCentralDirDisk == 0xffff) || | |||
2943 | (entriesForThisDisk == 0xffff) || | |||
2944 | (entriesForWholeCentralDir == 0xffff) || | |||
2945 | (centralDirSize == 0xffffffff) || | |||
2946 | (offsetOfCentralDir == 0xffffffff)) { | |||
2947 | isZip64 = true; | |||
2948 | | |||
2949 | long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, | |||
2950 | if (offset < 0) { | |||
2951 | throw new ZipException("Cannot find Zip64 locator"); | |||
2952 | } | |||
2953 | | |||
2954 | // number of the disk with the start of the zip64 end of central directory 4 bytes | |||
2955 | // relative offset of the zip64 end of central directory record 8 bytes | |||
2956 | // total number of disks 4 bytes | |||
2957 | ReadLEUint(); // startDisk64 is not currently used | |||
2958 | ulong offset64 = ReadLEUlong(); | |||
2959 | uint totalDisks = ReadLEUint(); | |||
2960 | | |||
2961 | baseStream_.Position = (long)offset64; | |||
2962 | long sig64 = ReadLEUint(); | |||
2963 | | |||
2964 | if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { | |||
2965 | throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); | |||
2966 | } | |||
2967 | | |||
2968 | // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. | |||
2969 | ulong recordSize = ReadLEUlong(); | |||
2970 | int versionMadeBy = ReadLEUshort(); | |||
2971 | int versionToExtract = ReadLEUshort(); | |||
2972 | uint thisDisk = ReadLEUint(); | |||
2973 | uint centralDirDisk = ReadLEUint(); | |||
2974 | entriesForThisDisk = ReadLEUlong(); | |||
2975 | entriesForWholeCentralDir = ReadLEUlong(); | |||
2976 | centralDirSize = ReadLEUlong(); | |||
2977 | offsetOfCentralDir = (long)ReadLEUlong(); | |||
2978 | | |||
2979 | // NOTE: zip64 extensible data sector (variable size) is ignored. | |||
2980 | } | |||
2981 | | |||
2982 | entries_ = new ZipEntry[entriesForThisDisk]; | |||
2983 | | |||
2984 | // SFX/embedded support, find the offset of the first entry vis the start of the stream | |||
2985 | // This applies to Zip files that are appended to the end of an SFX stub. | |||
2986 | // Or are appended as a resource to an executable. | |||
2987 | // Zip files created by some archivers have the offsets altered to reflect the true offsets | |||
2988 | // and so dont require any adjustment here... | |||
2989 | // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? | |||
2990 | if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) { | |||
2991 | offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); | |||
2992 | if (offsetOfFirstEntry <= 0) { | |||
2993 | throw new ZipException("Invalid embedded zip archive"); | |||
2994 | } | |||
2995 | } | |||
2996 | | |||
2997 | baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); | |||
2998 | | |||
2999 | for (ulong i = 0; i < entriesForThisDisk; i++) { | |||
3000 | if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { | |||
3001 | throw new ZipException("Wrong Central Directory signature"); | |||
3002 | } | |||
3003 | | |||
3004 | int versionMadeBy = ReadLEUshort(); | |||
3005 | int versionToExtract = ReadLEUshort(); | |||
3006 | int bitFlags = ReadLEUshort(); | |||
3007 | int method = ReadLEUshort(); | |||
3008 | uint dostime = ReadLEUint(); | |||
3009 | uint crc = ReadLEUint(); | |||
3010 | var csize = (long)ReadLEUint(); | |||
3011 | var size = (long)ReadLEUint(); | |||
3012 | int nameLen = ReadLEUshort(); | |||
3013 | int extraLen = ReadLEUshort(); | |||
3014 | int commentLen = ReadLEUshort(); | |||
3015 | | |||
3016 | int diskStartNo = ReadLEUshort(); // Not currently used | |||
3017 | int internalAttributes = ReadLEUshort(); // Not currently used | |||
3018 | | |||
3019 | uint externalAttributes = ReadLEUint(); | |||
3020 | long offset = ReadLEUint(); | |||
3021 | | |||
3022 | byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; | |||
3023 | | |||
3024 | StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); | |||
3025 | string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); | |||
3026 | | |||
3027 | var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); | |||
3028 | entry.Crc = crc & 0xffffffffL; | |||
3029 | entry.Size = size & 0xffffffffL; | |||
3030 | entry.CompressedSize = csize & 0xffffffffL; | |||
3031 | entry.Flags = bitFlags; | |||
3032 | entry.DosTime = (uint)dostime; | |||
3033 | entry.ZipFileIndex = (long)i; | |||
3034 | entry.Offset = offset; | |||
3035 | entry.ExternalFileAttributes = (int)externalAttributes; | |||
3036 | | |||
3037 | if ((bitFlags & 8) == 0) { | |||
3038 | entry.CryptoCheckValue = (byte)(crc >> 24); | |||
3039 | } else { | |||
3040 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | |||
3041 | } | |||
3042 | | |||
3043 | if (extraLen > 0) { | |||
3044 | byte[] extra = new byte[extraLen]; | |||
3045 | StreamUtils.ReadFully(baseStream_, extra); | |||
3046 | entry.ExtraData = extra; | |||
3047 | } | |||
3048 | | |||
3049 | entry.ProcessExtraData(false); | |||
3050 | | |||
3051 | if (commentLen > 0) { | |||
3052 | StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); | |||
3053 | entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); | |||
3054 | } | |||
3055 | | |||
3056 | entries_[i] = entry; | |||
3057 | } | |||
3058 | } | |||
3059 | | |||
3060 | /// <summary> | |||
3061 | /// Locate the data for a given entry. | |||
3062 | /// </summary> | |||
3063 | /// <returns> | |||
3064 | /// The start offset of the data. | |||
3065 | /// </returns> | |||
3066 | /// <exception cref="System.IO.EndOfStreamException"> | |||
3067 | /// The stream ends prematurely | |||
3068 | /// </exception> | |||
3069 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
3070 | /// The local header signature is invalid, the entry and central header file name lengths are different | |||
3071 | /// or the local and entry compression methods dont match | |||
3072 | /// </exception> | |||
3073 | long LocateEntry(ZipEntry entry) | |||
3074 | { | |||
3075 | return TestLocalHeader(entry, HeaderTest.Extract); | |||
3076 | } | |||
3077 | | |||
3078 | Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) | |||
3079 | { | |||
3080 | CryptoStream result = null; | |||
3081 | | |||
3082 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3083 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3084 | var classicManaged = new PkzipClassicManaged(); | |||
3085 | | |||
3086 | OnKeysRequired(entry.Name); | |||
3087 | if (HaveKeys == false) { | |||
3088 | throw new ZipException("No password available for encrypted stream"); | |||
3089 | } | |||
3090 | | |||
3091 | result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); | |||
3092 | CheckClassicPassword(result, entry); | |||
3093 | } else { | |||
3094 | if (entry.Version == ZipConstants.VERSION_AES) { | |||
3095 | // | |||
3096 | OnKeysRequired(entry.Name); | |||
3097 | if (HaveKeys == false) { | |||
3098 | throw new ZipException("No password available for AES encrypted stream"); | |||
3099 | } | |||
3100 | int saltLen = entry.AESSaltLen; | |||
3101 | byte[] saltBytes = new byte[saltLen]; | |||
3102 | int saltIn = baseStream.Read(saltBytes, 0, saltLen); | |||
3103 | if (saltIn != saltLen) | |||
3104 | throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); | |||
3105 | // | |||
3106 | byte[] pwdVerifyRead = new byte[2]; | |||
3107 | baseStream.Read(pwdVerifyRead, 0, 2); | |||
3108 | int blockSize = entry.AESKeySize / 8; // bits to bytes | |||
3109 | | |||
3110 | var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); | |||
3111 | byte[] pwdVerifyCalc = decryptor.PwdVerifier; | |||
3112 | if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) | |||
3113 | throw new ZipException("Invalid password for AES"); | |||
3114 | result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); | |||
3115 | } else { | |||
3116 | throw new ZipException("Decryption method not supported"); | |||
3117 | } | |||
3118 | } | |||
3119 | | |||
3120 | return result; | |||
3121 | } | |||
3122 | | |||
3123 | Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) | |||
3124 | { | |||
3125 | CryptoStream result = null; | |||
3126 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3127 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3128 | var classicManaged = new PkzipClassicManaged(); | |||
3129 | | |||
3130 | OnKeysRequired(entry.Name); | |||
3131 | if (HaveKeys == false) { | |||
3132 | throw new ZipException("No password available for encrypted stream"); | |||
3133 | } | |||
3134 | | |||
3135 | // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream | |||
3136 | // which doesnt do this. | |||
3137 | result = new CryptoStream(new UncompressedStream(baseStream), | |||
3138 | classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); | |||
3139 | | |||
3140 | if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { | |||
3141 | WriteEncryptionHeader(result, entry.DosTime << 16); | |||
3142 | } else { | |||
3143 | WriteEncryptionHeader(result, entry.Crc); | |||
3144 | } | |||
3145 | } | |||
3146 | return result; | |||
3147 | } | |||
3148 | | |||
3149 | static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) | |||
3150 | { | |||
3151 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3152 | StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); | |||
3153 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | |||
3154 | throw new ZipException("Invalid password"); | |||
3155 | } | |||
3156 | } | |||
3157 | | |||
3158 | static void WriteEncryptionHeader(Stream stream, long crcValue) | |||
3159 | { | |||
3160 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3161 | var rnd = new Random(); | |||
3162 | rnd.NextBytes(cryptBuffer); | |||
3163 | cryptBuffer[11] = (byte)(crcValue >> 24); | |||
3164 | stream.Write(cryptBuffer, 0, cryptBuffer.Length); | |||
3165 | } | |||
3166 | | |||
3167 | #endregion | |||
3168 | | |||
3169 | #region Instance Fields | |||
3170 | bool isDisposed_; | |||
3171 | string name_; | |||
3172 | string comment_; | |||
3173 | string rawPassword_; | |||
3174 | Stream baseStream_; | |||
3175 | bool isStreamOwner; | |||
3176 | long offsetOfFirstEntry; | |||
3177 | ZipEntry[] entries_; | |||
3178 | byte[] key; | |||
3179 | bool isNewArchive_; | |||
3180 | | |||
3181 | // Default is dynamic which is not backwards compatible and can cause problems | |||
3182 | // with XP's built in compression which cant read Zip64 archives. | |||
3183 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
3184 | // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. | |||
3185 | UseZip64 useZip64_ = UseZip64.Dynamic; | |||
3186 | | |||
3187 | #region Zip Update Instance Fields | |||
3188 | ArrayList updates_; | |||
3189 | long updateCount_; // Count is managed manually as updates_ can contain nulls! | |||
3190 | Hashtable updateIndex_; | |||
3191 | IArchiveStorage archiveStorage_; | |||
3192 | IDynamicDataSource updateDataSource_; | |||
3193 | bool contentsEdited_; | |||
3194 | int bufferSize_ = DefaultBufferSize; | |||
3195 | byte[] copyBuffer_; | |||
3196 | ZipString newComment_; | |||
3197 | bool commentEdited_; | |||
3198 | IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); | |||
3199 | #endregion | |||
3200 | #endregion | |||
3201 | | |||
3202 | #region Support Classes | |||
3203 | /// <summary> | |||
3204 | /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. | |||
3205 | /// </summary> | |||
3206 | class ZipString | |||
3207 | { | |||
3208 | #region Constructors | |||
3209 | /// <summary> | |||
3210 | /// Initialise a <see cref="ZipString"/> with a string. | |||
3211 | /// </summary> | |||
3212 | /// <param name="comment">The textual string form.</param> | |||
3213 | public ZipString(string comment) | |||
3214 | { | |||
3215 | comment_ = comment; | |||
3216 | isSourceString_ = true; | |||
3217 | } | |||
3218 | | |||
3219 | /// <summary> | |||
3220 | /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. | |||
3221 | /// </summary> | |||
3222 | /// <param name="rawString"></param> | |||
3223 | public ZipString(byte[] rawString) | |||
3224 | { | |||
3225 | rawComment_ = rawString; | |||
3226 | } | |||
3227 | #endregion | |||
3228 | | |||
3229 | /// <summary> | |||
3230 | /// Get a value indicating the original source of data for this instance. | |||
3231 | /// True if the source was a string; false if the source was binary data. | |||
3232 | /// </summary> | |||
3233 | public bool IsSourceString { | |||
3234 | get { return isSourceString_; } | |||
3235 | } | |||
3236 | | |||
3237 | /// <summary> | |||
3238 | /// Get the length of the comment when represented as raw bytes. | |||
3239 | /// </summary> | |||
3240 | public int RawLength { | |||
3241 | get { | |||
3242 | MakeBytesAvailable(); | |||
3243 | return rawComment_.Length; | |||
3244 | } | |||
3245 | } | |||
3246 | | |||
3247 | /// <summary> | |||
3248 | /// Get the comment in its 'raw' form as plain bytes. | |||
3249 | /// </summary> | |||
3250 | public byte[] RawComment { | |||
3251 | get { | |||
3252 | MakeBytesAvailable(); | |||
3253 | return (byte[])rawComment_.Clone(); | |||
3254 | } | |||
3255 | } | |||
3256 | | |||
3257 | /// <summary> | |||
3258 | /// Reset the comment to its initial state. | |||
3259 | /// </summary> | |||
3260 | public void Reset() | |||
3261 | { | |||
3262 | if (isSourceString_) { | |||
3263 | rawComment_ = null; | |||
3264 | } else { | |||
3265 | comment_ = null; | |||
3266 | } | |||
3267 | } | |||
3268 | | |||
3269 | void MakeTextAvailable() | |||
3270 | { | |||
3271 | if (comment_ == null) { | |||
3272 | comment_ = ZipConstants.ConvertToString(rawComment_); | |||
3273 | } | |||
3274 | } | |||
3275 | | |||
3276 | void MakeBytesAvailable() | |||
3277 | { | |||
3278 | if (rawComment_ == null) { | |||
3279 | rawComment_ = ZipConstants.ConvertToArray(comment_); | |||
3280 | } | |||
3281 | } | |||
3282 | | |||
3283 | /// <summary> | |||
3284 | /// Implicit conversion of comment to a string. | |||
3285 | /// </summary> | |||
3286 | /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> | |||
3287 | /// <returns>The textual equivalent for the input value.</returns> | |||
3288 | static public implicit operator string(ZipString zipString) | |||
3289 | { | |||
3290 | zipString.MakeTextAvailable(); | |||
3291 | return zipString.comment_; | |||
3292 | } | |||
3293 | | |||
3294 | #region Instance Fields | |||
3295 | string comment_; | |||
3296 | byte[] rawComment_; | |||
3297 | bool isSourceString_; | |||
3298 | #endregion | |||
3299 | } | |||
3300 | | |||
3301 | /// <summary> | |||
3302 | /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> | |||
3303 | /// </summary> | |||
3304 | class ZipEntryEnumerator : IEnumerator | |||
3305 | { | |||
3306 | #region Constructors | |||
3307 | public ZipEntryEnumerator(ZipEntry[] entries) | |||
3308 | { | |||
3309 | array = entries; | |||
3310 | } | |||
3311 | | |||
3312 | #endregion | |||
3313 | #region IEnumerator Members | |||
3314 | public object Current { | |||
3315 | get { | |||
3316 | return array[index]; | |||
3317 | } | |||
3318 | } | |||
3319 | | |||
3320 | public void Reset() | |||
3321 | { | |||
3322 | index = -1; | |||
3323 | } | |||
3324 | | |||
3325 | public bool MoveNext() | |||
3326 | { | |||
3327 | return (++index < array.Length); | |||
3328 | } | |||
3329 | #endregion | |||
3330 | #region Instance Fields | |||
3331 | ZipEntry[] array; | |||
3332 | int index = -1; | |||
3333 | #endregion | |||
3334 | } | |||
3335 | | |||
3336 | /// <summary> | |||
3337 | /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data | |||
3338 | /// to and flush, but cannot read, seek or do anything else to. | |||
3339 | /// </summary> | |||
3340 | class UncompressedStream : Stream | |||
3341 | { | |||
3342 | #region Constructors | |||
3343 | public UncompressedStream(Stream baseStream) | |||
3344 | { | |||
3345 | baseStream_ = baseStream; | |||
3346 | } | |||
3347 | | |||
3348 | #endregion | |||
3349 | | |||
3350 | /// <summary> | |||
3351 | /// Close this stream instance. | |||
3352 | /// </summary> | |||
3353 | public override void Close() | |||
3354 | { | |||
3355 | // Do nothing | |||
3356 | } | |||
3357 | | |||
3358 | /// <summary> | |||
3359 | /// Gets a value indicating whether the current stream supports reading. | |||
3360 | /// </summary> | |||
3361 | public override bool CanRead { | |||
3362 | get { | |||
3363 | return false; | |||
3364 | } | |||
3365 | } | |||
3366 | | |||
3367 | /// <summary> | |||
3368 | /// Write any buffered data to underlying storage. | |||
3369 | /// </summary> | |||
3370 | public override void Flush() | |||
3371 | { | |||
3372 | baseStream_.Flush(); | |||
3373 | } | |||
3374 | | |||
3375 | /// <summary> | |||
3376 | /// Gets a value indicating whether the current stream supports writing. | |||
3377 | /// </summary> | |||
3378 | public override bool CanWrite { | |||
3379 | get { | |||
3380 | return baseStream_.CanWrite; | |||
3381 | } | |||
3382 | } | |||
3383 | | |||
3384 | /// <summary> | |||
3385 | /// Gets a value indicating whether the current stream supports seeking. | |||
3386 | /// </summary> | |||
3387 | public override bool CanSeek { | |||
3388 | get { | |||
3389 | return false; | |||
3390 | } | |||
3391 | } | |||
3392 | | |||
3393 | /// <summary> | |||
3394 | /// Get the length in bytes of the stream. | |||
3395 | /// </summary> | |||
3396 | public override long Length { | |||
3397 | get { | |||
3398 | return 0; | |||
3399 | } | |||
3400 | } | |||
3401 | | |||
3402 | /// <summary> | |||
3403 | /// Gets or sets the position within the current stream. | |||
3404 | /// </summary> | |||
3405 | public override long Position { | |||
3406 | get { | |||
3407 | return baseStream_.Position; | |||
3408 | } | |||
3409 | set { | |||
3410 | throw new NotImplementedException(); | |||
3411 | } | |||
3412 | } | |||
3413 | | |||
3414 | /// <summary> | |||
3415 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3416 | /// </summary> | |||
3417 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3418 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3419 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3420 | /// <returns> | |||
3421 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3422 | /// </returns> | |||
3423 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3424 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3425 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3426 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3427 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3428 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3429 | public override int Read(byte[] buffer, int offset, int count) | |||
3430 | { | |||
3431 | return 0; | |||
3432 | } | |||
3433 | | |||
3434 | /// <summary> | |||
3435 | /// Sets the position within the current stream. | |||
3436 | /// </summary> | |||
3437 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3438 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3439 | /// <returns> | |||
3440 | /// The new position within the current stream. | |||
3441 | /// </returns> | |||
3442 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3443 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3444 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3445 | public override long Seek(long offset, SeekOrigin origin) | |||
3446 | { | |||
3447 | return 0; | |||
3448 | } | |||
3449 | | |||
3450 | /// <summary> | |||
3451 | /// Sets the length of the current stream. | |||
3452 | /// </summary> | |||
3453 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3454 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3455 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3456 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3457 | public override void SetLength(long value) | |||
3458 | { | |||
3459 | } | |||
3460 | | |||
3461 | /// <summary> | |||
3462 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3463 | /// </summary> | |||
3464 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3465 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3466 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3467 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3468 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3469 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3470 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3471 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3472 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3473 | public override void Write(byte[] buffer, int offset, int count) | |||
3474 | { | |||
3475 | baseStream_.Write(buffer, offset, count); | |||
3476 | } | |||
3477 | | |||
3478 | readonly | |||
3479 | | |||
3480 | #region Instance Fields | |||
3481 | Stream baseStream_; | |||
3482 | #endregion | |||
3483 | } | |||
3484 | | |||
3485 | /// <summary> | |||
3486 | /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> | |||
3487 | /// whose data is only a part or subsection of a file. | |||
3488 | /// </summary> | |||
3489 | class PartialInputStream : Stream | |||
3490 | { | |||
3491 | #region Constructors | |||
3492 | /// <summary> | |||
3493 | /// Initialise a new instance of the <see cref="PartialInputStream"/> class. | |||
3494 | /// </summary> | |||
3495 | /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param> | |||
3496 | /// <param name="start">The start of the partial data.</param> | |||
3497 | /// <param name="length">The length of the partial data.</param> | |||
3498 | public PartialInputStream(ZipFile zipFile, long start, long length) | |||
3499 | { | |||
3500 | start_ = start; | |||
3501 | length_ = length; | |||
3502 | | |||
3503 | // Although this is the only time the zipfile is used | |||
3504 | // keeping a reference here prevents premature closure of | |||
3505 | // this zip file and thus the baseStream_. | |||
3506 | | |||
3507 | // Code like this will cause apparently random failures depending | |||
3508 | // on the size of the files and when garbage is collected. | |||
3509 | // | |||
3510 | // ZipFile z = new ZipFile (stream); | |||
3511 | // Stream reader = z.GetInputStream(0); | |||
3512 | // uses reader here.... | |||
3513 | zipFile_ = zipFile; | |||
3514 | baseStream_ = zipFile_.baseStream_; | |||
3515 | readPos_ = start; | |||
3516 | end_ = start + length; | |||
3517 | } | |||
3518 | #endregion | |||
3519 | | |||
3520 | /// <summary> | |||
3521 | /// Read a byte from this stream. | |||
3522 | /// </summary> | |||
3523 | /// <returns>Returns the byte read or -1 on end of stream.</returns> | |||
3524 | public override int ReadByte() | |||
3525 | { | |||
3526 | if (readPos_ >= end_) { | |||
3527 | // -1 is the correct value at end of stream. | |||
3528 | return -1; | |||
3529 | } | |||
3530 | | |||
3531 | lock (baseStream_) { | |||
3532 | baseStream_.Seek(readPos_++, SeekOrigin.Begin); | |||
3533 | return baseStream_.ReadByte(); | |||
3534 | } | |||
3535 | } | |||
3536 | | |||
3537 | /// <summary> | |||
3538 | /// Close this <see cref="PartialInputStream">partial input stream</see>. | |||
3539 | /// </summary> | |||
3540 | /// <remarks> | |||
3541 | /// The underlying stream is not closed. Close the parent ZipFile class to do that. | |||
3542 | /// </remarks> | |||
3543 | public override void Close() | |||
3544 | { | |||
3545 | // Do nothing at all! | |||
3546 | } | |||
3547 | | |||
3548 | /// <summary> | |||
3549 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3550 | /// </summary> | |||
3551 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3552 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3553 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3554 | /// <returns> | |||
3555 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3556 | /// </returns> | |||
3557 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3558 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3559 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3560 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3561 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3562 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3563 | public override int Read(byte[] buffer, int offset, int count) | |||
3564 | { | |||
3565 | lock (baseStream_) { | |||
3566 | if (count > end_ - readPos_) { | |||
3567 | count = (int)(end_ - readPos_); | |||
3568 | if (count == 0) { | |||
3569 | return 0; | |||
3570 | } | |||
3571 | } | |||
3572 | // Protect against Stream implementations that throw away their buffer on every Seek | |||
3573 | // (for example, Mono FileStream) | |||
3574 | if (baseStream_.Position != readPos_) { | |||
3575 | baseStream_.Seek(readPos_, SeekOrigin.Begin); | |||
3576 | } | |||
3577 | int readCount = baseStream_.Read(buffer, offset, count); | |||
3578 | if (readCount > 0) { | |||
3579 | readPos_ += readCount; | |||
3580 | } | |||
3581 | return readCount; | |||
3582 | } | |||
3583 | } | |||
3584 | | |||
3585 | /// <summary> | |||
3586 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3587 | /// </summary> | |||
3588 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3589 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3590 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3591 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3592 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3593 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3594 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3595 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3596 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3597 | public override void Write(byte[] buffer, int offset, int count) | |||
3598 | { | |||
3599 | throw new NotSupportedException(); | |||
3600 | } | |||
3601 | | |||
3602 | /// <summary> | |||
3603 | /// When overridden in a derived class, sets the length of the current stream. | |||
3604 | /// </summary> | |||
3605 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3606 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3607 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3608 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3609 | public override void SetLength(long value) | |||
3610 | { | |||
3611 | throw new NotSupportedException(); | |||
3612 | } | |||
3613 | | |||
3614 | /// <summary> | |||
3615 | /// When overridden in a derived class, sets the position within the current stream. | |||
3616 | /// </summary> | |||
3617 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3618 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3619 | /// <returns> | |||
3620 | /// The new position within the current stream. | |||
3621 | /// </returns> | |||
3622 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3623 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3624 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3625 | public override long Seek(long offset, SeekOrigin origin) | |||
3626 | { | |||
3627 | long newPos = readPos_; | |||
3628 | | |||
3629 | switch (origin) { | |||
3630 | case SeekOrigin.Begin: | |||
3631 | newPos = start_ + offset; | |||
3632 | break; | |||
3633 | | |||
3634 | case SeekOrigin.Current: | |||
3635 | newPos = readPos_ + offset; | |||
3636 | break; | |||
3637 | | |||
3638 | case SeekOrigin.End: | |||
3639 | newPos = end_ + offset; | |||
3640 | break; | |||
3641 | } | |||
3642 | | |||
3643 | if (newPos < start_) { | |||
3644 | throw new ArgumentException("Negative position is invalid"); | |||
3645 | } | |||
3646 | | |||
3647 | if (newPos >= end_) { | |||
3648 | throw new IOException("Cannot seek past end"); | |||
3649 | } | |||
3650 | readPos_ = newPos; | |||
3651 | return readPos_; | |||
3652 | } | |||
3653 | | |||
3654 | /// <summary> | |||
3655 | /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | |||
3656 | /// </summary> | |||
3657 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3658 | public override void Flush() | |||
3659 | { | |||
3660 | // Nothing to do. | |||
3661 | } | |||
3662 | | |||
3663 | /// <summary> | |||
3664 | /// Gets or sets the position within the current stream. | |||
3665 | /// </summary> | |||
3666 | /// <value></value> | |||
3667 | /// <returns>The current position within the stream.</returns> | |||
3668 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3669 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> | |||
3670 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3671 | public override long Position { | |||
3672 | get { return readPos_ - start_; } | |||
3673 | set { | |||
3674 | long newPos = start_ + value; | |||
3675 | | |||
3676 | if (newPos < start_) { | |||
3677 | throw new ArgumentException("Negative position is invalid"); | |||
3678 | } | |||
3679 | | |||
3680 | if (newPos >= end_) { | |||
3681 | throw new InvalidOperationException("Cannot seek past end"); | |||
3682 | } | |||
3683 | readPos_ = newPos; | |||
3684 | } | |||
3685 | } | |||
3686 | | |||
3687 | /// <summary> | |||
3688 | /// Gets the length in bytes of the stream. | |||
3689 | /// </summary> | |||
3690 | /// <value></value> | |||
3691 | /// <returns>A long value representing the length of the stream in bytes.</returns> | |||
3692 | /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </excep | |||
3693 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3694 | public override long Length { | |||
3695 | get { return length_; } | |||
3696 | } | |||
3697 | | |||
3698 | /// <summary> | |||
3699 | /// Gets a value indicating whether the current stream supports writing. | |||
3700 | /// </summary> | |||
3701 | /// <value>false</value> | |||
3702 | /// <returns>true if the stream supports writing; otherwise, false.</returns> | |||
3703 | public override bool CanWrite { | |||
3704 | get { return false; } | |||
3705 | } | |||
3706 | | |||
3707 | /// <summary> | |||
3708 | /// Gets a value indicating whether the current stream supports seeking. | |||
3709 | /// </summary> | |||
3710 | /// <value>true</value> | |||
3711 | /// <returns>true if the stream supports seeking; otherwise, false.</returns> | |||
3712 | public override bool CanSeek { | |||
3713 | get { return true; } | |||
3714 | } | |||
3715 | | |||
3716 | /// <summary> | |||
3717 | /// Gets a value indicating whether the current stream supports reading. | |||
3718 | /// </summary> | |||
3719 | /// <value>true.</value> | |||
3720 | /// <returns>true if the stream supports reading; otherwise, false.</returns> | |||
3721 | public override bool CanRead { | |||
3722 | get { return true; } | |||
3723 | } | |||
3724 | | |||
3725 | /// <summary> | |||
3726 | /// Gets a value that determines whether the current stream can time out. | |||
3727 | /// </summary> | |||
3728 | /// <value></value> | |||
3729 | /// <returns>A value that determines whether the current stream can time out.</returns> | |||
3730 | public override bool CanTimeout { | |||
3731 | get { return baseStream_.CanTimeout; } | |||
3732 | } | |||
3733 | #region Instance Fields | |||
3734 | ZipFile zipFile_; | |||
3735 | Stream baseStream_; | |||
3736 | long start_; | |||
3737 | long length_; | |||
3738 | long readPos_; | |||
3739 | long end_; | |||
3740 | #endregion | |||
3741 | } | |||
3742 | #endregion | |||
3743 | } | |||
3744 | | |||
3745 | #endregion | |||
3746 | | |||
3747 | #region DataSources | |||
3748 | /// <summary> | |||
3749 | /// Provides a static way to obtain a source of data for an entry. | |||
3750 | /// </summary> | |||
3751 | public interface IStaticDataSource | |||
3752 | { | |||
3753 | /// <summary> | |||
3754 | /// Get a source of data by creating a new stream. | |||
3755 | /// </summary> | |||
3756 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3757 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3758 | Stream GetSource(); | |||
3759 | } | |||
3760 | | |||
3761 | /// <summary> | |||
3762 | /// Represents a source of data that can dynamically provide | |||
3763 | /// multiple <see cref="Stream">data sources</see> based on the parameters passed. | |||
3764 | /// </summary> | |||
3765 | public interface IDynamicDataSource | |||
3766 | { | |||
3767 | /// <summary> | |||
3768 | /// Get a data source. | |||
3769 | /// </summary> | |||
3770 | /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> | |||
3771 | /// <param name="name">The name for data if known.</param> | |||
3772 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3773 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3774 | Stream GetSource(ZipEntry entry, string name); | |||
3775 | } | |||
3776 | | |||
3777 | /// <summary> | |||
3778 | /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. | |||
3779 | /// </summary> | |||
3780 | public class StaticDiskDataSource : IStaticDataSource | |||
3781 | { | |||
3782 | /// <summary> | |||
3783 | /// Initialise a new instnace of <see cref="StaticDiskDataSource"/> | |||
3784 | /// </summary> | |||
3785 | /// <param name="fileName">The name of the file to obtain data from.</param> | |||
3786 | public StaticDiskDataSource(string fileName) | |||
3787 | { | |||
3788 | fileName_ = fileName; | |||
3789 | } | |||
3790 | | |||
3791 | #region IDataSource Members | |||
3792 | | |||
3793 | /// <summary> | |||
3794 | /// Get a <see cref="Stream"/> providing data. | |||
3795 | /// </summary> | |||
3796 | /// <returns>Returns a <see cref="Stream"/> provising data.</returns> | |||
3797 | public Stream GetSource() | |||
3798 | { | |||
3799 | return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3800 | } | |||
3801 | | |||
3802 | readonly | |||
3803 | | |||
3804 | #endregion | |||
3805 | #region Instance Fields | |||
3806 | string fileName_; | |||
3807 | #endregion | |||
3808 | } | |||
3809 | | |||
3810 | | |||
3811 | /// <summary> | |||
3812 | /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. | |||
3813 | /// </summary> | |||
3814 | public class DynamicDiskDataSource : IDynamicDataSource | |||
3815 | { | |||
3816 | | |||
3817 | #region IDataSource Members | |||
3818 | /// <summary> | |||
3819 | /// Get a <see cref="Stream"/> providing data for an entry. | |||
3820 | /// </summary> | |||
3821 | /// <param name="entry">The entry to provide data for.</param> | |||
3822 | /// <param name="name">The file name for data if known.</param> | |||
3823 | /// <returns>Returns a stream providing data; or null if not available</returns> | |||
3824 | public Stream GetSource(ZipEntry entry, string name) | |||
3825 | { | |||
3826 | Stream result = null; | |||
3827 | | |||
3828 | if (name != null) { | |||
3829 | result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3830 | } | |||
3831 | | |||
3832 | return result; | |||
3833 | } | |||
3834 | | |||
3835 | #endregion | |||
3836 | } | |||
3837 | | |||
3838 | #endregion | |||
3839 | | |||
3840 | #region Archive Storage | |||
3841 | /// <summary> | |||
3842 | /// Defines facilities for data storage when updating Zip Archives. | |||
3843 | /// </summary> | |||
3844 | public interface IArchiveStorage | |||
3845 | { | |||
3846 | /// <summary> | |||
3847 | /// Get the <see cref="FileUpdateMode"/> to apply during updates. | |||
3848 | /// </summary> | |||
3849 | FileUpdateMode UpdateMode { get; } | |||
3850 | | |||
3851 | /// <summary> | |||
3852 | /// Get an empty <see cref="Stream"/> that can be used for temporary output. | |||
3853 | /// </summary> | |||
3854 | /// <returns>Returns a temporary output <see cref="Stream"/></returns> | |||
3855 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3856 | Stream GetTemporaryOutput(); | |||
3857 | | |||
3858 | /// <summary> | |||
3859 | /// Convert a temporary output stream to a final stream. | |||
3860 | /// </summary> | |||
3861 | /// <returns>The resulting final <see cref="Stream"/></returns> | |||
3862 | /// <seealso cref="GetTemporaryOutput"/> | |||
3863 | Stream ConvertTemporaryToFinal(); | |||
3864 | | |||
3865 | /// <summary> | |||
3866 | /// Make a temporary copy of the original stream. | |||
3867 | /// </summary> | |||
3868 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
3869 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3870 | Stream MakeTemporaryCopy(Stream stream); | |||
3871 | | |||
3872 | /// <summary> | |||
3873 | /// Return a stream suitable for performing direct updates on the original source. | |||
3874 | /// </summary> | |||
3875 | /// <param name="stream">The current stream.</param> | |||
3876 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3877 | /// <remarks>This may be the current stream passed.</remarks> | |||
3878 | Stream OpenForDirectUpdate(Stream stream); | |||
3879 | | |||
3880 | /// <summary> | |||
3881 | /// Dispose of this instance. | |||
3882 | /// </summary> | |||
3883 | void Dispose(); | |||
3884 | } | |||
3885 | | |||
3886 | /// <summary> | |||
3887 | /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. | |||
3888 | /// </summary> | |||
3889 | abstract public class BaseArchiveStorage : IArchiveStorage | |||
3890 | { | |||
3891 | #region Constructors | |||
3892 | /// <summary> | |||
3893 | /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. | |||
3894 | /// </summary> | |||
3895 | /// <param name="updateMode">The update mode.</param> | |||
3896 | protected BaseArchiveStorage(FileUpdateMode updateMode) | |||
3897 | { | |||
3898 | updateMode_ = updateMode; | |||
3899 | } | |||
3900 | #endregion | |||
3901 | | |||
3902 | #region IArchiveStorage Members | |||
3903 | | |||
3904 | /// <summary> | |||
3905 | /// Gets a temporary output <see cref="Stream"/> | |||
3906 | /// </summary> | |||
3907 | /// <returns>Returns the temporary output stream.</returns> | |||
3908 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3909 | public abstract Stream GetTemporaryOutput(); | |||
3910 | | |||
3911 | /// <summary> | |||
3912 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
3913 | /// </summary> | |||
3914 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
3915 | /// the final storage for the archive.</returns> | |||
3916 | /// <seealso cref="GetTemporaryOutput"/> | |||
3917 | public abstract Stream ConvertTemporaryToFinal(); | |||
3918 | | |||
3919 | /// <summary> | |||
3920 | /// Make a temporary copy of a <see cref="Stream"/>. | |||
3921 | /// </summary> | |||
3922 | /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> | |||
3923 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3924 | public abstract Stream MakeTemporaryCopy(Stream stream); | |||
3925 | | |||
3926 | /// <summary> | |||
3927 | /// Return a stream suitable for performing direct updates on the original source. | |||
3928 | /// </summary> | |||
3929 | /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> | |||
3930 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3931 | public abstract Stream OpenForDirectUpdate(Stream stream); | |||
3932 | | |||
3933 | /// <summary> | |||
3934 | /// Disposes this instance. | |||
3935 | /// </summary> | |||
3936 | public abstract void Dispose(); | |||
3937 | | |||
3938 | /// <summary> | |||
3939 | /// Gets the update mode applicable. | |||
3940 | /// </summary> | |||
3941 | /// <value>The update mode.</value> | |||
3942 | public FileUpdateMode UpdateMode { | |||
3943 | get { | |||
3944 | return updateMode_; | |||
3945 | } | |||
3946 | } | |||
3947 | | |||
3948 | #endregion | |||
3949 | | |||
3950 | #region Instance Fields | |||
3951 | FileUpdateMode updateMode_; | |||
3952 | #endregion | |||
3953 | } | |||
3954 | | |||
3955 | /// <summary> | |||
3956 | /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. | |||
3957 | /// </summary> | |||
3958 | public class DiskArchiveStorage : BaseArchiveStorage | |||
3959 | { | |||
3960 | #region Constructors | |||
3961 | /// <summary> | |||
3962 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3963 | /// </summary> | |||
3964 | /// <param name="file">The file.</param> | |||
3965 | /// <param name="updateMode">The update mode.</param> | |||
3966 | public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) | |||
| 11 | 3967 | : base(updateMode) | ||
3968 | { | |||
| 11 | 3969 | if (file.Name == null) { | ||
| 0 | 3970 | throw new ZipException("Cant handle non file archives"); | ||
3971 | } | |||
3972 | | |||
| 11 | 3973 | fileName_ = file.Name; | ||
| 11 | 3974 | } | ||
3975 | | |||
3976 | /// <summary> | |||
3977 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3978 | /// </summary> | |||
3979 | /// <param name="file">The file.</param> | |||
3980 | public DiskArchiveStorage(ZipFile file) | |||
| 10 | 3981 | : this(file, FileUpdateMode.Safe) | ||
3982 | { | |||
| 10 | 3983 | } | ||
3984 | #endregion | |||
3985 | | |||
3986 | #region IArchiveStorage Members | |||
3987 | | |||
3988 | /// <summary> | |||
3989 | /// Gets a temporary output <see cref="Stream"/> for performing updates on. | |||
3990 | /// </summary> | |||
3991 | /// <returns>Returns the temporary output stream.</returns> | |||
3992 | public override Stream GetTemporaryOutput() | |||
3993 | { | |||
| 2 | 3994 | if (temporaryName_ != null) { | ||
| 0 | 3995 | temporaryName_ = GetTempFileName(temporaryName_, true); | ||
| 0 | 3996 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | ||
| 0 | 3997 | } else { | ||
3998 | // Determine where to place files based on internal strategy. | |||
3999 | // Currently this is always done in system temp directory. | |||
| 2 | 4000 | temporaryName_ = Path.GetTempFileName(); | ||
| 2 | 4001 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | ||
4002 | } | |||
4003 | | |||
| 2 | 4004 | return temporaryStream_; | ||
4005 | } | |||
4006 | | |||
4007 | /// <summary> | |||
4008 | /// Converts a temporary <see cref="Stream"/> to its final form. | |||
4009 | /// </summary> | |||
4010 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4011 | /// the final storage for the archive.</returns> | |||
4012 | public override Stream ConvertTemporaryToFinal() | |||
4013 | { | |||
| 3 | 4014 | if (temporaryStream_ == null) { | ||
| 0 | 4015 | throw new ZipException("No temporary stream has been created"); | ||
4016 | } | |||
4017 | | |||
| 3 | 4018 | Stream result = null; | ||
4019 | | |||
| 3 | 4020 | string moveTempName = GetTempFileName(fileName_, false); | ||
| 3 | 4021 | bool newFileCreated = false; | ||
4022 | | |||
4023 | try { | |||
| 3 | 4024 | temporaryStream_.Close(); | ||
| 3 | 4025 | File.Move(fileName_, moveTempName); | ||
| 3 | 4026 | File.Move(temporaryName_, fileName_); | ||
| 3 | 4027 | newFileCreated = true; | ||
| 3 | 4028 | File.Delete(moveTempName); | ||
4029 | | |||
| 3 | 4030 | result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
| 3 | 4031 | } catch (Exception) { | ||
| 0 | 4032 | result = null; | ||
4033 | | |||
4034 | // Try to roll back changes... | |||
| 0 | 4035 | if (!newFileCreated) { | ||
| 0 | 4036 | File.Move(moveTempName, fileName_); | ||
| 0 | 4037 | File.Delete(temporaryName_); | ||
4038 | } | |||
4039 | | |||
| 0 | 4040 | throw; | ||
4041 | } | |||
4042 | | |||
| 3 | 4043 | return result; | ||
4044 | } | |||
4045 | | |||
4046 | /// <summary> | |||
4047 | /// Make a temporary copy of a stream. | |||
4048 | /// </summary> | |||
4049 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4050 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4051 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4052 | { | |||
| 1 | 4053 | stream.Close(); | ||
4054 | | |||
| 1 | 4055 | temporaryName_ = GetTempFileName(fileName_, true); | ||
| 1 | 4056 | File.Copy(fileName_, temporaryName_, true); | ||
4057 | | |||
| 1 | 4058 | temporaryStream_ = new FileStream(temporaryName_, | ||
| 1 | 4059 | FileMode.Open, | ||
| 1 | 4060 | FileAccess.ReadWrite); | ||
| 1 | 4061 | return temporaryStream_; | ||
4062 | } | |||
4063 | | |||
4064 | /// <summary> | |||
4065 | /// Return a stream suitable for performing direct updates on the original source. | |||
4066 | /// </summary> | |||
4067 | /// <param name="stream">The current stream.</param> | |||
4068 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4069 | /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks> | |||
4070 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4071 | { | |||
4072 | Stream result; | |||
| 1 | 4073 | if ((stream == null) || !stream.CanWrite) { | ||
| 1 | 4074 | if (stream != null) { | ||
| 1 | 4075 | stream.Close(); | ||
4076 | } | |||
4077 | | |||
| 1 | 4078 | result = new FileStream(fileName_, | ||
| 1 | 4079 | FileMode.Open, | ||
| 1 | 4080 | FileAccess.ReadWrite); | ||
| 1 | 4081 | } else { | ||
| 0 | 4082 | result = stream; | ||
4083 | } | |||
4084 | | |||
| 1 | 4085 | return result; | ||
4086 | } | |||
4087 | | |||
4088 | /// <summary> | |||
4089 | /// Disposes this instance. | |||
4090 | /// </summary> | |||
4091 | public override void Dispose() | |||
4092 | { | |||
| 11 | 4093 | if (temporaryStream_ != null) { | ||
| 3 | 4094 | temporaryStream_.Close(); | ||
4095 | } | |||
| 11 | 4096 | } | ||
4097 | | |||
4098 | #endregion | |||
4099 | | |||
4100 | #region Internal routines | |||
4101 | static string GetTempFileName(string original, bool makeTempFile) | |||
4102 | { | |||
| 4 | 4103 | string result = null; | ||
4104 | | |||
| 4 | 4105 | if (original == null) { | ||
| 0 | 4106 | result = Path.GetTempFileName(); | ||
| 0 | 4107 | } else { | ||
| 4 | 4108 | int counter = 0; | ||
| 4 | 4109 | int suffixSeed = DateTime.Now.Second; | ||
4110 | | |||
| 9 | 4111 | while (result == null) { | ||
| 5 | 4112 | counter += 1; | ||
| 5 | 4113 | string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); | ||
| 5 | 4114 | if (!File.Exists(newName)) { | ||
| 4 | 4115 | if (makeTempFile) { | ||
4116 | try { | |||
4117 | // Try and create the file. | |||
| 1 | 4118 | using (FileStream stream = File.Create(newName)) { | ||
| 1 | 4119 | } | ||
| 1 | 4120 | result = newName; | ||
| 1 | 4121 | } catch { | ||
| 0 | 4122 | suffixSeed = DateTime.Now.Second; | ||
| 0 | 4123 | } | ||
4124 | } else { | |||
| 3 | 4125 | result = newName; | ||
4126 | } | |||
4127 | } | |||
4128 | } | |||
4129 | } | |||
| 4 | 4130 | return result; | ||
4131 | } | |||
4132 | #endregion | |||
4133 | | |||
4134 | #region Instance Fields | |||
4135 | Stream temporaryStream_; | |||
4136 | string fileName_; | |||
4137 | string temporaryName_; | |||
4138 | #endregion | |||
4139 | } | |||
4140 | | |||
4141 | /// <summary> | |||
4142 | /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. | |||
4143 | /// </summary> | |||
4144 | public class MemoryArchiveStorage : BaseArchiveStorage | |||
4145 | { | |||
4146 | #region Constructors | |||
4147 | /// <summary> | |||
4148 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4149 | /// </summary> | |||
4150 | public MemoryArchiveStorage() | |||
4151 | : base(FileUpdateMode.Direct) | |||
4152 | { | |||
4153 | } | |||
4154 | | |||
4155 | /// <summary> | |||
4156 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4157 | /// </summary> | |||
4158 | /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> | |||
4159 | /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> | |||
4160 | public MemoryArchiveStorage(FileUpdateMode updateMode) | |||
4161 | : base(updateMode) | |||
4162 | { | |||
4163 | } | |||
4164 | | |||
4165 | #endregion | |||
4166 | | |||
4167 | #region Properties | |||
4168 | /// <summary> | |||
4169 | /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. | |||
4170 | /// </summary> | |||
4171 | public MemoryStream FinalStream { | |||
4172 | get { return finalStream_; } | |||
4173 | } | |||
4174 | | |||
4175 | #endregion | |||
4176 | | |||
4177 | #region IArchiveStorage Members | |||
4178 | | |||
4179 | /// <summary> | |||
4180 | /// Gets the temporary output <see cref="Stream"/> | |||
4181 | /// </summary> | |||
4182 | /// <returns>Returns the temporary output stream.</returns> | |||
4183 | public override Stream GetTemporaryOutput() | |||
4184 | { | |||
4185 | temporaryStream_ = new MemoryStream(); | |||
4186 | return temporaryStream_; | |||
4187 | } | |||
4188 | | |||
4189 | /// <summary> | |||
4190 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
4191 | /// </summary> | |||
4192 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4193 | /// the final storage for the archive.</returns> | |||
4194 | public override Stream ConvertTemporaryToFinal() | |||
4195 | { | |||
4196 | if (temporaryStream_ == null) { | |||
4197 | throw new ZipException("No temporary stream has been created"); | |||
4198 | } | |||
4199 | | |||
4200 | finalStream_ = new MemoryStream(temporaryStream_.ToArray()); | |||
4201 | return finalStream_; | |||
4202 | } | |||
4203 | | |||
4204 | /// <summary> | |||
4205 | /// Make a temporary copy of the original stream. | |||
4206 | /// </summary> | |||
4207 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4208 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4209 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4210 | { | |||
4211 | temporaryStream_ = new MemoryStream(); | |||
4212 | stream.Position = 0; | |||
4213 | StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); | |||
4214 | return temporaryStream_; | |||
4215 | } | |||
4216 | | |||
4217 | /// <summary> | |||
4218 | /// Return a stream suitable for performing direct updates on the original source. | |||
4219 | /// </summary> | |||
4220 | /// <param name="stream">The original source stream</param> | |||
4221 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4222 | /// <remarks>If the <paramref name="stream"/> passed is not null this is used; | |||
4223 | /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> | |||
4224 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4225 | { | |||
4226 | Stream result; | |||
4227 | if ((stream == null) || !stream.CanWrite) { | |||
4228 | | |||
4229 | result = new MemoryStream(); | |||
4230 | | |||
4231 | if (stream != null) { | |||
4232 | stream.Position = 0; | |||
4233 | StreamUtils.Copy(stream, result, new byte[4096]); | |||
4234 | | |||
4235 | stream.Close(); | |||
4236 | } | |||
4237 | } else { | |||
4238 | result = stream; | |||
4239 | } | |||
4240 | | |||
4241 | return result; | |||
4242 | } | |||
4243 | | |||
4244 | /// <summary> | |||
4245 | /// Disposes this instance. | |||
4246 | /// </summary> | |||
4247 | public override void Dispose() | |||
4248 | { | |||
4249 | if (temporaryStream_ != null) { | |||
4250 | temporaryStream_.Close(); | |||
4251 | } | |||
4252 | } | |||
4253 | | |||
4254 | #endregion | |||
4255 | | |||
4256 | #region Instance Fields | |||
4257 | MemoryStream temporaryStream_; | |||
4258 | MemoryStream finalStream_; | |||
4259 | #endregion | |||
4260 | } | |||
4261 | | |||
4262 | #endregion | |||
4263 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.DynamicDiskDataSource |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs |
| Covered lines: | 4 |
| Uncovered lines: | 0 |
| Coverable lines: | 4 |
| Total lines: | 4263 |
| Line coverage: | 100% |
| Branch coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| GetSource(...) | 2 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.IO; | |||
4 | using System.Text; | |||
5 | using System.Globalization; | |||
6 | using System.Security.Cryptography; | |||
7 | using ICSharpCode.SharpZipLib.Encryption; | |||
8 | using ICSharpCode.SharpZipLib.Core; | |||
9 | using ICSharpCode.SharpZipLib.Checksum; | |||
10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
11 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
12 | | |||
13 | namespace ICSharpCode.SharpZipLib.Zip | |||
14 | { | |||
15 | #region Keys Required Event Args | |||
16 | /// <summary> | |||
17 | /// Arguments used with KeysRequiredEvent | |||
18 | /// </summary> | |||
19 | public class KeysRequiredEventArgs : EventArgs | |||
20 | { | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
24 | /// </summary> | |||
25 | /// <param name="name">The name of the file for which keys are required.</param> | |||
26 | public KeysRequiredEventArgs(string name) | |||
27 | { | |||
28 | fileName = name; | |||
29 | } | |||
30 | | |||
31 | /// <summary> | |||
32 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
33 | /// </summary> | |||
34 | /// <param name="name">The name of the file for which keys are required.</param> | |||
35 | /// <param name="keyValue">The current key value.</param> | |||
36 | public KeysRequiredEventArgs(string name, byte[] keyValue) | |||
37 | { | |||
38 | fileName = name; | |||
39 | key = keyValue; | |||
40 | } | |||
41 | | |||
42 | #endregion | |||
43 | #region Properties | |||
44 | /// <summary> | |||
45 | /// Gets the name of the file for which keys are required. | |||
46 | /// </summary> | |||
47 | public string FileName { | |||
48 | get { return fileName; } | |||
49 | } | |||
50 | | |||
51 | /// <summary> | |||
52 | /// Gets or sets the key value | |||
53 | /// </summary> | |||
54 | public byte[] Key { | |||
55 | get { return key; } | |||
56 | set { key = value; } | |||
57 | } | |||
58 | #endregion | |||
59 | | |||
60 | #region Instance Fields | |||
61 | string fileName; | |||
62 | byte[] key; | |||
63 | #endregion | |||
64 | } | |||
65 | #endregion | |||
66 | | |||
67 | #region Test Definitions | |||
68 | /// <summary> | |||
69 | /// The strategy to apply to testing. | |||
70 | /// </summary> | |||
71 | public enum TestStrategy | |||
72 | { | |||
73 | /// <summary> | |||
74 | /// Find the first error only. | |||
75 | /// </summary> | |||
76 | FindFirstError, | |||
77 | /// <summary> | |||
78 | /// Find all possible errors. | |||
79 | /// </summary> | |||
80 | FindAllErrors, | |||
81 | } | |||
82 | | |||
83 | /// <summary> | |||
84 | /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. | |||
85 | /// </summary> | |||
86 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
87 | public enum TestOperation | |||
88 | { | |||
89 | /// <summary> | |||
90 | /// Setting up testing. | |||
91 | /// </summary> | |||
92 | Initialising, | |||
93 | | |||
94 | /// <summary> | |||
95 | /// Testing an individual entries header | |||
96 | /// </summary> | |||
97 | EntryHeader, | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Testing an individual entries data | |||
101 | /// </summary> | |||
102 | EntryData, | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Testing an individual entry has completed. | |||
106 | /// </summary> | |||
107 | EntryComplete, | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Running miscellaneous tests | |||
111 | /// </summary> | |||
112 | MiscellaneousTests, | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Testing is complete | |||
116 | /// </summary> | |||
117 | Complete, | |||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. | |||
122 | /// </summary> | |||
123 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
124 | public class TestStatus | |||
125 | { | |||
126 | #region Constructors | |||
127 | /// <summary> | |||
128 | /// Initialise a new instance of <see cref="TestStatus"/> | |||
129 | /// </summary> | |||
130 | /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> | |||
131 | public TestStatus(ZipFile file) | |||
132 | { | |||
133 | file_ = file; | |||
134 | } | |||
135 | #endregion | |||
136 | | |||
137 | #region Properties | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Get the current <see cref="TestOperation"/> in progress. | |||
141 | /// </summary> | |||
142 | public TestOperation Operation { | |||
143 | get { return operation_; } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get the <see cref="ZipFile"/> this status is applicable to. | |||
148 | /// </summary> | |||
149 | public ZipFile File { | |||
150 | get { return file_; } | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Get the current/last entry tested. | |||
155 | /// </summary> | |||
156 | public ZipEntry Entry { | |||
157 | get { return entry_; } | |||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Get the number of errors detected so far. | |||
162 | /// </summary> | |||
163 | public int ErrorCount { | |||
164 | get { return errorCount_; } | |||
165 | } | |||
166 | | |||
167 | /// <summary> | |||
168 | /// Get the number of bytes tested so far for the current entry. | |||
169 | /// </summary> | |||
170 | public long BytesTested { | |||
171 | get { return bytesTested_; } | |||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Get a value indicating wether the last entry test was valid. | |||
176 | /// </summary> | |||
177 | public bool EntryValid { | |||
178 | get { return entryValid_; } | |||
179 | } | |||
180 | #endregion | |||
181 | | |||
182 | #region Internal API | |||
183 | internal void AddError() | |||
184 | { | |||
185 | errorCount_++; | |||
186 | entryValid_ = false; | |||
187 | } | |||
188 | | |||
189 | internal void SetOperation(TestOperation operation) | |||
190 | { | |||
191 | operation_ = operation; | |||
192 | } | |||
193 | | |||
194 | internal void SetEntry(ZipEntry entry) | |||
195 | { | |||
196 | entry_ = entry; | |||
197 | entryValid_ = true; | |||
198 | bytesTested_ = 0; | |||
199 | } | |||
200 | | |||
201 | internal void SetBytesTested(long value) | |||
202 | { | |||
203 | bytesTested_ = value; | |||
204 | } | |||
205 | #endregion | |||
206 | | |||
207 | #region Instance Fields | |||
208 | ZipFile file_; | |||
209 | ZipEntry entry_; | |||
210 | bool entryValid_; | |||
211 | int errorCount_; | |||
212 | long bytesTested_; | |||
213 | TestOperation operation_; | |||
214 | #endregion | |||
215 | } | |||
216 | | |||
217 | /// <summary> | |||
218 | /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if | |||
219 | /// </summary> | |||
220 | /// <remarks>If the message is non-null an error has occured. If the message is null | |||
221 | /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> | |||
222 | public delegate void ZipTestResultHandler(TestStatus status, string message); | |||
223 | #endregion | |||
224 | | |||
225 | #region Update Definitions | |||
226 | /// <summary> | |||
227 | /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. | |||
228 | /// </summary> | |||
229 | public enum FileUpdateMode | |||
230 | { | |||
231 | /// <summary> | |||
232 | /// Perform all updates on temporary files ensuring that the original file is saved. | |||
233 | /// </summary> | |||
234 | Safe, | |||
235 | /// <summary> | |||
236 | /// Update the archive directly, which is faster but less safe. | |||
237 | /// </summary> | |||
238 | Direct, | |||
239 | } | |||
240 | #endregion | |||
241 | | |||
242 | #region ZipFile Class | |||
243 | /// <summary> | |||
244 | /// This class represents a Zip archive. You can ask for the contained | |||
245 | /// entries, or get an input stream for a file entry. The entry is | |||
246 | /// automatically decompressed. | |||
247 | /// | |||
248 | /// You can also update the archive adding or deleting entries. | |||
249 | /// | |||
250 | /// This class is thread safe for input: You can open input streams for arbitrary | |||
251 | /// entries in different threads. | |||
252 | /// <br/> | |||
253 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
254 | /// </summary> | |||
255 | /// <example> | |||
256 | /// <code> | |||
257 | /// using System; | |||
258 | /// using System.Text; | |||
259 | /// using System.Collections; | |||
260 | /// using System.IO; | |||
261 | /// | |||
262 | /// using ICSharpCode.SharpZipLib.Zip; | |||
263 | /// | |||
264 | /// class MainClass | |||
265 | /// { | |||
266 | /// static public void Main(string[] args) | |||
267 | /// { | |||
268 | /// using (ZipFile zFile = new ZipFile(args[0])) { | |||
269 | /// Console.WriteLine("Listing of : " + zFile.Name); | |||
270 | /// Console.WriteLine(""); | |||
271 | /// Console.WriteLine("Raw Size Size Date Time Name"); | |||
272 | /// Console.WriteLine("-------- -------- -------- ------ ---------"); | |||
273 | /// foreach (ZipEntry e in zFile) { | |||
274 | /// if ( e.IsFile ) { | |||
275 | /// DateTime d = e.DateTime; | |||
276 | /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, | |||
277 | /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), | |||
278 | /// e.Name); | |||
279 | /// } | |||
280 | /// } | |||
281 | /// } | |||
282 | /// } | |||
283 | /// } | |||
284 | /// </code> | |||
285 | /// </example> | |||
286 | public class ZipFile : IEnumerable, IDisposable | |||
287 | { | |||
288 | #region KeyHandling | |||
289 | | |||
290 | /// <summary> | |||
291 | /// Delegate for handling keys/password setting during compresion/decompression. | |||
292 | /// </summary> | |||
293 | public delegate void KeysRequiredEventHandler( | |||
294 | object sender, | |||
295 | KeysRequiredEventArgs e | |||
296 | ); | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Event handler for handling encryption keys. | |||
300 | /// </summary> | |||
301 | public KeysRequiredEventHandler KeysRequired; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Handles getting of encryption keys when required. | |||
305 | /// </summary> | |||
306 | /// <param name="fileName">The file for which encryption keys are required.</param> | |||
307 | void OnKeysRequired(string fileName) | |||
308 | { | |||
309 | if (KeysRequired != null) { | |||
310 | var krea = new KeysRequiredEventArgs(fileName, key); | |||
311 | KeysRequired(this, krea); | |||
312 | key = krea.Key; | |||
313 | } | |||
314 | } | |||
315 | | |||
316 | /// <summary> | |||
317 | /// Get/set the encryption key value. | |||
318 | /// </summary> | |||
319 | byte[] Key { | |||
320 | get { return key; } | |||
321 | set { key = value; } | |||
322 | } | |||
323 | | |||
324 | /// <summary> | |||
325 | /// Password to be used for encrypting/decrypting files. | |||
326 | /// </summary> | |||
327 | /// <remarks>Set to null if no password is required.</remarks> | |||
328 | public string Password { | |||
329 | set { | |||
330 | if (string.IsNullOrEmpty(value)) { | |||
331 | key = null; | |||
332 | } else { | |||
333 | rawPassword_ = value; | |||
334 | key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); | |||
335 | } | |||
336 | } | |||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Get a value indicating wether encryption keys are currently available. | |||
341 | /// </summary> | |||
342 | bool HaveKeys { | |||
343 | get { return key != null; } | |||
344 | } | |||
345 | #endregion | |||
346 | | |||
347 | #region Constructors | |||
348 | /// <summary> | |||
349 | /// Opens a Zip file with the given name for reading. | |||
350 | /// </summary> | |||
351 | /// <param name="name">The name of the file to open.</param> | |||
352 | /// <exception cref="ArgumentNullException">The argument supplied is null.</exception> | |||
353 | /// <exception cref="IOException"> | |||
354 | /// An i/o error occurs | |||
355 | /// </exception> | |||
356 | /// <exception cref="ZipException"> | |||
357 | /// The file doesn't contain a valid zip archive. | |||
358 | /// </exception> | |||
359 | public ZipFile(string name) | |||
360 | { | |||
361 | if (name == null) { | |||
362 | throw new ArgumentNullException(nameof(name)); | |||
363 | } | |||
364 | | |||
365 | name_ = name; | |||
366 | | |||
367 | baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
368 | isStreamOwner = true; | |||
369 | | |||
370 | try { | |||
371 | ReadEntries(); | |||
372 | } catch { | |||
373 | DisposeInternal(true); | |||
374 | throw; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Opens a Zip file reading the given <see cref="FileStream"/>. | |||
380 | /// </summary> | |||
381 | /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> | |||
382 | /// <exception cref="ArgumentNullException">The supplied argument is null.</exception> | |||
383 | /// <exception cref="IOException"> | |||
384 | /// An i/o error occurs. | |||
385 | /// </exception> | |||
386 | /// <exception cref="ZipException"> | |||
387 | /// The file doesn't contain a valid zip archive. | |||
388 | /// </exception> | |||
389 | public ZipFile(FileStream file) | |||
390 | { | |||
391 | if (file == null) { | |||
392 | throw new ArgumentNullException(nameof(file)); | |||
393 | } | |||
394 | | |||
395 | if (!file.CanSeek) { | |||
396 | throw new ArgumentException("Stream is not seekable", nameof(file)); | |||
397 | } | |||
398 | | |||
399 | baseStream_ = file; | |||
400 | name_ = file.Name; | |||
401 | isStreamOwner = true; | |||
402 | | |||
403 | try { | |||
404 | ReadEntries(); | |||
405 | } catch { | |||
406 | DisposeInternal(true); | |||
407 | throw; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Opens a Zip file reading the given <see cref="Stream"/>. | |||
413 | /// </summary> | |||
414 | /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> | |||
415 | /// <exception cref="IOException"> | |||
416 | /// An i/o error occurs | |||
417 | /// </exception> | |||
418 | /// <exception cref="ZipException"> | |||
419 | /// The stream doesn't contain a valid zip archive.<br/> | |||
420 | /// </exception> | |||
421 | /// <exception cref="ArgumentException"> | |||
422 | /// The <see cref="Stream">stream</see> doesnt support seeking. | |||
423 | /// </exception> | |||
424 | /// <exception cref="ArgumentNullException"> | |||
425 | /// The <see cref="Stream">stream</see> argument is null. | |||
426 | /// </exception> | |||
427 | public ZipFile(Stream stream) | |||
428 | { | |||
429 | if (stream == null) { | |||
430 | throw new ArgumentNullException(nameof(stream)); | |||
431 | } | |||
432 | | |||
433 | if (!stream.CanSeek) { | |||
434 | throw new ArgumentException("Stream is not seekable", nameof(stream)); | |||
435 | } | |||
436 | | |||
437 | baseStream_ = stream; | |||
438 | isStreamOwner = true; | |||
439 | | |||
440 | if (baseStream_.Length > 0) { | |||
441 | try { | |||
442 | ReadEntries(); | |||
443 | } catch { | |||
444 | DisposeInternal(true); | |||
445 | throw; | |||
446 | } | |||
447 | } else { | |||
448 | entries_ = new ZipEntry[0]; | |||
449 | isNewArchive_ = true; | |||
450 | } | |||
451 | } | |||
452 | | |||
453 | /// <summary> | |||
454 | /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. | |||
455 | /// </summary> | |||
456 | internal ZipFile() | |||
457 | { | |||
458 | entries_ = new ZipEntry[0]; | |||
459 | isNewArchive_ = true; | |||
460 | } | |||
461 | | |||
462 | #endregion | |||
463 | | |||
464 | #region Destructors and Closing | |||
465 | /// <summary> | |||
466 | /// Finalize this instance. | |||
467 | /// </summary> | |||
468 | ~ZipFile() | |||
469 | { | |||
470 | Dispose(false); | |||
471 | } | |||
472 | | |||
473 | /// <summary> | |||
474 | /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying | |||
475 | /// Once closed, no further instance methods should be called. | |||
476 | /// </summary> | |||
477 | /// <exception cref="System.IO.IOException"> | |||
478 | /// An i/o error occurs. | |||
479 | /// </exception> | |||
480 | public void Close() | |||
481 | { | |||
482 | DisposeInternal(true); | |||
483 | GC.SuppressFinalize(this); | |||
484 | } | |||
485 | | |||
486 | #endregion | |||
487 | | |||
488 | #region Creators | |||
489 | /// <summary> | |||
490 | /// Create a new <see cref="ZipFile"/> whose data will be stored in a file. | |||
491 | /// </summary> | |||
492 | /// <param name="fileName">The name of the archive to create.</param> | |||
493 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
494 | /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception> | |||
495 | public static ZipFile Create(string fileName) | |||
496 | { | |||
497 | if (fileName == null) { | |||
498 | throw new ArgumentNullException(nameof(fileName)); | |||
499 | } | |||
500 | | |||
501 | FileStream fs = File.Create(fileName); | |||
502 | | |||
503 | var result = new ZipFile(); | |||
504 | result.name_ = fileName; | |||
505 | result.baseStream_ = fs; | |||
506 | result.isStreamOwner = true; | |||
507 | return result; | |||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. | |||
512 | /// </summary> | |||
513 | /// <param name="outStream">The stream providing data storage.</param> | |||
514 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
515 | /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception> | |||
516 | /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception> | |||
517 | public static ZipFile Create(Stream outStream) | |||
518 | { | |||
519 | if (outStream == null) { | |||
520 | throw new ArgumentNullException(nameof(outStream)); | |||
521 | } | |||
522 | | |||
523 | if (!outStream.CanWrite) { | |||
524 | throw new ArgumentException("Stream is not writeable", nameof(outStream)); | |||
525 | } | |||
526 | | |||
527 | if (!outStream.CanSeek) { | |||
528 | throw new ArgumentException("Stream is not seekable", nameof(outStream)); | |||
529 | } | |||
530 | | |||
531 | var result = new ZipFile(); | |||
532 | result.baseStream_ = outStream; | |||
533 | return result; | |||
534 | } | |||
535 | | |||
536 | #endregion | |||
537 | | |||
538 | #region Properties | |||
539 | /// <summary> | |||
540 | /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. | |||
541 | /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. | |||
542 | /// </summary> | |||
543 | /// <remarks> | |||
544 | /// The default value is true in all cases. | |||
545 | /// </remarks> | |||
546 | public bool IsStreamOwner { | |||
547 | get { return isStreamOwner; } | |||
548 | set { isStreamOwner = value; } | |||
549 | } | |||
550 | | |||
551 | /// <summary> | |||
552 | /// Get a value indicating wether | |||
553 | /// this archive is embedded in another file or not. | |||
554 | /// </summary> | |||
555 | public bool IsEmbeddedArchive { | |||
556 | // Not strictly correct in all circumstances currently | |||
557 | get { return offsetOfFirstEntry > 0; } | |||
558 | } | |||
559 | | |||
560 | /// <summary> | |||
561 | /// Get a value indicating that this archive is a new one. | |||
562 | /// </summary> | |||
563 | public bool IsNewArchive { | |||
564 | get { return isNewArchive_; } | |||
565 | } | |||
566 | | |||
567 | /// <summary> | |||
568 | /// Gets the comment for the zip file. | |||
569 | /// </summary> | |||
570 | public string ZipFileComment { | |||
571 | get { return comment_; } | |||
572 | } | |||
573 | | |||
574 | /// <summary> | |||
575 | /// Gets the name of this zip file. | |||
576 | /// </summary> | |||
577 | public string Name { | |||
578 | get { return name_; } | |||
579 | } | |||
580 | | |||
581 | /// <summary> | |||
582 | /// Gets the number of entries in this zip file. | |||
583 | /// </summary> | |||
584 | /// <exception cref="InvalidOperationException"> | |||
585 | /// The Zip file has been closed. | |||
586 | /// </exception> | |||
587 | [Obsolete("Use the Count property instead")] | |||
588 | public int Size { | |||
589 | get { | |||
590 | return entries_.Length; | |||
591 | } | |||
592 | } | |||
593 | | |||
594 | /// <summary> | |||
595 | /// Get the number of entries contained in this <see cref="ZipFile"/>. | |||
596 | /// </summary> | |||
597 | public long Count { | |||
598 | get { | |||
599 | return entries_.Length; | |||
600 | } | |||
601 | } | |||
602 | | |||
603 | /// <summary> | |||
604 | /// Indexer property for ZipEntries | |||
605 | /// </summary> | |||
606 | [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] | |||
607 | public ZipEntry this[int index] { | |||
608 | get { | |||
609 | return (ZipEntry)entries_[index].Clone(); | |||
610 | } | |||
611 | } | |||
612 | | |||
613 | #endregion | |||
614 | | |||
615 | #region Input Handling | |||
616 | /// <summary> | |||
617 | /// Gets an enumerator for the Zip entries in this Zip file. | |||
618 | /// </summary> | |||
619 | /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> | |||
620 | /// <exception cref="ObjectDisposedException"> | |||
621 | /// The Zip file has been closed. | |||
622 | /// </exception> | |||
623 | public IEnumerator GetEnumerator() | |||
624 | { | |||
625 | if (isDisposed_) { | |||
626 | throw new ObjectDisposedException("ZipFile"); | |||
627 | } | |||
628 | | |||
629 | return new ZipEntryEnumerator(entries_); | |||
630 | } | |||
631 | | |||
632 | /// <summary> | |||
633 | /// Return the index of the entry with a matching name | |||
634 | /// </summary> | |||
635 | /// <param name="name">Entry name to find</param> | |||
636 | /// <param name="ignoreCase">If true the comparison is case insensitive</param> | |||
637 | /// <returns>The index position of the matching entry or -1 if not found</returns> | |||
638 | /// <exception cref="ObjectDisposedException"> | |||
639 | /// The Zip file has been closed. | |||
640 | /// </exception> | |||
641 | public int FindEntry(string name, bool ignoreCase) | |||
642 | { | |||
643 | if (isDisposed_) { | |||
644 | throw new ObjectDisposedException("ZipFile"); | |||
645 | } | |||
646 | | |||
647 | // TODO: This will be slow as the next ice age for huge archives! | |||
648 | for (int i = 0; i < entries_.Length; i++) { | |||
649 | if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) { | |||
650 | return i; | |||
651 | } | |||
652 | } | |||
653 | return -1; | |||
654 | } | |||
655 | | |||
656 | /// <summary> | |||
657 | /// Searches for a zip entry in this archive with the given name. | |||
658 | /// String comparisons are case insensitive | |||
659 | /// </summary> | |||
660 | /// <param name="name"> | |||
661 | /// The name to find. May contain directory components separated by slashes ('/'). | |||
662 | /// </param> | |||
663 | /// <returns> | |||
664 | /// A clone of the zip entry, or null if no entry with that name exists. | |||
665 | /// </returns> | |||
666 | /// <exception cref="ObjectDisposedException"> | |||
667 | /// The Zip file has been closed. | |||
668 | /// </exception> | |||
669 | public ZipEntry GetEntry(string name) | |||
670 | { | |||
671 | if (isDisposed_) { | |||
672 | throw new ObjectDisposedException("ZipFile"); | |||
673 | } | |||
674 | | |||
675 | int index = FindEntry(name, true); | |||
676 | return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; | |||
677 | } | |||
678 | | |||
679 | /// <summary> | |||
680 | /// Gets an input stream for reading the given zip entry data in an uncompressed form. | |||
681 | /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). | |||
682 | /// </summary> | |||
683 | /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> | |||
684 | /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> | |||
685 | /// <exception cref="ObjectDisposedException"> | |||
686 | /// The ZipFile has already been closed | |||
687 | /// </exception> | |||
688 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
689 | /// The compression method for the entry is unknown | |||
690 | /// </exception> | |||
691 | /// <exception cref="IndexOutOfRangeException"> | |||
692 | /// The entry is not found in the ZipFile | |||
693 | /// </exception> | |||
694 | public Stream GetInputStream(ZipEntry entry) | |||
695 | { | |||
696 | if (entry == null) { | |||
697 | throw new ArgumentNullException(nameof(entry)); | |||
698 | } | |||
699 | | |||
700 | if (isDisposed_) { | |||
701 | throw new ObjectDisposedException("ZipFile"); | |||
702 | } | |||
703 | | |||
704 | long index = entry.ZipFileIndex; | |||
705 | if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { | |||
706 | index = FindEntry(entry.Name, true); | |||
707 | if (index < 0) { | |||
708 | throw new ZipException("Entry cannot be found"); | |||
709 | } | |||
710 | } | |||
711 | return GetInputStream(index); | |||
712 | } | |||
713 | | |||
714 | /// <summary> | |||
715 | /// Creates an input stream reading a zip entry | |||
716 | /// </summary> | |||
717 | /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> | |||
718 | /// <returns> | |||
719 | /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> | |||
720 | /// </returns> | |||
721 | /// <exception cref="ObjectDisposedException"> | |||
722 | /// The ZipFile has already been closed | |||
723 | /// </exception> | |||
724 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
725 | /// The compression method for the entry is unknown | |||
726 | /// </exception> | |||
727 | /// <exception cref="IndexOutOfRangeException"> | |||
728 | /// The entry is not found in the ZipFile | |||
729 | /// </exception> | |||
730 | public Stream GetInputStream(long entryIndex) | |||
731 | { | |||
732 | if (isDisposed_) { | |||
733 | throw new ObjectDisposedException("ZipFile"); | |||
734 | } | |||
735 | | |||
736 | long start = LocateEntry(entries_[entryIndex]); | |||
737 | CompressionMethod method = entries_[entryIndex].CompressionMethod; | |||
738 | Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); | |||
739 | | |||
740 | if (entries_[entryIndex].IsCrypted == true) { | |||
741 | result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); | |||
742 | if (result == null) { | |||
743 | throw new ZipException("Unable to decrypt this entry"); | |||
744 | } | |||
745 | } | |||
746 | | |||
747 | switch (method) { | |||
748 | case CompressionMethod.Stored: | |||
749 | // read as is. | |||
750 | break; | |||
751 | | |||
752 | case CompressionMethod.Deflated: | |||
753 | // No need to worry about ownership and closing as underlying stream close does nothing. | |||
754 | result = new InflaterInputStream(result, new Inflater(true)); | |||
755 | break; | |||
756 | | |||
757 | default: | |||
758 | throw new ZipException("Unsupported compression method " + method); | |||
759 | } | |||
760 | | |||
761 | return result; | |||
762 | } | |||
763 | | |||
764 | #endregion | |||
765 | | |||
766 | #region Archive Testing | |||
767 | /// <summary> | |||
768 | /// Test an archive for integrity/validity | |||
769 | /// </summary> | |||
770 | /// <param name="testData">Perform low level data Crc check</param> | |||
771 | /// <returns>true if all tests pass, false otherwise</returns> | |||
772 | /// <remarks>Testing will terminate on the first error found.</remarks> | |||
773 | public bool TestArchive(bool testData) | |||
774 | { | |||
775 | return TestArchive(testData, TestStrategy.FindFirstError, null); | |||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Test an archive for integrity/validity | |||
780 | /// </summary> | |||
781 | /// <param name="testData">Perform low level data Crc check</param> | |||
782 | /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> | |||
783 | /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> | |||
784 | /// <returns>true if all tests pass, false otherwise</returns> | |||
785 | /// <exception cref="ObjectDisposedException">The object has already been closed.</exception> | |||
786 | public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) | |||
787 | { | |||
788 | if (isDisposed_) { | |||
789 | throw new ObjectDisposedException("ZipFile"); | |||
790 | } | |||
791 | | |||
792 | var status = new TestStatus(this); | |||
793 | | |||
794 | if (resultHandler != null) { | |||
795 | resultHandler(status, null); | |||
796 | } | |||
797 | | |||
798 | HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; | |||
799 | | |||
800 | bool testing = true; | |||
801 | | |||
802 | try { | |||
803 | int entryIndex = 0; | |||
804 | | |||
805 | while (testing && (entryIndex < Count)) { | |||
806 | if (resultHandler != null) { | |||
807 | status.SetEntry(this[entryIndex]); | |||
808 | status.SetOperation(TestOperation.EntryHeader); | |||
809 | resultHandler(status, null); | |||
810 | } | |||
811 | | |||
812 | try { | |||
813 | TestLocalHeader(this[entryIndex], test); | |||
814 | } catch (ZipException ex) { | |||
815 | status.AddError(); | |||
816 | | |||
817 | if (resultHandler != null) { | |||
818 | resultHandler(status, | |||
819 | string.Format("Exception during test - '{0}'", ex.Message)); | |||
820 | } | |||
821 | | |||
822 | testing &= strategy != TestStrategy.FindFirstError; | |||
823 | } | |||
824 | | |||
825 | if (testing && testData && this[entryIndex].IsFile) { | |||
826 | if (resultHandler != null) { | |||
827 | status.SetOperation(TestOperation.EntryData); | |||
828 | resultHandler(status, null); | |||
829 | } | |||
830 | | |||
831 | var crc = new Crc32(); | |||
832 | | |||
833 | using (Stream entryStream = this.GetInputStream(this[entryIndex])) { | |||
834 | | |||
835 | byte[] buffer = new byte[4096]; | |||
836 | long totalBytes = 0; | |||
837 | int bytesRead; | |||
838 | while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { | |||
839 | crc.Update(buffer, 0, bytesRead); | |||
840 | | |||
841 | if (resultHandler != null) { | |||
842 | totalBytes += bytesRead; | |||
843 | status.SetBytesTested(totalBytes); | |||
844 | resultHandler(status, null); | |||
845 | } | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | if (this[entryIndex].Crc != crc.Value) { | |||
850 | status.AddError(); | |||
851 | | |||
852 | if (resultHandler != null) { | |||
853 | resultHandler(status, "CRC mismatch"); | |||
854 | } | |||
855 | | |||
856 | testing &= strategy != TestStrategy.FindFirstError; | |||
857 | } | |||
858 | | |||
859 | if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
860 | var helper = new ZipHelperStream(baseStream_); | |||
861 | var data = new DescriptorData(); | |||
862 | helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); | |||
863 | if (this[entryIndex].Crc != data.Crc) { | |||
864 | status.AddError(); | |||
865 | } | |||
866 | | |||
867 | if (this[entryIndex].CompressedSize != data.CompressedSize) { | |||
868 | status.AddError(); | |||
869 | } | |||
870 | | |||
871 | if (this[entryIndex].Size != data.Size) { | |||
872 | status.AddError(); | |||
873 | } | |||
874 | } | |||
875 | } | |||
876 | | |||
877 | if (resultHandler != null) { | |||
878 | status.SetOperation(TestOperation.EntryComplete); | |||
879 | resultHandler(status, null); | |||
880 | } | |||
881 | | |||
882 | entryIndex += 1; | |||
883 | } | |||
884 | | |||
885 | if (resultHandler != null) { | |||
886 | status.SetOperation(TestOperation.MiscellaneousTests); | |||
887 | resultHandler(status, null); | |||
888 | } | |||
889 | | |||
890 | // TODO: the 'Corrina Johns' test where local headers are missing from | |||
891 | // the central directory. They are therefore invisible to many archivers. | |||
892 | } catch (Exception ex) { | |||
893 | status.AddError(); | |||
894 | | |||
895 | if (resultHandler != null) { | |||
896 | resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); | |||
897 | } | |||
898 | } | |||
899 | | |||
900 | if (resultHandler != null) { | |||
901 | status.SetOperation(TestOperation.Complete); | |||
902 | status.SetEntry(null); | |||
903 | resultHandler(status, null); | |||
904 | } | |||
905 | | |||
906 | return (status.ErrorCount == 0); | |||
907 | } | |||
908 | | |||
909 | [Flags] | |||
910 | enum HeaderTest | |||
911 | { | |||
912 | Extract = 0x01, // Check that this header represents an entry whose data can be extracted | |||
913 | Header = 0x02, // Check that this header contents are valid | |||
914 | } | |||
915 | | |||
916 | /// <summary> | |||
917 | /// Test a local header against that provided from the central directory | |||
918 | /// </summary> | |||
919 | /// <param name="entry"> | |||
920 | /// The entry to test against | |||
921 | /// </param> | |||
922 | /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> | |||
923 | /// <returns>The offset of the entries data in the file</returns> | |||
924 | long TestLocalHeader(ZipEntry entry, HeaderTest tests) | |||
925 | { | |||
926 | lock (baseStream_) { | |||
927 | bool testHeader = (tests & HeaderTest.Header) != 0; | |||
928 | bool testData = (tests & HeaderTest.Extract) != 0; | |||
929 | | |||
930 | baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); | |||
931 | if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { | |||
932 | throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset) | |||
933 | } | |||
934 | | |||
935 | var extractVersion = (short)(ReadLEUshort() & 0x00ff); | |||
936 | var localFlags = (short)ReadLEUshort(); | |||
937 | var compressionMethod = (short)ReadLEUshort(); | |||
938 | var fileTime = (short)ReadLEUshort(); | |||
939 | var fileDate = (short)ReadLEUshort(); | |||
940 | uint crcValue = ReadLEUint(); | |||
941 | long compressedSize = ReadLEUint(); | |||
942 | long size = ReadLEUint(); | |||
943 | int storedNameLength = ReadLEUshort(); | |||
944 | int extraDataLength = ReadLEUshort(); | |||
945 | | |||
946 | byte[] nameData = new byte[storedNameLength]; | |||
947 | StreamUtils.ReadFully(baseStream_, nameData); | |||
948 | | |||
949 | byte[] extraData = new byte[extraDataLength]; | |||
950 | StreamUtils.ReadFully(baseStream_, extraData); | |||
951 | | |||
952 | var localExtraData = new ZipExtraData(extraData); | |||
953 | | |||
954 | // Extra data / zip64 checks | |||
955 | if (localExtraData.Find(1)) { | |||
956 | // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 | |||
957 | // and size or compressedSize = MaxValue, due to rogue creators. | |||
958 | | |||
959 | size = localExtraData.ReadLong(); | |||
960 | compressedSize = localExtraData.ReadLong(); | |||
961 | | |||
962 | if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
963 | // These may be valid if patched later | |||
964 | if ((size != -1) && (size != entry.Size)) { | |||
965 | throw new ZipException("Size invalid for descriptor"); | |||
966 | } | |||
967 | | |||
968 | if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) { | |||
969 | throw new ZipException("Compressed size invalid for descriptor"); | |||
970 | } | |||
971 | } | |||
972 | } else { | |||
973 | // No zip64 extra data but entry requires it. | |||
974 | if ((extractVersion >= ZipConstants.VersionZip64) && | |||
975 | (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) { | |||
976 | throw new ZipException("Required Zip64 extended information missing"); | |||
977 | } | |||
978 | } | |||
979 | | |||
980 | if (testData) { | |||
981 | if (entry.IsFile) { | |||
982 | if (!entry.IsCompressionMethodSupported()) { | |||
983 | throw new ZipException("Compression method not supported"); | |||
984 | } | |||
985 | | |||
986 | if ((extractVersion > ZipConstants.VersionMadeBy) | |||
987 | || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { | |||
988 | throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract | |||
989 | } | |||
990 | | |||
991 | if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance | |||
992 | throw new ZipException("The library does not support the zip version required to extract this entry"); | |||
993 | } | |||
994 | } | |||
995 | } | |||
996 | | |||
997 | if (testHeader) { | |||
998 | if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. | |||
999 | (extractVersion != 10) && | |||
1000 | (extractVersion != 11) && | |||
1001 | (extractVersion != 20) && | |||
1002 | (extractVersion != 21) && | |||
1003 | (extractVersion != 25) && | |||
1004 | (extractVersion != 27) && | |||
1005 | (extractVersion != 45) && | |||
1006 | (extractVersion != 46) && | |||
1007 | (extractVersion != 50) && | |||
1008 | (extractVersion != 51) && | |||
1009 | (extractVersion != 52) && | |||
1010 | (extractVersion != 61) && | |||
1011 | (extractVersion != 62) && | |||
1012 | (extractVersion != 63) | |||
1013 | ) { | |||
1014 | throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersi | |||
1015 | } | |||
1016 | | |||
1017 | // Local entry flags dont have reserved bit set on. | |||
1018 | if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R | |||
1019 | throw new ZipException("Reserved bit flags cannot be set."); | |||
1020 | } | |||
1021 | | |||
1022 | // Encryption requires extract version >= 20 | |||
1023 | if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { | |||
1024 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0}) | |||
1025 | } | |||
1026 | | |||
1027 | // Strong encryption requires encryption flag to be set and extract version >= 50. | |||
1028 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1029 | if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) { | |||
1030 | throw new ZipException("Strong encryption flag set but encryption flag is not set"); | |||
1031 | } | |||
1032 | | |||
1033 | if (extractVersion < 50) { | |||
1034 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0 | |||
1035 | } | |||
1036 | } | |||
1037 | | |||
1038 | // Patched entries require extract version >= 27 | |||
1039 | if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { | |||
1040 | throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); | |||
1041 | } | |||
1042 | | |||
1043 | // Central header flags match local entry flags. | |||
1044 | if (localFlags != entry.Flags) { | |||
1045 | throw new ZipException("Central header/local header flags mismatch"); | |||
1046 | } | |||
1047 | | |||
1048 | // Central header compression method matches local entry | |||
1049 | if (entry.CompressionMethod != (CompressionMethod)compressionMethod) { | |||
1050 | throw new ZipException("Central header/local header compression method mismatch"); | |||
1051 | } | |||
1052 | | |||
1053 | if (entry.Version != extractVersion) { | |||
1054 | throw new ZipException("Extract version mismatch"); | |||
1055 | } | |||
1056 | | |||
1057 | // Strong encryption and extract version match | |||
1058 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1059 | if (extractVersion < 62) { | |||
1060 | throw new ZipException("Strong encryption flag set but version not high enough"); | |||
1061 | } | |||
1062 | } | |||
1063 | | |||
1064 | if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) { | |||
1065 | if ((fileTime != 0) || (fileDate != 0)) { | |||
1066 | throw new ZipException("Header masked set but date/time values non-zero"); | |||
1067 | } | |||
1068 | } | |||
1069 | | |||
1070 | if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) { | |||
1071 | if (crcValue != (uint)entry.Crc) { | |||
1072 | throw new ZipException("Central header/local header crc mismatch"); | |||
1073 | } | |||
1074 | } | |||
1075 | | |||
1076 | // Crc valid for empty entry. | |||
1077 | // This will also apply to streamed entries where size isnt known and the header cant be patched | |||
1078 | if ((size == 0) && (compressedSize == 0)) { | |||
1079 | if (crcValue != 0) { | |||
1080 | throw new ZipException("Invalid CRC for empty entry"); | |||
1081 | } | |||
1082 | } | |||
1083 | | |||
1084 | // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS str | |||
1085 | // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably | |||
1086 | if (entry.Name.Length > storedNameLength) { | |||
1087 | throw new ZipException("File name length mismatch"); | |||
1088 | } | |||
1089 | | |||
1090 | // Name data has already been read convert it and compare. | |||
1091 | string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); | |||
1092 | | |||
1093 | // Central directory and local entry name match | |||
1094 | if (localName != entry.Name) { | |||
1095 | throw new ZipException("Central header and local header file name mismatch"); | |||
1096 | } | |||
1097 | | |||
1098 | // Directories have zero actual size but can have compressed size | |||
1099 | if (entry.IsDirectory) { | |||
1100 | if (size > 0) { | |||
1101 | throw new ZipException("Directory cannot have size"); | |||
1102 | } | |||
1103 | | |||
1104 | // There may be other cases where the compressed size can be greater than this? | |||
1105 | // If so until details are known we will be strict. | |||
1106 | if (entry.IsCrypted) { | |||
1107 | if (compressedSize > ZipConstants.CryptoHeaderSize + 2) { | |||
1108 | throw new ZipException("Directory compressed size invalid"); | |||
1109 | } | |||
1110 | } else if (compressedSize > 2) { | |||
1111 | // When not compressed the directory size can validly be 2 bytes | |||
1112 | // if the true size wasnt known when data was originally being written. | |||
1113 | // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes | |||
1114 | throw new ZipException("Directory compressed size invalid"); | |||
1115 | } | |||
1116 | } | |||
1117 | | |||
1118 | if (!ZipNameTransform.IsValidName(localName, true)) { | |||
1119 | throw new ZipException("Name is invalid"); | |||
1120 | } | |||
1121 | } | |||
1122 | | |||
1123 | // Tests that apply to both data and header. | |||
1124 | | |||
1125 | // Size can be verified only if it is known in the local header. | |||
1126 | // it will always be known in the central header. | |||
1127 | if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || | |||
1128 | ((size > 0 || compressedSize > 0) && entry.Size > 0)) { | |||
1129 | | |||
1130 | if ((size != 0) | |||
1131 | && (size != entry.Size)) { | |||
1132 | throw new ZipException( | |||
1133 | string.Format("Size mismatch between central header({0}) and local header({1})", | |||
1134 | entry.Size, size)); | |||
1135 | } | |||
1136 | | |||
1137 | if ((compressedSize != 0) | |||
1138 | && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) { | |||
1139 | throw new ZipException( | |||
1140 | string.Format("Compressed size mismatch between central header({0}) and local header({1})", | |||
1141 | entry.CompressedSize, compressedSize)); | |||
1142 | } | |||
1143 | } | |||
1144 | | |||
1145 | int extraLength = storedNameLength + extraDataLength; | |||
1146 | return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; | |||
1147 | } | |||
1148 | } | |||
1149 | | |||
1150 | #endregion | |||
1151 | | |||
1152 | #region Updating | |||
1153 | | |||
1154 | const int DefaultBufferSize = 4096; | |||
1155 | | |||
1156 | /// <summary> | |||
1157 | /// The kind of update to apply. | |||
1158 | /// </summary> | |||
1159 | enum UpdateCommand | |||
1160 | { | |||
1161 | Copy, // Copy original file contents. | |||
1162 | Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. | |||
1163 | Add, // Add a new file to the archive. | |||
1164 | } | |||
1165 | | |||
1166 | #region Properties | |||
1167 | /// <summary> | |||
1168 | /// Get / set the <see cref="INameTransform"/> to apply to names when updating. | |||
1169 | /// </summary> | |||
1170 | public INameTransform NameTransform { | |||
1171 | get { | |||
1172 | return updateEntryFactory_.NameTransform; | |||
1173 | } | |||
1174 | | |||
1175 | set { | |||
1176 | updateEntryFactory_.NameTransform = value; | |||
1177 | } | |||
1178 | } | |||
1179 | | |||
1180 | /// <summary> | |||
1181 | /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values | |||
1182 | /// during updates. | |||
1183 | /// </summary> | |||
1184 | public IEntryFactory EntryFactory { | |||
1185 | get { | |||
1186 | return updateEntryFactory_; | |||
1187 | } | |||
1188 | | |||
1189 | set { | |||
1190 | if (value == null) { | |||
1191 | updateEntryFactory_ = new ZipEntryFactory(); | |||
1192 | } else { | |||
1193 | updateEntryFactory_ = value; | |||
1194 | } | |||
1195 | } | |||
1196 | } | |||
1197 | | |||
1198 | /// <summary> | |||
1199 | /// Get /set the buffer size to be used when updating this zip file. | |||
1200 | /// </summary> | |||
1201 | public int BufferSize { | |||
1202 | get { return bufferSize_; } | |||
1203 | set { | |||
1204 | if (value < 1024) { | |||
1205 | throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); | |||
1206 | } | |||
1207 | | |||
1208 | if (bufferSize_ != value) { | |||
1209 | bufferSize_ = value; | |||
1210 | copyBuffer_ = null; | |||
1211 | } | |||
1212 | } | |||
1213 | } | |||
1214 | | |||
1215 | /// <summary> | |||
1216 | /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. | |||
1217 | /// </summary> | |||
1218 | public bool IsUpdating { | |||
1219 | get { return updates_ != null; } | |||
1220 | } | |||
1221 | | |||
1222 | /// <summary> | |||
1223 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
1224 | /// </summary> | |||
1225 | public UseZip64 UseZip64 { | |||
1226 | get { return useZip64_; } | |||
1227 | set { useZip64_ = value; } | |||
1228 | } | |||
1229 | | |||
1230 | #endregion | |||
1231 | | |||
1232 | #region Immediate updating | |||
1233 | // TBD: Direct form of updating | |||
1234 | // | |||
1235 | // public void Update(IEntryMatcher deleteMatcher) | |||
1236 | // { | |||
1237 | // } | |||
1238 | // | |||
1239 | // public void Update(IScanner addScanner) | |||
1240 | // { | |||
1241 | // } | |||
1242 | #endregion | |||
1243 | | |||
1244 | #region Deferred Updating | |||
1245 | /// <summary> | |||
1246 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1247 | /// </summary> | |||
1248 | /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</p | |||
1249 | /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param | |||
1250 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1251 | /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception> | |||
1252 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1253 | public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) | |||
1254 | { | |||
1255 | if (archiveStorage == null) { | |||
1256 | throw new ArgumentNullException(nameof(archiveStorage)); | |||
1257 | } | |||
1258 | | |||
1259 | if (dataSource == null) { | |||
1260 | throw new ArgumentNullException(nameof(dataSource)); | |||
1261 | } | |||
1262 | | |||
1263 | if (isDisposed_) { | |||
1264 | throw new ObjectDisposedException("ZipFile"); | |||
1265 | } | |||
1266 | | |||
1267 | if (IsEmbeddedArchive) { | |||
1268 | throw new ZipException("Cannot update embedded/SFX archives"); | |||
1269 | } | |||
1270 | | |||
1271 | archiveStorage_ = archiveStorage; | |||
1272 | updateDataSource_ = dataSource; | |||
1273 | | |||
1274 | // NOTE: the baseStream_ may not currently support writing or seeking. | |||
1275 | | |||
1276 | updateIndex_ = new Hashtable(); | |||
1277 | | |||
1278 | updates_ = new ArrayList(entries_.Length); | |||
1279 | foreach (ZipEntry entry in entries_) { | |||
1280 | int index = updates_.Add(new ZipUpdate(entry)); | |||
1281 | updateIndex_.Add(entry.Name, index); | |||
1282 | } | |||
1283 | | |||
1284 | // We must sort by offset before using offset's calculated sizes | |||
1285 | updates_.Sort(new UpdateComparer()); | |||
1286 | | |||
1287 | int idx = 0; | |||
1288 | foreach (ZipUpdate update in updates_) { | |||
1289 | //If last entry, there is no next entry offset to use | |||
1290 | if (idx == updates_.Count - 1) | |||
1291 | break; | |||
1292 | | |||
1293 | update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; | |||
1294 | idx++; | |||
1295 | } | |||
1296 | updateCount_ = updates_.Count; | |||
1297 | | |||
1298 | contentsEdited_ = false; | |||
1299 | commentEdited_ = false; | |||
1300 | newComment_ = null; | |||
1301 | } | |||
1302 | | |||
1303 | /// <summary> | |||
1304 | /// Begin updating to this <see cref="ZipFile"/> archive. | |||
1305 | /// </summary> | |||
1306 | /// <param name="archiveStorage">The storage to use during the update.</param> | |||
1307 | public void BeginUpdate(IArchiveStorage archiveStorage) | |||
1308 | { | |||
1309 | BeginUpdate(archiveStorage, new DynamicDiskDataSource()); | |||
1310 | } | |||
1311 | | |||
1312 | /// <summary> | |||
1313 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1314 | /// </summary> | |||
1315 | /// <seealso cref="BeginUpdate(IArchiveStorage)"/> | |||
1316 | /// <seealso cref="CommitUpdate"></seealso> | |||
1317 | /// <seealso cref="AbortUpdate"></seealso> | |||
1318 | public void BeginUpdate() | |||
1319 | { | |||
1320 | if (Name == null) { | |||
1321 | BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); | |||
1322 | } else { | |||
1323 | BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); | |||
1324 | } | |||
1325 | } | |||
1326 | | |||
1327 | /// <summary> | |||
1328 | /// Commit current updates, updating this archive. | |||
1329 | /// </summary> | |||
1330 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1331 | /// <seealso cref="AbortUpdate"></seealso> | |||
1332 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1333 | public void CommitUpdate() | |||
1334 | { | |||
1335 | if (isDisposed_) { | |||
1336 | throw new ObjectDisposedException("ZipFile"); | |||
1337 | } | |||
1338 | | |||
1339 | CheckUpdating(); | |||
1340 | | |||
1341 | try { | |||
1342 | updateIndex_.Clear(); | |||
1343 | updateIndex_ = null; | |||
1344 | | |||
1345 | if (contentsEdited_) { | |||
1346 | RunUpdates(); | |||
1347 | } else if (commentEdited_) { | |||
1348 | UpdateCommentOnly(); | |||
1349 | } else { | |||
1350 | // Create an empty archive if none existed originally. | |||
1351 | if (entries_.Length == 0) { | |||
1352 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
1353 | using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) { | |||
1354 | zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); | |||
1355 | } | |||
1356 | } | |||
1357 | } | |||
1358 | | |||
1359 | } finally { | |||
1360 | PostUpdateCleanup(); | |||
1361 | } | |||
1362 | } | |||
1363 | | |||
1364 | /// <summary> | |||
1365 | /// Abort updating leaving the archive unchanged. | |||
1366 | /// </summary> | |||
1367 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1368 | /// <seealso cref="CommitUpdate"></seealso> | |||
1369 | public void AbortUpdate() | |||
1370 | { | |||
1371 | PostUpdateCleanup(); | |||
1372 | } | |||
1373 | | |||
1374 | /// <summary> | |||
1375 | /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. | |||
1376 | /// </summary> | |||
1377 | /// <param name="comment">The comment to record.</param> | |||
1378 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1379 | public void SetComment(string comment) | |||
1380 | { | |||
1381 | if (isDisposed_) { | |||
1382 | throw new ObjectDisposedException("ZipFile"); | |||
1383 | } | |||
1384 | | |||
1385 | CheckUpdating(); | |||
1386 | | |||
1387 | newComment_ = new ZipString(comment); | |||
1388 | | |||
1389 | if (newComment_.RawLength > 0xffff) { | |||
1390 | newComment_ = null; | |||
1391 | throw new ZipException("Comment length exceeds maximum - 65535"); | |||
1392 | } | |||
1393 | | |||
1394 | // We dont take account of the original and current comment appearing to be the same | |||
1395 | // as encoding may be different. | |||
1396 | commentEdited_ = true; | |||
1397 | } | |||
1398 | | |||
1399 | #endregion | |||
1400 | | |||
1401 | #region Adding Entries | |||
1402 | | |||
1403 | void AddUpdate(ZipUpdate update) | |||
1404 | { | |||
1405 | contentsEdited_ = true; | |||
1406 | | |||
1407 | int index = FindExistingUpdate(update.Entry.Name); | |||
1408 | | |||
1409 | if (index >= 0) { | |||
1410 | if (updates_[index] == null) { | |||
1411 | updateCount_ += 1; | |||
1412 | } | |||
1413 | | |||
1414 | // Direct replacement is faster than delete and add. | |||
1415 | updates_[index] = update; | |||
1416 | } else { | |||
1417 | index = updates_.Add(update); | |||
1418 | updateCount_ += 1; | |||
1419 | updateIndex_.Add(update.Entry.Name, index); | |||
1420 | } | |||
1421 | } | |||
1422 | | |||
1423 | /// <summary> | |||
1424 | /// Add a new entry to the archive. | |||
1425 | /// </summary> | |||
1426 | /// <param name="fileName">The name of the file to add.</param> | |||
1427 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1428 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> | |||
1429 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1430 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1431 | /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception> | |||
1432 | public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) | |||
1433 | { | |||
1434 | if (fileName == null) { | |||
1435 | throw new ArgumentNullException(nameof(fileName)); | |||
1436 | } | |||
1437 | | |||
1438 | if (isDisposed_) { | |||
1439 | throw new ObjectDisposedException("ZipFile"); | |||
1440 | } | |||
1441 | | |||
1442 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1443 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1444 | } | |||
1445 | | |||
1446 | CheckUpdating(); | |||
1447 | contentsEdited_ = true; | |||
1448 | | |||
1449 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1450 | entry.IsUnicodeText = useUnicodeText; | |||
1451 | entry.CompressionMethod = compressionMethod; | |||
1452 | | |||
1453 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1454 | } | |||
1455 | | |||
1456 | /// <summary> | |||
1457 | /// Add a new entry to the archive. | |||
1458 | /// </summary> | |||
1459 | /// <param name="fileName">The name of the file to add.</param> | |||
1460 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1461 | /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception> | |||
1462 | /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception> | |||
1463 | public void Add(string fileName, CompressionMethod compressionMethod) | |||
1464 | { | |||
1465 | if (fileName == null) { | |||
1466 | throw new ArgumentNullException(nameof(fileName)); | |||
1467 | } | |||
1468 | | |||
1469 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1470 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1471 | } | |||
1472 | | |||
1473 | CheckUpdating(); | |||
1474 | contentsEdited_ = true; | |||
1475 | | |||
1476 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1477 | entry.CompressionMethod = compressionMethod; | |||
1478 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1479 | } | |||
1480 | | |||
1481 | /// <summary> | |||
1482 | /// Add a file to the archive. | |||
1483 | /// </summary> | |||
1484 | /// <param name="fileName">The name of the file to add.</param> | |||
1485 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1486 | public void Add(string fileName) | |||
1487 | { | |||
1488 | if (fileName == null) { | |||
1489 | throw new ArgumentNullException(nameof(fileName)); | |||
1490 | } | |||
1491 | | |||
1492 | CheckUpdating(); | |||
1493 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); | |||
1494 | } | |||
1495 | | |||
1496 | /// <summary> | |||
1497 | /// Add a file to the archive. | |||
1498 | /// </summary> | |||
1499 | /// <param name="fileName">The name of the file to add.</param> | |||
1500 | /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param> | |||
1501 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1502 | public void Add(string fileName, string entryName) | |||
1503 | { | |||
1504 | if (fileName == null) { | |||
1505 | throw new ArgumentNullException(nameof(fileName)); | |||
1506 | } | |||
1507 | | |||
1508 | if (entryName == null) { | |||
1509 | throw new ArgumentNullException(nameof(entryName)); | |||
1510 | } | |||
1511 | | |||
1512 | CheckUpdating(); | |||
1513 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); | |||
1514 | } | |||
1515 | | |||
1516 | | |||
1517 | /// <summary> | |||
1518 | /// Add a file entry with data. | |||
1519 | /// </summary> | |||
1520 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1521 | /// <param name="entryName">The name to give to the entry.</param> | |||
1522 | public void Add(IStaticDataSource dataSource, string entryName) | |||
1523 | { | |||
1524 | if (dataSource == null) { | |||
1525 | throw new ArgumentNullException(nameof(dataSource)); | |||
1526 | } | |||
1527 | | |||
1528 | if (entryName == null) { | |||
1529 | throw new ArgumentNullException(nameof(entryName)); | |||
1530 | } | |||
1531 | | |||
1532 | CheckUpdating(); | |||
1533 | AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); | |||
1534 | } | |||
1535 | | |||
1536 | /// <summary> | |||
1537 | /// Add a file entry with data. | |||
1538 | /// </summary> | |||
1539 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1540 | /// <param name="entryName">The name to give to the entry.</param> | |||
1541 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1542 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
1543 | { | |||
1544 | if (dataSource == null) { | |||
1545 | throw new ArgumentNullException(nameof(dataSource)); | |||
1546 | } | |||
1547 | | |||
1548 | if (entryName == null) { | |||
1549 | throw new ArgumentNullException(nameof(entryName)); | |||
1550 | } | |||
1551 | | |||
1552 | CheckUpdating(); | |||
1553 | | |||
1554 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1555 | entry.CompressionMethod = compressionMethod; | |||
1556 | | |||
1557 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1558 | } | |||
1559 | | |||
1560 | /// <summary> | |||
1561 | /// Add a file entry with data. | |||
1562 | /// </summary> | |||
1563 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1564 | /// <param name="entryName">The name to give to the entry.</param> | |||
1565 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1566 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> | |||
1567 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicode | |||
1568 | { | |||
1569 | if (dataSource == null) { | |||
1570 | throw new ArgumentNullException(nameof(dataSource)); | |||
1571 | } | |||
1572 | | |||
1573 | if (entryName == null) { | |||
1574 | throw new ArgumentNullException(nameof(entryName)); | |||
1575 | } | |||
1576 | | |||
1577 | CheckUpdating(); | |||
1578 | | |||
1579 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1580 | entry.IsUnicodeText = useUnicodeText; | |||
1581 | entry.CompressionMethod = compressionMethod; | |||
1582 | | |||
1583 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1584 | } | |||
1585 | | |||
1586 | /// <summary> | |||
1587 | /// Add a <see cref="ZipEntry"/> that contains no data. | |||
1588 | /// </summary> | |||
1589 | /// <param name="entry">The entry to add.</param> | |||
1590 | /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> | |||
1591 | public void Add(ZipEntry entry) | |||
1592 | { | |||
1593 | if (entry == null) { | |||
1594 | throw new ArgumentNullException(nameof(entry)); | |||
1595 | } | |||
1596 | | |||
1597 | CheckUpdating(); | |||
1598 | | |||
1599 | if ((entry.Size != 0) || (entry.CompressedSize != 0)) { | |||
1600 | throw new ZipException("Entry cannot have any data"); | |||
1601 | } | |||
1602 | | |||
1603 | AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); | |||
1604 | } | |||
1605 | | |||
1606 | /// <summary> | |||
1607 | /// Add a directory entry to the archive. | |||
1608 | /// </summary> | |||
1609 | /// <param name="directoryName">The directory to add.</param> | |||
1610 | public void AddDirectory(string directoryName) | |||
1611 | { | |||
1612 | if (directoryName == null) { | |||
1613 | throw new ArgumentNullException(nameof(directoryName)); | |||
1614 | } | |||
1615 | | |||
1616 | CheckUpdating(); | |||
1617 | | |||
1618 | ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); | |||
1619 | AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); | |||
1620 | } | |||
1621 | | |||
1622 | #endregion | |||
1623 | | |||
1624 | #region Modifying Entries | |||
1625 | /* Modify not yet ready for public consumption. | |||
1626 | Direct modification of an entry should not overwrite original data before its read. | |||
1627 | Safe mode is trivial in this sense. | |||
1628 | public void Modify(ZipEntry original, ZipEntry updated) | |||
1629 | { | |||
1630 | if ( original == null ) { | |||
1631 | throw new ArgumentNullException("original"); | |||
1632 | } | |||
1633 | | |||
1634 | if ( updated == null ) { | |||
1635 | throw new ArgumentNullException("updated"); | |||
1636 | } | |||
1637 | | |||
1638 | CheckUpdating(); | |||
1639 | contentsEdited_ = true; | |||
1640 | updates_.Add(new ZipUpdate(original, updated)); | |||
1641 | } | |||
1642 | */ | |||
1643 | #endregion | |||
1644 | | |||
1645 | #region Deleting Entries | |||
1646 | /// <summary> | |||
1647 | /// Delete an entry by name | |||
1648 | /// </summary> | |||
1649 | /// <param name="fileName">The filename to delete</param> | |||
1650 | /// <returns>True if the entry was found and deleted; false otherwise.</returns> | |||
1651 | public bool Delete(string fileName) | |||
1652 | { | |||
1653 | if (fileName == null) { | |||
1654 | throw new ArgumentNullException(nameof(fileName)); | |||
1655 | } | |||
1656 | | |||
1657 | CheckUpdating(); | |||
1658 | | |||
1659 | bool result = false; | |||
1660 | int index = FindExistingUpdate(fileName); | |||
1661 | if ((index >= 0) && (updates_[index] != null)) { | |||
1662 | result = true; | |||
1663 | contentsEdited_ = true; | |||
1664 | updates_[index] = null; | |||
1665 | updateCount_ -= 1; | |||
1666 | } else { | |||
1667 | throw new ZipException("Cannot find entry to delete"); | |||
1668 | } | |||
1669 | return result; | |||
1670 | } | |||
1671 | | |||
1672 | /// <summary> | |||
1673 | /// Delete a <see cref="ZipEntry"/> from the archive. | |||
1674 | /// </summary> | |||
1675 | /// <param name="entry">The entry to delete.</param> | |||
1676 | public void Delete(ZipEntry entry) | |||
1677 | { | |||
1678 | if (entry == null) { | |||
1679 | throw new ArgumentNullException(nameof(entry)); | |||
1680 | } | |||
1681 | | |||
1682 | CheckUpdating(); | |||
1683 | | |||
1684 | int index = FindExistingUpdate(entry); | |||
1685 | if (index >= 0) { | |||
1686 | contentsEdited_ = true; | |||
1687 | updates_[index] = null; | |||
1688 | updateCount_ -= 1; | |||
1689 | } else { | |||
1690 | throw new ZipException("Cannot find entry to delete"); | |||
1691 | } | |||
1692 | } | |||
1693 | | |||
1694 | #endregion | |||
1695 | | |||
1696 | #region Update Support | |||
1697 | | |||
1698 | #region Writing Values/Headers | |||
1699 | void WriteLEShort(int value) | |||
1700 | { | |||
1701 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1702 | baseStream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
1703 | } | |||
1704 | | |||
1705 | /// <summary> | |||
1706 | /// Write an unsigned short in little endian byte order. | |||
1707 | /// </summary> | |||
1708 | void WriteLEUshort(ushort value) | |||
1709 | { | |||
1710 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1711 | baseStream_.WriteByte((byte)(value >> 8)); | |||
1712 | } | |||
1713 | | |||
1714 | /// <summary> | |||
1715 | /// Write an int in little endian byte order. | |||
1716 | /// </summary> | |||
1717 | void WriteLEInt(int value) | |||
1718 | { | |||
1719 | WriteLEShort(value & 0xffff); | |||
1720 | WriteLEShort(value >> 16); | |||
1721 | } | |||
1722 | | |||
1723 | /// <summary> | |||
1724 | /// Write an unsigned int in little endian byte order. | |||
1725 | /// </summary> | |||
1726 | void WriteLEUint(uint value) | |||
1727 | { | |||
1728 | WriteLEUshort((ushort)(value & 0xffff)); | |||
1729 | WriteLEUshort((ushort)(value >> 16)); | |||
1730 | } | |||
1731 | | |||
1732 | /// <summary> | |||
1733 | /// Write a long in little endian byte order. | |||
1734 | /// </summary> | |||
1735 | void WriteLeLong(long value) | |||
1736 | { | |||
1737 | WriteLEInt((int)(value & 0xffffffff)); | |||
1738 | WriteLEInt((int)(value >> 32)); | |||
1739 | } | |||
1740 | | |||
1741 | void WriteLEUlong(ulong value) | |||
1742 | { | |||
1743 | WriteLEUint((uint)(value & 0xffffffff)); | |||
1744 | WriteLEUint((uint)(value >> 32)); | |||
1745 | } | |||
1746 | | |||
1747 | void WriteLocalEntryHeader(ZipUpdate update) | |||
1748 | { | |||
1749 | ZipEntry entry = update.OutEntry; | |||
1750 | | |||
1751 | // TODO: Local offset will require adjusting for multi-disk zip files. | |||
1752 | entry.Offset = baseStream_.Position; | |||
1753 | | |||
1754 | // TODO: Need to clear any entry flags that dont make sense or throw an exception here. | |||
1755 | if (update.Command != UpdateCommand.Copy) { | |||
1756 | if (entry.CompressionMethod == CompressionMethod.Deflated) { | |||
1757 | if (entry.Size == 0) { | |||
1758 | // No need to compress - no data. | |||
1759 | entry.CompressedSize = entry.Size; | |||
1760 | entry.Crc = 0; | |||
1761 | entry.CompressionMethod = CompressionMethod.Stored; | |||
1762 | } | |||
1763 | } else if (entry.CompressionMethod == CompressionMethod.Stored) { | |||
1764 | entry.Flags &= ~(int)GeneralBitFlags.Descriptor; | |||
1765 | } | |||
1766 | | |||
1767 | if (HaveKeys) { | |||
1768 | entry.IsCrypted = true; | |||
1769 | if (entry.Crc < 0) { | |||
1770 | entry.Flags |= (int)GeneralBitFlags.Descriptor; | |||
1771 | } | |||
1772 | } else { | |||
1773 | entry.IsCrypted = false; | |||
1774 | } | |||
1775 | | |||
1776 | switch (useZip64_) { | |||
1777 | case UseZip64.Dynamic: | |||
1778 | if (entry.Size < 0) { | |||
1779 | entry.ForceZip64(); | |||
1780 | } | |||
1781 | break; | |||
1782 | | |||
1783 | case UseZip64.On: | |||
1784 | entry.ForceZip64(); | |||
1785 | break; | |||
1786 | | |||
1787 | case UseZip64.Off: | |||
1788 | // Do nothing. The entry itself may be using Zip64 independantly. | |||
1789 | break; | |||
1790 | } | |||
1791 | } | |||
1792 | | |||
1793 | // Write the local file header | |||
1794 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
1795 | | |||
1796 | WriteLEShort(entry.Version); | |||
1797 | WriteLEShort(entry.Flags); | |||
1798 | | |||
1799 | WriteLEShort((byte)entry.CompressionMethod); | |||
1800 | WriteLEInt((int)entry.DosTime); | |||
1801 | | |||
1802 | if (!entry.HasCrc) { | |||
1803 | // Note patch address for updating CRC later. | |||
1804 | update.CrcPatchOffset = baseStream_.Position; | |||
1805 | WriteLEInt((int)0); | |||
1806 | } else { | |||
1807 | WriteLEInt(unchecked((int)entry.Crc)); | |||
1808 | } | |||
1809 | | |||
1810 | if (entry.LocalHeaderRequiresZip64) { | |||
1811 | WriteLEInt(-1); | |||
1812 | WriteLEInt(-1); | |||
1813 | } else { | |||
1814 | if ((entry.CompressedSize < 0) || (entry.Size < 0)) { | |||
1815 | update.SizePatchOffset = baseStream_.Position; | |||
1816 | } | |||
1817 | | |||
1818 | WriteLEInt((int)entry.CompressedSize); | |||
1819 | WriteLEInt((int)entry.Size); | |||
1820 | } | |||
1821 | | |||
1822 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1823 | | |||
1824 | if (name.Length > 0xFFFF) { | |||
1825 | throw new ZipException("Entry name too long."); | |||
1826 | } | |||
1827 | | |||
1828 | var ed = new ZipExtraData(entry.ExtraData); | |||
1829 | | |||
1830 | if (entry.LocalHeaderRequiresZip64) { | |||
1831 | ed.StartNewEntry(); | |||
1832 | | |||
1833 | // Local entry header always includes size and compressed size. | |||
1834 | // NOTE the order of these fields is reversed when compared to the normal headers! | |||
1835 | ed.AddLeLong(entry.Size); | |||
1836 | ed.AddLeLong(entry.CompressedSize); | |||
1837 | ed.AddNewEntry(1); | |||
1838 | } else { | |||
1839 | ed.Delete(1); | |||
1840 | } | |||
1841 | | |||
1842 | entry.ExtraData = ed.GetEntryData(); | |||
1843 | | |||
1844 | WriteLEShort(name.Length); | |||
1845 | WriteLEShort(entry.ExtraData.Length); | |||
1846 | | |||
1847 | if (name.Length > 0) { | |||
1848 | baseStream_.Write(name, 0, name.Length); | |||
1849 | } | |||
1850 | | |||
1851 | if (entry.LocalHeaderRequiresZip64) { | |||
1852 | if (!ed.Find(1)) { | |||
1853 | throw new ZipException("Internal error cannot find extra data"); | |||
1854 | } | |||
1855 | | |||
1856 | update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; | |||
1857 | } | |||
1858 | | |||
1859 | if (entry.ExtraData.Length > 0) { | |||
1860 | baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); | |||
1861 | } | |||
1862 | } | |||
1863 | | |||
1864 | int WriteCentralDirectoryHeader(ZipEntry entry) | |||
1865 | { | |||
1866 | if (entry.CompressedSize < 0) { | |||
1867 | throw new ZipException("Attempt to write central directory entry with unknown csize"); | |||
1868 | } | |||
1869 | | |||
1870 | if (entry.Size < 0) { | |||
1871 | throw new ZipException("Attempt to write central directory entry with unknown size"); | |||
1872 | } | |||
1873 | | |||
1874 | if (entry.Crc < 0) { | |||
1875 | throw new ZipException("Attempt to write central directory entry with unknown crc"); | |||
1876 | } | |||
1877 | | |||
1878 | // Write the central file header | |||
1879 | WriteLEInt(ZipConstants.CentralHeaderSignature); | |||
1880 | | |||
1881 | // Version made by | |||
1882 | WriteLEShort(ZipConstants.VersionMadeBy); | |||
1883 | | |||
1884 | // Version required to extract | |||
1885 | WriteLEShort(entry.Version); | |||
1886 | | |||
1887 | WriteLEShort(entry.Flags); | |||
1888 | | |||
1889 | unchecked { | |||
1890 | WriteLEShort((byte)entry.CompressionMethod); | |||
1891 | WriteLEInt((int)entry.DosTime); | |||
1892 | WriteLEInt((int)entry.Crc); | |||
1893 | } | |||
1894 | | |||
1895 | if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { | |||
1896 | WriteLEInt(-1); | |||
1897 | } else { | |||
1898 | WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); | |||
1899 | } | |||
1900 | | |||
1901 | if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { | |||
1902 | WriteLEInt(-1); | |||
1903 | } else { | |||
1904 | WriteLEInt((int)entry.Size); | |||
1905 | } | |||
1906 | | |||
1907 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1908 | | |||
1909 | if (name.Length > 0xFFFF) { | |||
1910 | throw new ZipException("Entry name is too long."); | |||
1911 | } | |||
1912 | | |||
1913 | WriteLEShort(name.Length); | |||
1914 | | |||
1915 | // Central header extra data is different to local header version so regenerate. | |||
1916 | var ed = new ZipExtraData(entry.ExtraData); | |||
1917 | | |||
1918 | if (entry.CentralHeaderRequiresZip64) { | |||
1919 | ed.StartNewEntry(); | |||
1920 | | |||
1921 | if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1922 | ed.AddLeLong(entry.Size); | |||
1923 | } | |||
1924 | | |||
1925 | if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1926 | ed.AddLeLong(entry.CompressedSize); | |||
1927 | } | |||
1928 | | |||
1929 | if (entry.Offset >= 0xffffffff) { | |||
1930 | ed.AddLeLong(entry.Offset); | |||
1931 | } | |||
1932 | | |||
1933 | // Number of disk on which this file starts isnt supported and is never written here. | |||
1934 | ed.AddNewEntry(1); | |||
1935 | } else { | |||
1936 | // Should have already be done when local header was added. | |||
1937 | ed.Delete(1); | |||
1938 | } | |||
1939 | | |||
1940 | byte[] centralExtraData = ed.GetEntryData(); | |||
1941 | | |||
1942 | WriteLEShort(centralExtraData.Length); | |||
1943 | WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); | |||
1944 | | |||
1945 | WriteLEShort(0); // disk number | |||
1946 | WriteLEShort(0); // internal file attributes | |||
1947 | | |||
1948 | // External file attributes... | |||
1949 | if (entry.ExternalFileAttributes != -1) { | |||
1950 | WriteLEInt(entry.ExternalFileAttributes); | |||
1951 | } else { | |||
1952 | if (entry.IsDirectory) { | |||
1953 | WriteLEUint(16); | |||
1954 | } else { | |||
1955 | WriteLEUint(0); | |||
1956 | } | |||
1957 | } | |||
1958 | | |||
1959 | if (entry.Offset >= 0xffffffff) { | |||
1960 | WriteLEUint(0xffffffff); | |||
1961 | } else { | |||
1962 | WriteLEUint((uint)(int)entry.Offset); | |||
1963 | } | |||
1964 | | |||
1965 | if (name.Length > 0) { | |||
1966 | baseStream_.Write(name, 0, name.Length); | |||
1967 | } | |||
1968 | | |||
1969 | if (centralExtraData.Length > 0) { | |||
1970 | baseStream_.Write(centralExtraData, 0, centralExtraData.Length); | |||
1971 | } | |||
1972 | | |||
1973 | byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; | |||
1974 | | |||
1975 | if (rawComment.Length > 0) { | |||
1976 | baseStream_.Write(rawComment, 0, rawComment.Length); | |||
1977 | } | |||
1978 | | |||
1979 | return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; | |||
1980 | } | |||
1981 | #endregion | |||
1982 | | |||
1983 | void PostUpdateCleanup() | |||
1984 | { | |||
1985 | updateDataSource_ = null; | |||
1986 | updates_ = null; | |||
1987 | updateIndex_ = null; | |||
1988 | | |||
1989 | if (archiveStorage_ != null) { | |||
1990 | archiveStorage_.Dispose(); | |||
1991 | archiveStorage_ = null; | |||
1992 | } | |||
1993 | } | |||
1994 | | |||
1995 | string GetTransformedFileName(string name) | |||
1996 | { | |||
1997 | INameTransform transform = NameTransform; | |||
1998 | return (transform != null) ? | |||
1999 | transform.TransformFile(name) : | |||
2000 | name; | |||
2001 | } | |||
2002 | | |||
2003 | string GetTransformedDirectoryName(string name) | |||
2004 | { | |||
2005 | INameTransform transform = NameTransform; | |||
2006 | return (transform != null) ? | |||
2007 | transform.TransformDirectory(name) : | |||
2008 | name; | |||
2009 | } | |||
2010 | | |||
2011 | /// <summary> | |||
2012 | /// Get a raw memory buffer. | |||
2013 | /// </summary> | |||
2014 | /// <returns>Returns a raw memory buffer.</returns> | |||
2015 | byte[] GetBuffer() | |||
2016 | { | |||
2017 | if (copyBuffer_ == null) { | |||
2018 | copyBuffer_ = new byte[bufferSize_]; | |||
2019 | } | |||
2020 | return copyBuffer_; | |||
2021 | } | |||
2022 | | |||
2023 | void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) | |||
2024 | { | |||
2025 | int bytesToCopy = GetDescriptorSize(update); | |||
2026 | | |||
2027 | if (bytesToCopy > 0) { | |||
2028 | byte[] buffer = GetBuffer(); | |||
2029 | | |||
2030 | while (bytesToCopy > 0) { | |||
2031 | int readSize = Math.Min(buffer.Length, bytesToCopy); | |||
2032 | | |||
2033 | int bytesRead = source.Read(buffer, 0, readSize); | |||
2034 | if (bytesRead > 0) { | |||
2035 | dest.Write(buffer, 0, bytesRead); | |||
2036 | bytesToCopy -= bytesRead; | |||
2037 | } else { | |||
2038 | throw new ZipException("Unxpected end of stream"); | |||
2039 | } | |||
2040 | } | |||
2041 | } | |||
2042 | } | |||
2043 | | |||
2044 | void CopyBytes(ZipUpdate update, Stream destination, Stream source, | |||
2045 | long bytesToCopy, bool updateCrc) | |||
2046 | { | |||
2047 | if (destination == source) { | |||
2048 | throw new InvalidOperationException("Destination and source are the same"); | |||
2049 | } | |||
2050 | | |||
2051 | // NOTE: Compressed size is updated elsewhere. | |||
2052 | var crc = new Crc32(); | |||
2053 | byte[] buffer = GetBuffer(); | |||
2054 | | |||
2055 | long targetBytes = bytesToCopy; | |||
2056 | long totalBytesRead = 0; | |||
2057 | | |||
2058 | int bytesRead; | |||
2059 | do { | |||
2060 | int readSize = buffer.Length; | |||
2061 | | |||
2062 | if (bytesToCopy < readSize) { | |||
2063 | readSize = (int)bytesToCopy; | |||
2064 | } | |||
2065 | | |||
2066 | bytesRead = source.Read(buffer, 0, readSize); | |||
2067 | if (bytesRead > 0) { | |||
2068 | if (updateCrc) { | |||
2069 | crc.Update(buffer, 0, bytesRead); | |||
2070 | } | |||
2071 | destination.Write(buffer, 0, bytesRead); | |||
2072 | bytesToCopy -= bytesRead; | |||
2073 | totalBytesRead += bytesRead; | |||
2074 | } | |||
2075 | } | |||
2076 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2077 | | |||
2078 | if (totalBytesRead != targetBytes) { | |||
2079 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2080 | } | |||
2081 | | |||
2082 | if (updateCrc) { | |||
2083 | update.OutEntry.Crc = crc.Value; | |||
2084 | } | |||
2085 | } | |||
2086 | | |||
2087 | /// <summary> | |||
2088 | /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. | |||
2089 | /// </summary> | |||
2090 | /// <param name="update">The update to get the size for.</param> | |||
2091 | /// <returns>The descriptor size, zero if there isnt one.</returns> | |||
2092 | int GetDescriptorSize(ZipUpdate update) | |||
2093 | { | |||
2094 | int result = 0; | |||
2095 | if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
2096 | result = ZipConstants.DataDescriptorSize - 4; | |||
2097 | if (update.Entry.LocalHeaderRequiresZip64) { | |||
2098 | result = ZipConstants.Zip64DataDescriptorSize - 4; | |||
2099 | } | |||
2100 | } | |||
2101 | return result; | |||
2102 | } | |||
2103 | | |||
2104 | void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) | |||
2105 | { | |||
2106 | int bytesToCopy = GetDescriptorSize(update); | |||
2107 | | |||
2108 | while (bytesToCopy > 0) { | |||
2109 | var readSize = (int)bytesToCopy; | |||
2110 | byte[] buffer = GetBuffer(); | |||
2111 | | |||
2112 | stream.Position = sourcePosition; | |||
2113 | int bytesRead = stream.Read(buffer, 0, readSize); | |||
2114 | if (bytesRead > 0) { | |||
2115 | stream.Position = destinationPosition; | |||
2116 | stream.Write(buffer, 0, bytesRead); | |||
2117 | bytesToCopy -= bytesRead; | |||
2118 | destinationPosition += bytesRead; | |||
2119 | sourcePosition += bytesRead; | |||
2120 | } else { | |||
2121 | throw new ZipException("Unxpected end of stream"); | |||
2122 | } | |||
2123 | } | |||
2124 | } | |||
2125 | | |||
2126 | void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou | |||
2127 | { | |||
2128 | long bytesToCopy = update.Entry.CompressedSize; | |||
2129 | | |||
2130 | // NOTE: Compressed size is updated elsewhere. | |||
2131 | var crc = new Crc32(); | |||
2132 | byte[] buffer = GetBuffer(); | |||
2133 | | |||
2134 | long targetBytes = bytesToCopy; | |||
2135 | long totalBytesRead = 0; | |||
2136 | | |||
2137 | int bytesRead; | |||
2138 | do { | |||
2139 | int readSize = buffer.Length; | |||
2140 | | |||
2141 | if (bytesToCopy < readSize) { | |||
2142 | readSize = (int)bytesToCopy; | |||
2143 | } | |||
2144 | | |||
2145 | stream.Position = sourcePosition; | |||
2146 | bytesRead = stream.Read(buffer, 0, readSize); | |||
2147 | if (bytesRead > 0) { | |||
2148 | if (updateCrc) { | |||
2149 | crc.Update(buffer, 0, bytesRead); | |||
2150 | } | |||
2151 | stream.Position = destinationPosition; | |||
2152 | stream.Write(buffer, 0, bytesRead); | |||
2153 | | |||
2154 | destinationPosition += bytesRead; | |||
2155 | sourcePosition += bytesRead; | |||
2156 | bytesToCopy -= bytesRead; | |||
2157 | totalBytesRead += bytesRead; | |||
2158 | } | |||
2159 | } | |||
2160 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2161 | | |||
2162 | if (totalBytesRead != targetBytes) { | |||
2163 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2164 | } | |||
2165 | | |||
2166 | if (updateCrc) { | |||
2167 | update.OutEntry.Crc = crc.Value; | |||
2168 | } | |||
2169 | } | |||
2170 | | |||
2171 | int FindExistingUpdate(ZipEntry entry) | |||
2172 | { | |||
2173 | int result = -1; | |||
2174 | string convertedName = GetTransformedFileName(entry.Name); | |||
2175 | | |||
2176 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2177 | result = (int)updateIndex_[convertedName]; | |||
2178 | } | |||
2179 | /* | |||
2180 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2181 | // for CF? | |||
2182 | for (int index = 0; index < updates_.Count; ++index) | |||
2183 | { | |||
2184 | ZipUpdate zu = ( ZipUpdate )updates_[index]; | |||
2185 | if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && | |||
2186 | (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { | |||
2187 | result = index; | |||
2188 | break; | |||
2189 | } | |||
2190 | } | |||
2191 | */ | |||
2192 | return result; | |||
2193 | } | |||
2194 | | |||
2195 | int FindExistingUpdate(string fileName) | |||
2196 | { | |||
2197 | int result = -1; | |||
2198 | | |||
2199 | string convertedName = GetTransformedFileName(fileName); | |||
2200 | | |||
2201 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2202 | result = (int)updateIndex_[convertedName]; | |||
2203 | } | |||
2204 | | |||
2205 | /* | |||
2206 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2207 | // for CF? | |||
2208 | for ( int index = 0; index < updates_.Count; ++index ) { | |||
2209 | if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, | |||
2210 | true, CultureInfo.InvariantCulture) == 0 ) { | |||
2211 | result = index; | |||
2212 | break; | |||
2213 | } | |||
2214 | } | |||
2215 | */ | |||
2216 | | |||
2217 | return result; | |||
2218 | } | |||
2219 | | |||
2220 | /// <summary> | |||
2221 | /// Get an output stream for the specified <see cref="ZipEntry"/> | |||
2222 | /// </summary> | |||
2223 | /// <param name="entry">The entry to get an output stream for.</param> | |||
2224 | /// <returns>The output stream obtained for the entry.</returns> | |||
2225 | Stream GetOutputStream(ZipEntry entry) | |||
2226 | { | |||
2227 | Stream result = baseStream_; | |||
2228 | | |||
2229 | if (entry.IsCrypted == true) { | |||
2230 | result = CreateAndInitEncryptionStream(result, entry); | |||
2231 | } | |||
2232 | | |||
2233 | switch (entry.CompressionMethod) { | |||
2234 | case CompressionMethod.Stored: | |||
2235 | result = new UncompressedStream(result); | |||
2236 | break; | |||
2237 | | |||
2238 | case CompressionMethod.Deflated: | |||
2239 | var dos = new DeflaterOutputStream(result, new Deflater(9, true)); | |||
2240 | dos.IsStreamOwner = false; | |||
2241 | result = dos; | |||
2242 | break; | |||
2243 | | |||
2244 | default: | |||
2245 | throw new ZipException("Unknown compression method " + entry.CompressionMethod); | |||
2246 | } | |||
2247 | return result; | |||
2248 | } | |||
2249 | | |||
2250 | void AddEntry(ZipFile workFile, ZipUpdate update) | |||
2251 | { | |||
2252 | Stream source = null; | |||
2253 | | |||
2254 | if (update.Entry.IsFile) { | |||
2255 | source = update.GetSource(); | |||
2256 | | |||
2257 | if (source == null) { | |||
2258 | source = updateDataSource_.GetSource(update.Entry, update.Filename); | |||
2259 | } | |||
2260 | } | |||
2261 | | |||
2262 | if (source != null) { | |||
2263 | using (source) { | |||
2264 | long sourceStreamLength = source.Length; | |||
2265 | if (update.OutEntry.Size < 0) { | |||
2266 | update.OutEntry.Size = sourceStreamLength; | |||
2267 | } else { | |||
2268 | // Check for errant entries. | |||
2269 | if (update.OutEntry.Size != sourceStreamLength) { | |||
2270 | throw new ZipException("Entry size/stream size mismatch"); | |||
2271 | } | |||
2272 | } | |||
2273 | | |||
2274 | workFile.WriteLocalEntryHeader(update); | |||
2275 | | |||
2276 | long dataStart = workFile.baseStream_.Position; | |||
2277 | | |||
2278 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2279 | CopyBytes(update, output, source, sourceStreamLength, true); | |||
2280 | } | |||
2281 | | |||
2282 | long dataEnd = workFile.baseStream_.Position; | |||
2283 | update.OutEntry.CompressedSize = dataEnd - dataStart; | |||
2284 | | |||
2285 | if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) { | |||
2286 | var helper = new ZipHelperStream(workFile.baseStream_); | |||
2287 | helper.WriteDataDescriptor(update.OutEntry); | |||
2288 | } | |||
2289 | } | |||
2290 | } else { | |||
2291 | workFile.WriteLocalEntryHeader(update); | |||
2292 | update.OutEntry.CompressedSize = 0; | |||
2293 | } | |||
2294 | | |||
2295 | } | |||
2296 | | |||
2297 | void ModifyEntry(ZipFile workFile, ZipUpdate update) | |||
2298 | { | |||
2299 | workFile.WriteLocalEntryHeader(update); | |||
2300 | long dataStart = workFile.baseStream_.Position; | |||
2301 | | |||
2302 | // TODO: This is slow if the changes don't effect the data!! | |||
2303 | if (update.Entry.IsFile && (update.Filename != null)) { | |||
2304 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2305 | using (Stream source = this.GetInputStream(update.Entry)) { | |||
2306 | CopyBytes(update, output, source, source.Length, true); | |||
2307 | } | |||
2308 | } | |||
2309 | } | |||
2310 | | |||
2311 | long dataEnd = workFile.baseStream_.Position; | |||
2312 | update.Entry.CompressedSize = dataEnd - dataStart; | |||
2313 | } | |||
2314 | | |||
2315 | void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) | |||
2316 | { | |||
2317 | bool skipOver = false || update.Entry.Offset == destinationPosition; | |||
2318 | | |||
2319 | if (!skipOver) { | |||
2320 | baseStream_.Position = destinationPosition; | |||
2321 | workFile.WriteLocalEntryHeader(update); | |||
2322 | destinationPosition = baseStream_.Position; | |||
2323 | } | |||
2324 | | |||
2325 | long sourcePosition = 0; | |||
2326 | | |||
2327 | const int NameLengthOffset = 26; | |||
2328 | | |||
2329 | // TODO: Add base for SFX friendly handling | |||
2330 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2331 | | |||
2332 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2333 | | |||
2334 | // Clumsy way of handling retrieving the original name and extra data length for now. | |||
2335 | // TODO: Stop re-reading name and data length in CopyEntryDirect. | |||
2336 | uint nameLength = ReadLEUshort(); | |||
2337 | uint extraLength = ReadLEUshort(); | |||
2338 | | |||
2339 | sourcePosition = baseStream_.Position + nameLength + extraLength; | |||
2340 | | |||
2341 | if (skipOver) { | |||
2342 | if (update.OffsetBasedSize != -1) | |||
2343 | destinationPosition += update.OffsetBasedSize; | |||
2344 | else | |||
2345 | // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) ar | |||
2346 | // WinZip produces a warning on these entries: | |||
2347 | // "caution: value of lrec.csize (compressed size) changed from ..." | |||
2348 | destinationPosition += | |||
2349 | (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size | |||
2350 | update.Entry.CompressedSize + GetDescriptorSize(update); | |||
2351 | } else { | |||
2352 | if (update.Entry.CompressedSize > 0) { | |||
2353 | CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); | |||
2354 | } | |||
2355 | CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); | |||
2356 | } | |||
2357 | } | |||
2358 | | |||
2359 | void CopyEntry(ZipFile workFile, ZipUpdate update) | |||
2360 | { | |||
2361 | workFile.WriteLocalEntryHeader(update); | |||
2362 | | |||
2363 | if (update.Entry.CompressedSize > 0) { | |||
2364 | const int NameLengthOffset = 26; | |||
2365 | | |||
2366 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2367 | | |||
2368 | // TODO: This wont work for SFX files! | |||
2369 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2370 | | |||
2371 | uint nameLength = ReadLEUshort(); | |||
2372 | uint extraLength = ReadLEUshort(); | |||
2373 | | |||
2374 | baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); | |||
2375 | | |||
2376 | CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); | |||
2377 | } | |||
2378 | CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); | |||
2379 | } | |||
2380 | | |||
2381 | void Reopen(Stream source) | |||
2382 | { | |||
2383 | if (source == null) { | |||
2384 | throw new ZipException("Failed to reopen archive - no source"); | |||
2385 | } | |||
2386 | | |||
2387 | isNewArchive_ = false; | |||
2388 | baseStream_ = source; | |||
2389 | ReadEntries(); | |||
2390 | } | |||
2391 | | |||
2392 | void Reopen() | |||
2393 | { | |||
2394 | if (Name == null) { | |||
2395 | throw new InvalidOperationException("Name is not known cannot Reopen"); | |||
2396 | } | |||
2397 | | |||
2398 | Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); | |||
2399 | } | |||
2400 | | |||
2401 | void UpdateCommentOnly() | |||
2402 | { | |||
2403 | long baseLength = baseStream_.Length; | |||
2404 | | |||
2405 | ZipHelperStream updateFile = null; | |||
2406 | | |||
2407 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2408 | Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); | |||
2409 | updateFile = new ZipHelperStream(copyStream); | |||
2410 | updateFile.IsStreamOwner = true; | |||
2411 | | |||
2412 | baseStream_.Close(); | |||
2413 | baseStream_ = null; | |||
2414 | } else { | |||
2415 | if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2416 | // TODO: archiveStorage wasnt originally intended for this use. | |||
2417 | // Need to revisit this to tidy up handling as archive storage currently doesnt | |||
2418 | // handle the original stream well. | |||
2419 | // The problem is when using an existing zip archive with an in memory archive storage. | |||
2420 | // The open stream wont support writing but the memory storage should open the same file not an in memory one. | |||
2421 | | |||
2422 | // Need to tidy up the archive storage interface and contract basically. | |||
2423 | baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); | |||
2424 | updateFile = new ZipHelperStream(baseStream_); | |||
2425 | } else { | |||
2426 | baseStream_.Close(); | |||
2427 | baseStream_ = null; | |||
2428 | updateFile = new ZipHelperStream(Name); | |||
2429 | } | |||
2430 | } | |||
2431 | | |||
2432 | using (updateFile) { | |||
2433 | long locatedCentralDirOffset = | |||
2434 | updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2435 | baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2436 | if (locatedCentralDirOffset < 0) { | |||
2437 | throw new ZipException("Cannot find central directory"); | |||
2438 | } | |||
2439 | | |||
2440 | const int CentralHeaderCommentSizeOffset = 16; | |||
2441 | updateFile.Position += CentralHeaderCommentSizeOffset; | |||
2442 | | |||
2443 | byte[] rawComment = newComment_.RawComment; | |||
2444 | | |||
2445 | updateFile.WriteLEShort(rawComment.Length); | |||
2446 | updateFile.Write(rawComment, 0, rawComment.Length); | |||
2447 | updateFile.SetLength(updateFile.Position); | |||
2448 | } | |||
2449 | | |||
2450 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2451 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2452 | } else { | |||
2453 | ReadEntries(); | |||
2454 | } | |||
2455 | } | |||
2456 | | |||
2457 | /// <summary> | |||
2458 | /// Class used to sort updates. | |||
2459 | /// </summary> | |||
2460 | class UpdateComparer : IComparer | |||
2461 | { | |||
2462 | /// <summary> | |||
2463 | /// Compares two objects and returns a value indicating whether one is | |||
2464 | /// less than, equal to or greater than the other. | |||
2465 | /// </summary> | |||
2466 | /// <param name="x">First object to compare</param> | |||
2467 | /// <param name="y">Second object to compare.</param> | |||
2468 | /// <returns>Compare result.</returns> | |||
2469 | public int Compare( | |||
2470 | object x, | |||
2471 | object y) | |||
2472 | { | |||
2473 | var zx = x as ZipUpdate; | |||
2474 | var zy = y as ZipUpdate; | |||
2475 | | |||
2476 | int result; | |||
2477 | | |||
2478 | if (zx == null) { | |||
2479 | if (zy == null) { | |||
2480 | result = 0; | |||
2481 | } else { | |||
2482 | result = -1; | |||
2483 | } | |||
2484 | } else if (zy == null) { | |||
2485 | result = 1; | |||
2486 | } else { | |||
2487 | int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2488 | int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2489 | | |||
2490 | result = xCmdValue - yCmdValue; | |||
2491 | if (result == 0) { | |||
2492 | long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; | |||
2493 | if (offsetDiff < 0) { | |||
2494 | result = -1; | |||
2495 | } else if (offsetDiff == 0) { | |||
2496 | result = 0; | |||
2497 | } else { | |||
2498 | result = 1; | |||
2499 | } | |||
2500 | } | |||
2501 | } | |||
2502 | return result; | |||
2503 | } | |||
2504 | } | |||
2505 | | |||
2506 | void RunUpdates() | |||
2507 | { | |||
2508 | long sizeEntries = 0; | |||
2509 | long endOfStream = 0; | |||
2510 | bool directUpdate = false; | |||
2511 | long destinationPosition = 0; // NOT SFX friendly | |||
2512 | | |||
2513 | ZipFile workFile; | |||
2514 | | |||
2515 | if (IsNewArchive) { | |||
2516 | workFile = this; | |||
2517 | workFile.baseStream_.Position = 0; | |||
2518 | directUpdate = true; | |||
2519 | } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2520 | workFile = this; | |||
2521 | workFile.baseStream_.Position = 0; | |||
2522 | directUpdate = true; | |||
2523 | | |||
2524 | // Sort the updates by offset within copies/modifies, then adds. | |||
2525 | // This ensures that data required by copies will not be overwritten. | |||
2526 | updates_.Sort(new UpdateComparer()); | |||
2527 | } else { | |||
2528 | workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); | |||
2529 | workFile.UseZip64 = UseZip64; | |||
2530 | | |||
2531 | if (key != null) { | |||
2532 | workFile.key = (byte[])key.Clone(); | |||
2533 | } | |||
2534 | } | |||
2535 | | |||
2536 | try { | |||
2537 | foreach (ZipUpdate update in updates_) { | |||
2538 | if (update != null) { | |||
2539 | switch (update.Command) { | |||
2540 | case UpdateCommand.Copy: | |||
2541 | if (directUpdate) { | |||
2542 | CopyEntryDirect(workFile, update, ref destinationPosition); | |||
2543 | } else { | |||
2544 | CopyEntry(workFile, update); | |||
2545 | } | |||
2546 | break; | |||
2547 | | |||
2548 | case UpdateCommand.Modify: | |||
2549 | // TODO: Direct modifying of an entry will take some legwork. | |||
2550 | ModifyEntry(workFile, update); | |||
2551 | break; | |||
2552 | | |||
2553 | case UpdateCommand.Add: | |||
2554 | if (!IsNewArchive && directUpdate) { | |||
2555 | workFile.baseStream_.Position = destinationPosition; | |||
2556 | } | |||
2557 | | |||
2558 | AddEntry(workFile, update); | |||
2559 | | |||
2560 | if (directUpdate) { | |||
2561 | destinationPosition = workFile.baseStream_.Position; | |||
2562 | } | |||
2563 | break; | |||
2564 | } | |||
2565 | } | |||
2566 | } | |||
2567 | | |||
2568 | if (!IsNewArchive && directUpdate) { | |||
2569 | workFile.baseStream_.Position = destinationPosition; | |||
2570 | } | |||
2571 | | |||
2572 | long centralDirOffset = workFile.baseStream_.Position; | |||
2573 | | |||
2574 | foreach (ZipUpdate update in updates_) { | |||
2575 | if (update != null) { | |||
2576 | sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); | |||
2577 | } | |||
2578 | } | |||
2579 | | |||
2580 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
2581 | using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) { | |||
2582 | zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); | |||
2583 | } | |||
2584 | | |||
2585 | endOfStream = workFile.baseStream_.Position; | |||
2586 | | |||
2587 | // And now patch entries... | |||
2588 | foreach (ZipUpdate update in updates_) { | |||
2589 | if (update != null) { | |||
2590 | // If the size of the entry is zero leave the crc as 0 as well. | |||
2591 | // The calculated crc will be all bits on... | |||
2592 | if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { | |||
2593 | workFile.baseStream_.Position = update.CrcPatchOffset; | |||
2594 | workFile.WriteLEInt((int)update.OutEntry.Crc); | |||
2595 | } | |||
2596 | | |||
2597 | if (update.SizePatchOffset > 0) { | |||
2598 | workFile.baseStream_.Position = update.SizePatchOffset; | |||
2599 | if (update.OutEntry.LocalHeaderRequiresZip64) { | |||
2600 | workFile.WriteLeLong(update.OutEntry.Size); | |||
2601 | workFile.WriteLeLong(update.OutEntry.CompressedSize); | |||
2602 | } else { | |||
2603 | workFile.WriteLEInt((int)update.OutEntry.CompressedSize); | |||
2604 | workFile.WriteLEInt((int)update.OutEntry.Size); | |||
2605 | } | |||
2606 | } | |||
2607 | } | |||
2608 | } | |||
2609 | } catch { | |||
2610 | workFile.Close(); | |||
2611 | if (!directUpdate && (workFile.Name != null)) { | |||
2612 | File.Delete(workFile.Name); | |||
2613 | } | |||
2614 | throw; | |||
2615 | } | |||
2616 | | |||
2617 | if (directUpdate) { | |||
2618 | workFile.baseStream_.SetLength(endOfStream); | |||
2619 | workFile.baseStream_.Flush(); | |||
2620 | isNewArchive_ = false; | |||
2621 | ReadEntries(); | |||
2622 | } else { | |||
2623 | baseStream_.Close(); | |||
2624 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2625 | } | |||
2626 | } | |||
2627 | | |||
2628 | void CheckUpdating() | |||
2629 | { | |||
2630 | if (updates_ == null) { | |||
2631 | throw new InvalidOperationException("BeginUpdate has not been called"); | |||
2632 | } | |||
2633 | } | |||
2634 | | |||
2635 | #endregion | |||
2636 | | |||
2637 | #region ZipUpdate class | |||
2638 | /// <summary> | |||
2639 | /// Represents a pending update to a Zip file. | |||
2640 | /// </summary> | |||
2641 | class ZipUpdate | |||
2642 | { | |||
2643 | #region Constructors | |||
2644 | public ZipUpdate(string fileName, ZipEntry entry) | |||
2645 | { | |||
2646 | command_ = UpdateCommand.Add; | |||
2647 | entry_ = entry; | |||
2648 | filename_ = fileName; | |||
2649 | } | |||
2650 | | |||
2651 | [Obsolete] | |||
2652 | public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) | |||
2653 | { | |||
2654 | command_ = UpdateCommand.Add; | |||
2655 | entry_ = new ZipEntry(entryName); | |||
2656 | entry_.CompressionMethod = compressionMethod; | |||
2657 | filename_ = fileName; | |||
2658 | } | |||
2659 | | |||
2660 | [Obsolete] | |||
2661 | public ZipUpdate(string fileName, string entryName) | |||
2662 | : this(fileName, entryName, CompressionMethod.Deflated) | |||
2663 | { | |||
2664 | // Do nothing. | |||
2665 | } | |||
2666 | | |||
2667 | [Obsolete] | |||
2668 | public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
2669 | { | |||
2670 | command_ = UpdateCommand.Add; | |||
2671 | entry_ = new ZipEntry(entryName); | |||
2672 | entry_.CompressionMethod = compressionMethod; | |||
2673 | dataSource_ = dataSource; | |||
2674 | } | |||
2675 | | |||
2676 | public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) | |||
2677 | { | |||
2678 | command_ = UpdateCommand.Add; | |||
2679 | entry_ = entry; | |||
2680 | dataSource_ = dataSource; | |||
2681 | } | |||
2682 | | |||
2683 | public ZipUpdate(ZipEntry original, ZipEntry updated) | |||
2684 | { | |||
2685 | throw new ZipException("Modify not currently supported"); | |||
2686 | /* | |||
2687 | command_ = UpdateCommand.Modify; | |||
2688 | entry_ = ( ZipEntry )original.Clone(); | |||
2689 | outEntry_ = ( ZipEntry )updated.Clone(); | |||
2690 | */ | |||
2691 | } | |||
2692 | | |||
2693 | public ZipUpdate(UpdateCommand command, ZipEntry entry) | |||
2694 | { | |||
2695 | command_ = command; | |||
2696 | entry_ = (ZipEntry)entry.Clone(); | |||
2697 | } | |||
2698 | | |||
2699 | | |||
2700 | /// <summary> | |||
2701 | /// Copy an existing entry. | |||
2702 | /// </summary> | |||
2703 | /// <param name="entry">The existing entry to copy.</param> | |||
2704 | public ZipUpdate(ZipEntry entry) | |||
2705 | : this(UpdateCommand.Copy, entry) | |||
2706 | { | |||
2707 | // Do nothing. | |||
2708 | } | |||
2709 | #endregion | |||
2710 | | |||
2711 | /// <summary> | |||
2712 | /// Get the <see cref="ZipEntry"/> for this update. | |||
2713 | /// </summary> | |||
2714 | /// <remarks>This is the source or original entry.</remarks> | |||
2715 | public ZipEntry Entry { | |||
2716 | get { return entry_; } | |||
2717 | } | |||
2718 | | |||
2719 | /// <summary> | |||
2720 | /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. | |||
2721 | /// </summary> | |||
2722 | public ZipEntry OutEntry { | |||
2723 | get { | |||
2724 | if (outEntry_ == null) { | |||
2725 | outEntry_ = (ZipEntry)entry_.Clone(); | |||
2726 | } | |||
2727 | | |||
2728 | return outEntry_; | |||
2729 | } | |||
2730 | } | |||
2731 | | |||
2732 | /// <summary> | |||
2733 | /// Get the command for this update. | |||
2734 | /// </summary> | |||
2735 | public UpdateCommand Command { | |||
2736 | get { return command_; } | |||
2737 | } | |||
2738 | | |||
2739 | /// <summary> | |||
2740 | /// Get the filename if any for this update. Null if none exists. | |||
2741 | /// </summary> | |||
2742 | public string Filename { | |||
2743 | get { return filename_; } | |||
2744 | } | |||
2745 | | |||
2746 | /// <summary> | |||
2747 | /// Get/set the location of the size patch for this update. | |||
2748 | /// </summary> | |||
2749 | public long SizePatchOffset { | |||
2750 | get { return sizePatchOffset_; } | |||
2751 | set { sizePatchOffset_ = value; } | |||
2752 | } | |||
2753 | | |||
2754 | /// <summary> | |||
2755 | /// Get /set the location of the crc patch for this update. | |||
2756 | /// </summary> | |||
2757 | public long CrcPatchOffset { | |||
2758 | get { return crcPatchOffset_; } | |||
2759 | set { crcPatchOffset_ = value; } | |||
2760 | } | |||
2761 | | |||
2762 | /// <summary> | |||
2763 | /// Get/set the size calculated by offset. | |||
2764 | /// Specifically, the difference between this and next entry's starting offset. | |||
2765 | /// </summary> | |||
2766 | public long OffsetBasedSize { | |||
2767 | get { return _offsetBasedSize; } | |||
2768 | set { _offsetBasedSize = value; } | |||
2769 | } | |||
2770 | | |||
2771 | public Stream GetSource() | |||
2772 | { | |||
2773 | Stream result = null; | |||
2774 | if (dataSource_ != null) { | |||
2775 | result = dataSource_.GetSource(); | |||
2776 | } | |||
2777 | | |||
2778 | return result; | |||
2779 | } | |||
2780 | | |||
2781 | #region Instance Fields | |||
2782 | ZipEntry entry_; | |||
2783 | ZipEntry outEntry_; | |||
2784 | UpdateCommand command_; | |||
2785 | IStaticDataSource dataSource_; | |||
2786 | string filename_; | |||
2787 | long sizePatchOffset_ = -1; | |||
2788 | long crcPatchOffset_ = -1; | |||
2789 | long _offsetBasedSize = -1; | |||
2790 | #endregion | |||
2791 | } | |||
2792 | | |||
2793 | #endregion | |||
2794 | #endregion | |||
2795 | | |||
2796 | #region Disposing | |||
2797 | | |||
2798 | #region IDisposable Members | |||
2799 | void IDisposable.Dispose() | |||
2800 | { | |||
2801 | Close(); | |||
2802 | } | |||
2803 | #endregion | |||
2804 | | |||
2805 | void DisposeInternal(bool disposing) | |||
2806 | { | |||
2807 | if (!isDisposed_) { | |||
2808 | isDisposed_ = true; | |||
2809 | entries_ = new ZipEntry[0]; | |||
2810 | | |||
2811 | if (IsStreamOwner && (baseStream_ != null)) { | |||
2812 | lock (baseStream_) { | |||
2813 | baseStream_.Close(); | |||
2814 | } | |||
2815 | } | |||
2816 | | |||
2817 | PostUpdateCleanup(); | |||
2818 | } | |||
2819 | } | |||
2820 | | |||
2821 | /// <summary> | |||
2822 | /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. | |||
2823 | /// </summary> | |||
2824 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
2825 | /// false to release only unmanaged resources.</param> | |||
2826 | protected virtual void Dispose(bool disposing) | |||
2827 | { | |||
2828 | DisposeInternal(disposing); | |||
2829 | } | |||
2830 | | |||
2831 | #endregion | |||
2832 | | |||
2833 | #region Internal routines | |||
2834 | #region Reading | |||
2835 | /// <summary> | |||
2836 | /// Read an unsigned short in little endian byte order. | |||
2837 | /// </summary> | |||
2838 | /// <returns>Returns the value read.</returns> | |||
2839 | /// <exception cref="EndOfStreamException"> | |||
2840 | /// The stream ends prematurely | |||
2841 | /// </exception> | |||
2842 | ushort ReadLEUshort() | |||
2843 | { | |||
2844 | int data1 = baseStream_.ReadByte(); | |||
2845 | | |||
2846 | if (data1 < 0) { | |||
2847 | throw new EndOfStreamException("End of stream"); | |||
2848 | } | |||
2849 | | |||
2850 | int data2 = baseStream_.ReadByte(); | |||
2851 | | |||
2852 | if (data2 < 0) { | |||
2853 | throw new EndOfStreamException("End of stream"); | |||
2854 | } | |||
2855 | | |||
2856 | | |||
2857 | return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); | |||
2858 | } | |||
2859 | | |||
2860 | /// <summary> | |||
2861 | /// Read a uint in little endian byte order. | |||
2862 | /// </summary> | |||
2863 | /// <returns>Returns the value read.</returns> | |||
2864 | /// <exception cref="IOException"> | |||
2865 | /// An i/o error occurs. | |||
2866 | /// </exception> | |||
2867 | /// <exception cref="System.IO.EndOfStreamException"> | |||
2868 | /// The file ends prematurely | |||
2869 | /// </exception> | |||
2870 | uint ReadLEUint() | |||
2871 | { | |||
2872 | return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); | |||
2873 | } | |||
2874 | | |||
2875 | ulong ReadLEUlong() | |||
2876 | { | |||
2877 | return ReadLEUint() | ((ulong)ReadLEUint() << 32); | |||
2878 | } | |||
2879 | | |||
2880 | #endregion | |||
2881 | // NOTE this returns the offset of the first byte after the signature. | |||
2882 | long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
2883 | { | |||
2884 | using (ZipHelperStream les = new ZipHelperStream(baseStream_)) { | |||
2885 | return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); | |||
2886 | } | |||
2887 | } | |||
2888 | | |||
2889 | /// <summary> | |||
2890 | /// Search for and read the central directory of a zip file filling the entries array. | |||
2891 | /// </summary> | |||
2892 | /// <exception cref="System.IO.IOException"> | |||
2893 | /// An i/o error occurs. | |||
2894 | /// </exception> | |||
2895 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
2896 | /// The central directory is malformed or cannot be found | |||
2897 | /// </exception> | |||
2898 | void ReadEntries() | |||
2899 | { | |||
2900 | // Search for the End Of Central Directory. When a zip comment is | |||
2901 | // present the directory will start earlier | |||
2902 | // | |||
2903 | // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. | |||
2904 | // This should be compatible with both SFX and ZIP files but has only been tested for Zip files | |||
2905 | // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then | |||
2906 | // this could be invalid. | |||
2907 | // Could also speed this up by reading memory in larger blocks. | |||
2908 | | |||
2909 | if (baseStream_.CanSeek == false) { | |||
2910 | throw new ZipException("ZipFile stream must be seekable"); | |||
2911 | } | |||
2912 | | |||
2913 | long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2914 | baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2915 | | |||
2916 | if (locatedEndOfCentralDir < 0) { | |||
2917 | throw new ZipException("Cannot find central directory"); | |||
2918 | } | |||
2919 | | |||
2920 | // Read end of central directory record | |||
2921 | ushort thisDiskNumber = ReadLEUshort(); | |||
2922 | ushort startCentralDirDisk = ReadLEUshort(); | |||
2923 | ulong entriesForThisDisk = ReadLEUshort(); | |||
2924 | ulong entriesForWholeCentralDir = ReadLEUshort(); | |||
2925 | ulong centralDirSize = ReadLEUint(); | |||
2926 | long offsetOfCentralDir = ReadLEUint(); | |||
2927 | uint commentSize = ReadLEUshort(); | |||
2928 | | |||
2929 | if (commentSize > 0) { | |||
2930 | byte[] comment = new byte[commentSize]; | |||
2931 | | |||
2932 | StreamUtils.ReadFully(baseStream_, comment); | |||
2933 | comment_ = ZipConstants.ConvertToString(comment); | |||
2934 | } else { | |||
2935 | comment_ = string.Empty; | |||
2936 | } | |||
2937 | | |||
2938 | bool isZip64 = false; | |||
2939 | | |||
2940 | // Check if zip64 header information is required. | |||
2941 | if ((thisDiskNumber == 0xffff) || | |||
2942 | (startCentralDirDisk == 0xffff) || | |||
2943 | (entriesForThisDisk == 0xffff) || | |||
2944 | (entriesForWholeCentralDir == 0xffff) || | |||
2945 | (centralDirSize == 0xffffffff) || | |||
2946 | (offsetOfCentralDir == 0xffffffff)) { | |||
2947 | isZip64 = true; | |||
2948 | | |||
2949 | long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, | |||
2950 | if (offset < 0) { | |||
2951 | throw new ZipException("Cannot find Zip64 locator"); | |||
2952 | } | |||
2953 | | |||
2954 | // number of the disk with the start of the zip64 end of central directory 4 bytes | |||
2955 | // relative offset of the zip64 end of central directory record 8 bytes | |||
2956 | // total number of disks 4 bytes | |||
2957 | ReadLEUint(); // startDisk64 is not currently used | |||
2958 | ulong offset64 = ReadLEUlong(); | |||
2959 | uint totalDisks = ReadLEUint(); | |||
2960 | | |||
2961 | baseStream_.Position = (long)offset64; | |||
2962 | long sig64 = ReadLEUint(); | |||
2963 | | |||
2964 | if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { | |||
2965 | throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); | |||
2966 | } | |||
2967 | | |||
2968 | // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. | |||
2969 | ulong recordSize = ReadLEUlong(); | |||
2970 | int versionMadeBy = ReadLEUshort(); | |||
2971 | int versionToExtract = ReadLEUshort(); | |||
2972 | uint thisDisk = ReadLEUint(); | |||
2973 | uint centralDirDisk = ReadLEUint(); | |||
2974 | entriesForThisDisk = ReadLEUlong(); | |||
2975 | entriesForWholeCentralDir = ReadLEUlong(); | |||
2976 | centralDirSize = ReadLEUlong(); | |||
2977 | offsetOfCentralDir = (long)ReadLEUlong(); | |||
2978 | | |||
2979 | // NOTE: zip64 extensible data sector (variable size) is ignored. | |||
2980 | } | |||
2981 | | |||
2982 | entries_ = new ZipEntry[entriesForThisDisk]; | |||
2983 | | |||
2984 | // SFX/embedded support, find the offset of the first entry vis the start of the stream | |||
2985 | // This applies to Zip files that are appended to the end of an SFX stub. | |||
2986 | // Or are appended as a resource to an executable. | |||
2987 | // Zip files created by some archivers have the offsets altered to reflect the true offsets | |||
2988 | // and so dont require any adjustment here... | |||
2989 | // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? | |||
2990 | if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) { | |||
2991 | offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); | |||
2992 | if (offsetOfFirstEntry <= 0) { | |||
2993 | throw new ZipException("Invalid embedded zip archive"); | |||
2994 | } | |||
2995 | } | |||
2996 | | |||
2997 | baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); | |||
2998 | | |||
2999 | for (ulong i = 0; i < entriesForThisDisk; i++) { | |||
3000 | if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { | |||
3001 | throw new ZipException("Wrong Central Directory signature"); | |||
3002 | } | |||
3003 | | |||
3004 | int versionMadeBy = ReadLEUshort(); | |||
3005 | int versionToExtract = ReadLEUshort(); | |||
3006 | int bitFlags = ReadLEUshort(); | |||
3007 | int method = ReadLEUshort(); | |||
3008 | uint dostime = ReadLEUint(); | |||
3009 | uint crc = ReadLEUint(); | |||
3010 | var csize = (long)ReadLEUint(); | |||
3011 | var size = (long)ReadLEUint(); | |||
3012 | int nameLen = ReadLEUshort(); | |||
3013 | int extraLen = ReadLEUshort(); | |||
3014 | int commentLen = ReadLEUshort(); | |||
3015 | | |||
3016 | int diskStartNo = ReadLEUshort(); // Not currently used | |||
3017 | int internalAttributes = ReadLEUshort(); // Not currently used | |||
3018 | | |||
3019 | uint externalAttributes = ReadLEUint(); | |||
3020 | long offset = ReadLEUint(); | |||
3021 | | |||
3022 | byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; | |||
3023 | | |||
3024 | StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); | |||
3025 | string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); | |||
3026 | | |||
3027 | var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); | |||
3028 | entry.Crc = crc & 0xffffffffL; | |||
3029 | entry.Size = size & 0xffffffffL; | |||
3030 | entry.CompressedSize = csize & 0xffffffffL; | |||
3031 | entry.Flags = bitFlags; | |||
3032 | entry.DosTime = (uint)dostime; | |||
3033 | entry.ZipFileIndex = (long)i; | |||
3034 | entry.Offset = offset; | |||
3035 | entry.ExternalFileAttributes = (int)externalAttributes; | |||
3036 | | |||
3037 | if ((bitFlags & 8) == 0) { | |||
3038 | entry.CryptoCheckValue = (byte)(crc >> 24); | |||
3039 | } else { | |||
3040 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | |||
3041 | } | |||
3042 | | |||
3043 | if (extraLen > 0) { | |||
3044 | byte[] extra = new byte[extraLen]; | |||
3045 | StreamUtils.ReadFully(baseStream_, extra); | |||
3046 | entry.ExtraData = extra; | |||
3047 | } | |||
3048 | | |||
3049 | entry.ProcessExtraData(false); | |||
3050 | | |||
3051 | if (commentLen > 0) { | |||
3052 | StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); | |||
3053 | entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); | |||
3054 | } | |||
3055 | | |||
3056 | entries_[i] = entry; | |||
3057 | } | |||
3058 | } | |||
3059 | | |||
3060 | /// <summary> | |||
3061 | /// Locate the data for a given entry. | |||
3062 | /// </summary> | |||
3063 | /// <returns> | |||
3064 | /// The start offset of the data. | |||
3065 | /// </returns> | |||
3066 | /// <exception cref="System.IO.EndOfStreamException"> | |||
3067 | /// The stream ends prematurely | |||
3068 | /// </exception> | |||
3069 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
3070 | /// The local header signature is invalid, the entry and central header file name lengths are different | |||
3071 | /// or the local and entry compression methods dont match | |||
3072 | /// </exception> | |||
3073 | long LocateEntry(ZipEntry entry) | |||
3074 | { | |||
3075 | return TestLocalHeader(entry, HeaderTest.Extract); | |||
3076 | } | |||
3077 | | |||
3078 | Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) | |||
3079 | { | |||
3080 | CryptoStream result = null; | |||
3081 | | |||
3082 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3083 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3084 | var classicManaged = new PkzipClassicManaged(); | |||
3085 | | |||
3086 | OnKeysRequired(entry.Name); | |||
3087 | if (HaveKeys == false) { | |||
3088 | throw new ZipException("No password available for encrypted stream"); | |||
3089 | } | |||
3090 | | |||
3091 | result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); | |||
3092 | CheckClassicPassword(result, entry); | |||
3093 | } else { | |||
3094 | if (entry.Version == ZipConstants.VERSION_AES) { | |||
3095 | // | |||
3096 | OnKeysRequired(entry.Name); | |||
3097 | if (HaveKeys == false) { | |||
3098 | throw new ZipException("No password available for AES encrypted stream"); | |||
3099 | } | |||
3100 | int saltLen = entry.AESSaltLen; | |||
3101 | byte[] saltBytes = new byte[saltLen]; | |||
3102 | int saltIn = baseStream.Read(saltBytes, 0, saltLen); | |||
3103 | if (saltIn != saltLen) | |||
3104 | throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); | |||
3105 | // | |||
3106 | byte[] pwdVerifyRead = new byte[2]; | |||
3107 | baseStream.Read(pwdVerifyRead, 0, 2); | |||
3108 | int blockSize = entry.AESKeySize / 8; // bits to bytes | |||
3109 | | |||
3110 | var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); | |||
3111 | byte[] pwdVerifyCalc = decryptor.PwdVerifier; | |||
3112 | if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) | |||
3113 | throw new ZipException("Invalid password for AES"); | |||
3114 | result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); | |||
3115 | } else { | |||
3116 | throw new ZipException("Decryption method not supported"); | |||
3117 | } | |||
3118 | } | |||
3119 | | |||
3120 | return result; | |||
3121 | } | |||
3122 | | |||
3123 | Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) | |||
3124 | { | |||
3125 | CryptoStream result = null; | |||
3126 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3127 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3128 | var classicManaged = new PkzipClassicManaged(); | |||
3129 | | |||
3130 | OnKeysRequired(entry.Name); | |||
3131 | if (HaveKeys == false) { | |||
3132 | throw new ZipException("No password available for encrypted stream"); | |||
3133 | } | |||
3134 | | |||
3135 | // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream | |||
3136 | // which doesnt do this. | |||
3137 | result = new CryptoStream(new UncompressedStream(baseStream), | |||
3138 | classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); | |||
3139 | | |||
3140 | if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { | |||
3141 | WriteEncryptionHeader(result, entry.DosTime << 16); | |||
3142 | } else { | |||
3143 | WriteEncryptionHeader(result, entry.Crc); | |||
3144 | } | |||
3145 | } | |||
3146 | return result; | |||
3147 | } | |||
3148 | | |||
3149 | static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) | |||
3150 | { | |||
3151 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3152 | StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); | |||
3153 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | |||
3154 | throw new ZipException("Invalid password"); | |||
3155 | } | |||
3156 | } | |||
3157 | | |||
3158 | static void WriteEncryptionHeader(Stream stream, long crcValue) | |||
3159 | { | |||
3160 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3161 | var rnd = new Random(); | |||
3162 | rnd.NextBytes(cryptBuffer); | |||
3163 | cryptBuffer[11] = (byte)(crcValue >> 24); | |||
3164 | stream.Write(cryptBuffer, 0, cryptBuffer.Length); | |||
3165 | } | |||
3166 | | |||
3167 | #endregion | |||
3168 | | |||
3169 | #region Instance Fields | |||
3170 | bool isDisposed_; | |||
3171 | string name_; | |||
3172 | string comment_; | |||
3173 | string rawPassword_; | |||
3174 | Stream baseStream_; | |||
3175 | bool isStreamOwner; | |||
3176 | long offsetOfFirstEntry; | |||
3177 | ZipEntry[] entries_; | |||
3178 | byte[] key; | |||
3179 | bool isNewArchive_; | |||
3180 | | |||
3181 | // Default is dynamic which is not backwards compatible and can cause problems | |||
3182 | // with XP's built in compression which cant read Zip64 archives. | |||
3183 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
3184 | // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. | |||
3185 | UseZip64 useZip64_ = UseZip64.Dynamic; | |||
3186 | | |||
3187 | #region Zip Update Instance Fields | |||
3188 | ArrayList updates_; | |||
3189 | long updateCount_; // Count is managed manually as updates_ can contain nulls! | |||
3190 | Hashtable updateIndex_; | |||
3191 | IArchiveStorage archiveStorage_; | |||
3192 | IDynamicDataSource updateDataSource_; | |||
3193 | bool contentsEdited_; | |||
3194 | int bufferSize_ = DefaultBufferSize; | |||
3195 | byte[] copyBuffer_; | |||
3196 | ZipString newComment_; | |||
3197 | bool commentEdited_; | |||
3198 | IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); | |||
3199 | #endregion | |||
3200 | #endregion | |||
3201 | | |||
3202 | #region Support Classes | |||
3203 | /// <summary> | |||
3204 | /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. | |||
3205 | /// </summary> | |||
3206 | class ZipString | |||
3207 | { | |||
3208 | #region Constructors | |||
3209 | /// <summary> | |||
3210 | /// Initialise a <see cref="ZipString"/> with a string. | |||
3211 | /// </summary> | |||
3212 | /// <param name="comment">The textual string form.</param> | |||
3213 | public ZipString(string comment) | |||
3214 | { | |||
3215 | comment_ = comment; | |||
3216 | isSourceString_ = true; | |||
3217 | } | |||
3218 | | |||
3219 | /// <summary> | |||
3220 | /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. | |||
3221 | /// </summary> | |||
3222 | /// <param name="rawString"></param> | |||
3223 | public ZipString(byte[] rawString) | |||
3224 | { | |||
3225 | rawComment_ = rawString; | |||
3226 | } | |||
3227 | #endregion | |||
3228 | | |||
3229 | /// <summary> | |||
3230 | /// Get a value indicating the original source of data for this instance. | |||
3231 | /// True if the source was a string; false if the source was binary data. | |||
3232 | /// </summary> | |||
3233 | public bool IsSourceString { | |||
3234 | get { return isSourceString_; } | |||
3235 | } | |||
3236 | | |||
3237 | /// <summary> | |||
3238 | /// Get the length of the comment when represented as raw bytes. | |||
3239 | /// </summary> | |||
3240 | public int RawLength { | |||
3241 | get { | |||
3242 | MakeBytesAvailable(); | |||
3243 | return rawComment_.Length; | |||
3244 | } | |||
3245 | } | |||
3246 | | |||
3247 | /// <summary> | |||
3248 | /// Get the comment in its 'raw' form as plain bytes. | |||
3249 | /// </summary> | |||
3250 | public byte[] RawComment { | |||
3251 | get { | |||
3252 | MakeBytesAvailable(); | |||
3253 | return (byte[])rawComment_.Clone(); | |||
3254 | } | |||
3255 | } | |||
3256 | | |||
3257 | /// <summary> | |||
3258 | /// Reset the comment to its initial state. | |||
3259 | /// </summary> | |||
3260 | public void Reset() | |||
3261 | { | |||
3262 | if (isSourceString_) { | |||
3263 | rawComment_ = null; | |||
3264 | } else { | |||
3265 | comment_ = null; | |||
3266 | } | |||
3267 | } | |||
3268 | | |||
3269 | void MakeTextAvailable() | |||
3270 | { | |||
3271 | if (comment_ == null) { | |||
3272 | comment_ = ZipConstants.ConvertToString(rawComment_); | |||
3273 | } | |||
3274 | } | |||
3275 | | |||
3276 | void MakeBytesAvailable() | |||
3277 | { | |||
3278 | if (rawComment_ == null) { | |||
3279 | rawComment_ = ZipConstants.ConvertToArray(comment_); | |||
3280 | } | |||
3281 | } | |||
3282 | | |||
3283 | /// <summary> | |||
3284 | /// Implicit conversion of comment to a string. | |||
3285 | /// </summary> | |||
3286 | /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> | |||
3287 | /// <returns>The textual equivalent for the input value.</returns> | |||
3288 | static public implicit operator string(ZipString zipString) | |||
3289 | { | |||
3290 | zipString.MakeTextAvailable(); | |||
3291 | return zipString.comment_; | |||
3292 | } | |||
3293 | | |||
3294 | #region Instance Fields | |||
3295 | string comment_; | |||
3296 | byte[] rawComment_; | |||
3297 | bool isSourceString_; | |||
3298 | #endregion | |||
3299 | } | |||
3300 | | |||
3301 | /// <summary> | |||
3302 | /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> | |||
3303 | /// </summary> | |||
3304 | class ZipEntryEnumerator : IEnumerator | |||
3305 | { | |||
3306 | #region Constructors | |||
3307 | public ZipEntryEnumerator(ZipEntry[] entries) | |||
3308 | { | |||
3309 | array = entries; | |||
3310 | } | |||
3311 | | |||
3312 | #endregion | |||
3313 | #region IEnumerator Members | |||
3314 | public object Current { | |||
3315 | get { | |||
3316 | return array[index]; | |||
3317 | } | |||
3318 | } | |||
3319 | | |||
3320 | public void Reset() | |||
3321 | { | |||
3322 | index = -1; | |||
3323 | } | |||
3324 | | |||
3325 | public bool MoveNext() | |||
3326 | { | |||
3327 | return (++index < array.Length); | |||
3328 | } | |||
3329 | #endregion | |||
3330 | #region Instance Fields | |||
3331 | ZipEntry[] array; | |||
3332 | int index = -1; | |||
3333 | #endregion | |||
3334 | } | |||
3335 | | |||
3336 | /// <summary> | |||
3337 | /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data | |||
3338 | /// to and flush, but cannot read, seek or do anything else to. | |||
3339 | /// </summary> | |||
3340 | class UncompressedStream : Stream | |||
3341 | { | |||
3342 | #region Constructors | |||
3343 | public UncompressedStream(Stream baseStream) | |||
3344 | { | |||
3345 | baseStream_ = baseStream; | |||
3346 | } | |||
3347 | | |||
3348 | #endregion | |||
3349 | | |||
3350 | /// <summary> | |||
3351 | /// Close this stream instance. | |||
3352 | /// </summary> | |||
3353 | public override void Close() | |||
3354 | { | |||
3355 | // Do nothing | |||
3356 | } | |||
3357 | | |||
3358 | /// <summary> | |||
3359 | /// Gets a value indicating whether the current stream supports reading. | |||
3360 | /// </summary> | |||
3361 | public override bool CanRead { | |||
3362 | get { | |||
3363 | return false; | |||
3364 | } | |||
3365 | } | |||
3366 | | |||
3367 | /// <summary> | |||
3368 | /// Write any buffered data to underlying storage. | |||
3369 | /// </summary> | |||
3370 | public override void Flush() | |||
3371 | { | |||
3372 | baseStream_.Flush(); | |||
3373 | } | |||
3374 | | |||
3375 | /// <summary> | |||
3376 | /// Gets a value indicating whether the current stream supports writing. | |||
3377 | /// </summary> | |||
3378 | public override bool CanWrite { | |||
3379 | get { | |||
3380 | return baseStream_.CanWrite; | |||
3381 | } | |||
3382 | } | |||
3383 | | |||
3384 | /// <summary> | |||
3385 | /// Gets a value indicating whether the current stream supports seeking. | |||
3386 | /// </summary> | |||
3387 | public override bool CanSeek { | |||
3388 | get { | |||
3389 | return false; | |||
3390 | } | |||
3391 | } | |||
3392 | | |||
3393 | /// <summary> | |||
3394 | /// Get the length in bytes of the stream. | |||
3395 | /// </summary> | |||
3396 | public override long Length { | |||
3397 | get { | |||
3398 | return 0; | |||
3399 | } | |||
3400 | } | |||
3401 | | |||
3402 | /// <summary> | |||
3403 | /// Gets or sets the position within the current stream. | |||
3404 | /// </summary> | |||
3405 | public override long Position { | |||
3406 | get { | |||
3407 | return baseStream_.Position; | |||
3408 | } | |||
3409 | set { | |||
3410 | throw new NotImplementedException(); | |||
3411 | } | |||
3412 | } | |||
3413 | | |||
3414 | /// <summary> | |||
3415 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3416 | /// </summary> | |||
3417 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3418 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3419 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3420 | /// <returns> | |||
3421 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3422 | /// </returns> | |||
3423 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3424 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3425 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3426 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3427 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3428 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3429 | public override int Read(byte[] buffer, int offset, int count) | |||
3430 | { | |||
3431 | return 0; | |||
3432 | } | |||
3433 | | |||
3434 | /// <summary> | |||
3435 | /// Sets the position within the current stream. | |||
3436 | /// </summary> | |||
3437 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3438 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3439 | /// <returns> | |||
3440 | /// The new position within the current stream. | |||
3441 | /// </returns> | |||
3442 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3443 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3444 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3445 | public override long Seek(long offset, SeekOrigin origin) | |||
3446 | { | |||
3447 | return 0; | |||
3448 | } | |||
3449 | | |||
3450 | /// <summary> | |||
3451 | /// Sets the length of the current stream. | |||
3452 | /// </summary> | |||
3453 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3454 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3455 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3456 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3457 | public override void SetLength(long value) | |||
3458 | { | |||
3459 | } | |||
3460 | | |||
3461 | /// <summary> | |||
3462 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3463 | /// </summary> | |||
3464 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3465 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3466 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3467 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3468 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3469 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3470 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3471 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3472 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3473 | public override void Write(byte[] buffer, int offset, int count) | |||
3474 | { | |||
3475 | baseStream_.Write(buffer, offset, count); | |||
3476 | } | |||
3477 | | |||
3478 | readonly | |||
3479 | | |||
3480 | #region Instance Fields | |||
3481 | Stream baseStream_; | |||
3482 | #endregion | |||
3483 | } | |||
3484 | | |||
3485 | /// <summary> | |||
3486 | /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> | |||
3487 | /// whose data is only a part or subsection of a file. | |||
3488 | /// </summary> | |||
3489 | class PartialInputStream : Stream | |||
3490 | { | |||
3491 | #region Constructors | |||
3492 | /// <summary> | |||
3493 | /// Initialise a new instance of the <see cref="PartialInputStream"/> class. | |||
3494 | /// </summary> | |||
3495 | /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param> | |||
3496 | /// <param name="start">The start of the partial data.</param> | |||
3497 | /// <param name="length">The length of the partial data.</param> | |||
3498 | public PartialInputStream(ZipFile zipFile, long start, long length) | |||
3499 | { | |||
3500 | start_ = start; | |||
3501 | length_ = length; | |||
3502 | | |||
3503 | // Although this is the only time the zipfile is used | |||
3504 | // keeping a reference here prevents premature closure of | |||
3505 | // this zip file and thus the baseStream_. | |||
3506 | | |||
3507 | // Code like this will cause apparently random failures depending | |||
3508 | // on the size of the files and when garbage is collected. | |||
3509 | // | |||
3510 | // ZipFile z = new ZipFile (stream); | |||
3511 | // Stream reader = z.GetInputStream(0); | |||
3512 | // uses reader here.... | |||
3513 | zipFile_ = zipFile; | |||
3514 | baseStream_ = zipFile_.baseStream_; | |||
3515 | readPos_ = start; | |||
3516 | end_ = start + length; | |||
3517 | } | |||
3518 | #endregion | |||
3519 | | |||
3520 | /// <summary> | |||
3521 | /// Read a byte from this stream. | |||
3522 | /// </summary> | |||
3523 | /// <returns>Returns the byte read or -1 on end of stream.</returns> | |||
3524 | public override int ReadByte() | |||
3525 | { | |||
3526 | if (readPos_ >= end_) { | |||
3527 | // -1 is the correct value at end of stream. | |||
3528 | return -1; | |||
3529 | } | |||
3530 | | |||
3531 | lock (baseStream_) { | |||
3532 | baseStream_.Seek(readPos_++, SeekOrigin.Begin); | |||
3533 | return baseStream_.ReadByte(); | |||
3534 | } | |||
3535 | } | |||
3536 | | |||
3537 | /// <summary> | |||
3538 | /// Close this <see cref="PartialInputStream">partial input stream</see>. | |||
3539 | /// </summary> | |||
3540 | /// <remarks> | |||
3541 | /// The underlying stream is not closed. Close the parent ZipFile class to do that. | |||
3542 | /// </remarks> | |||
3543 | public override void Close() | |||
3544 | { | |||
3545 | // Do nothing at all! | |||
3546 | } | |||
3547 | | |||
3548 | /// <summary> | |||
3549 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3550 | /// </summary> | |||
3551 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3552 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3553 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3554 | /// <returns> | |||
3555 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3556 | /// </returns> | |||
3557 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3558 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3559 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3560 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3561 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3562 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3563 | public override int Read(byte[] buffer, int offset, int count) | |||
3564 | { | |||
3565 | lock (baseStream_) { | |||
3566 | if (count > end_ - readPos_) { | |||
3567 | count = (int)(end_ - readPos_); | |||
3568 | if (count == 0) { | |||
3569 | return 0; | |||
3570 | } | |||
3571 | } | |||
3572 | // Protect against Stream implementations that throw away their buffer on every Seek | |||
3573 | // (for example, Mono FileStream) | |||
3574 | if (baseStream_.Position != readPos_) { | |||
3575 | baseStream_.Seek(readPos_, SeekOrigin.Begin); | |||
3576 | } | |||
3577 | int readCount = baseStream_.Read(buffer, offset, count); | |||
3578 | if (readCount > 0) { | |||
3579 | readPos_ += readCount; | |||
3580 | } | |||
3581 | return readCount; | |||
3582 | } | |||
3583 | } | |||
3584 | | |||
3585 | /// <summary> | |||
3586 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3587 | /// </summary> | |||
3588 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3589 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3590 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3591 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3592 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3593 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3594 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3595 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3596 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3597 | public override void Write(byte[] buffer, int offset, int count) | |||
3598 | { | |||
3599 | throw new NotSupportedException(); | |||
3600 | } | |||
3601 | | |||
3602 | /// <summary> | |||
3603 | /// When overridden in a derived class, sets the length of the current stream. | |||
3604 | /// </summary> | |||
3605 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3606 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3607 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3608 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3609 | public override void SetLength(long value) | |||
3610 | { | |||
3611 | throw new NotSupportedException(); | |||
3612 | } | |||
3613 | | |||
3614 | /// <summary> | |||
3615 | /// When overridden in a derived class, sets the position within the current stream. | |||
3616 | /// </summary> | |||
3617 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3618 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3619 | /// <returns> | |||
3620 | /// The new position within the current stream. | |||
3621 | /// </returns> | |||
3622 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3623 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3624 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3625 | public override long Seek(long offset, SeekOrigin origin) | |||
3626 | { | |||
3627 | long newPos = readPos_; | |||
3628 | | |||
3629 | switch (origin) { | |||
3630 | case SeekOrigin.Begin: | |||
3631 | newPos = start_ + offset; | |||
3632 | break; | |||
3633 | | |||
3634 | case SeekOrigin.Current: | |||
3635 | newPos = readPos_ + offset; | |||
3636 | break; | |||
3637 | | |||
3638 | case SeekOrigin.End: | |||
3639 | newPos = end_ + offset; | |||
3640 | break; | |||
3641 | } | |||
3642 | | |||
3643 | if (newPos < start_) { | |||
3644 | throw new ArgumentException("Negative position is invalid"); | |||
3645 | } | |||
3646 | | |||
3647 | if (newPos >= end_) { | |||
3648 | throw new IOException("Cannot seek past end"); | |||
3649 | } | |||
3650 | readPos_ = newPos; | |||
3651 | return readPos_; | |||
3652 | } | |||
3653 | | |||
3654 | /// <summary> | |||
3655 | /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | |||
3656 | /// </summary> | |||
3657 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3658 | public override void Flush() | |||
3659 | { | |||
3660 | // Nothing to do. | |||
3661 | } | |||
3662 | | |||
3663 | /// <summary> | |||
3664 | /// Gets or sets the position within the current stream. | |||
3665 | /// </summary> | |||
3666 | /// <value></value> | |||
3667 | /// <returns>The current position within the stream.</returns> | |||
3668 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3669 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> | |||
3670 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3671 | public override long Position { | |||
3672 | get { return readPos_ - start_; } | |||
3673 | set { | |||
3674 | long newPos = start_ + value; | |||
3675 | | |||
3676 | if (newPos < start_) { | |||
3677 | throw new ArgumentException("Negative position is invalid"); | |||
3678 | } | |||
3679 | | |||
3680 | if (newPos >= end_) { | |||
3681 | throw new InvalidOperationException("Cannot seek past end"); | |||
3682 | } | |||
3683 | readPos_ = newPos; | |||
3684 | } | |||
3685 | } | |||
3686 | | |||
3687 | /// <summary> | |||
3688 | /// Gets the length in bytes of the stream. | |||
3689 | /// </summary> | |||
3690 | /// <value></value> | |||
3691 | /// <returns>A long value representing the length of the stream in bytes.</returns> | |||
3692 | /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </excep | |||
3693 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3694 | public override long Length { | |||
3695 | get { return length_; } | |||
3696 | } | |||
3697 | | |||
3698 | /// <summary> | |||
3699 | /// Gets a value indicating whether the current stream supports writing. | |||
3700 | /// </summary> | |||
3701 | /// <value>false</value> | |||
3702 | /// <returns>true if the stream supports writing; otherwise, false.</returns> | |||
3703 | public override bool CanWrite { | |||
3704 | get { return false; } | |||
3705 | } | |||
3706 | | |||
3707 | /// <summary> | |||
3708 | /// Gets a value indicating whether the current stream supports seeking. | |||
3709 | /// </summary> | |||
3710 | /// <value>true</value> | |||
3711 | /// <returns>true if the stream supports seeking; otherwise, false.</returns> | |||
3712 | public override bool CanSeek { | |||
3713 | get { return true; } | |||
3714 | } | |||
3715 | | |||
3716 | /// <summary> | |||
3717 | /// Gets a value indicating whether the current stream supports reading. | |||
3718 | /// </summary> | |||
3719 | /// <value>true.</value> | |||
3720 | /// <returns>true if the stream supports reading; otherwise, false.</returns> | |||
3721 | public override bool CanRead { | |||
3722 | get { return true; } | |||
3723 | } | |||
3724 | | |||
3725 | /// <summary> | |||
3726 | /// Gets a value that determines whether the current stream can time out. | |||
3727 | /// </summary> | |||
3728 | /// <value></value> | |||
3729 | /// <returns>A value that determines whether the current stream can time out.</returns> | |||
3730 | public override bool CanTimeout { | |||
3731 | get { return baseStream_.CanTimeout; } | |||
3732 | } | |||
3733 | #region Instance Fields | |||
3734 | ZipFile zipFile_; | |||
3735 | Stream baseStream_; | |||
3736 | long start_; | |||
3737 | long length_; | |||
3738 | long readPos_; | |||
3739 | long end_; | |||
3740 | #endregion | |||
3741 | } | |||
3742 | #endregion | |||
3743 | } | |||
3744 | | |||
3745 | #endregion | |||
3746 | | |||
3747 | #region DataSources | |||
3748 | /// <summary> | |||
3749 | /// Provides a static way to obtain a source of data for an entry. | |||
3750 | /// </summary> | |||
3751 | public interface IStaticDataSource | |||
3752 | { | |||
3753 | /// <summary> | |||
3754 | /// Get a source of data by creating a new stream. | |||
3755 | /// </summary> | |||
3756 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3757 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3758 | Stream GetSource(); | |||
3759 | } | |||
3760 | | |||
3761 | /// <summary> | |||
3762 | /// Represents a source of data that can dynamically provide | |||
3763 | /// multiple <see cref="Stream">data sources</see> based on the parameters passed. | |||
3764 | /// </summary> | |||
3765 | public interface IDynamicDataSource | |||
3766 | { | |||
3767 | /// <summary> | |||
3768 | /// Get a data source. | |||
3769 | /// </summary> | |||
3770 | /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> | |||
3771 | /// <param name="name">The name for data if known.</param> | |||
3772 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3773 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3774 | Stream GetSource(ZipEntry entry, string name); | |||
3775 | } | |||
3776 | | |||
3777 | /// <summary> | |||
3778 | /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. | |||
3779 | /// </summary> | |||
3780 | public class StaticDiskDataSource : IStaticDataSource | |||
3781 | { | |||
3782 | /// <summary> | |||
3783 | /// Initialise a new instnace of <see cref="StaticDiskDataSource"/> | |||
3784 | /// </summary> | |||
3785 | /// <param name="fileName">The name of the file to obtain data from.</param> | |||
3786 | public StaticDiskDataSource(string fileName) | |||
3787 | { | |||
3788 | fileName_ = fileName; | |||
3789 | } | |||
3790 | | |||
3791 | #region IDataSource Members | |||
3792 | | |||
3793 | /// <summary> | |||
3794 | /// Get a <see cref="Stream"/> providing data. | |||
3795 | /// </summary> | |||
3796 | /// <returns>Returns a <see cref="Stream"/> provising data.</returns> | |||
3797 | public Stream GetSource() | |||
3798 | { | |||
3799 | return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3800 | } | |||
3801 | | |||
3802 | readonly | |||
3803 | | |||
3804 | #endregion | |||
3805 | #region Instance Fields | |||
3806 | string fileName_; | |||
3807 | #endregion | |||
3808 | } | |||
3809 | | |||
3810 | | |||
3811 | /// <summary> | |||
3812 | /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. | |||
3813 | /// </summary> | |||
3814 | public class DynamicDiskDataSource : IDynamicDataSource | |||
3815 | { | |||
3816 | | |||
3817 | #region IDataSource Members | |||
3818 | /// <summary> | |||
3819 | /// Get a <see cref="Stream"/> providing data for an entry. | |||
3820 | /// </summary> | |||
3821 | /// <param name="entry">The entry to provide data for.</param> | |||
3822 | /// <param name="name">The file name for data if known.</param> | |||
3823 | /// <returns>Returns a stream providing data; or null if not available</returns> | |||
3824 | public Stream GetSource(ZipEntry entry, string name) | |||
3825 | { | |||
| 65540 | 3826 | Stream result = null; | ||
3827 | | |||
| 65540 | 3828 | if (name != null) { | ||
| 3 | 3829 | result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
3830 | } | |||
3831 | | |||
| 65540 | 3832 | return result; | ||
3833 | } | |||
3834 | | |||
3835 | #endregion | |||
3836 | } | |||
3837 | | |||
3838 | #endregion | |||
3839 | | |||
3840 | #region Archive Storage | |||
3841 | /// <summary> | |||
3842 | /// Defines facilities for data storage when updating Zip Archives. | |||
3843 | /// </summary> | |||
3844 | public interface IArchiveStorage | |||
3845 | { | |||
3846 | /// <summary> | |||
3847 | /// Get the <see cref="FileUpdateMode"/> to apply during updates. | |||
3848 | /// </summary> | |||
3849 | FileUpdateMode UpdateMode { get; } | |||
3850 | | |||
3851 | /// <summary> | |||
3852 | /// Get an empty <see cref="Stream"/> that can be used for temporary output. | |||
3853 | /// </summary> | |||
3854 | /// <returns>Returns a temporary output <see cref="Stream"/></returns> | |||
3855 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3856 | Stream GetTemporaryOutput(); | |||
3857 | | |||
3858 | /// <summary> | |||
3859 | /// Convert a temporary output stream to a final stream. | |||
3860 | /// </summary> | |||
3861 | /// <returns>The resulting final <see cref="Stream"/></returns> | |||
3862 | /// <seealso cref="GetTemporaryOutput"/> | |||
3863 | Stream ConvertTemporaryToFinal(); | |||
3864 | | |||
3865 | /// <summary> | |||
3866 | /// Make a temporary copy of the original stream. | |||
3867 | /// </summary> | |||
3868 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
3869 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3870 | Stream MakeTemporaryCopy(Stream stream); | |||
3871 | | |||
3872 | /// <summary> | |||
3873 | /// Return a stream suitable for performing direct updates on the original source. | |||
3874 | /// </summary> | |||
3875 | /// <param name="stream">The current stream.</param> | |||
3876 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3877 | /// <remarks>This may be the current stream passed.</remarks> | |||
3878 | Stream OpenForDirectUpdate(Stream stream); | |||
3879 | | |||
3880 | /// <summary> | |||
3881 | /// Dispose of this instance. | |||
3882 | /// </summary> | |||
3883 | void Dispose(); | |||
3884 | } | |||
3885 | | |||
3886 | /// <summary> | |||
3887 | /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. | |||
3888 | /// </summary> | |||
3889 | abstract public class BaseArchiveStorage : IArchiveStorage | |||
3890 | { | |||
3891 | #region Constructors | |||
3892 | /// <summary> | |||
3893 | /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. | |||
3894 | /// </summary> | |||
3895 | /// <param name="updateMode">The update mode.</param> | |||
3896 | protected BaseArchiveStorage(FileUpdateMode updateMode) | |||
3897 | { | |||
3898 | updateMode_ = updateMode; | |||
3899 | } | |||
3900 | #endregion | |||
3901 | | |||
3902 | #region IArchiveStorage Members | |||
3903 | | |||
3904 | /// <summary> | |||
3905 | /// Gets a temporary output <see cref="Stream"/> | |||
3906 | /// </summary> | |||
3907 | /// <returns>Returns the temporary output stream.</returns> | |||
3908 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3909 | public abstract Stream GetTemporaryOutput(); | |||
3910 | | |||
3911 | /// <summary> | |||
3912 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
3913 | /// </summary> | |||
3914 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
3915 | /// the final storage for the archive.</returns> | |||
3916 | /// <seealso cref="GetTemporaryOutput"/> | |||
3917 | public abstract Stream ConvertTemporaryToFinal(); | |||
3918 | | |||
3919 | /// <summary> | |||
3920 | /// Make a temporary copy of a <see cref="Stream"/>. | |||
3921 | /// </summary> | |||
3922 | /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> | |||
3923 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3924 | public abstract Stream MakeTemporaryCopy(Stream stream); | |||
3925 | | |||
3926 | /// <summary> | |||
3927 | /// Return a stream suitable for performing direct updates on the original source. | |||
3928 | /// </summary> | |||
3929 | /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> | |||
3930 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3931 | public abstract Stream OpenForDirectUpdate(Stream stream); | |||
3932 | | |||
3933 | /// <summary> | |||
3934 | /// Disposes this instance. | |||
3935 | /// </summary> | |||
3936 | public abstract void Dispose(); | |||
3937 | | |||
3938 | /// <summary> | |||
3939 | /// Gets the update mode applicable. | |||
3940 | /// </summary> | |||
3941 | /// <value>The update mode.</value> | |||
3942 | public FileUpdateMode UpdateMode { | |||
3943 | get { | |||
3944 | return updateMode_; | |||
3945 | } | |||
3946 | } | |||
3947 | | |||
3948 | #endregion | |||
3949 | | |||
3950 | #region Instance Fields | |||
3951 | FileUpdateMode updateMode_; | |||
3952 | #endregion | |||
3953 | } | |||
3954 | | |||
3955 | /// <summary> | |||
3956 | /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. | |||
3957 | /// </summary> | |||
3958 | public class DiskArchiveStorage : BaseArchiveStorage | |||
3959 | { | |||
3960 | #region Constructors | |||
3961 | /// <summary> | |||
3962 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3963 | /// </summary> | |||
3964 | /// <param name="file">The file.</param> | |||
3965 | /// <param name="updateMode">The update mode.</param> | |||
3966 | public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) | |||
3967 | : base(updateMode) | |||
3968 | { | |||
3969 | if (file.Name == null) { | |||
3970 | throw new ZipException("Cant handle non file archives"); | |||
3971 | } | |||
3972 | | |||
3973 | fileName_ = file.Name; | |||
3974 | } | |||
3975 | | |||
3976 | /// <summary> | |||
3977 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3978 | /// </summary> | |||
3979 | /// <param name="file">The file.</param> | |||
3980 | public DiskArchiveStorage(ZipFile file) | |||
3981 | : this(file, FileUpdateMode.Safe) | |||
3982 | { | |||
3983 | } | |||
3984 | #endregion | |||
3985 | | |||
3986 | #region IArchiveStorage Members | |||
3987 | | |||
3988 | /// <summary> | |||
3989 | /// Gets a temporary output <see cref="Stream"/> for performing updates on. | |||
3990 | /// </summary> | |||
3991 | /// <returns>Returns the temporary output stream.</returns> | |||
3992 | public override Stream GetTemporaryOutput() | |||
3993 | { | |||
3994 | if (temporaryName_ != null) { | |||
3995 | temporaryName_ = GetTempFileName(temporaryName_, true); | |||
3996 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
3997 | } else { | |||
3998 | // Determine where to place files based on internal strategy. | |||
3999 | // Currently this is always done in system temp directory. | |||
4000 | temporaryName_ = Path.GetTempFileName(); | |||
4001 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
4002 | } | |||
4003 | | |||
4004 | return temporaryStream_; | |||
4005 | } | |||
4006 | | |||
4007 | /// <summary> | |||
4008 | /// Converts a temporary <see cref="Stream"/> to its final form. | |||
4009 | /// </summary> | |||
4010 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4011 | /// the final storage for the archive.</returns> | |||
4012 | public override Stream ConvertTemporaryToFinal() | |||
4013 | { | |||
4014 | if (temporaryStream_ == null) { | |||
4015 | throw new ZipException("No temporary stream has been created"); | |||
4016 | } | |||
4017 | | |||
4018 | Stream result = null; | |||
4019 | | |||
4020 | string moveTempName = GetTempFileName(fileName_, false); | |||
4021 | bool newFileCreated = false; | |||
4022 | | |||
4023 | try { | |||
4024 | temporaryStream_.Close(); | |||
4025 | File.Move(fileName_, moveTempName); | |||
4026 | File.Move(temporaryName_, fileName_); | |||
4027 | newFileCreated = true; | |||
4028 | File.Delete(moveTempName); | |||
4029 | | |||
4030 | result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
4031 | } catch (Exception) { | |||
4032 | result = null; | |||
4033 | | |||
4034 | // Try to roll back changes... | |||
4035 | if (!newFileCreated) { | |||
4036 | File.Move(moveTempName, fileName_); | |||
4037 | File.Delete(temporaryName_); | |||
4038 | } | |||
4039 | | |||
4040 | throw; | |||
4041 | } | |||
4042 | | |||
4043 | return result; | |||
4044 | } | |||
4045 | | |||
4046 | /// <summary> | |||
4047 | /// Make a temporary copy of a stream. | |||
4048 | /// </summary> | |||
4049 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4050 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4051 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4052 | { | |||
4053 | stream.Close(); | |||
4054 | | |||
4055 | temporaryName_ = GetTempFileName(fileName_, true); | |||
4056 | File.Copy(fileName_, temporaryName_, true); | |||
4057 | | |||
4058 | temporaryStream_ = new FileStream(temporaryName_, | |||
4059 | FileMode.Open, | |||
4060 | FileAccess.ReadWrite); | |||
4061 | return temporaryStream_; | |||
4062 | } | |||
4063 | | |||
4064 | /// <summary> | |||
4065 | /// Return a stream suitable for performing direct updates on the original source. | |||
4066 | /// </summary> | |||
4067 | /// <param name="stream">The current stream.</param> | |||
4068 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4069 | /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks> | |||
4070 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4071 | { | |||
4072 | Stream result; | |||
4073 | if ((stream == null) || !stream.CanWrite) { | |||
4074 | if (stream != null) { | |||
4075 | stream.Close(); | |||
4076 | } | |||
4077 | | |||
4078 | result = new FileStream(fileName_, | |||
4079 | FileMode.Open, | |||
4080 | FileAccess.ReadWrite); | |||
4081 | } else { | |||
4082 | result = stream; | |||
4083 | } | |||
4084 | | |||
4085 | return result; | |||
4086 | } | |||
4087 | | |||
4088 | /// <summary> | |||
4089 | /// Disposes this instance. | |||
4090 | /// </summary> | |||
4091 | public override void Dispose() | |||
4092 | { | |||
4093 | if (temporaryStream_ != null) { | |||
4094 | temporaryStream_.Close(); | |||
4095 | } | |||
4096 | } | |||
4097 | | |||
4098 | #endregion | |||
4099 | | |||
4100 | #region Internal routines | |||
4101 | static string GetTempFileName(string original, bool makeTempFile) | |||
4102 | { | |||
4103 | string result = null; | |||
4104 | | |||
4105 | if (original == null) { | |||
4106 | result = Path.GetTempFileName(); | |||
4107 | } else { | |||
4108 | int counter = 0; | |||
4109 | int suffixSeed = DateTime.Now.Second; | |||
4110 | | |||
4111 | while (result == null) { | |||
4112 | counter += 1; | |||
4113 | string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); | |||
4114 | if (!File.Exists(newName)) { | |||
4115 | if (makeTempFile) { | |||
4116 | try { | |||
4117 | // Try and create the file. | |||
4118 | using (FileStream stream = File.Create(newName)) { | |||
4119 | } | |||
4120 | result = newName; | |||
4121 | } catch { | |||
4122 | suffixSeed = DateTime.Now.Second; | |||
4123 | } | |||
4124 | } else { | |||
4125 | result = newName; | |||
4126 | } | |||
4127 | } | |||
4128 | } | |||
4129 | } | |||
4130 | return result; | |||
4131 | } | |||
4132 | #endregion | |||
4133 | | |||
4134 | #region Instance Fields | |||
4135 | Stream temporaryStream_; | |||
4136 | string fileName_; | |||
4137 | string temporaryName_; | |||
4138 | #endregion | |||
4139 | } | |||
4140 | | |||
4141 | /// <summary> | |||
4142 | /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. | |||
4143 | /// </summary> | |||
4144 | public class MemoryArchiveStorage : BaseArchiveStorage | |||
4145 | { | |||
4146 | #region Constructors | |||
4147 | /// <summary> | |||
4148 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4149 | /// </summary> | |||
4150 | public MemoryArchiveStorage() | |||
4151 | : base(FileUpdateMode.Direct) | |||
4152 | { | |||
4153 | } | |||
4154 | | |||
4155 | /// <summary> | |||
4156 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4157 | /// </summary> | |||
4158 | /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> | |||
4159 | /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> | |||
4160 | public MemoryArchiveStorage(FileUpdateMode updateMode) | |||
4161 | : base(updateMode) | |||
4162 | { | |||
4163 | } | |||
4164 | | |||
4165 | #endregion | |||
4166 | | |||
4167 | #region Properties | |||
4168 | /// <summary> | |||
4169 | /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. | |||
4170 | /// </summary> | |||
4171 | public MemoryStream FinalStream { | |||
4172 | get { return finalStream_; } | |||
4173 | } | |||
4174 | | |||
4175 | #endregion | |||
4176 | | |||
4177 | #region IArchiveStorage Members | |||
4178 | | |||
4179 | /// <summary> | |||
4180 | /// Gets the temporary output <see cref="Stream"/> | |||
4181 | /// </summary> | |||
4182 | /// <returns>Returns the temporary output stream.</returns> | |||
4183 | public override Stream GetTemporaryOutput() | |||
4184 | { | |||
4185 | temporaryStream_ = new MemoryStream(); | |||
4186 | return temporaryStream_; | |||
4187 | } | |||
4188 | | |||
4189 | /// <summary> | |||
4190 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
4191 | /// </summary> | |||
4192 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4193 | /// the final storage for the archive.</returns> | |||
4194 | public override Stream ConvertTemporaryToFinal() | |||
4195 | { | |||
4196 | if (temporaryStream_ == null) { | |||
4197 | throw new ZipException("No temporary stream has been created"); | |||
4198 | } | |||
4199 | | |||
4200 | finalStream_ = new MemoryStream(temporaryStream_.ToArray()); | |||
4201 | return finalStream_; | |||
4202 | } | |||
4203 | | |||
4204 | /// <summary> | |||
4205 | /// Make a temporary copy of the original stream. | |||
4206 | /// </summary> | |||
4207 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4208 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4209 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4210 | { | |||
4211 | temporaryStream_ = new MemoryStream(); | |||
4212 | stream.Position = 0; | |||
4213 | StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); | |||
4214 | return temporaryStream_; | |||
4215 | } | |||
4216 | | |||
4217 | /// <summary> | |||
4218 | /// Return a stream suitable for performing direct updates on the original source. | |||
4219 | /// </summary> | |||
4220 | /// <param name="stream">The original source stream</param> | |||
4221 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4222 | /// <remarks>If the <paramref name="stream"/> passed is not null this is used; | |||
4223 | /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> | |||
4224 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4225 | { | |||
4226 | Stream result; | |||
4227 | if ((stream == null) || !stream.CanWrite) { | |||
4228 | | |||
4229 | result = new MemoryStream(); | |||
4230 | | |||
4231 | if (stream != null) { | |||
4232 | stream.Position = 0; | |||
4233 | StreamUtils.Copy(stream, result, new byte[4096]); | |||
4234 | | |||
4235 | stream.Close(); | |||
4236 | } | |||
4237 | } else { | |||
4238 | result = stream; | |||
4239 | } | |||
4240 | | |||
4241 | return result; | |||
4242 | } | |||
4243 | | |||
4244 | /// <summary> | |||
4245 | /// Disposes this instance. | |||
4246 | /// </summary> | |||
4247 | public override void Dispose() | |||
4248 | { | |||
4249 | if (temporaryStream_ != null) { | |||
4250 | temporaryStream_.Close(); | |||
4251 | } | |||
4252 | } | |||
4253 | | |||
4254 | #endregion | |||
4255 | | |||
4256 | #region Instance Fields | |||
4257 | MemoryStream temporaryStream_; | |||
4258 | MemoryStream finalStream_; | |||
4259 | #endregion | |||
4260 | } | |||
4261 | | |||
4262 | #endregion | |||
4263 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.EntryPatchData |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipHelperStream.cs |
| Covered lines: | 0 |
| Uncovered lines: | 4 |
| Coverable lines: | 4 |
| Total lines: | 560 |
| Line coverage: | 0% |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Text; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// Holds data pertinent to a data descriptor. | |||
9 | /// </summary> | |||
10 | public class DescriptorData | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Get /set the compressed size of data. | |||
14 | /// </summary> | |||
15 | public long CompressedSize { | |||
16 | get { return compressedSize; } | |||
17 | set { compressedSize = value; } | |||
18 | } | |||
19 | | |||
20 | /// <summary> | |||
21 | /// Get / set the uncompressed size of data | |||
22 | /// </summary> | |||
23 | public long Size { | |||
24 | get { return size; } | |||
25 | set { size = value; } | |||
26 | } | |||
27 | | |||
28 | /// <summary> | |||
29 | /// Get /set the crc value. | |||
30 | /// </summary> | |||
31 | public long Crc { | |||
32 | get { return crc; } | |||
33 | set { crc = (value & 0xffffffff); } | |||
34 | } | |||
35 | | |||
36 | #region Instance Fields | |||
37 | long size; | |||
38 | long compressedSize; | |||
39 | long crc; | |||
40 | #endregion | |||
41 | } | |||
42 | | |||
43 | class EntryPatchData | |||
44 | { | |||
45 | public long SizePatchOffset { | |||
| 0 | 46 | get { return sizePatchOffset_; } | ||
| 0 | 47 | set { sizePatchOffset_ = value; } | ||
48 | } | |||
49 | | |||
50 | public long CrcPatchOffset { | |||
| 0 | 51 | get { return crcPatchOffset_; } | ||
| 0 | 52 | set { crcPatchOffset_ = value; } | ||
53 | } | |||
54 | | |||
55 | #region Instance Fields | |||
56 | long sizePatchOffset_; | |||
57 | long crcPatchOffset_; | |||
58 | #endregion | |||
59 | } | |||
60 | | |||
61 | /// <summary> | |||
62 | /// This class assists with writing/reading from Zip files. | |||
63 | /// </summary> | |||
64 | internal class ZipHelperStream : Stream | |||
65 | { | |||
66 | #region Constructors | |||
67 | /// <summary> | |||
68 | /// Initialise an instance of this class. | |||
69 | /// </summary> | |||
70 | /// <param name="name">The name of the file to open.</param> | |||
71 | public ZipHelperStream(string name) | |||
72 | { | |||
73 | stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite); | |||
74 | isOwner_ = true; | |||
75 | } | |||
76 | | |||
77 | /// <summary> | |||
78 | /// Initialise a new instance of <see cref="ZipHelperStream"/>. | |||
79 | /// </summary> | |||
80 | /// <param name="stream">The stream to use.</param> | |||
81 | public ZipHelperStream(Stream stream) | |||
82 | { | |||
83 | stream_ = stream; | |||
84 | } | |||
85 | #endregion | |||
86 | | |||
87 | /// <summary> | |||
88 | /// Get / set a value indicating wether the the underlying stream is owned or not. | |||
89 | /// </summary> | |||
90 | /// <remarks>If the stream is owned it is closed when this instance is closed.</remarks> | |||
91 | public bool IsStreamOwner { | |||
92 | get { return isOwner_; } | |||
93 | set { isOwner_ = value; } | |||
94 | } | |||
95 | | |||
96 | #region Base Stream Methods | |||
97 | public override bool CanRead { | |||
98 | get { return stream_.CanRead; } | |||
99 | } | |||
100 | | |||
101 | public override bool CanSeek { | |||
102 | get { return stream_.CanSeek; } | |||
103 | } | |||
104 | | |||
105 | public override bool CanTimeout { | |||
106 | get { return stream_.CanTimeout; } | |||
107 | } | |||
108 | | |||
109 | public override long Length { | |||
110 | get { return stream_.Length; } | |||
111 | } | |||
112 | | |||
113 | public override long Position { | |||
114 | get { return stream_.Position; } | |||
115 | set { stream_.Position = value; } | |||
116 | } | |||
117 | | |||
118 | public override bool CanWrite { | |||
119 | get { return stream_.CanWrite; } | |||
120 | } | |||
121 | | |||
122 | public override void Flush() | |||
123 | { | |||
124 | stream_.Flush(); | |||
125 | } | |||
126 | | |||
127 | public override long Seek(long offset, SeekOrigin origin) | |||
128 | { | |||
129 | return stream_.Seek(offset, origin); | |||
130 | } | |||
131 | | |||
132 | public override void SetLength(long value) | |||
133 | { | |||
134 | stream_.SetLength(value); | |||
135 | } | |||
136 | | |||
137 | public override int Read(byte[] buffer, int offset, int count) | |||
138 | { | |||
139 | return stream_.Read(buffer, offset, count); | |||
140 | } | |||
141 | | |||
142 | public override void Write(byte[] buffer, int offset, int count) | |||
143 | { | |||
144 | stream_.Write(buffer, offset, count); | |||
145 | } | |||
146 | | |||
147 | /// <summary> | |||
148 | /// Close the stream. | |||
149 | /// </summary> | |||
150 | /// <remarks> | |||
151 | /// The underlying stream is closed only if <see cref="IsStreamOwner"/> is true. | |||
152 | /// </remarks> | |||
153 | override public void Close() | |||
154 | { | |||
155 | Stream toClose = stream_; | |||
156 | stream_ = null; | |||
157 | if (isOwner_ && (toClose != null)) { | |||
158 | isOwner_ = false; | |||
159 | toClose.Close(); | |||
160 | } | |||
161 | } | |||
162 | #endregion | |||
163 | | |||
164 | // Write the local file header | |||
165 | // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage | |||
166 | void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData) | |||
167 | { | |||
168 | CompressionMethod method = entry.CompressionMethod; | |||
169 | bool headerInfoAvailable = true; // How to get this? | |||
170 | bool patchEntryHeader = false; | |||
171 | | |||
172 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
173 | | |||
174 | WriteLEShort(entry.Version); | |||
175 | WriteLEShort(entry.Flags); | |||
176 | WriteLEShort((byte)method); | |||
177 | WriteLEInt((int)entry.DosTime); | |||
178 | | |||
179 | if (headerInfoAvailable == true) { | |||
180 | WriteLEInt((int)entry.Crc); | |||
181 | if (entry.LocalHeaderRequiresZip64) { | |||
182 | WriteLEInt(-1); | |||
183 | WriteLEInt(-1); | |||
184 | } else { | |||
185 | WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.Compressed | |||
186 | WriteLEInt((int)entry.Size); | |||
187 | } | |||
188 | } else { | |||
189 | if (patchData != null) { | |||
190 | patchData.CrcPatchOffset = stream_.Position; | |||
191 | } | |||
192 | WriteLEInt(0); // Crc | |||
193 | | |||
194 | if (patchData != null) { | |||
195 | patchData.SizePatchOffset = stream_.Position; | |||
196 | } | |||
197 | | |||
198 | // For local header both sizes appear in Zip64 Extended Information | |||
199 | if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { | |||
200 | WriteLEInt(-1); | |||
201 | WriteLEInt(-1); | |||
202 | } else { | |||
203 | WriteLEInt(0); // Compressed size | |||
204 | WriteLEInt(0); // Uncompressed size | |||
205 | } | |||
206 | } | |||
207 | | |||
208 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
209 | | |||
210 | if (name.Length > 0xFFFF) { | |||
211 | throw new ZipException("Entry name too long."); | |||
212 | } | |||
213 | | |||
214 | var ed = new ZipExtraData(entry.ExtraData); | |||
215 | | |||
216 | if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) { | |||
217 | ed.StartNewEntry(); | |||
218 | if (headerInfoAvailable) { | |||
219 | ed.AddLeLong(entry.Size); | |||
220 | ed.AddLeLong(entry.CompressedSize); | |||
221 | } else { | |||
222 | ed.AddLeLong(-1); | |||
223 | ed.AddLeLong(-1); | |||
224 | } | |||
225 | ed.AddNewEntry(1); | |||
226 | | |||
227 | if (!ed.Find(1)) { | |||
228 | throw new ZipException("Internal error cant find extra data"); | |||
229 | } | |||
230 | | |||
231 | if (patchData != null) { | |||
232 | patchData.SizePatchOffset = ed.CurrentReadIndex; | |||
233 | } | |||
234 | } else { | |||
235 | ed.Delete(1); | |||
236 | } | |||
237 | | |||
238 | byte[] extra = ed.GetEntryData(); | |||
239 | | |||
240 | WriteLEShort(name.Length); | |||
241 | WriteLEShort(extra.Length); | |||
242 | | |||
243 | if (name.Length > 0) { | |||
244 | stream_.Write(name, 0, name.Length); | |||
245 | } | |||
246 | | |||
247 | if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { | |||
248 | patchData.SizePatchOffset += stream_.Position; | |||
249 | } | |||
250 | | |||
251 | if (extra.Length > 0) { | |||
252 | stream_.Write(extra, 0, extra.Length); | |||
253 | } | |||
254 | } | |||
255 | | |||
256 | /// <summary> | |||
257 | /// Locates a block with the desired <paramref name="signature"/>. | |||
258 | /// </summary> | |||
259 | /// <param name="signature">The signature to find.</param> | |||
260 | /// <param name="endLocation">Location, marking the end of block.</param> | |||
261 | /// <param name="minimumBlockSize">Minimum size of the block.</param> | |||
262 | /// <param name="maximumVariableData">The maximum variable data.</param> | |||
263 | /// <returns>Eeturns the offset of the first byte after the signature; -1 if not found</returns> | |||
264 | public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
265 | { | |||
266 | long pos = endLocation - minimumBlockSize; | |||
267 | if (pos < 0) { | |||
268 | return -1; | |||
269 | } | |||
270 | | |||
271 | long giveUpMarker = Math.Max(pos - maximumVariableData, 0); | |||
272 | | |||
273 | // TODO: This loop could be optimised for speed. | |||
274 | do { | |||
275 | if (pos < giveUpMarker) { | |||
276 | return -1; | |||
277 | } | |||
278 | Seek(pos--, SeekOrigin.Begin); | |||
279 | } while (ReadLEInt() != signature); | |||
280 | | |||
281 | return Position; | |||
282 | } | |||
283 | | |||
284 | /// <summary> | |||
285 | /// Write Zip64 end of central directory records (File header and locator). | |||
286 | /// </summary> | |||
287 | /// <param name="noOfEntries">The number of entries in the central directory.</param> | |||
288 | /// <param name="sizeEntries">The size of entries in the central directory.</param> | |||
289 | /// <param name="centralDirOffset">The offset of the dentral directory.</param> | |||
290 | public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset) | |||
291 | { | |||
292 | long centralSignatureOffset = stream_.Position; | |||
293 | WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature); | |||
294 | WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12) | |||
295 | WriteLEShort(ZipConstants.VersionMadeBy); // Version made by | |||
296 | WriteLEShort(ZipConstants.VersionZip64); // Version to extract | |||
297 | WriteLEInt(0); // Number of this disk | |||
298 | WriteLEInt(0); // number of the disk with the start of the central directory | |||
299 | WriteLELong(noOfEntries); // No of entries on this disk | |||
300 | WriteLELong(noOfEntries); // Total No of entries in central directory | |||
301 | WriteLELong(sizeEntries); // Size of the central directory | |||
302 | WriteLELong(centralDirOffset); // offset of start of central directory | |||
303 | // zip64 extensible data sector not catered for here (variable size) | |||
304 | | |||
305 | // Write the Zip64 end of central directory locator | |||
306 | WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature); | |||
307 | | |||
308 | // no of the disk with the start of the zip64 end of central directory | |||
309 | WriteLEInt(0); | |||
310 | | |||
311 | // relative offset of the zip64 end of central directory record | |||
312 | WriteLELong(centralSignatureOffset); | |||
313 | | |||
314 | // total number of disks | |||
315 | WriteLEInt(1); | |||
316 | } | |||
317 | | |||
318 | /// <summary> | |||
319 | /// Write the required records to end the central directory. | |||
320 | /// </summary> | |||
321 | /// <param name="noOfEntries">The number of entries in the directory.</param> | |||
322 | /// <param name="sizeEntries">The size of the entries in the directory.</param> | |||
323 | /// <param name="startOfCentralDirectory">The start of the central directory.</param> | |||
324 | /// <param name="comment">The archive comment. (This can be null).</param> | |||
325 | public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries, | |||
326 | long startOfCentralDirectory, byte[] comment) | |||
327 | { | |||
328 | | |||
329 | if ((noOfEntries >= 0xffff) || | |||
330 | (startOfCentralDirectory >= 0xffffffff) || | |||
331 | (sizeEntries >= 0xffffffff)) { | |||
332 | WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory); | |||
333 | } | |||
334 | | |||
335 | WriteLEInt(ZipConstants.EndOfCentralDirectorySignature); | |||
336 | | |||
337 | // TODO: ZipFile Multi disk handling not done | |||
338 | WriteLEShort(0); // number of this disk | |||
339 | WriteLEShort(0); // no of disk with start of central dir | |||
340 | | |||
341 | | |||
342 | // Number of entries | |||
343 | if (noOfEntries >= 0xffff) { | |||
344 | WriteLEUshort(0xffff); // Zip64 marker | |||
345 | WriteLEUshort(0xffff); | |||
346 | } else { | |||
347 | WriteLEShort((short)noOfEntries); // entries in central dir for this disk | |||
348 | WriteLEShort((short)noOfEntries); // total entries in central directory | |||
349 | } | |||
350 | | |||
351 | // Size of the central directory | |||
352 | if (sizeEntries >= 0xffffffff) { | |||
353 | WriteLEUint(0xffffffff); // Zip64 marker | |||
354 | } else { | |||
355 | WriteLEInt((int)sizeEntries); | |||
356 | } | |||
357 | | |||
358 | | |||
359 | // offset of start of central directory | |||
360 | if (startOfCentralDirectory >= 0xffffffff) { | |||
361 | WriteLEUint(0xffffffff); // Zip64 marker | |||
362 | } else { | |||
363 | WriteLEInt((int)startOfCentralDirectory); | |||
364 | } | |||
365 | | |||
366 | int commentLength = (comment != null) ? comment.Length : 0; | |||
367 | | |||
368 | if (commentLength > 0xffff) { | |||
369 | throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength)); | |||
370 | } | |||
371 | | |||
372 | WriteLEShort(commentLength); | |||
373 | | |||
374 | if (commentLength > 0) { | |||
375 | Write(comment, 0, comment.Length); | |||
376 | } | |||
377 | } | |||
378 | | |||
379 | #region LE value reading/writing | |||
380 | /// <summary> | |||
381 | /// Read an unsigned short in little endian byte order. | |||
382 | /// </summary> | |||
383 | /// <returns>Returns the value read.</returns> | |||
384 | /// <exception cref="IOException"> | |||
385 | /// An i/o error occurs. | |||
386 | /// </exception> | |||
387 | /// <exception cref="EndOfStreamException"> | |||
388 | /// The file ends prematurely | |||
389 | /// </exception> | |||
390 | public int ReadLEShort() | |||
391 | { | |||
392 | int byteValue1 = stream_.ReadByte(); | |||
393 | | |||
394 | if (byteValue1 < 0) { | |||
395 | throw new EndOfStreamException(); | |||
396 | } | |||
397 | | |||
398 | int byteValue2 = stream_.ReadByte(); | |||
399 | if (byteValue2 < 0) { | |||
400 | throw new EndOfStreamException(); | |||
401 | } | |||
402 | | |||
403 | return byteValue1 | (byteValue2 << 8); | |||
404 | } | |||
405 | | |||
406 | /// <summary> | |||
407 | /// Read an int in little endian byte order. | |||
408 | /// </summary> | |||
409 | /// <returns>Returns the value read.</returns> | |||
410 | /// <exception cref="IOException"> | |||
411 | /// An i/o error occurs. | |||
412 | /// </exception> | |||
413 | /// <exception cref="System.IO.EndOfStreamException"> | |||
414 | /// The file ends prematurely | |||
415 | /// </exception> | |||
416 | public int ReadLEInt() | |||
417 | { | |||
418 | return ReadLEShort() | (ReadLEShort() << 16); | |||
419 | } | |||
420 | | |||
421 | /// <summary> | |||
422 | /// Read a long in little endian byte order. | |||
423 | /// </summary> | |||
424 | /// <returns>The value read.</returns> | |||
425 | public long ReadLELong() | |||
426 | { | |||
427 | return (uint)ReadLEInt() | ((long)ReadLEInt() << 32); | |||
428 | } | |||
429 | | |||
430 | /// <summary> | |||
431 | /// Write an unsigned short in little endian byte order. | |||
432 | /// </summary> | |||
433 | /// <param name="value">The value to write.</param> | |||
434 | public void WriteLEShort(int value) | |||
435 | { | |||
436 | stream_.WriteByte((byte)(value & 0xff)); | |||
437 | stream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
438 | } | |||
439 | | |||
440 | /// <summary> | |||
441 | /// Write a ushort in little endian byte order. | |||
442 | /// </summary> | |||
443 | /// <param name="value">The value to write.</param> | |||
444 | public void WriteLEUshort(ushort value) | |||
445 | { | |||
446 | stream_.WriteByte((byte)(value & 0xff)); | |||
447 | stream_.WriteByte((byte)(value >> 8)); | |||
448 | } | |||
449 | | |||
450 | /// <summary> | |||
451 | /// Write an int in little endian byte order. | |||
452 | /// </summary> | |||
453 | /// <param name="value">The value to write.</param> | |||
454 | public void WriteLEInt(int value) | |||
455 | { | |||
456 | WriteLEShort(value); | |||
457 | WriteLEShort(value >> 16); | |||
458 | } | |||
459 | | |||
460 | /// <summary> | |||
461 | /// Write a uint in little endian byte order. | |||
462 | /// </summary> | |||
463 | /// <param name="value">The value to write.</param> | |||
464 | public void WriteLEUint(uint value) | |||
465 | { | |||
466 | WriteLEUshort((ushort)(value & 0xffff)); | |||
467 | WriteLEUshort((ushort)(value >> 16)); | |||
468 | } | |||
469 | | |||
470 | /// <summary> | |||
471 | /// Write a long in little endian byte order. | |||
472 | /// </summary> | |||
473 | /// <param name="value">The value to write.</param> | |||
474 | public void WriteLELong(long value) | |||
475 | { | |||
476 | WriteLEInt((int)value); | |||
477 | WriteLEInt((int)(value >> 32)); | |||
478 | } | |||
479 | | |||
480 | /// <summary> | |||
481 | /// Write a ulong in little endian byte order. | |||
482 | /// </summary> | |||
483 | /// <param name="value">The value to write.</param> | |||
484 | public void WriteLEUlong(ulong value) | |||
485 | { | |||
486 | WriteLEUint((uint)(value & 0xffffffff)); | |||
487 | WriteLEUint((uint)(value >> 32)); | |||
488 | } | |||
489 | | |||
490 | #endregion | |||
491 | | |||
492 | /// <summary> | |||
493 | /// Write a data descriptor. | |||
494 | /// </summary> | |||
495 | /// <param name="entry">The entry to write a descriptor for.</param> | |||
496 | /// <returns>Returns the number of descriptor bytes written.</returns> | |||
497 | public int WriteDataDescriptor(ZipEntry entry) | |||
498 | { | |||
499 | if (entry == null) { | |||
500 | throw new ArgumentNullException(nameof(entry)); | |||
501 | } | |||
502 | | |||
503 | int result = 0; | |||
504 | | |||
505 | // Add data descriptor if flagged as required | |||
506 | if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
507 | // The signature is not PKZIP originally but is now described as optional | |||
508 | // in the PKZIP Appnote documenting trhe format. | |||
509 | WriteLEInt(ZipConstants.DataDescriptorSignature); | |||
510 | WriteLEInt(unchecked((int)(entry.Crc))); | |||
511 | | |||
512 | result += 8; | |||
513 | | |||
514 | if (entry.LocalHeaderRequiresZip64) { | |||
515 | WriteLELong(entry.CompressedSize); | |||
516 | WriteLELong(entry.Size); | |||
517 | result += 16; | |||
518 | } else { | |||
519 | WriteLEInt((int)entry.CompressedSize); | |||
520 | WriteLEInt((int)entry.Size); | |||
521 | result += 8; | |||
522 | } | |||
523 | } | |||
524 | | |||
525 | return result; | |||
526 | } | |||
527 | | |||
528 | /// <summary> | |||
529 | /// Read data descriptor at the end of compressed data. | |||
530 | /// </summary> | |||
531 | /// <param name="zip64">if set to <c>true</c> [zip64].</param> | |||
532 | /// <param name="data">The data to fill in.</param> | |||
533 | /// <returns>Returns the number of bytes read in the descriptor.</returns> | |||
534 | public void ReadDataDescriptor(bool zip64, DescriptorData data) | |||
535 | { | |||
536 | int intValue = ReadLEInt(); | |||
537 | | |||
538 | // In theory this may not be a descriptor according to PKZIP appnote. | |||
539 | // In practise its always there. | |||
540 | if (intValue != ZipConstants.DataDescriptorSignature) { | |||
541 | throw new ZipException("Data descriptor signature not found"); | |||
542 | } | |||
543 | | |||
544 | data.Crc = ReadLEInt(); | |||
545 | | |||
546 | if (zip64) { | |||
547 | data.CompressedSize = ReadLELong(); | |||
548 | data.Size = ReadLELong(); | |||
549 | } else { | |||
550 | data.CompressedSize = ReadLEInt(); | |||
551 | data.Size = ReadLEInt(); | |||
552 | } | |||
553 | } | |||
554 | | |||
555 | #region Instance Fields | |||
556 | bool isOwner_; | |||
557 | Stream stream_; | |||
558 | #endregion | |||
559 | } | |||
560 | } |
| Class: | ICSharpCode.SharpZipLib.Core.ExtendedPathFilter |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\PathFilter.cs |
| Covered lines: | 0 |
| Uncovered lines: | 47 |
| Coverable lines: | 47 |
| Total lines: | 280 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| IsMatch(...) | 5 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Core | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// PathFilter filters directories and files using a form of <see cref="System.Text.RegularExpressions.Regex">regular | |||
8 | /// by full path name. | |||
9 | /// See <see cref="NameFilter">NameFilter</see> for more detail on filtering. | |||
10 | /// </summary> | |||
11 | public class PathFilter : IScanFilter | |||
12 | { | |||
13 | #region Constructors | |||
14 | /// <summary> | |||
15 | /// Initialise a new instance of <see cref="PathFilter"></see>. | |||
16 | /// </summary> | |||
17 | /// <param name="filter">The <see cref="NameFilter">filter</see> expression to apply.</param> | |||
18 | public PathFilter(string filter) | |||
19 | { | |||
20 | nameFilter_ = new NameFilter(filter); | |||
21 | } | |||
22 | #endregion | |||
23 | | |||
24 | #region IScanFilter Members | |||
25 | /// <summary> | |||
26 | /// Test a name to see if it matches the filter. | |||
27 | /// </summary> | |||
28 | /// <param name="name">The name to test.</param> | |||
29 | /// <returns>True if the name matches, false otherwise.</returns> | |||
30 | /// <remarks><see cref="Path.GetFullPath(string)"/> is used to get the full path before matching.</remarks> | |||
31 | public virtual bool IsMatch(string name) | |||
32 | { | |||
33 | bool result = false; | |||
34 | | |||
35 | if (name != null) { | |||
36 | string cooked = (name.Length > 0) ? Path.GetFullPath(name) : ""; | |||
37 | result = nameFilter_.IsMatch(cooked); | |||
38 | } | |||
39 | return result; | |||
40 | } | |||
41 | | |||
42 | readonly | |||
43 | #endregion | |||
44 | | |||
45 | #region Instance Fields | |||
46 | NameFilter nameFilter_; | |||
47 | #endregion | |||
48 | } | |||
49 | | |||
50 | /// <summary> | |||
51 | /// ExtendedPathFilter filters based on name, file size, and the last write time of the file. | |||
52 | /// </summary> | |||
53 | /// <remarks>Provides an example of how to customise filtering.</remarks> | |||
54 | public class ExtendedPathFilter : PathFilter | |||
55 | { | |||
56 | #region Constructors | |||
57 | /// <summary> | |||
58 | /// Initialise a new instance of ExtendedPathFilter. | |||
59 | /// </summary> | |||
60 | /// <param name="filter">The filter to apply.</param> | |||
61 | /// <param name="minSize">The minimum file size to include.</param> | |||
62 | /// <param name="maxSize">The maximum file size to include.</param> | |||
63 | public ExtendedPathFilter(string filter, | |||
64 | long minSize, long maxSize) | |||
| 0 | 65 | : base(filter) | ||
66 | { | |||
| 0 | 67 | MinSize = minSize; | ||
| 0 | 68 | MaxSize = maxSize; | ||
| 0 | 69 | } | ||
70 | | |||
71 | /// <summary> | |||
72 | /// Initialise a new instance of ExtendedPathFilter. | |||
73 | /// </summary> | |||
74 | /// <param name="filter">The filter to apply.</param> | |||
75 | /// <param name="minDate">The minimum <see cref="DateTime"/> to include.</param> | |||
76 | /// <param name="maxDate">The maximum <see cref="DateTime"/> to include.</param> | |||
77 | public ExtendedPathFilter(string filter, | |||
78 | DateTime minDate, DateTime maxDate) | |||
| 0 | 79 | : base(filter) | ||
80 | { | |||
| 0 | 81 | MinDate = minDate; | ||
| 0 | 82 | MaxDate = maxDate; | ||
| 0 | 83 | } | ||
84 | | |||
85 | /// <summary> | |||
86 | /// Initialise a new instance of ExtendedPathFilter. | |||
87 | /// </summary> | |||
88 | /// <param name="filter">The filter to apply.</param> | |||
89 | /// <param name="minSize">The minimum file size to include.</param> | |||
90 | /// <param name="maxSize">The maximum file size to include.</param> | |||
91 | /// <param name="minDate">The minimum <see cref="DateTime"/> to include.</param> | |||
92 | /// <param name="maxDate">The maximum <see cref="DateTime"/> to include.</param> | |||
93 | public ExtendedPathFilter(string filter, | |||
94 | long minSize, long maxSize, | |||
95 | DateTime minDate, DateTime maxDate) | |||
| 0 | 96 | : base(filter) | ||
97 | { | |||
| 0 | 98 | MinSize = minSize; | ||
| 0 | 99 | MaxSize = maxSize; | ||
| 0 | 100 | MinDate = minDate; | ||
| 0 | 101 | MaxDate = maxDate; | ||
| 0 | 102 | } | ||
103 | #endregion | |||
104 | | |||
105 | #region IScanFilter Members | |||
106 | /// <summary> | |||
107 | /// Test a filename to see if it matches the filter. | |||
108 | /// </summary> | |||
109 | /// <param name="name">The filename to test.</param> | |||
110 | /// <returns>True if the filter matches, false otherwise.</returns> | |||
111 | /// <exception cref="System.IO.FileNotFoundException">The <see paramref="fileName"/> doesnt exist</exception> | |||
112 | public override bool IsMatch(string name) | |||
113 | { | |||
| 0 | 114 | bool result = base.IsMatch(name); | ||
115 | | |||
| 0 | 116 | if (result) { | ||
| 0 | 117 | var fileInfo = new FileInfo(name); | ||
| 0 | 118 | result = | ||
| 0 | 119 | (MinSize <= fileInfo.Length) && | ||
| 0 | 120 | (MaxSize >= fileInfo.Length) && | ||
| 0 | 121 | (MinDate <= fileInfo.LastWriteTime) && | ||
| 0 | 122 | (MaxDate >= fileInfo.LastWriteTime) | ||
| 0 | 123 | ; | ||
124 | } | |||
| 0 | 125 | return result; | ||
126 | } | |||
127 | #endregion | |||
128 | | |||
129 | #region Properties | |||
130 | /// <summary> | |||
131 | /// Get/set the minimum size/length for a file that will match this filter. | |||
132 | /// </summary> | |||
133 | /// <remarks>The default value is zero.</remarks> | |||
134 | /// <exception cref="ArgumentOutOfRangeException">value is less than zero; greater than <see cref="MaxSize"/></excep | |||
135 | public long MinSize { | |||
| 0 | 136 | get { return minSize_; } | ||
137 | set { | |||
| 0 | 138 | if ((value < 0) || (maxSize_ < value)) { | ||
| 0 | 139 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
140 | } | |||
141 | | |||
| 0 | 142 | minSize_ = value; | ||
| 0 | 143 | } | ||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get/set the maximum size/length for a file that will match this filter. | |||
148 | /// </summary> | |||
149 | /// <remarks>The default value is <see cref="System.Int64.MaxValue"/></remarks> | |||
150 | /// <exception cref="ArgumentOutOfRangeException">value is less than zero or less than <see cref="MinSize"/></except | |||
151 | public long MaxSize { | |||
| 0 | 152 | get { return maxSize_; } | ||
153 | set { | |||
| 0 | 154 | if ((value < 0) || (minSize_ > value)) { | ||
| 0 | 155 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
156 | } | |||
157 | | |||
| 0 | 158 | maxSize_ = value; | ||
| 0 | 159 | } | ||
160 | } | |||
161 | | |||
162 | /// <summary> | |||
163 | /// Get/set the minimum <see cref="DateTime"/> value that will match for this filter. | |||
164 | /// </summary> | |||
165 | /// <remarks>Files with a LastWrite time less than this value are excluded by the filter.</remarks> | |||
166 | public DateTime MinDate { | |||
167 | get { | |||
| 0 | 168 | return minDate_; | ||
169 | } | |||
170 | | |||
171 | set { | |||
| 0 | 172 | if (value > maxDate_) { | ||
| 0 | 173 | throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MaxDate"); | ||
174 | } | |||
175 | | |||
| 0 | 176 | minDate_ = value; | ||
| 0 | 177 | } | ||
178 | } | |||
179 | | |||
180 | /// <summary> | |||
181 | /// Get/set the maximum <see cref="DateTime"/> value that will match for this filter. | |||
182 | /// </summary> | |||
183 | /// <remarks>Files with a LastWrite time greater than this value are excluded by the filter.</remarks> | |||
184 | public DateTime MaxDate { | |||
185 | get { | |||
| 0 | 186 | return maxDate_; | ||
187 | } | |||
188 | | |||
189 | set { | |||
| 0 | 190 | if (minDate_ > value) { | ||
| 0 | 191 | throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MinDate"); | ||
192 | } | |||
193 | | |||
| 0 | 194 | maxDate_ = value; | ||
| 0 | 195 | } | ||
196 | } | |||
197 | #endregion | |||
198 | | |||
199 | #region Instance Fields | |||
200 | long minSize_; | |||
| 0 | 201 | long maxSize_ = long.MaxValue; | ||
| 0 | 202 | DateTime minDate_ = DateTime.MinValue; | ||
| 0 | 203 | DateTime maxDate_ = DateTime.MaxValue; | ||
204 | #endregion | |||
205 | } | |||
206 | | |||
207 | /// <summary> | |||
208 | /// NameAndSizeFilter filters based on name and file size. | |||
209 | /// </summary> | |||
210 | /// <remarks>A sample showing how filters might be extended.</remarks> | |||
211 | [Obsolete("Use ExtendedPathFilter instead")] | |||
212 | public class NameAndSizeFilter : PathFilter | |||
213 | { | |||
214 | | |||
215 | /// <summary> | |||
216 | /// Initialise a new instance of NameAndSizeFilter. | |||
217 | /// </summary> | |||
218 | /// <param name="filter">The filter to apply.</param> | |||
219 | /// <param name="minSize">The minimum file size to include.</param> | |||
220 | /// <param name="maxSize">The maximum file size to include.</param> | |||
221 | public NameAndSizeFilter(string filter, long minSize, long maxSize) | |||
222 | : base(filter) | |||
223 | { | |||
224 | MinSize = minSize; | |||
225 | MaxSize = maxSize; | |||
226 | } | |||
227 | | |||
228 | /// <summary> | |||
229 | /// Test a filename to see if it matches the filter. | |||
230 | /// </summary> | |||
231 | /// <param name="name">The filename to test.</param> | |||
232 | /// <returns>True if the filter matches, false otherwise.</returns> | |||
233 | public override bool IsMatch(string name) | |||
234 | { | |||
235 | bool result = base.IsMatch(name); | |||
236 | | |||
237 | if (result) { | |||
238 | var fileInfo = new FileInfo(name); | |||
239 | long length = fileInfo.Length; | |||
240 | result = | |||
241 | (MinSize <= length) && | |||
242 | (MaxSize >= length); | |||
243 | } | |||
244 | return result; | |||
245 | } | |||
246 | | |||
247 | /// <summary> | |||
248 | /// Get/set the minimum size for a file that will match this filter. | |||
249 | /// </summary> | |||
250 | public long MinSize { | |||
251 | get { return minSize_; } | |||
252 | set { | |||
253 | if ((value < 0) || (maxSize_ < value)) { | |||
254 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
255 | } | |||
256 | | |||
257 | minSize_ = value; | |||
258 | } | |||
259 | } | |||
260 | | |||
261 | /// <summary> | |||
262 | /// Get/set the maximum size for a file that will match this filter. | |||
263 | /// </summary> | |||
264 | public long MaxSize { | |||
265 | get { return maxSize_; } | |||
266 | set { | |||
267 | if ((value < 0) || (minSize_ > value)) { | |||
268 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
269 | } | |||
270 | | |||
271 | maxSize_ = value; | |||
272 | } | |||
273 | } | |||
274 | | |||
275 | #region Instance Fields | |||
276 | long minSize_; | |||
277 | long maxSize_ = long.MaxValue; | |||
278 | #endregion | |||
279 | } | |||
280 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ExtendedUnixData |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipExtraData.cs |
| Covered lines: | 32 |
| Uncovered lines: | 30 |
| Coverable lines: | 62 |
| Total lines: | 896 |
| Line coverage: | 51.6% |
| Branch coverage: | 30% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| SetData(...) | 7 | 56.25 | 33.33 |
| GetData() | 6 | 66.67 | 57.14 |
| IsValidValue(...) | 2 | 100 | 100 |
| .ctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip | |||
5 | { | |||
6 | // TODO: Sort out wether tagged data is useful and what a good implementation might look like. | |||
7 | // Its just a sketch of an idea at the moment. | |||
8 | | |||
9 | /// <summary> | |||
10 | /// ExtraData tagged value interface. | |||
11 | /// </summary> | |||
12 | public interface ITaggedData | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Get the ID for this tagged data value. | |||
16 | /// </summary> | |||
17 | short TagID { get; } | |||
18 | | |||
19 | /// <summary> | |||
20 | /// Set the contents of this instance from the data passed. | |||
21 | /// </summary> | |||
22 | /// <param name="data">The data to extract contents from.</param> | |||
23 | /// <param name="offset">The offset to begin extracting data from.</param> | |||
24 | /// <param name="count">The number of bytes to extract.</param> | |||
25 | void SetData(byte[] data, int offset, int count); | |||
26 | | |||
27 | /// <summary> | |||
28 | /// Get the data representing this instance. | |||
29 | /// </summary> | |||
30 | /// <returns>Returns the data for this instance.</returns> | |||
31 | byte[] GetData(); | |||
32 | } | |||
33 | | |||
34 | /// <summary> | |||
35 | /// A raw binary tagged value | |||
36 | /// </summary> | |||
37 | public class RawTaggedData : ITaggedData | |||
38 | { | |||
39 | /// <summary> | |||
40 | /// Initialise a new instance. | |||
41 | /// </summary> | |||
42 | /// <param name="tag">The tag ID.</param> | |||
43 | public RawTaggedData(short tag) | |||
44 | { | |||
45 | _tag = tag; | |||
46 | } | |||
47 | | |||
48 | #region ITaggedData Members | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Get the ID for this tagged data value. | |||
52 | /// </summary> | |||
53 | public short TagID { | |||
54 | get { return _tag; } | |||
55 | set { _tag = value; } | |||
56 | } | |||
57 | | |||
58 | /// <summary> | |||
59 | /// Set the data from the raw values provided. | |||
60 | /// </summary> | |||
61 | /// <param name="data">The raw data to extract values from.</param> | |||
62 | /// <param name="offset">The index to start extracting values from.</param> | |||
63 | /// <param name="count">The number of bytes available.</param> | |||
64 | public void SetData(byte[] data, int offset, int count) | |||
65 | { | |||
66 | if (data == null) { | |||
67 | throw new ArgumentNullException(nameof(data)); | |||
68 | } | |||
69 | | |||
70 | _data = new byte[count]; | |||
71 | Array.Copy(data, offset, _data, 0, count); | |||
72 | } | |||
73 | | |||
74 | /// <summary> | |||
75 | /// Get the binary data representing this instance. | |||
76 | /// </summary> | |||
77 | /// <returns>The raw binary data representing this instance.</returns> | |||
78 | public byte[] GetData() | |||
79 | { | |||
80 | return _data; | |||
81 | } | |||
82 | | |||
83 | #endregion | |||
84 | | |||
85 | /// <summary> | |||
86 | /// Get /set the binary data representing this instance. | |||
87 | /// </summary> | |||
88 | /// <returns>The raw binary data representing this instance.</returns> | |||
89 | public byte[] Data { | |||
90 | get { return _data; } | |||
91 | set { _data = value; } | |||
92 | } | |||
93 | | |||
94 | #region Instance Fields | |||
95 | /// <summary> | |||
96 | /// The tag ID for this instance. | |||
97 | /// </summary> | |||
98 | short _tag; | |||
99 | | |||
100 | byte[] _data; | |||
101 | #endregion | |||
102 | } | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Class representing extended unix date time values. | |||
106 | /// </summary> | |||
107 | public class ExtendedUnixData : ITaggedData | |||
108 | { | |||
109 | /// <summary> | |||
110 | /// Flags indicate which values are included in this instance. | |||
111 | /// </summary> | |||
112 | [Flags] | |||
113 | public enum Flags : byte | |||
114 | { | |||
115 | /// <summary> | |||
116 | /// The modification time is included | |||
117 | /// </summary> | |||
118 | ModificationTime = 0x01, | |||
119 | | |||
120 | /// <summary> | |||
121 | /// The access time is included | |||
122 | /// </summary> | |||
123 | AccessTime = 0x02, | |||
124 | | |||
125 | /// <summary> | |||
126 | /// The create time is included. | |||
127 | /// </summary> | |||
128 | CreateTime = 0x04, | |||
129 | } | |||
130 | | |||
131 | #region ITaggedData Members | |||
132 | | |||
133 | /// <summary> | |||
134 | /// Get the ID | |||
135 | /// </summary> | |||
136 | public short TagID { | |||
| 66038 | 137 | get { return 0x5455; } | ||
138 | } | |||
139 | | |||
140 | /// <summary> | |||
141 | /// Set the data from the raw values provided. | |||
142 | /// </summary> | |||
143 | /// <param name="data">The raw data to extract values from.</param> | |||
144 | /// <param name="index">The index to start extracting values from.</param> | |||
145 | /// <param name="count">The number of bytes available.</param> | |||
146 | public void SetData(byte[] data, int index, int count) | |||
147 | { | |||
| 1 | 148 | using (MemoryStream ms = new MemoryStream(data, index, count, false)) | ||
| 1 | 149 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | ||
150 | // bit 0 if set, modification time is present | |||
151 | // bit 1 if set, access time is present | |||
152 | // bit 2 if set, creation time is present | |||
153 | | |||
| 1 | 154 | _flags = (Flags)helperStream.ReadByte(); | ||
| 1 | 155 | if (((_flags & Flags.ModificationTime) != 0)) | ||
156 | { | |||
| 1 | 157 | int iTime = helperStream.ReadLEInt(); | ||
158 | | |||
| 1 | 159 | _modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | ||
| 1 | 160 | new TimeSpan(0, 0, 0, iTime, 0); | ||
161 | | |||
162 | // Central-header version is truncated after modification time | |||
| 2 | 163 | if (count <= 5) return; | ||
164 | } | |||
165 | | |||
| 0 | 166 | if ((_flags & Flags.AccessTime) != 0) { | ||
| 0 | 167 | int iTime = helperStream.ReadLEInt(); | ||
168 | | |||
| 0 | 169 | _lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | ||
| 0 | 170 | new TimeSpan(0, 0, 0, iTime, 0); | ||
171 | } | |||
172 | | |||
| 0 | 173 | if ((_flags & Flags.CreateTime) != 0) { | ||
| 0 | 174 | int iTime = helperStream.ReadLEInt(); | ||
175 | | |||
| 0 | 176 | _createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | ||
| 0 | 177 | new TimeSpan(0, 0, 0, iTime, 0); | ||
178 | } | |||
| 0 | 179 | } | ||
| 1 | 180 | } | ||
181 | | |||
182 | /// <summary> | |||
183 | /// Get the binary data representing this instance. | |||
184 | /// </summary> | |||
185 | /// <returns>The raw binary data representing this instance.</returns> | |||
186 | public byte[] GetData() | |||
187 | { | |||
| 1 | 188 | using (MemoryStream ms = new MemoryStream()) | ||
| 1 | 189 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | ||
| 1 | 190 | helperStream.IsStreamOwner = false; | ||
| 1 | 191 | helperStream.WriteByte((byte)_flags); // Flags | ||
| 1 | 192 | if ((_flags & Flags.ModificationTime) != 0) { | ||
| 1 | 193 | TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | ||
| 1 | 194 | var seconds = (int)span.TotalSeconds; | ||
| 1 | 195 | helperStream.WriteLEInt(seconds); | ||
196 | } | |||
| 1 | 197 | if ((_flags & Flags.AccessTime) != 0) { | ||
| 0 | 198 | TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | ||
| 0 | 199 | var seconds = (int)span.TotalSeconds; | ||
| 0 | 200 | helperStream.WriteLEInt(seconds); | ||
201 | } | |||
| 1 | 202 | if ((_flags & Flags.CreateTime) != 0) { | ||
| 0 | 203 | TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | ||
| 0 | 204 | var seconds = (int)span.TotalSeconds; | ||
| 0 | 205 | helperStream.WriteLEInt(seconds); | ||
206 | } | |||
| 1 | 207 | return ms.ToArray(); | ||
208 | } | |||
| 1 | 209 | } | ||
210 | | |||
211 | #endregion | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Test a <see cref="DateTime"> value to see if is valid and can be represented here.</see> | |||
215 | /// </summary> | |||
216 | /// <param name="value">The <see cref="DateTime">value</see> to test.</param> | |||
217 | /// <returns>Returns true if the value is valid and can be represented; false if not.</returns> | |||
218 | /// <remarks>The standard Unix time is a signed integer data type, directly encoding the Unix time number, | |||
219 | /// which is the number of seconds since 1970-01-01. | |||
220 | /// Being 32 bits means the values here cover a range of about 136 years. | |||
221 | /// The minimum representable time is 1901-12-13 20:45:52, | |||
222 | /// and the maximum representable time is 2038-01-19 03:14:07. | |||
223 | /// </remarks> | |||
224 | public static bool IsValidValue(DateTime value) | |||
225 | { | |||
| 2 | 226 | return ((value >= new DateTime(1901, 12, 13, 20, 45, 52)) || | ||
| 2 | 227 | (value <= new DateTime(2038, 1, 19, 03, 14, 07))); | ||
228 | } | |||
229 | | |||
230 | /// <summary> | |||
231 | /// Get /set the Modification Time | |||
232 | /// </summary> | |||
233 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
234 | /// <seealso cref="IsValidValue"></seealso> | |||
235 | public DateTime ModificationTime { | |||
| 4 | 236 | get { return _modificationTime; } | ||
237 | set { | |||
| 2 | 238 | if (!IsValidValue(value)) { | ||
| 0 | 239 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
240 | } | |||
241 | | |||
| 2 | 242 | _flags |= Flags.ModificationTime; | ||
| 2 | 243 | _modificationTime = value; | ||
| 2 | 244 | } | ||
245 | } | |||
246 | | |||
247 | /// <summary> | |||
248 | /// Get / set the Access Time | |||
249 | /// </summary> | |||
250 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
251 | /// <seealso cref="IsValidValue"></seealso> | |||
252 | public DateTime AccessTime { | |||
| 0 | 253 | get { return _lastAccessTime; } | ||
254 | set { | |||
| 0 | 255 | if (!IsValidValue(value)) { | ||
| 0 | 256 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
257 | } | |||
258 | | |||
| 0 | 259 | _flags |= Flags.AccessTime; | ||
| 0 | 260 | _lastAccessTime = value; | ||
| 0 | 261 | } | ||
262 | } | |||
263 | | |||
264 | /// <summary> | |||
265 | /// Get / Set the Create Time | |||
266 | /// </summary> | |||
267 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
268 | /// <seealso cref="IsValidValue"></seealso> | |||
269 | public DateTime CreateTime { | |||
| 0 | 270 | get { return _createTime; } | ||
271 | set { | |||
| 0 | 272 | if (!IsValidValue(value)) { | ||
| 0 | 273 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
274 | } | |||
275 | | |||
| 0 | 276 | _flags |= Flags.CreateTime; | ||
| 0 | 277 | _createTime = value; | ||
| 0 | 278 | } | ||
279 | } | |||
280 | | |||
281 | /// <summary> | |||
282 | /// Get/set the <see cref="Flags">values</see> to include. | |||
283 | /// </summary> | |||
284 | public Flags Include | |||
285 | { | |||
| 0 | 286 | get { return _flags; } | ||
| 0 | 287 | set { _flags = value; } | ||
288 | } | |||
289 | | |||
290 | #region Instance Fields | |||
291 | Flags _flags; | |||
| 66038 | 292 | DateTime _modificationTime = new DateTime(1970, 1, 1); | ||
| 66038 | 293 | DateTime _lastAccessTime = new DateTime(1970, 1, 1); | ||
| 66038 | 294 | DateTime _createTime = new DateTime(1970, 1, 1); | ||
295 | #endregion | |||
296 | } | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Class handling NT date time values. | |||
300 | /// </summary> | |||
301 | public class NTTaggedData : ITaggedData | |||
302 | { | |||
303 | /// <summary> | |||
304 | /// Get the ID for this tagged data value. | |||
305 | /// </summary> | |||
306 | public short TagID { | |||
307 | get { return 10; } | |||
308 | } | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Set the data from the raw values provided. | |||
312 | /// </summary> | |||
313 | /// <param name="data">The raw data to extract values from.</param> | |||
314 | /// <param name="index">The index to start extracting values from.</param> | |||
315 | /// <param name="count">The number of bytes available.</param> | |||
316 | public void SetData(byte[] data, int index, int count) | |||
317 | { | |||
318 | using (MemoryStream ms = new MemoryStream(data, index, count, false)) | |||
319 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
320 | helperStream.ReadLEInt(); // Reserved | |||
321 | while (helperStream.Position < helperStream.Length) { | |||
322 | int ntfsTag = helperStream.ReadLEShort(); | |||
323 | int ntfsLength = helperStream.ReadLEShort(); | |||
324 | if (ntfsTag == 1) { | |||
325 | if (ntfsLength >= 24) { | |||
326 | long lastModificationTicks = helperStream.ReadLELong(); | |||
327 | _lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks); | |||
328 | | |||
329 | long lastAccessTicks = helperStream.ReadLELong(); | |||
330 | _lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks); | |||
331 | | |||
332 | long createTimeTicks = helperStream.ReadLELong(); | |||
333 | _createTime = DateTime.FromFileTimeUtc(createTimeTicks); | |||
334 | } | |||
335 | break; | |||
336 | } else { | |||
337 | // An unknown NTFS tag so simply skip it. | |||
338 | helperStream.Seek(ntfsLength, SeekOrigin.Current); | |||
339 | } | |||
340 | } | |||
341 | } | |||
342 | } | |||
343 | | |||
344 | /// <summary> | |||
345 | /// Get the binary data representing this instance. | |||
346 | /// </summary> | |||
347 | /// <returns>The raw binary data representing this instance.</returns> | |||
348 | public byte[] GetData() | |||
349 | { | |||
350 | using (MemoryStream ms = new MemoryStream()) | |||
351 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
352 | helperStream.IsStreamOwner = false; | |||
353 | helperStream.WriteLEInt(0); // Reserved | |||
354 | helperStream.WriteLEShort(1); // Tag | |||
355 | helperStream.WriteLEShort(24); // Length = 3 x 8. | |||
356 | helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc()); | |||
357 | helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc()); | |||
358 | helperStream.WriteLELong(_createTime.ToFileTimeUtc()); | |||
359 | return ms.ToArray(); | |||
360 | } | |||
361 | } | |||
362 | | |||
363 | /// <summary> | |||
364 | /// Test a <see cref="DateTime"> valuie to see if is valid and can be represented here.</see> | |||
365 | /// </summary> | |||
366 | /// <param name="value">The <see cref="DateTime">value</see> to test.</param> | |||
367 | /// <returns>Returns true if the value is valid and can be represented; false if not.</returns> | |||
368 | /// <remarks> | |||
369 | /// NTFS filetimes are 64-bit unsigned integers, stored in Intel | |||
370 | /// (least significant byte first) byte order. They determine the | |||
371 | /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch", | |||
372 | /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit | |||
373 | /// </remarks> | |||
374 | public static bool IsValidValue(DateTime value) | |||
375 | { | |||
376 | bool result = true; | |||
377 | try { | |||
378 | value.ToFileTimeUtc(); | |||
379 | } catch { | |||
380 | result = false; | |||
381 | } | |||
382 | return result; | |||
383 | } | |||
384 | | |||
385 | /// <summary> | |||
386 | /// Get/set the <see cref="DateTime">last modification time</see>. | |||
387 | /// </summary> | |||
388 | public DateTime LastModificationTime { | |||
389 | get { return _lastModificationTime; } | |||
390 | set { | |||
391 | if (!IsValidValue(value)) { | |||
392 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
393 | } | |||
394 | _lastModificationTime = value; | |||
395 | } | |||
396 | } | |||
397 | | |||
398 | /// <summary> | |||
399 | /// Get /set the <see cref="DateTime">create time</see> | |||
400 | /// </summary> | |||
401 | public DateTime CreateTime { | |||
402 | get { return _createTime; } | |||
403 | set { | |||
404 | if (!IsValidValue(value)) { | |||
405 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
406 | } | |||
407 | _createTime = value; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Get /set the <see cref="DateTime">last access time</see>. | |||
413 | /// </summary> | |||
414 | public DateTime LastAccessTime { | |||
415 | get { return _lastAccessTime; } | |||
416 | set { | |||
417 | if (!IsValidValue(value)) { | |||
418 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
419 | } | |||
420 | _lastAccessTime = value; | |||
421 | } | |||
422 | } | |||
423 | | |||
424 | #region Instance Fields | |||
425 | DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0); | |||
426 | DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0); | |||
427 | DateTime _createTime = DateTime.FromFileTimeUtc(0); | |||
428 | #endregion | |||
429 | } | |||
430 | | |||
431 | /// <summary> | |||
432 | /// A factory that creates <see cref="ITaggedData">tagged data</see> instances. | |||
433 | /// </summary> | |||
434 | interface ITaggedDataFactory | |||
435 | { | |||
436 | /// <summary> | |||
437 | /// Get data for a specific tag value. | |||
438 | /// </summary> | |||
439 | /// <param name="tag">The tag ID to find.</param> | |||
440 | /// <param name="data">The data to search.</param> | |||
441 | /// <param name="offset">The offset to begin extracting data from.</param> | |||
442 | /// <param name="count">The number of bytes to extract.</param> | |||
443 | /// <returns>The located <see cref="ITaggedData">value found</see>, or null if not found.</returns> | |||
444 | ITaggedData Create(short tag, byte[] data, int offset, int count); | |||
445 | } | |||
446 | | |||
447 | /// | |||
448 | /// <summary> | |||
449 | /// A class to handle the extra data field for Zip entries | |||
450 | /// </summary> | |||
451 | /// <remarks> | |||
452 | /// Extra data contains 0 or more values each prefixed by a header tag and length. | |||
453 | /// They contain zero or more bytes of actual data. | |||
454 | /// The data is held internally using a copy on write strategy. This is more efficient but | |||
455 | /// means that for extra data created by passing in data can have the values modified by the caller | |||
456 | /// in some circumstances. | |||
457 | /// </remarks> | |||
458 | sealed public class ZipExtraData : IDisposable | |||
459 | { | |||
460 | #region Constructors | |||
461 | /// <summary> | |||
462 | /// Initialise a default instance. | |||
463 | /// </summary> | |||
464 | public ZipExtraData() | |||
465 | { | |||
466 | Clear(); | |||
467 | } | |||
468 | | |||
469 | /// <summary> | |||
470 | /// Initialise with known extra data. | |||
471 | /// </summary> | |||
472 | /// <param name="data">The extra data.</param> | |||
473 | public ZipExtraData(byte[] data) | |||
474 | { | |||
475 | if (data == null) { | |||
476 | _data = new byte[0]; | |||
477 | } else { | |||
478 | _data = data; | |||
479 | } | |||
480 | } | |||
481 | #endregion | |||
482 | | |||
483 | /// <summary> | |||
484 | /// Get the raw extra data value | |||
485 | /// </summary> | |||
486 | /// <returns>Returns the raw byte[] extra data this instance represents.</returns> | |||
487 | public byte[] GetEntryData() | |||
488 | { | |||
489 | if (Length > ushort.MaxValue) { | |||
490 | throw new ZipException("Data exceeds maximum length"); | |||
491 | } | |||
492 | | |||
493 | return (byte[])_data.Clone(); | |||
494 | } | |||
495 | | |||
496 | /// <summary> | |||
497 | /// Clear the stored data. | |||
498 | /// </summary> | |||
499 | public void Clear() | |||
500 | { | |||
501 | if ((_data == null) || (_data.Length != 0)) { | |||
502 | _data = new byte[0]; | |||
503 | } | |||
504 | } | |||
505 | | |||
506 | /// <summary> | |||
507 | /// Gets the current extra data length. | |||
508 | /// </summary> | |||
509 | public int Length { | |||
510 | get { return _data.Length; } | |||
511 | } | |||
512 | | |||
513 | /// <summary> | |||
514 | /// Get a read-only <see cref="Stream"/> for the associated tag. | |||
515 | /// </summary> | |||
516 | /// <param name="tag">The tag to locate data for.</param> | |||
517 | /// <returns>Returns a <see cref="Stream"/> containing tag data or null if no tag was found.</returns> | |||
518 | public Stream GetStreamForTag(int tag) | |||
519 | { | |||
520 | Stream result = null; | |||
521 | if (Find(tag)) { | |||
522 | result = new MemoryStream(_data, _index, _readValueLength, false); | |||
523 | } | |||
524 | return result; | |||
525 | } | |||
526 | | |||
527 | /// <summary> | |||
528 | /// Get the <see cref="ITaggedData">tagged data</see> for a tag. | |||
529 | /// </summary> | |||
530 | /// <typeparam name="T">The tag to search for.</typeparam> | |||
531 | /// <returns>Returns a <see cref="ITaggedData">tagged value</see> or null if none found.</returns> | |||
532 | public T GetData<T>() | |||
533 | where T : class, ITaggedData, new() | |||
534 | { | |||
535 | T result = new T(); | |||
536 | if (Find(result.TagID)) | |||
537 | { | |||
538 | result.SetData(_data, _readValueStart, _readValueLength); | |||
539 | return result; | |||
540 | } | |||
541 | else return null; | |||
542 | } | |||
543 | | |||
544 | /// <summary> | |||
545 | /// Get the length of the last value found by <see cref="Find"/> | |||
546 | /// </summary> | |||
547 | /// <remarks>This is only valid if <see cref="Find"/> has previously returned true.</remarks> | |||
548 | public int ValueLength { | |||
549 | get { return _readValueLength; } | |||
550 | } | |||
551 | | |||
552 | /// <summary> | |||
553 | /// Get the index for the current read value. | |||
554 | /// </summary> | |||
555 | /// <remarks>This is only valid if <see cref="Find"/> has previously returned true. | |||
556 | /// Initially the result will be the index of the first byte of actual data. The value is updated after calls to | |||
557 | /// <see cref="ReadInt"/>, <see cref="ReadShort"/> and <see cref="ReadLong"/>. </remarks> | |||
558 | public int CurrentReadIndex { | |||
559 | get { return _index; } | |||
560 | } | |||
561 | | |||
562 | /// <summary> | |||
563 | /// Get the number of bytes remaining to be read for the current value; | |||
564 | /// </summary> | |||
565 | public int UnreadCount { | |||
566 | get { | |||
567 | if ((_readValueStart > _data.Length) || | |||
568 | (_readValueStart < 4)) { | |||
569 | throw new ZipException("Find must be called before calling a Read method"); | |||
570 | } | |||
571 | | |||
572 | return _readValueStart + _readValueLength - _index; | |||
573 | } | |||
574 | } | |||
575 | | |||
576 | /// <summary> | |||
577 | /// Find an extra data value | |||
578 | /// </summary> | |||
579 | /// <param name="headerID">The identifier for the value to find.</param> | |||
580 | /// <returns>Returns true if the value was found; false otherwise.</returns> | |||
581 | public bool Find(int headerID) | |||
582 | { | |||
583 | _readValueStart = _data.Length; | |||
584 | _readValueLength = 0; | |||
585 | _index = 0; | |||
586 | | |||
587 | int localLength = _readValueStart; | |||
588 | int localTag = headerID - 1; | |||
589 | | |||
590 | // Trailing bytes that cant make up an entry (as there arent enough | |||
591 | // bytes for a tag and length) are ignored! | |||
592 | while ((localTag != headerID) && (_index < _data.Length - 3)) { | |||
593 | localTag = ReadShortInternal(); | |||
594 | localLength = ReadShortInternal(); | |||
595 | if (localTag != headerID) { | |||
596 | _index += localLength; | |||
597 | } | |||
598 | } | |||
599 | | |||
600 | bool result = (localTag == headerID) && ((_index + localLength) <= _data.Length); | |||
601 | | |||
602 | if (result) { | |||
603 | _readValueStart = _index; | |||
604 | _readValueLength = localLength; | |||
605 | } | |||
606 | | |||
607 | return result; | |||
608 | } | |||
609 | | |||
610 | /// <summary> | |||
611 | /// Add a new entry to extra data. | |||
612 | /// </summary> | |||
613 | /// <param name="taggedData">The <see cref="ITaggedData"/> value to add.</param> | |||
614 | public void AddEntry(ITaggedData taggedData) | |||
615 | { | |||
616 | if (taggedData == null) { | |||
617 | throw new ArgumentNullException(nameof(taggedData)); | |||
618 | } | |||
619 | AddEntry(taggedData.TagID, taggedData.GetData()); | |||
620 | } | |||
621 | | |||
622 | /// <summary> | |||
623 | /// Add a new entry to extra data | |||
624 | /// </summary> | |||
625 | /// <param name="headerID">The ID for this entry.</param> | |||
626 | /// <param name="fieldData">The data to add.</param> | |||
627 | /// <remarks>If the ID already exists its contents are replaced.</remarks> | |||
628 | public void AddEntry(int headerID, byte[] fieldData) | |||
629 | { | |||
630 | if ((headerID > ushort.MaxValue) || (headerID < 0)) { | |||
631 | throw new ArgumentOutOfRangeException(nameof(headerID)); | |||
632 | } | |||
633 | | |||
634 | int addLength = (fieldData == null) ? 0 : fieldData.Length; | |||
635 | | |||
636 | if (addLength > ushort.MaxValue) { | |||
637 | throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length"); | |||
638 | } | |||
639 | | |||
640 | // Test for new length before adjusting data. | |||
641 | int newLength = _data.Length + addLength + 4; | |||
642 | | |||
643 | if (Find(headerID)) { | |||
644 | newLength -= (ValueLength + 4); | |||
645 | } | |||
646 | | |||
647 | if (newLength > ushort.MaxValue) { | |||
648 | throw new ZipException("Data exceeds maximum length"); | |||
649 | } | |||
650 | | |||
651 | Delete(headerID); | |||
652 | | |||
653 | byte[] newData = new byte[newLength]; | |||
654 | _data.CopyTo(newData, 0); | |||
655 | int index = _data.Length; | |||
656 | _data = newData; | |||
657 | SetShort(ref index, headerID); | |||
658 | SetShort(ref index, addLength); | |||
659 | if (fieldData != null) { | |||
660 | fieldData.CopyTo(newData, index); | |||
661 | } | |||
662 | } | |||
663 | | |||
664 | /// <summary> | |||
665 | /// Start adding a new entry. | |||
666 | /// </summary> | |||
667 | /// <remarks>Add data using <see cref="AddData(byte[])"/>, <see cref="AddLeShort"/>, <see cref="AddLeInt"/>, or <see | |||
668 | /// The new entry is completed and actually added by calling <see cref="AddNewEntry"/></remarks> | |||
669 | /// <seealso cref="AddEntry(ITaggedData)"/> | |||
670 | public void StartNewEntry() | |||
671 | { | |||
672 | _newEntry = new MemoryStream(); | |||
673 | } | |||
674 | | |||
675 | /// <summary> | |||
676 | /// Add entry data added since <see cref="StartNewEntry"/> using the ID passed. | |||
677 | /// </summary> | |||
678 | /// <param name="headerID">The identifier to use for this entry.</param> | |||
679 | public void AddNewEntry(int headerID) | |||
680 | { | |||
681 | byte[] newData = _newEntry.ToArray(); | |||
682 | _newEntry = null; | |||
683 | AddEntry(headerID, newData); | |||
684 | } | |||
685 | | |||
686 | /// <summary> | |||
687 | /// Add a byte of data to the pending new entry. | |||
688 | /// </summary> | |||
689 | /// <param name="data">The byte to add.</param> | |||
690 | /// <seealso cref="StartNewEntry"/> | |||
691 | public void AddData(byte data) | |||
692 | { | |||
693 | _newEntry.WriteByte(data); | |||
694 | } | |||
695 | | |||
696 | /// <summary> | |||
697 | /// Add data to a pending new entry. | |||
698 | /// </summary> | |||
699 | /// <param name="data">The data to add.</param> | |||
700 | /// <seealso cref="StartNewEntry"/> | |||
701 | public void AddData(byte[] data) | |||
702 | { | |||
703 | if (data == null) { | |||
704 | throw new ArgumentNullException(nameof(data)); | |||
705 | } | |||
706 | | |||
707 | _newEntry.Write(data, 0, data.Length); | |||
708 | } | |||
709 | | |||
710 | /// <summary> | |||
711 | /// Add a short value in little endian order to the pending new entry. | |||
712 | /// </summary> | |||
713 | /// <param name="toAdd">The data to add.</param> | |||
714 | /// <seealso cref="StartNewEntry"/> | |||
715 | public void AddLeShort(int toAdd) | |||
716 | { | |||
717 | unchecked { | |||
718 | _newEntry.WriteByte((byte)toAdd); | |||
719 | _newEntry.WriteByte((byte)(toAdd >> 8)); | |||
720 | } | |||
721 | } | |||
722 | | |||
723 | /// <summary> | |||
724 | /// Add an integer value in little endian order to the pending new entry. | |||
725 | /// </summary> | |||
726 | /// <param name="toAdd">The data to add.</param> | |||
727 | /// <seealso cref="StartNewEntry"/> | |||
728 | public void AddLeInt(int toAdd) | |||
729 | { | |||
730 | unchecked { | |||
731 | AddLeShort((short)toAdd); | |||
732 | AddLeShort((short)(toAdd >> 16)); | |||
733 | } | |||
734 | } | |||
735 | | |||
736 | /// <summary> | |||
737 | /// Add a long value in little endian order to the pending new entry. | |||
738 | /// </summary> | |||
739 | /// <param name="toAdd">The data to add.</param> | |||
740 | /// <seealso cref="StartNewEntry"/> | |||
741 | public void AddLeLong(long toAdd) | |||
742 | { | |||
743 | unchecked { | |||
744 | AddLeInt((int)(toAdd & 0xffffffff)); | |||
745 | AddLeInt((int)(toAdd >> 32)); | |||
746 | } | |||
747 | } | |||
748 | | |||
749 | /// <summary> | |||
750 | /// Delete an extra data field. | |||
751 | /// </summary> | |||
752 | /// <param name="headerID">The identifier of the field to delete.</param> | |||
753 | /// <returns>Returns true if the field was found and deleted.</returns> | |||
754 | public bool Delete(int headerID) | |||
755 | { | |||
756 | bool result = false; | |||
757 | | |||
758 | if (Find(headerID)) { | |||
759 | result = true; | |||
760 | int trueStart = _readValueStart - 4; | |||
761 | | |||
762 | byte[] newData = new byte[_data.Length - (ValueLength + 4)]; | |||
763 | Array.Copy(_data, 0, newData, 0, trueStart); | |||
764 | | |||
765 | int trueEnd = trueStart + ValueLength + 4; | |||
766 | Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd); | |||
767 | _data = newData; | |||
768 | } | |||
769 | return result; | |||
770 | } | |||
771 | | |||
772 | #region Reading Support | |||
773 | /// <summary> | |||
774 | /// Read a long in little endian form from the last <see cref="Find">found</see> data value | |||
775 | /// </summary> | |||
776 | /// <returns>Returns the long value read.</returns> | |||
777 | public long ReadLong() | |||
778 | { | |||
779 | ReadCheck(8); | |||
780 | return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32); | |||
781 | } | |||
782 | | |||
783 | /// <summary> | |||
784 | /// Read an integer in little endian form from the last <see cref="Find">found</see> data value. | |||
785 | /// </summary> | |||
786 | /// <returns>Returns the integer read.</returns> | |||
787 | public int ReadInt() | |||
788 | { | |||
789 | ReadCheck(4); | |||
790 | | |||
791 | int result = _data[_index] + (_data[_index + 1] << 8) + | |||
792 | (_data[_index + 2] << 16) + (_data[_index + 3] << 24); | |||
793 | _index += 4; | |||
794 | return result; | |||
795 | } | |||
796 | | |||
797 | /// <summary> | |||
798 | /// Read a short value in little endian form from the last <see cref="Find">found</see> data value. | |||
799 | /// </summary> | |||
800 | /// <returns>Returns the short value read.</returns> | |||
801 | public int ReadShort() | |||
802 | { | |||
803 | ReadCheck(2); | |||
804 | int result = _data[_index] + (_data[_index + 1] << 8); | |||
805 | _index += 2; | |||
806 | return result; | |||
807 | } | |||
808 | | |||
809 | /// <summary> | |||
810 | /// Read a byte from an extra data | |||
811 | /// </summary> | |||
812 | /// <returns>The byte value read or -1 if the end of data has been reached.</returns> | |||
813 | public int ReadByte() | |||
814 | { | |||
815 | int result = -1; | |||
816 | if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index)) { | |||
817 | result = _data[_index]; | |||
818 | _index += 1; | |||
819 | } | |||
820 | return result; | |||
821 | } | |||
822 | | |||
823 | /// <summary> | |||
824 | /// Skip data during reading. | |||
825 | /// </summary> | |||
826 | /// <param name="amount">The number of bytes to skip.</param> | |||
827 | public void Skip(int amount) | |||
828 | { | |||
829 | ReadCheck(amount); | |||
830 | _index += amount; | |||
831 | } | |||
832 | | |||
833 | void ReadCheck(int length) | |||
834 | { | |||
835 | if ((_readValueStart > _data.Length) || | |||
836 | (_readValueStart < 4)) { | |||
837 | throw new ZipException("Find must be called before calling a Read method"); | |||
838 | } | |||
839 | | |||
840 | if (_index > _readValueStart + _readValueLength - length) { | |||
841 | throw new ZipException("End of extra data"); | |||
842 | } | |||
843 | | |||
844 | if (_index + length < 4) { | |||
845 | throw new ZipException("Cannot read before start of tag"); | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | /// <summary> | |||
850 | /// Internal form of <see cref="ReadShort"/> that reads data at any location. | |||
851 | /// </summary> | |||
852 | /// <returns>Returns the short value read.</returns> | |||
853 | int ReadShortInternal() | |||
854 | { | |||
855 | if (_index > _data.Length - 2) { | |||
856 | throw new ZipException("End of extra data"); | |||
857 | } | |||
858 | | |||
859 | int result = _data[_index] + (_data[_index + 1] << 8); | |||
860 | _index += 2; | |||
861 | return result; | |||
862 | } | |||
863 | | |||
864 | void SetShort(ref int index, int source) | |||
865 | { | |||
866 | _data[index] = (byte)source; | |||
867 | _data[index + 1] = (byte)(source >> 8); | |||
868 | index += 2; | |||
869 | } | |||
870 | | |||
871 | #endregion | |||
872 | | |||
873 | #region IDisposable Members | |||
874 | | |||
875 | /// <summary> | |||
876 | /// Dispose of this instance. | |||
877 | /// </summary> | |||
878 | public void Dispose() | |||
879 | { | |||
880 | if (_newEntry != null) { | |||
881 | _newEntry.Close(); | |||
882 | } | |||
883 | } | |||
884 | | |||
885 | #endregion | |||
886 | | |||
887 | #region Instance Fields | |||
888 | int _index; | |||
889 | int _readValueStart; | |||
890 | int _readValueLength; | |||
891 | | |||
892 | MemoryStream _newEntry; | |||
893 | byte[] _data; | |||
894 | #endregion | |||
895 | } | |||
896 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.FastZip |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\FastZip.cs |
| Covered lines: | 85 |
| Uncovered lines: | 99 |
| Coverable lines: | 184 |
| Total lines: | 648 |
| Line coverage: | 46.1% |
| Branch coverage: | 28.3% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| CreateZip(...) | 1 | 0 | 0 |
| CreateZip(...) | 1 | 0 | 0 |
| CreateZip(...) | 7 | 72.22 | 45.45 |
| ExtractZip(...) | 1 | 100 | 100 |
| ExtractZip(...) | 1 | 100 | 100 |
| ExtractZip(...) | 13 | 80 | 47.83 |
| ProcessDirectory(...) | 6 | 0 | 0 |
| ProcessFile(...) | 6 | 52.94 | 44.44 |
| AddFileContents(...) | 6 | 63.64 | 54.55 |
| ExtractFileEntry(...) | 18 | 0 | 0 |
| ExtractEntry(...) | 14 | 54.55 | 44.44 |
| MakeExternalAttributes(...) | 1 | 0 | 0 |
| NameIsValid(...) | 2 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using ICSharpCode.SharpZipLib.Core; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// FastZipEvents supports all events applicable to <see cref="FastZip">FastZip</see> operations. | |||
9 | /// </summary> | |||
10 | public class FastZipEvents | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Delegate to invoke when processing directories. | |||
14 | /// </summary> | |||
15 | public event EventHandler<DirectoryEventArgs> ProcessDirectory; | |||
16 | | |||
17 | /// <summary> | |||
18 | /// Delegate to invoke when processing files. | |||
19 | /// </summary> | |||
20 | public ProcessFileHandler ProcessFile; | |||
21 | | |||
22 | /// <summary> | |||
23 | /// Delegate to invoke during processing of files. | |||
24 | /// </summary> | |||
25 | public ProgressHandler Progress; | |||
26 | | |||
27 | /// <summary> | |||
28 | /// Delegate to invoke when processing for a file has been completed. | |||
29 | /// </summary> | |||
30 | public CompletedFileHandler CompletedFile; | |||
31 | | |||
32 | /// <summary> | |||
33 | /// Delegate to invoke when processing directory failures. | |||
34 | /// </summary> | |||
35 | public DirectoryFailureHandler DirectoryFailure; | |||
36 | | |||
37 | /// <summary> | |||
38 | /// Delegate to invoke when processing file failures. | |||
39 | /// </summary> | |||
40 | public FileFailureHandler FileFailure; | |||
41 | | |||
42 | /// <summary> | |||
43 | /// Raise the <see cref="DirectoryFailure">directory failure</see> event. | |||
44 | /// </summary> | |||
45 | /// <param name="directory">The directory causing the failure.</param> | |||
46 | /// <param name="e">The exception for this event.</param> | |||
47 | /// <returns>A boolean indicating if execution should continue or not.</returns> | |||
48 | public bool OnDirectoryFailure(string directory, Exception e) | |||
49 | { | |||
50 | bool result = false; | |||
51 | DirectoryFailureHandler handler = DirectoryFailure; | |||
52 | | |||
53 | if (handler != null) { | |||
54 | var args = new ScanFailureEventArgs(directory, e); | |||
55 | handler(this, args); | |||
56 | result = args.ContinueRunning; | |||
57 | } | |||
58 | return result; | |||
59 | } | |||
60 | | |||
61 | /// <summary> | |||
62 | /// Fires the <see cref="FileFailure"> file failure handler delegate</see>. | |||
63 | /// </summary> | |||
64 | /// <param name="file">The file causing the failure.</param> | |||
65 | /// <param name="e">The exception for this failure.</param> | |||
66 | /// <returns>A boolean indicating if execution should continue or not.</returns> | |||
67 | public bool OnFileFailure(string file, Exception e) | |||
68 | { | |||
69 | FileFailureHandler handler = FileFailure; | |||
70 | bool result = (handler != null); | |||
71 | | |||
72 | if (result) { | |||
73 | var args = new ScanFailureEventArgs(file, e); | |||
74 | handler(this, args); | |||
75 | result = args.ContinueRunning; | |||
76 | } | |||
77 | return result; | |||
78 | } | |||
79 | | |||
80 | /// <summary> | |||
81 | /// Fires the <see cref="ProcessFile">ProcessFile delegate</see>. | |||
82 | /// </summary> | |||
83 | /// <param name="file">The file being processed.</param> | |||
84 | /// <returns>A boolean indicating if execution should continue or not.</returns> | |||
85 | public bool OnProcessFile(string file) | |||
86 | { | |||
87 | bool result = true; | |||
88 | ProcessFileHandler handler = ProcessFile; | |||
89 | | |||
90 | if (handler != null) { | |||
91 | var args = new ScanEventArgs(file); | |||
92 | handler(this, args); | |||
93 | result = args.ContinueRunning; | |||
94 | } | |||
95 | return result; | |||
96 | } | |||
97 | | |||
98 | /// <summary> | |||
99 | /// Fires the <see cref="CompletedFile"/> delegate | |||
100 | /// </summary> | |||
101 | /// <param name="file">The file whose processing has been completed.</param> | |||
102 | /// <returns>A boolean indicating if execution should continue or not.</returns> | |||
103 | public bool OnCompletedFile(string file) | |||
104 | { | |||
105 | bool result = true; | |||
106 | CompletedFileHandler handler = CompletedFile; | |||
107 | if (handler != null) { | |||
108 | var args = new ScanEventArgs(file); | |||
109 | handler(this, args); | |||
110 | result = args.ContinueRunning; | |||
111 | } | |||
112 | return result; | |||
113 | } | |||
114 | | |||
115 | /// <summary> | |||
116 | /// Fires the <see cref="ProcessDirectory">process directory</see> delegate. | |||
117 | /// </summary> | |||
118 | /// <param name="directory">The directory being processed.</param> | |||
119 | /// <param name="hasMatchingFiles">Flag indicating if the directory has matching files as determined by the current | |||
120 | /// <returns>A <see cref="bool"/> of true if the operation should continue; false otherwise.</returns> | |||
121 | public bool OnProcessDirectory(string directory, bool hasMatchingFiles) | |||
122 | { | |||
123 | bool result = true; | |||
124 | EventHandler<DirectoryEventArgs> handler = ProcessDirectory; | |||
125 | if (handler != null) { | |||
126 | var args = new DirectoryEventArgs(directory, hasMatchingFiles); | |||
127 | handler(this, args); | |||
128 | result = args.ContinueRunning; | |||
129 | } | |||
130 | return result; | |||
131 | } | |||
132 | | |||
133 | /// <summary> | |||
134 | /// The minimum timespan between <see cref="Progress"/> events. | |||
135 | /// </summary> | |||
136 | /// <value>The minimum period of time between <see cref="Progress"/> events.</value> | |||
137 | /// <seealso cref="Progress"/> | |||
138 | /// <remarks>The default interval is three seconds.</remarks> | |||
139 | public TimeSpan ProgressInterval { | |||
140 | get { return progressInterval_; } | |||
141 | set { progressInterval_ = value; } | |||
142 | } | |||
143 | | |||
144 | #region Instance Fields | |||
145 | TimeSpan progressInterval_ = TimeSpan.FromSeconds(3); | |||
146 | #endregion | |||
147 | } | |||
148 | | |||
149 | /// <summary> | |||
150 | /// FastZip provides facilities for creating and extracting zip files. | |||
151 | /// </summary> | |||
152 | public class FastZip | |||
153 | { | |||
154 | #region Enumerations | |||
155 | /// <summary> | |||
156 | /// Defines the desired handling when overwriting files during extraction. | |||
157 | /// </summary> | |||
158 | public enum Overwrite | |||
159 | { | |||
160 | /// <summary> | |||
161 | /// Prompt the user to confirm overwriting | |||
162 | /// </summary> | |||
163 | Prompt, | |||
164 | /// <summary> | |||
165 | /// Never overwrite files. | |||
166 | /// </summary> | |||
167 | Never, | |||
168 | /// <summary> | |||
169 | /// Always overwrite files. | |||
170 | /// </summary> | |||
171 | Always | |||
172 | } | |||
173 | #endregion | |||
174 | | |||
175 | #region Constructors | |||
176 | /// <summary> | |||
177 | /// Initialise a default instance of <see cref="FastZip"/>. | |||
178 | /// </summary> | |||
| 5 | 179 | public FastZip() | ||
180 | { | |||
| 5 | 181 | } | ||
182 | | |||
183 | /// <summary> | |||
184 | /// Initialise a new instance of <see cref="FastZip"/> | |||
185 | /// </summary> | |||
186 | /// <param name="events">The <see cref="FastZipEvents">events</see> to use during operations.</param> | |||
| 0 | 187 | public FastZip(FastZipEvents events) | ||
188 | { | |||
| 0 | 189 | events_ = events; | ||
| 0 | 190 | } | ||
191 | #endregion | |||
192 | | |||
193 | #region Properties | |||
194 | /// <summary> | |||
195 | /// Get/set a value indicating wether empty directories should be created. | |||
196 | /// </summary> | |||
197 | public bool CreateEmptyDirectories { | |||
| 6 | 198 | get { return createEmptyDirectories_; } | ||
| 2 | 199 | set { createEmptyDirectories_ = value; } | ||
200 | } | |||
201 | | |||
202 | /// <summary> | |||
203 | /// Get / set the password value. | |||
204 | /// </summary> | |||
205 | public string Password { | |||
| 0 | 206 | get { return password_; } | ||
| 4 | 207 | set { password_ = value; } | ||
208 | } | |||
209 | | |||
210 | /// <summary> | |||
211 | /// Get or set the <see cref="INameTransform"></see> active when creating Zip files. | |||
212 | /// </summary> | |||
213 | /// <seealso cref="EntryFactory"></seealso> | |||
214 | public INameTransform NameTransform { | |||
| 0 | 215 | get { return entryFactory_.NameTransform; } | ||
216 | set { | |||
| 4 | 217 | entryFactory_.NameTransform = value; | ||
| 4 | 218 | } | ||
219 | } | |||
220 | | |||
221 | /// <summary> | |||
222 | /// Get or set the <see cref="IEntryFactory"></see> active when creating Zip files. | |||
223 | /// </summary> | |||
224 | public IEntryFactory EntryFactory { | |||
| 0 | 225 | get { return entryFactory_; } | ||
226 | set { | |||
| 1 | 227 | if (value == null) { | ||
| 0 | 228 | entryFactory_ = new ZipEntryFactory(); | ||
| 0 | 229 | } else { | ||
| 1 | 230 | entryFactory_ = value; | ||
231 | } | |||
| 1 | 232 | } | ||
233 | } | |||
234 | | |||
235 | /// <summary> | |||
236 | /// Gets or sets the setting for <see cref="UseZip64">Zip64 handling when writing.</see> | |||
237 | /// </summary> | |||
238 | /// <remarks> | |||
239 | /// The default value is dynamic which is not backwards compatible with old | |||
240 | /// programs and can cause problems with XP's built in compression which cant | |||
241 | /// read Zip64 archives. However it does avoid the situation were a large file | |||
242 | /// is added and cannot be completed correctly. | |||
243 | /// NOTE: Setting the size for entries before they are added is the best solution! | |||
244 | /// By default the EntryFactory used by FastZip will set fhe file size. | |||
245 | /// </remarks> | |||
246 | public UseZip64 UseZip64 { | |||
| 4 | 247 | get { return useZip64_; } | ||
| 0 | 248 | set { useZip64_ = value; } | ||
249 | } | |||
250 | | |||
251 | /// <summary> | |||
252 | /// Get/set a value indicating wether file dates and times should | |||
253 | /// be restored when extracting files from an archive. | |||
254 | /// </summary> | |||
255 | /// <remarks>The default value is false.</remarks> | |||
256 | public bool RestoreDateTimeOnExtract { | |||
257 | get { | |||
| 0 | 258 | return restoreDateTimeOnExtract_; | ||
259 | } | |||
260 | set { | |||
| 0 | 261 | restoreDateTimeOnExtract_ = value; | ||
| 0 | 262 | } | ||
263 | } | |||
264 | | |||
265 | /// <summary> | |||
266 | /// Get/set a value indicating wether file attributes should | |||
267 | /// be restored during extract operations | |||
268 | /// </summary> | |||
269 | public bool RestoreAttributesOnExtract { | |||
| 0 | 270 | get { return restoreAttributesOnExtract_; } | ||
| 0 | 271 | set { restoreAttributesOnExtract_ = value; } | ||
272 | } | |||
273 | #endregion | |||
274 | | |||
275 | #region Delegates | |||
276 | /// <summary> | |||
277 | /// Delegate called when confirming overwriting of files. | |||
278 | /// </summary> | |||
279 | public delegate bool ConfirmOverwriteDelegate(string fileName); | |||
280 | #endregion | |||
281 | | |||
282 | #region CreateZip | |||
283 | /// <summary> | |||
284 | /// Create a zip file. | |||
285 | /// </summary> | |||
286 | /// <param name="zipFileName">The name of the zip file to create.</param> | |||
287 | /// <param name="sourceDirectory">The directory to source files from.</param> | |||
288 | /// <param name="recurse">True to recurse directories, false for no recursion.</param> | |||
289 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
290 | /// <param name="directoryFilter">The <see cref="PathFilter">directory filter</see> to apply.</param> | |||
291 | public void CreateZip(string zipFileName, string sourceDirectory, | |||
292 | bool recurse, string fileFilter, string directoryFilter) | |||
293 | { | |||
| 0 | 294 | CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter); | ||
| 0 | 295 | } | ||
296 | | |||
297 | /// <summary> | |||
298 | /// Create a zip file/archive. | |||
299 | /// </summary> | |||
300 | /// <param name="zipFileName">The name of the zip file to create.</param> | |||
301 | /// <param name="sourceDirectory">The directory to obtain files and directories from.</param> | |||
302 | /// <param name="recurse">True to recurse directories, false for no recursion.</param> | |||
303 | /// <param name="fileFilter">The file filter to apply.</param> | |||
304 | public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter) | |||
305 | { | |||
| 0 | 306 | CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null); | ||
| 0 | 307 | } | ||
308 | | |||
309 | /// <summary> | |||
310 | /// Create a zip archive sending output to the <paramref name="outputStream"/> passed. | |||
311 | /// </summary> | |||
312 | /// <param name="outputStream">The stream to write archive data to.</param> | |||
313 | /// <param name="sourceDirectory">The directory to source files from.</param> | |||
314 | /// <param name="recurse">True to recurse directories, false for no recursion.</param> | |||
315 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
316 | /// <param name="directoryFilter">The <see cref="PathFilter">directory filter</see> to apply.</param> | |||
317 | /// <remarks>The <paramref name="outputStream"/> is closed after creation.</remarks> | |||
318 | public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directory | |||
319 | { | |||
| 4 | 320 | NameTransform = new ZipNameTransform(sourceDirectory); | ||
| 4 | 321 | sourceDirectory_ = sourceDirectory; | ||
322 | | |||
| 4 | 323 | using (outputStream_ = new ZipOutputStream(outputStream)) { | ||
324 | | |||
| 4 | 325 | if (password_ != null) { | ||
| 2 | 326 | outputStream_.Password = password_; | ||
327 | } | |||
328 | | |||
| 4 | 329 | outputStream_.UseZip64 = UseZip64; | ||
| 4 | 330 | var scanner = new FileSystemScanner(fileFilter, directoryFilter); | ||
| 4 | 331 | scanner.ProcessFile += ProcessFile; | ||
| 4 | 332 | if (this.CreateEmptyDirectories) { | ||
| 0 | 333 | scanner.ProcessDirectory += ProcessDirectory; | ||
334 | } | |||
335 | | |||
| 4 | 336 | if (events_ != null) { | ||
| 0 | 337 | if (events_.FileFailure != null) { | ||
| 0 | 338 | scanner.FileFailure += events_.FileFailure; | ||
339 | } | |||
340 | | |||
| 0 | 341 | if (events_.DirectoryFailure != null) { | ||
| 0 | 342 | scanner.DirectoryFailure += events_.DirectoryFailure; | ||
343 | } | |||
344 | } | |||
345 | | |||
| 4 | 346 | scanner.Scan(sourceDirectory, recurse); | ||
| 4 | 347 | } | ||
| 4 | 348 | } | ||
349 | | |||
350 | #endregion | |||
351 | | |||
352 | #region ExtractZip | |||
353 | /// <summary> | |||
354 | /// Extract the contents of a zip file. | |||
355 | /// </summary> | |||
356 | /// <param name="zipFileName">The zip file to extract from.</param> | |||
357 | /// <param name="targetDirectory">The directory to save extracted information in.</param> | |||
358 | /// <param name="fileFilter">A filter to apply to files.</param> | |||
359 | public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter) | |||
360 | { | |||
| 1 | 361 | ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_); | ||
| 1 | 362 | } | ||
363 | | |||
364 | /// <summary> | |||
365 | /// Extract the contents of a zip file. | |||
366 | /// </summary> | |||
367 | /// <param name="zipFileName">The zip file to extract from.</param> | |||
368 | /// <param name="targetDirectory">The directory to save extracted information in.</param> | |||
369 | /// <param name="overwrite">The style of <see cref="Overwrite">overwriting</see> to apply.</param> | |||
370 | /// <param name="confirmDelegate">A delegate to invoke when confirming overwriting.</param> | |||
371 | /// <param name="fileFilter">A filter to apply to files.</param> | |||
372 | /// <param name="directoryFilter">A filter to apply to directories.</param> | |||
373 | /// <param name="restoreDateTime">Flag indicating whether to restore the date and time for extracted files.</param> | |||
374 | public void ExtractZip(string zipFileName, string targetDirectory, | |||
375 | Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, | |||
376 | string fileFilter, string directoryFilter, bool restoreDateTime) | |||
377 | { | |||
| 1 | 378 | Stream inputStream = File.Open(zipFileName, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
| 1 | 379 | ExtractZip(inputStream, targetDirectory, overwrite, confirmDelegate, fileFilter, directoryFilter, restoreDateTime, | ||
| 1 | 380 | } | ||
381 | | |||
382 | /// <summary> | |||
383 | /// Extract the contents of a zip file held in a stream. | |||
384 | /// </summary> | |||
385 | /// <param name="inputStream">The seekable input stream containing the zip to extract from.</param> | |||
386 | /// <param name="targetDirectory">The directory to save extracted information in.</param> | |||
387 | /// <param name="overwrite">The style of <see cref="Overwrite">overwriting</see> to apply.</param> | |||
388 | /// <param name="confirmDelegate">A delegate to invoke when confirming overwriting.</param> | |||
389 | /// <param name="fileFilter">A filter to apply to files.</param> | |||
390 | /// <param name="directoryFilter">A filter to apply to directories.</param> | |||
391 | /// <param name="restoreDateTime">Flag indicating whether to restore the date and time for extracted files.</param> | |||
392 | /// <param name="isStreamOwner">Flag indicating whether the inputStream will be closed by this method.</param> | |||
393 | public void ExtractZip(Stream inputStream, string targetDirectory, | |||
394 | Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, | |||
395 | string fileFilter, string directoryFilter, bool restoreDateTime, | |||
396 | bool isStreamOwner) | |||
397 | { | |||
| 1 | 398 | if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null)) { | ||
| 0 | 399 | throw new ArgumentNullException(nameof(confirmDelegate)); | ||
400 | } | |||
401 | | |||
| 1 | 402 | continueRunning_ = true; | ||
| 1 | 403 | overwrite_ = overwrite; | ||
| 1 | 404 | confirmDelegate_ = confirmDelegate; | ||
| 1 | 405 | extractNameTransform_ = new WindowsNameTransform(targetDirectory); | ||
406 | | |||
| 1 | 407 | fileFilter_ = new NameFilter(fileFilter); | ||
| 1 | 408 | directoryFilter_ = new NameFilter(directoryFilter); | ||
| 1 | 409 | restoreDateTimeOnExtract_ = restoreDateTime; | ||
410 | | |||
| 1 | 411 | using (zipFile_ = new ZipFile(inputStream)) { | ||
412 | | |||
| 1 | 413 | if (password_ != null) { | ||
| 0 | 414 | zipFile_.Password = password_; | ||
415 | } | |||
| 1 | 416 | zipFile_.IsStreamOwner = isStreamOwner; | ||
| 1 | 417 | System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator(); | ||
| 2 | 418 | while (continueRunning_ && enumerator.MoveNext()) { | ||
| 1 | 419 | var entry = (ZipEntry)enumerator.Current; | ||
| 1 | 420 | if (entry.IsFile) { | ||
421 | // TODO Path.GetDirectory can fail here on invalid characters. | |||
| 0 | 422 | if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name)) { | ||
| 0 | 423 | ExtractEntry(entry); | ||
424 | } | |||
| 1 | 425 | } else if (entry.IsDirectory) { | ||
| 1 | 426 | if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories) { | ||
| 1 | 427 | ExtractEntry(entry); | ||
428 | } | |||
429 | } else { | |||
430 | // Do nothing for volume labels etc... | |||
431 | } | |||
432 | } | |||
| 1 | 433 | } | ||
| 1 | 434 | } | ||
435 | #endregion | |||
436 | | |||
437 | #region Internal Processing | |||
438 | void ProcessDirectory(object sender, DirectoryEventArgs e) | |||
439 | { | |||
| 0 | 440 | if (!e.HasMatchingFiles && CreateEmptyDirectories) { | ||
| 0 | 441 | if (events_ != null) { | ||
| 0 | 442 | events_.OnProcessDirectory(e.Name, e.HasMatchingFiles); | ||
443 | } | |||
444 | | |||
| 0 | 445 | if (e.ContinueRunning) { | ||
| 0 | 446 | if (e.Name != sourceDirectory_) { | ||
| 0 | 447 | ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name); | ||
| 0 | 448 | outputStream_.PutNextEntry(entry); | ||
449 | } | |||
450 | } | |||
451 | } | |||
| 0 | 452 | } | ||
453 | | |||
454 | void ProcessFile(object sender, ScanEventArgs e) | |||
455 | { | |||
| 4 | 456 | if ((events_ != null) && (events_.ProcessFile != null)) { | ||
| 0 | 457 | events_.ProcessFile(sender, e); | ||
458 | } | |||
459 | | |||
| 4 | 460 | if (e.ContinueRunning) { | ||
461 | try { | |||
462 | // The open below is equivalent to OpenRead which gaurantees that if opened the | |||
463 | // file will not be changed by subsequent openers, but precludes opening in some cases | |||
464 | // were it could succeed. ie the open may fail as its already open for writing and the share mode should refle | |||
| 4 | 465 | using (FileStream stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read)) { | ||
| 4 | 466 | ZipEntry entry = entryFactory_.MakeFileEntry(e.Name); | ||
| 4 | 467 | outputStream_.PutNextEntry(entry); | ||
| 4 | 468 | AddFileContents(e.Name, stream); | ||
| 4 | 469 | } | ||
| 4 | 470 | } catch (Exception ex) { | ||
| 0 | 471 | if (events_ != null) { | ||
| 0 | 472 | continueRunning_ = events_.OnFileFailure(e.Name, ex); | ||
| 0 | 473 | } else { | ||
| 0 | 474 | continueRunning_ = false; | ||
| 0 | 475 | throw; | ||
476 | } | |||
| 0 | 477 | } | ||
478 | } | |||
| 4 | 479 | } | ||
480 | | |||
481 | void AddFileContents(string name, Stream stream) | |||
482 | { | |||
| 4 | 483 | if (stream == null) { | ||
| 0 | 484 | throw new ArgumentNullException(nameof(stream)); | ||
485 | } | |||
486 | | |||
| 4 | 487 | if (buffer_ == null) { | ||
| 4 | 488 | buffer_ = new byte[4096]; | ||
489 | } | |||
490 | | |||
| 4 | 491 | if ((events_ != null) && (events_.Progress != null)) { | ||
| 0 | 492 | StreamUtils.Copy(stream, outputStream_, buffer_, | ||
| 0 | 493 | events_.Progress, events_.ProgressInterval, this, name); | ||
| 0 | 494 | } else { | ||
| 4 | 495 | StreamUtils.Copy(stream, outputStream_, buffer_); | ||
496 | } | |||
497 | | |||
| 4 | 498 | if (events_ != null) { | ||
| 0 | 499 | continueRunning_ = events_.OnCompletedFile(name); | ||
500 | } | |||
| 4 | 501 | } | ||
502 | | |||
503 | void ExtractFileEntry(ZipEntry entry, string targetName) | |||
504 | { | |||
| 0 | 505 | bool proceed = true; | ||
| 0 | 506 | if (overwrite_ != Overwrite.Always) { | ||
| 0 | 507 | if (File.Exists(targetName)) { | ||
| 0 | 508 | if ((overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null)) { | ||
| 0 | 509 | proceed = confirmDelegate_(targetName); | ||
| 0 | 510 | } else { | ||
| 0 | 511 | proceed = false; | ||
512 | } | |||
513 | } | |||
514 | } | |||
515 | | |||
| 0 | 516 | if (proceed) { | ||
| 0 | 517 | if (events_ != null) { | ||
| 0 | 518 | continueRunning_ = events_.OnProcessFile(entry.Name); | ||
519 | } | |||
520 | | |||
| 0 | 521 | if (continueRunning_) { | ||
522 | try { | |||
| 0 | 523 | using (FileStream outputStream = File.Create(targetName)) { | ||
| 0 | 524 | if (buffer_ == null) { | ||
| 0 | 525 | buffer_ = new byte[4096]; | ||
526 | } | |||
| 0 | 527 | if ((events_ != null) && (events_.Progress != null)) { | ||
| 0 | 528 | StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_, | ||
| 0 | 529 | events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size); | ||
| 0 | 530 | } else { | ||
| 0 | 531 | StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_); | ||
532 | } | |||
533 | | |||
| 0 | 534 | if (events_ != null) { | ||
| 0 | 535 | continueRunning_ = events_.OnCompletedFile(entry.Name); | ||
536 | } | |||
| 0 | 537 | } | ||
538 | | |||
| 0 | 539 | if (restoreDateTimeOnExtract_) { | ||
| 0 | 540 | File.SetLastWriteTime(targetName, entry.DateTime); | ||
541 | } | |||
542 | | |||
| 0 | 543 | if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1)) { | ||
| 0 | 544 | var fileAttributes = (FileAttributes)entry.ExternalFileAttributes; | ||
545 | // TODO: FastZip - Setting of other file attributes on extraction is a little trickier. | |||
| 0 | 546 | fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttribut | ||
| 0 | 547 | File.SetAttributes(targetName, fileAttributes); | ||
548 | } | |||
| 0 | 549 | } catch (Exception ex) { | ||
| 0 | 550 | if (events_ != null) { | ||
| 0 | 551 | continueRunning_ = events_.OnFileFailure(targetName, ex); | ||
| 0 | 552 | } else { | ||
| 0 | 553 | continueRunning_ = false; | ||
| 0 | 554 | throw; | ||
555 | } | |||
| 0 | 556 | } | ||
557 | } | |||
558 | } | |||
| 0 | 559 | } | ||
560 | | |||
561 | void ExtractEntry(ZipEntry entry) | |||
562 | { | |||
| 1 | 563 | bool doExtraction = entry.IsCompressionMethodSupported(); | ||
| 1 | 564 | string targetName = entry.Name; | ||
565 | | |||
| 1 | 566 | if (doExtraction) { | ||
| 1 | 567 | if (entry.IsFile) { | ||
| 0 | 568 | targetName = extractNameTransform_.TransformFile(targetName); | ||
| 1 | 569 | } else if (entry.IsDirectory) { | ||
| 1 | 570 | targetName = extractNameTransform_.TransformDirectory(targetName); | ||
571 | } | |||
572 | | |||
| 1 | 573 | doExtraction = !(string.IsNullOrEmpty(targetName)); | ||
574 | } | |||
575 | | |||
576 | // TODO: Fire delegate/throw exception were compression method not supported, or name is invalid? | |||
577 | | |||
| 1 | 578 | string dirName = null; | ||
579 | | |||
| 1 | 580 | if (doExtraction) { | ||
| 1 | 581 | if (entry.IsDirectory) { | ||
| 1 | 582 | dirName = targetName; | ||
| 1 | 583 | } else { | ||
| 0 | 584 | dirName = Path.GetDirectoryName(Path.GetFullPath(targetName)); | ||
585 | } | |||
586 | } | |||
587 | | |||
| 1 | 588 | if (doExtraction && !Directory.Exists(dirName)) { | ||
| 1 | 589 | if (!entry.IsDirectory || CreateEmptyDirectories) { | ||
590 | try { | |||
| 1 | 591 | Directory.CreateDirectory(dirName); | ||
| 1 | 592 | } catch (Exception ex) { | ||
| 0 | 593 | doExtraction = false; | ||
| 0 | 594 | if (events_ != null) { | ||
| 0 | 595 | if (entry.IsDirectory) { | ||
| 0 | 596 | continueRunning_ = events_.OnDirectoryFailure(targetName, ex); | ||
| 0 | 597 | } else { | ||
| 0 | 598 | continueRunning_ = events_.OnFileFailure(targetName, ex); | ||
599 | } | |||
| 0 | 600 | } else { | ||
| 0 | 601 | continueRunning_ = false; | ||
| 0 | 602 | throw; | ||
603 | } | |||
| 0 | 604 | } | ||
605 | } | |||
606 | } | |||
607 | | |||
| 1 | 608 | if (doExtraction && entry.IsFile) { | ||
| 0 | 609 | ExtractFileEntry(entry, targetName); | ||
610 | } | |||
| 1 | 611 | } | ||
612 | | |||
613 | static int MakeExternalAttributes(FileInfo info) | |||
614 | { | |||
| 0 | 615 | return (int)info.Attributes; | ||
616 | } | |||
617 | | |||
618 | static bool NameIsValid(string name) | |||
619 | { | |||
| 0 | 620 | return !string.IsNullOrEmpty(name) && | ||
| 0 | 621 | (name.IndexOfAny(Path.GetInvalidPathChars()) < 0); | ||
622 | } | |||
623 | #endregion | |||
624 | | |||
625 | #region Instance Fields | |||
626 | bool continueRunning_; | |||
627 | byte[] buffer_; | |||
628 | ZipOutputStream outputStream_; | |||
629 | ZipFile zipFile_; | |||
630 | string sourceDirectory_; | |||
631 | NameFilter fileFilter_; | |||
632 | NameFilter directoryFilter_; | |||
633 | Overwrite overwrite_; | |||
634 | ConfirmOverwriteDelegate confirmDelegate_; | |||
635 | | |||
636 | bool restoreDateTimeOnExtract_; | |||
637 | bool restoreAttributesOnExtract_; | |||
638 | bool createEmptyDirectories_; | |||
639 | FastZipEvents events_; | |||
| 5 | 640 | IEntryFactory entryFactory_ = new ZipEntryFactory(); | ||
641 | INameTransform extractNameTransform_; | |||
| 5 | 642 | UseZip64 useZip64_ = UseZip64.Dynamic; | ||
643 | | |||
644 | string password_; | |||
645 | | |||
646 | #endregion | |||
647 | } | |||
648 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.FastZipEvents |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\FastZip.cs |
| Covered lines: | 0 |
| Uncovered lines: | 38 |
| Coverable lines: | 38 |
| Total lines: | 648 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| OnDirectoryFailure(...) | 2 | 0 | 0 |
| OnFileFailure(...) | 2 | 0 | 0 |
| OnProcessFile(...) | 2 | 0 | 0 |
| OnCompletedFile(...) | 2 | 0 | 0 |
| OnProcessDirectory(...) | 2 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using ICSharpCode.SharpZipLib.Core; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// FastZipEvents supports all events applicable to <see cref="FastZip">FastZip</see> operations. | |||
9 | /// </summary> | |||
10 | public class FastZipEvents | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Delegate to invoke when processing directories. | |||
14 | /// </summary> | |||
15 | public event EventHandler<DirectoryEventArgs> ProcessDirectory; | |||
16 | | |||
17 | /// <summary> | |||
18 | /// Delegate to invoke when processing files. | |||
19 | /// </summary> | |||
20 | public ProcessFileHandler ProcessFile; | |||
21 | | |||
22 | /// <summary> | |||
23 | /// Delegate to invoke during processing of files. | |||
24 | /// </summary> | |||
25 | public ProgressHandler Progress; | |||
26 | | |||
27 | /// <summary> | |||
28 | /// Delegate to invoke when processing for a file has been completed. | |||
29 | /// </summary> | |||
30 | public CompletedFileHandler CompletedFile; | |||
31 | | |||
32 | /// <summary> | |||
33 | /// Delegate to invoke when processing directory failures. | |||
34 | /// </summary> | |||
35 | public DirectoryFailureHandler DirectoryFailure; | |||
36 | | |||
37 | /// <summary> | |||
38 | /// Delegate to invoke when processing file failures. | |||
39 | /// </summary> | |||
40 | public FileFailureHandler FileFailure; | |||
41 | | |||
42 | /// <summary> | |||
43 | /// Raise the <see cref="DirectoryFailure">directory failure</see> event. | |||
44 | /// </summary> | |||
45 | /// <param name="directory">The directory causing the failure.</param> | |||
46 | /// <param name="e">The exception for this event.</param> | |||
47 | /// <returns>A boolean indicating if execution should continue or not.</returns> | |||
48 | public bool OnDirectoryFailure(string directory, Exception e) | |||
49 | { | |||
| 0 | 50 | bool result = false; | ||
| 0 | 51 | DirectoryFailureHandler handler = DirectoryFailure; | ||
52 | | |||
| 0 | 53 | if (handler != null) { | ||
| 0 | 54 | var args = new ScanFailureEventArgs(directory, e); | ||
| 0 | 55 | handler(this, args); | ||
| 0 | 56 | result = args.ContinueRunning; | ||
57 | } | |||
| 0 | 58 | return result; | ||
59 | } | |||
60 | | |||
61 | /// <summary> | |||
62 | /// Fires the <see cref="FileFailure"> file failure handler delegate</see>. | |||
63 | /// </summary> | |||
64 | /// <param name="file">The file causing the failure.</param> | |||
65 | /// <param name="e">The exception for this failure.</param> | |||
66 | /// <returns>A boolean indicating if execution should continue or not.</returns> | |||
67 | public bool OnFileFailure(string file, Exception e) | |||
68 | { | |||
| 0 | 69 | FileFailureHandler handler = FileFailure; | ||
| 0 | 70 | bool result = (handler != null); | ||
71 | | |||
| 0 | 72 | if (result) { | ||
| 0 | 73 | var args = new ScanFailureEventArgs(file, e); | ||
| 0 | 74 | handler(this, args); | ||
| 0 | 75 | result = args.ContinueRunning; | ||
76 | } | |||
| 0 | 77 | return result; | ||
78 | } | |||
79 | | |||
80 | /// <summary> | |||
81 | /// Fires the <see cref="ProcessFile">ProcessFile delegate</see>. | |||
82 | /// </summary> | |||
83 | /// <param name="file">The file being processed.</param> | |||
84 | /// <returns>A boolean indicating if execution should continue or not.</returns> | |||
85 | public bool OnProcessFile(string file) | |||
86 | { | |||
| 0 | 87 | bool result = true; | ||
| 0 | 88 | ProcessFileHandler handler = ProcessFile; | ||
89 | | |||
| 0 | 90 | if (handler != null) { | ||
| 0 | 91 | var args = new ScanEventArgs(file); | ||
| 0 | 92 | handler(this, args); | ||
| 0 | 93 | result = args.ContinueRunning; | ||
94 | } | |||
| 0 | 95 | return result; | ||
96 | } | |||
97 | | |||
98 | /// <summary> | |||
99 | /// Fires the <see cref="CompletedFile"/> delegate | |||
100 | /// </summary> | |||
101 | /// <param name="file">The file whose processing has been completed.</param> | |||
102 | /// <returns>A boolean indicating if execution should continue or not.</returns> | |||
103 | public bool OnCompletedFile(string file) | |||
104 | { | |||
| 0 | 105 | bool result = true; | ||
| 0 | 106 | CompletedFileHandler handler = CompletedFile; | ||
| 0 | 107 | if (handler != null) { | ||
| 0 | 108 | var args = new ScanEventArgs(file); | ||
| 0 | 109 | handler(this, args); | ||
| 0 | 110 | result = args.ContinueRunning; | ||
111 | } | |||
| 0 | 112 | return result; | ||
113 | } | |||
114 | | |||
115 | /// <summary> | |||
116 | /// Fires the <see cref="ProcessDirectory">process directory</see> delegate. | |||
117 | /// </summary> | |||
118 | /// <param name="directory">The directory being processed.</param> | |||
119 | /// <param name="hasMatchingFiles">Flag indicating if the directory has matching files as determined by the current | |||
120 | /// <returns>A <see cref="bool"/> of true if the operation should continue; false otherwise.</returns> | |||
121 | public bool OnProcessDirectory(string directory, bool hasMatchingFiles) | |||
122 | { | |||
| 0 | 123 | bool result = true; | ||
| 0 | 124 | EventHandler<DirectoryEventArgs> handler = ProcessDirectory; | ||
| 0 | 125 | if (handler != null) { | ||
| 0 | 126 | var args = new DirectoryEventArgs(directory, hasMatchingFiles); | ||
| 0 | 127 | handler(this, args); | ||
| 0 | 128 | result = args.ContinueRunning; | ||
129 | } | |||
| 0 | 130 | return result; | ||
131 | } | |||
132 | | |||
133 | /// <summary> | |||
134 | /// The minimum timespan between <see cref="Progress"/> events. | |||
135 | /// </summary> | |||
136 | /// <value>The minimum period of time between <see cref="Progress"/> events.</value> | |||
137 | /// <seealso cref="Progress"/> | |||
138 | /// <remarks>The default interval is three seconds.</remarks> | |||
139 | public TimeSpan ProgressInterval { | |||
| 0 | 140 | get { return progressInterval_; } | ||
| 0 | 141 | set { progressInterval_ = value; } | ||
142 | } | |||
143 | | |||
144 | #region Instance Fields | |||
| 0 | 145 | TimeSpan progressInterval_ = TimeSpan.FromSeconds(3); | ||
146 | #endregion | |||
147 | } | |||
148 | | |||
149 | /// <summary> | |||
150 | /// FastZip provides facilities for creating and extracting zip files. | |||
151 | /// </summary> | |||
152 | public class FastZip | |||
153 | { | |||
154 | #region Enumerations | |||
155 | /// <summary> | |||
156 | /// Defines the desired handling when overwriting files during extraction. | |||
157 | /// </summary> | |||
158 | public enum Overwrite | |||
159 | { | |||
160 | /// <summary> | |||
161 | /// Prompt the user to confirm overwriting | |||
162 | /// </summary> | |||
163 | Prompt, | |||
164 | /// <summary> | |||
165 | /// Never overwrite files. | |||
166 | /// </summary> | |||
167 | Never, | |||
168 | /// <summary> | |||
169 | /// Always overwrite files. | |||
170 | /// </summary> | |||
171 | Always | |||
172 | } | |||
173 | #endregion | |||
174 | | |||
175 | #region Constructors | |||
176 | /// <summary> | |||
177 | /// Initialise a default instance of <see cref="FastZip"/>. | |||
178 | /// </summary> | |||
179 | public FastZip() | |||
180 | { | |||
181 | } | |||
182 | | |||
183 | /// <summary> | |||
184 | /// Initialise a new instance of <see cref="FastZip"/> | |||
185 | /// </summary> | |||
186 | /// <param name="events">The <see cref="FastZipEvents">events</see> to use during operations.</param> | |||
187 | public FastZip(FastZipEvents events) | |||
188 | { | |||
189 | events_ = events; | |||
190 | } | |||
191 | #endregion | |||
192 | | |||
193 | #region Properties | |||
194 | /// <summary> | |||
195 | /// Get/set a value indicating wether empty directories should be created. | |||
196 | /// </summary> | |||
197 | public bool CreateEmptyDirectories { | |||
198 | get { return createEmptyDirectories_; } | |||
199 | set { createEmptyDirectories_ = value; } | |||
200 | } | |||
201 | | |||
202 | /// <summary> | |||
203 | /// Get / set the password value. | |||
204 | /// </summary> | |||
205 | public string Password { | |||
206 | get { return password_; } | |||
207 | set { password_ = value; } | |||
208 | } | |||
209 | | |||
210 | /// <summary> | |||
211 | /// Get or set the <see cref="INameTransform"></see> active when creating Zip files. | |||
212 | /// </summary> | |||
213 | /// <seealso cref="EntryFactory"></seealso> | |||
214 | public INameTransform NameTransform { | |||
215 | get { return entryFactory_.NameTransform; } | |||
216 | set { | |||
217 | entryFactory_.NameTransform = value; | |||
218 | } | |||
219 | } | |||
220 | | |||
221 | /// <summary> | |||
222 | /// Get or set the <see cref="IEntryFactory"></see> active when creating Zip files. | |||
223 | /// </summary> | |||
224 | public IEntryFactory EntryFactory { | |||
225 | get { return entryFactory_; } | |||
226 | set { | |||
227 | if (value == null) { | |||
228 | entryFactory_ = new ZipEntryFactory(); | |||
229 | } else { | |||
230 | entryFactory_ = value; | |||
231 | } | |||
232 | } | |||
233 | } | |||
234 | | |||
235 | /// <summary> | |||
236 | /// Gets or sets the setting for <see cref="UseZip64">Zip64 handling when writing.</see> | |||
237 | /// </summary> | |||
238 | /// <remarks> | |||
239 | /// The default value is dynamic which is not backwards compatible with old | |||
240 | /// programs and can cause problems with XP's built in compression which cant | |||
241 | /// read Zip64 archives. However it does avoid the situation were a large file | |||
242 | /// is added and cannot be completed correctly. | |||
243 | /// NOTE: Setting the size for entries before they are added is the best solution! | |||
244 | /// By default the EntryFactory used by FastZip will set fhe file size. | |||
245 | /// </remarks> | |||
246 | public UseZip64 UseZip64 { | |||
247 | get { return useZip64_; } | |||
248 | set { useZip64_ = value; } | |||
249 | } | |||
250 | | |||
251 | /// <summary> | |||
252 | /// Get/set a value indicating wether file dates and times should | |||
253 | /// be restored when extracting files from an archive. | |||
254 | /// </summary> | |||
255 | /// <remarks>The default value is false.</remarks> | |||
256 | public bool RestoreDateTimeOnExtract { | |||
257 | get { | |||
258 | return restoreDateTimeOnExtract_; | |||
259 | } | |||
260 | set { | |||
261 | restoreDateTimeOnExtract_ = value; | |||
262 | } | |||
263 | } | |||
264 | | |||
265 | /// <summary> | |||
266 | /// Get/set a value indicating wether file attributes should | |||
267 | /// be restored during extract operations | |||
268 | /// </summary> | |||
269 | public bool RestoreAttributesOnExtract { | |||
270 | get { return restoreAttributesOnExtract_; } | |||
271 | set { restoreAttributesOnExtract_ = value; } | |||
272 | } | |||
273 | #endregion | |||
274 | | |||
275 | #region Delegates | |||
276 | /// <summary> | |||
277 | /// Delegate called when confirming overwriting of files. | |||
278 | /// </summary> | |||
279 | public delegate bool ConfirmOverwriteDelegate(string fileName); | |||
280 | #endregion | |||
281 | | |||
282 | #region CreateZip | |||
283 | /// <summary> | |||
284 | /// Create a zip file. | |||
285 | /// </summary> | |||
286 | /// <param name="zipFileName">The name of the zip file to create.</param> | |||
287 | /// <param name="sourceDirectory">The directory to source files from.</param> | |||
288 | /// <param name="recurse">True to recurse directories, false for no recursion.</param> | |||
289 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
290 | /// <param name="directoryFilter">The <see cref="PathFilter">directory filter</see> to apply.</param> | |||
291 | public void CreateZip(string zipFileName, string sourceDirectory, | |||
292 | bool recurse, string fileFilter, string directoryFilter) | |||
293 | { | |||
294 | CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter); | |||
295 | } | |||
296 | | |||
297 | /// <summary> | |||
298 | /// Create a zip file/archive. | |||
299 | /// </summary> | |||
300 | /// <param name="zipFileName">The name of the zip file to create.</param> | |||
301 | /// <param name="sourceDirectory">The directory to obtain files and directories from.</param> | |||
302 | /// <param name="recurse">True to recurse directories, false for no recursion.</param> | |||
303 | /// <param name="fileFilter">The file filter to apply.</param> | |||
304 | public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter) | |||
305 | { | |||
306 | CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null); | |||
307 | } | |||
308 | | |||
309 | /// <summary> | |||
310 | /// Create a zip archive sending output to the <paramref name="outputStream"/> passed. | |||
311 | /// </summary> | |||
312 | /// <param name="outputStream">The stream to write archive data to.</param> | |||
313 | /// <param name="sourceDirectory">The directory to source files from.</param> | |||
314 | /// <param name="recurse">True to recurse directories, false for no recursion.</param> | |||
315 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
316 | /// <param name="directoryFilter">The <see cref="PathFilter">directory filter</see> to apply.</param> | |||
317 | /// <remarks>The <paramref name="outputStream"/> is closed after creation.</remarks> | |||
318 | public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directory | |||
319 | { | |||
320 | NameTransform = new ZipNameTransform(sourceDirectory); | |||
321 | sourceDirectory_ = sourceDirectory; | |||
322 | | |||
323 | using (outputStream_ = new ZipOutputStream(outputStream)) { | |||
324 | | |||
325 | if (password_ != null) { | |||
326 | outputStream_.Password = password_; | |||
327 | } | |||
328 | | |||
329 | outputStream_.UseZip64 = UseZip64; | |||
330 | var scanner = new FileSystemScanner(fileFilter, directoryFilter); | |||
331 | scanner.ProcessFile += ProcessFile; | |||
332 | if (this.CreateEmptyDirectories) { | |||
333 | scanner.ProcessDirectory += ProcessDirectory; | |||
334 | } | |||
335 | | |||
336 | if (events_ != null) { | |||
337 | if (events_.FileFailure != null) { | |||
338 | scanner.FileFailure += events_.FileFailure; | |||
339 | } | |||
340 | | |||
341 | if (events_.DirectoryFailure != null) { | |||
342 | scanner.DirectoryFailure += events_.DirectoryFailure; | |||
343 | } | |||
344 | } | |||
345 | | |||
346 | scanner.Scan(sourceDirectory, recurse); | |||
347 | } | |||
348 | } | |||
349 | | |||
350 | #endregion | |||
351 | | |||
352 | #region ExtractZip | |||
353 | /// <summary> | |||
354 | /// Extract the contents of a zip file. | |||
355 | /// </summary> | |||
356 | /// <param name="zipFileName">The zip file to extract from.</param> | |||
357 | /// <param name="targetDirectory">The directory to save extracted information in.</param> | |||
358 | /// <param name="fileFilter">A filter to apply to files.</param> | |||
359 | public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter) | |||
360 | { | |||
361 | ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_); | |||
362 | } | |||
363 | | |||
364 | /// <summary> | |||
365 | /// Extract the contents of a zip file. | |||
366 | /// </summary> | |||
367 | /// <param name="zipFileName">The zip file to extract from.</param> | |||
368 | /// <param name="targetDirectory">The directory to save extracted information in.</param> | |||
369 | /// <param name="overwrite">The style of <see cref="Overwrite">overwriting</see> to apply.</param> | |||
370 | /// <param name="confirmDelegate">A delegate to invoke when confirming overwriting.</param> | |||
371 | /// <param name="fileFilter">A filter to apply to files.</param> | |||
372 | /// <param name="directoryFilter">A filter to apply to directories.</param> | |||
373 | /// <param name="restoreDateTime">Flag indicating whether to restore the date and time for extracted files.</param> | |||
374 | public void ExtractZip(string zipFileName, string targetDirectory, | |||
375 | Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, | |||
376 | string fileFilter, string directoryFilter, bool restoreDateTime) | |||
377 | { | |||
378 | Stream inputStream = File.Open(zipFileName, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
379 | ExtractZip(inputStream, targetDirectory, overwrite, confirmDelegate, fileFilter, directoryFilter, restoreDateTime, | |||
380 | } | |||
381 | | |||
382 | /// <summary> | |||
383 | /// Extract the contents of a zip file held in a stream. | |||
384 | /// </summary> | |||
385 | /// <param name="inputStream">The seekable input stream containing the zip to extract from.</param> | |||
386 | /// <param name="targetDirectory">The directory to save extracted information in.</param> | |||
387 | /// <param name="overwrite">The style of <see cref="Overwrite">overwriting</see> to apply.</param> | |||
388 | /// <param name="confirmDelegate">A delegate to invoke when confirming overwriting.</param> | |||
389 | /// <param name="fileFilter">A filter to apply to files.</param> | |||
390 | /// <param name="directoryFilter">A filter to apply to directories.</param> | |||
391 | /// <param name="restoreDateTime">Flag indicating whether to restore the date and time for extracted files.</param> | |||
392 | /// <param name="isStreamOwner">Flag indicating whether the inputStream will be closed by this method.</param> | |||
393 | public void ExtractZip(Stream inputStream, string targetDirectory, | |||
394 | Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, | |||
395 | string fileFilter, string directoryFilter, bool restoreDateTime, | |||
396 | bool isStreamOwner) | |||
397 | { | |||
398 | if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null)) { | |||
399 | throw new ArgumentNullException(nameof(confirmDelegate)); | |||
400 | } | |||
401 | | |||
402 | continueRunning_ = true; | |||
403 | overwrite_ = overwrite; | |||
404 | confirmDelegate_ = confirmDelegate; | |||
405 | extractNameTransform_ = new WindowsNameTransform(targetDirectory); | |||
406 | | |||
407 | fileFilter_ = new NameFilter(fileFilter); | |||
408 | directoryFilter_ = new NameFilter(directoryFilter); | |||
409 | restoreDateTimeOnExtract_ = restoreDateTime; | |||
410 | | |||
411 | using (zipFile_ = new ZipFile(inputStream)) { | |||
412 | | |||
413 | if (password_ != null) { | |||
414 | zipFile_.Password = password_; | |||
415 | } | |||
416 | zipFile_.IsStreamOwner = isStreamOwner; | |||
417 | System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator(); | |||
418 | while (continueRunning_ && enumerator.MoveNext()) { | |||
419 | var entry = (ZipEntry)enumerator.Current; | |||
420 | if (entry.IsFile) { | |||
421 | // TODO Path.GetDirectory can fail here on invalid characters. | |||
422 | if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name)) { | |||
423 | ExtractEntry(entry); | |||
424 | } | |||
425 | } else if (entry.IsDirectory) { | |||
426 | if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories) { | |||
427 | ExtractEntry(entry); | |||
428 | } | |||
429 | } else { | |||
430 | // Do nothing for volume labels etc... | |||
431 | } | |||
432 | } | |||
433 | } | |||
434 | } | |||
435 | #endregion | |||
436 | | |||
437 | #region Internal Processing | |||
438 | void ProcessDirectory(object sender, DirectoryEventArgs e) | |||
439 | { | |||
440 | if (!e.HasMatchingFiles && CreateEmptyDirectories) { | |||
441 | if (events_ != null) { | |||
442 | events_.OnProcessDirectory(e.Name, e.HasMatchingFiles); | |||
443 | } | |||
444 | | |||
445 | if (e.ContinueRunning) { | |||
446 | if (e.Name != sourceDirectory_) { | |||
447 | ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name); | |||
448 | outputStream_.PutNextEntry(entry); | |||
449 | } | |||
450 | } | |||
451 | } | |||
452 | } | |||
453 | | |||
454 | void ProcessFile(object sender, ScanEventArgs e) | |||
455 | { | |||
456 | if ((events_ != null) && (events_.ProcessFile != null)) { | |||
457 | events_.ProcessFile(sender, e); | |||
458 | } | |||
459 | | |||
460 | if (e.ContinueRunning) { | |||
461 | try { | |||
462 | // The open below is equivalent to OpenRead which gaurantees that if opened the | |||
463 | // file will not be changed by subsequent openers, but precludes opening in some cases | |||
464 | // were it could succeed. ie the open may fail as its already open for writing and the share mode should refle | |||
465 | using (FileStream stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read)) { | |||
466 | ZipEntry entry = entryFactory_.MakeFileEntry(e.Name); | |||
467 | outputStream_.PutNextEntry(entry); | |||
468 | AddFileContents(e.Name, stream); | |||
469 | } | |||
470 | } catch (Exception ex) { | |||
471 | if (events_ != null) { | |||
472 | continueRunning_ = events_.OnFileFailure(e.Name, ex); | |||
473 | } else { | |||
474 | continueRunning_ = false; | |||
475 | throw; | |||
476 | } | |||
477 | } | |||
478 | } | |||
479 | } | |||
480 | | |||
481 | void AddFileContents(string name, Stream stream) | |||
482 | { | |||
483 | if (stream == null) { | |||
484 | throw new ArgumentNullException(nameof(stream)); | |||
485 | } | |||
486 | | |||
487 | if (buffer_ == null) { | |||
488 | buffer_ = new byte[4096]; | |||
489 | } | |||
490 | | |||
491 | if ((events_ != null) && (events_.Progress != null)) { | |||
492 | StreamUtils.Copy(stream, outputStream_, buffer_, | |||
493 | events_.Progress, events_.ProgressInterval, this, name); | |||
494 | } else { | |||
495 | StreamUtils.Copy(stream, outputStream_, buffer_); | |||
496 | } | |||
497 | | |||
498 | if (events_ != null) { | |||
499 | continueRunning_ = events_.OnCompletedFile(name); | |||
500 | } | |||
501 | } | |||
502 | | |||
503 | void ExtractFileEntry(ZipEntry entry, string targetName) | |||
504 | { | |||
505 | bool proceed = true; | |||
506 | if (overwrite_ != Overwrite.Always) { | |||
507 | if (File.Exists(targetName)) { | |||
508 | if ((overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null)) { | |||
509 | proceed = confirmDelegate_(targetName); | |||
510 | } else { | |||
511 | proceed = false; | |||
512 | } | |||
513 | } | |||
514 | } | |||
515 | | |||
516 | if (proceed) { | |||
517 | if (events_ != null) { | |||
518 | continueRunning_ = events_.OnProcessFile(entry.Name); | |||
519 | } | |||
520 | | |||
521 | if (continueRunning_) { | |||
522 | try { | |||
523 | using (FileStream outputStream = File.Create(targetName)) { | |||
524 | if (buffer_ == null) { | |||
525 | buffer_ = new byte[4096]; | |||
526 | } | |||
527 | if ((events_ != null) && (events_.Progress != null)) { | |||
528 | StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_, | |||
529 | events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size); | |||
530 | } else { | |||
531 | StreamUtils.Copy(zipFile_.GetInputStream(entry), outputStream, buffer_); | |||
532 | } | |||
533 | | |||
534 | if (events_ != null) { | |||
535 | continueRunning_ = events_.OnCompletedFile(entry.Name); | |||
536 | } | |||
537 | } | |||
538 | | |||
539 | if (restoreDateTimeOnExtract_) { | |||
540 | File.SetLastWriteTime(targetName, entry.DateTime); | |||
541 | } | |||
542 | | |||
543 | if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1)) { | |||
544 | var fileAttributes = (FileAttributes)entry.ExternalFileAttributes; | |||
545 | // TODO: FastZip - Setting of other file attributes on extraction is a little trickier. | |||
546 | fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttribut | |||
547 | File.SetAttributes(targetName, fileAttributes); | |||
548 | } | |||
549 | } catch (Exception ex) { | |||
550 | if (events_ != null) { | |||
551 | continueRunning_ = events_.OnFileFailure(targetName, ex); | |||
552 | } else { | |||
553 | continueRunning_ = false; | |||
554 | throw; | |||
555 | } | |||
556 | } | |||
557 | } | |||
558 | } | |||
559 | } | |||
560 | | |||
561 | void ExtractEntry(ZipEntry entry) | |||
562 | { | |||
563 | bool doExtraction = entry.IsCompressionMethodSupported(); | |||
564 | string targetName = entry.Name; | |||
565 | | |||
566 | if (doExtraction) { | |||
567 | if (entry.IsFile) { | |||
568 | targetName = extractNameTransform_.TransformFile(targetName); | |||
569 | } else if (entry.IsDirectory) { | |||
570 | targetName = extractNameTransform_.TransformDirectory(targetName); | |||
571 | } | |||
572 | | |||
573 | doExtraction = !(string.IsNullOrEmpty(targetName)); | |||
574 | } | |||
575 | | |||
576 | // TODO: Fire delegate/throw exception were compression method not supported, or name is invalid? | |||
577 | | |||
578 | string dirName = null; | |||
579 | | |||
580 | if (doExtraction) { | |||
581 | if (entry.IsDirectory) { | |||
582 | dirName = targetName; | |||
583 | } else { | |||
584 | dirName = Path.GetDirectoryName(Path.GetFullPath(targetName)); | |||
585 | } | |||
586 | } | |||
587 | | |||
588 | if (doExtraction && !Directory.Exists(dirName)) { | |||
589 | if (!entry.IsDirectory || CreateEmptyDirectories) { | |||
590 | try { | |||
591 | Directory.CreateDirectory(dirName); | |||
592 | } catch (Exception ex) { | |||
593 | doExtraction = false; | |||
594 | if (events_ != null) { | |||
595 | if (entry.IsDirectory) { | |||
596 | continueRunning_ = events_.OnDirectoryFailure(targetName, ex); | |||
597 | } else { | |||
598 | continueRunning_ = events_.OnFileFailure(targetName, ex); | |||
599 | } | |||
600 | } else { | |||
601 | continueRunning_ = false; | |||
602 | throw; | |||
603 | } | |||
604 | } | |||
605 | } | |||
606 | } | |||
607 | | |||
608 | if (doExtraction && entry.IsFile) { | |||
609 | ExtractFileEntry(entry, targetName); | |||
610 | } | |||
611 | } | |||
612 | | |||
613 | static int MakeExternalAttributes(FileInfo info) | |||
614 | { | |||
615 | return (int)info.Attributes; | |||
616 | } | |||
617 | | |||
618 | static bool NameIsValid(string name) | |||
619 | { | |||
620 | return !string.IsNullOrEmpty(name) && | |||
621 | (name.IndexOfAny(Path.GetInvalidPathChars()) < 0); | |||
622 | } | |||
623 | #endregion | |||
624 | | |||
625 | #region Instance Fields | |||
626 | bool continueRunning_; | |||
627 | byte[] buffer_; | |||
628 | ZipOutputStream outputStream_; | |||
629 | ZipFile zipFile_; | |||
630 | string sourceDirectory_; | |||
631 | NameFilter fileFilter_; | |||
632 | NameFilter directoryFilter_; | |||
633 | Overwrite overwrite_; | |||
634 | ConfirmOverwriteDelegate confirmDelegate_; | |||
635 | | |||
636 | bool restoreDateTimeOnExtract_; | |||
637 | bool restoreAttributesOnExtract_; | |||
638 | bool createEmptyDirectories_; | |||
639 | FastZipEvents events_; | |||
640 | IEntryFactory entryFactory_ = new ZipEntryFactory(); | |||
641 | INameTransform extractNameTransform_; | |||
642 | UseZip64 useZip64_ = UseZip64.Dynamic; | |||
643 | | |||
644 | string password_; | |||
645 | | |||
646 | #endregion | |||
647 | } | |||
648 | } |
| Class: | ICSharpCode.SharpZipLib.Core.FileFailureHandler |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | |
| Covered lines: | 0 |
| Uncovered lines: | 0 |
| Coverable lines: | 0 |
| Total lines: | 0 |
| Line coverage: |
No files found. This usually happens if a file isn't covered by a test or the class does not contain any sequence points (e.g. a class that only contains auto properties).
+| Class: | ICSharpCode.SharpZipLib.Core.FileSystemScanner |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\FileSystemScanner.cs |
| Covered lines: | 33 |
| Uncovered lines: | 49 |
| Coverable lines: | 82 |
| Total lines: | 475 |
| Line coverage: | 40.2% |
| Branch coverage: | 32.3% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| OnDirectoryFailure(...) | 2 | 0 | 0 |
| OnFileFailure(...) | 2 | 0 | 0 |
| OnProcessFile(...) | 2 | 100 | 66.67 |
| OnCompleteFile(...) | 2 | 0 | 0 |
| OnProcessDirectory(...) | 2 | 50 | 66.67 |
| Scan(...) | 1 | 100 | 100 |
| ScanDir(...) | 15 | 50 | 40 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Core | |||
4 | { | |||
5 | #region EventArgs | |||
6 | /// <summary> | |||
7 | /// Event arguments for scanning. | |||
8 | /// </summary> | |||
9 | public class ScanEventArgs : EventArgs | |||
10 | { | |||
11 | #region Constructors | |||
12 | /// <summary> | |||
13 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
14 | /// </summary> | |||
15 | /// <param name="name">The file or directory name.</param> | |||
16 | public ScanEventArgs(string name) | |||
17 | { | |||
18 | name_ = name; | |||
19 | } | |||
20 | #endregion | |||
21 | | |||
22 | /// <summary> | |||
23 | /// The file or directory name for this event. | |||
24 | /// </summary> | |||
25 | public string Name { | |||
26 | get { return name_; } | |||
27 | } | |||
28 | | |||
29 | /// <summary> | |||
30 | /// Get set a value indicating if scanning should continue or not. | |||
31 | /// </summary> | |||
32 | public bool ContinueRunning { | |||
33 | get { return continueRunning_; } | |||
34 | set { continueRunning_ = value; } | |||
35 | } | |||
36 | | |||
37 | #region Instance Fields | |||
38 | string name_; | |||
39 | bool continueRunning_ = true; | |||
40 | #endregion | |||
41 | } | |||
42 | | |||
43 | /// <summary> | |||
44 | /// Event arguments during processing of a single file or directory. | |||
45 | /// </summary> | |||
46 | public class ProgressEventArgs : EventArgs | |||
47 | { | |||
48 | #region Constructors | |||
49 | /// <summary> | |||
50 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
51 | /// </summary> | |||
52 | /// <param name="name">The file or directory name if known.</param> | |||
53 | /// <param name="processed">The number of bytes processed so far</param> | |||
54 | /// <param name="target">The total number of bytes to process, 0 if not known</param> | |||
55 | public ProgressEventArgs(string name, long processed, long target) | |||
56 | { | |||
57 | name_ = name; | |||
58 | processed_ = processed; | |||
59 | target_ = target; | |||
60 | } | |||
61 | #endregion | |||
62 | | |||
63 | /// <summary> | |||
64 | /// The name for this event if known. | |||
65 | /// </summary> | |||
66 | public string Name { | |||
67 | get { return name_; } | |||
68 | } | |||
69 | | |||
70 | /// <summary> | |||
71 | /// Get set a value indicating wether scanning should continue or not. | |||
72 | /// </summary> | |||
73 | public bool ContinueRunning { | |||
74 | get { return continueRunning_; } | |||
75 | set { continueRunning_ = value; } | |||
76 | } | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Get a percentage representing how much of the <see cref="Target"></see> has been processed | |||
80 | /// </summary> | |||
81 | /// <value>0.0 to 100.0 percent; 0 if target is not known.</value> | |||
82 | public float PercentComplete { | |||
83 | get { | |||
84 | float result; | |||
85 | if (target_ <= 0) { | |||
86 | result = 0; | |||
87 | } else { | |||
88 | result = ((float)processed_ / (float)target_) * 100.0f; | |||
89 | } | |||
90 | return result; | |||
91 | } | |||
92 | } | |||
93 | | |||
94 | /// <summary> | |||
95 | /// The number of bytes processed so far | |||
96 | /// </summary> | |||
97 | public long Processed { | |||
98 | get { return processed_; } | |||
99 | } | |||
100 | | |||
101 | /// <summary> | |||
102 | /// The number of bytes to process. | |||
103 | /// </summary> | |||
104 | /// <remarks>Target may be 0 or negative if the value isnt known.</remarks> | |||
105 | public long Target { | |||
106 | get { return target_; } | |||
107 | } | |||
108 | | |||
109 | #region Instance Fields | |||
110 | string name_; | |||
111 | long processed_; | |||
112 | long target_; | |||
113 | bool continueRunning_ = true; | |||
114 | #endregion | |||
115 | } | |||
116 | | |||
117 | /// <summary> | |||
118 | /// Event arguments for directories. | |||
119 | /// </summary> | |||
120 | public class DirectoryEventArgs : ScanEventArgs | |||
121 | { | |||
122 | #region Constructors | |||
123 | /// <summary> | |||
124 | /// Initialize an instance of <see cref="DirectoryEventArgs"></see>. | |||
125 | /// </summary> | |||
126 | /// <param name="name">The name for this directory.</param> | |||
127 | /// <param name="hasMatchingFiles">Flag value indicating if any matching files are contained in this directory.</par | |||
128 | public DirectoryEventArgs(string name, bool hasMatchingFiles) | |||
129 | : base(name) | |||
130 | { | |||
131 | hasMatchingFiles_ = hasMatchingFiles; | |||
132 | } | |||
133 | #endregion | |||
134 | | |||
135 | /// <summary> | |||
136 | /// Get a value indicating if the directory contains any matching files or not. | |||
137 | /// </summary> | |||
138 | public bool HasMatchingFiles { | |||
139 | get { return hasMatchingFiles_; } | |||
140 | } | |||
141 | | |||
142 | readonly | |||
143 | | |||
144 | #region Instance Fields | |||
145 | bool hasMatchingFiles_; | |||
146 | #endregion | |||
147 | } | |||
148 | | |||
149 | /// <summary> | |||
150 | /// Arguments passed when scan failures are detected. | |||
151 | /// </summary> | |||
152 | public class ScanFailureEventArgs : EventArgs | |||
153 | { | |||
154 | #region Constructors | |||
155 | /// <summary> | |||
156 | /// Initialise a new instance of <see cref="ScanFailureEventArgs"></see> | |||
157 | /// </summary> | |||
158 | /// <param name="name">The name to apply.</param> | |||
159 | /// <param name="e">The exception to use.</param> | |||
160 | public ScanFailureEventArgs(string name, Exception e) | |||
161 | { | |||
162 | name_ = name; | |||
163 | exception_ = e; | |||
164 | continueRunning_ = true; | |||
165 | } | |||
166 | #endregion | |||
167 | | |||
168 | /// <summary> | |||
169 | /// The applicable name. | |||
170 | /// </summary> | |||
171 | public string Name { | |||
172 | get { return name_; } | |||
173 | } | |||
174 | | |||
175 | /// <summary> | |||
176 | /// The applicable exception. | |||
177 | /// </summary> | |||
178 | public Exception Exception { | |||
179 | get { return exception_; } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Get / set a value indicating wether scanning should continue. | |||
184 | /// </summary> | |||
185 | public bool ContinueRunning { | |||
186 | get { return continueRunning_; } | |||
187 | set { continueRunning_ = value; } | |||
188 | } | |||
189 | | |||
190 | #region Instance Fields | |||
191 | string name_; | |||
192 | Exception exception_; | |||
193 | bool continueRunning_; | |||
194 | #endregion | |||
195 | } | |||
196 | | |||
197 | #endregion | |||
198 | | |||
199 | #region Delegates | |||
200 | /// <summary> | |||
201 | /// Delegate invoked before starting to process a file. | |||
202 | /// </summary> | |||
203 | /// <param name="sender">The source of the event</param> | |||
204 | /// <param name="e">The event arguments.</param> | |||
205 | public delegate void ProcessFileHandler(object sender, ScanEventArgs e); | |||
206 | | |||
207 | /// <summary> | |||
208 | /// Delegate invoked during processing of a file or directory | |||
209 | /// </summary> | |||
210 | /// <param name="sender">The source of the event</param> | |||
211 | /// <param name="e">The event arguments.</param> | |||
212 | public delegate void ProgressHandler(object sender, ProgressEventArgs e); | |||
213 | | |||
214 | /// <summary> | |||
215 | /// Delegate invoked when a file has been completely processed. | |||
216 | /// </summary> | |||
217 | /// <param name="sender">The source of the event</param> | |||
218 | /// <param name="e">The event arguments.</param> | |||
219 | public delegate void CompletedFileHandler(object sender, ScanEventArgs e); | |||
220 | | |||
221 | /// <summary> | |||
222 | /// Delegate invoked when a directory failure is detected. | |||
223 | /// </summary> | |||
224 | /// <param name="sender">The source of the event</param> | |||
225 | /// <param name="e">The event arguments.</param> | |||
226 | public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e); | |||
227 | | |||
228 | /// <summary> | |||
229 | /// Delegate invoked when a file failure is detected. | |||
230 | /// </summary> | |||
231 | /// <param name="sender">The source of the event</param> | |||
232 | /// <param name="e">The event arguments.</param> | |||
233 | public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e); | |||
234 | #endregion | |||
235 | | |||
236 | /// <summary> | |||
237 | /// FileSystemScanner provides facilities scanning of files and directories. | |||
238 | /// </summary> | |||
239 | public class FileSystemScanner | |||
240 | { | |||
241 | #region Constructors | |||
242 | /// <summary> | |||
243 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
244 | /// </summary> | |||
245 | /// <param name="filter">The <see cref="PathFilter">file filter</see> to apply when scanning.</param> | |||
| 0 | 246 | public FileSystemScanner(string filter) | ||
247 | { | |||
| 0 | 248 | fileFilter_ = new PathFilter(filter); | ||
| 0 | 249 | } | ||
250 | | |||
251 | /// <summary> | |||
252 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
253 | /// </summary> | |||
254 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
255 | /// <param name="directoryFilter">The <see cref="PathFilter"> directory filter</see> to apply.</param> | |||
| 4 | 256 | public FileSystemScanner(string fileFilter, string directoryFilter) | ||
257 | { | |||
| 4 | 258 | fileFilter_ = new PathFilter(fileFilter); | ||
| 4 | 259 | directoryFilter_ = new PathFilter(directoryFilter); | ||
| 4 | 260 | } | ||
261 | | |||
262 | /// <summary> | |||
263 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
264 | /// </summary> | |||
265 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
| 0 | 266 | public FileSystemScanner(IScanFilter fileFilter) | ||
267 | { | |||
| 0 | 268 | fileFilter_ = fileFilter; | ||
| 0 | 269 | } | ||
270 | | |||
271 | /// <summary> | |||
272 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
273 | /// </summary> | |||
274 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
275 | /// <param name="directoryFilter">The directory <see cref="IScanFilter">filter</see> to apply.</param> | |||
| 0 | 276 | public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter) | ||
277 | { | |||
| 0 | 278 | fileFilter_ = fileFilter; | ||
| 0 | 279 | directoryFilter_ = directoryFilter; | ||
| 0 | 280 | } | ||
281 | #endregion | |||
282 | | |||
283 | #region Delegates | |||
284 | /// <summary> | |||
285 | /// Delegate to invoke when a directory is processed. | |||
286 | /// </summary> | |||
287 | public event EventHandler<DirectoryEventArgs> ProcessDirectory; | |||
288 | | |||
289 | /// <summary> | |||
290 | /// Delegate to invoke when a file is processed. | |||
291 | /// </summary> | |||
292 | public ProcessFileHandler ProcessFile; | |||
293 | | |||
294 | /// <summary> | |||
295 | /// Delegate to invoke when processing for a file has finished. | |||
296 | /// </summary> | |||
297 | public CompletedFileHandler CompletedFile; | |||
298 | | |||
299 | /// <summary> | |||
300 | /// Delegate to invoke when a directory failure is detected. | |||
301 | /// </summary> | |||
302 | public DirectoryFailureHandler DirectoryFailure; | |||
303 | | |||
304 | /// <summary> | |||
305 | /// Delegate to invoke when a file failure is detected. | |||
306 | /// </summary> | |||
307 | public FileFailureHandler FileFailure; | |||
308 | #endregion | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Raise the DirectoryFailure event. | |||
312 | /// </summary> | |||
313 | /// <param name="directory">The directory name.</param> | |||
314 | /// <param name="e">The exception detected.</param> | |||
315 | bool OnDirectoryFailure(string directory, Exception e) | |||
316 | { | |||
| 0 | 317 | DirectoryFailureHandler handler = DirectoryFailure; | ||
| 0 | 318 | bool result = (handler != null); | ||
| 0 | 319 | if (result) { | ||
| 0 | 320 | var args = new ScanFailureEventArgs(directory, e); | ||
| 0 | 321 | handler(this, args); | ||
| 0 | 322 | alive_ = args.ContinueRunning; | ||
323 | } | |||
| 0 | 324 | return result; | ||
325 | } | |||
326 | | |||
327 | /// <summary> | |||
328 | /// Raise the FileFailure event. | |||
329 | /// </summary> | |||
330 | /// <param name="file">The file name.</param> | |||
331 | /// <param name="e">The exception detected.</param> | |||
332 | bool OnFileFailure(string file, Exception e) | |||
333 | { | |||
| 0 | 334 | FileFailureHandler handler = FileFailure; | ||
335 | | |||
| 0 | 336 | bool result = (handler != null); | ||
337 | | |||
| 0 | 338 | if (result) { | ||
| 0 | 339 | var args = new ScanFailureEventArgs(file, e); | ||
| 0 | 340 | FileFailure(this, args); | ||
| 0 | 341 | alive_ = args.ContinueRunning; | ||
342 | } | |||
| 0 | 343 | return result; | ||
344 | } | |||
345 | | |||
346 | /// <summary> | |||
347 | /// Raise the ProcessFile event. | |||
348 | /// </summary> | |||
349 | /// <param name="file">The file name.</param> | |||
350 | void OnProcessFile(string file) | |||
351 | { | |||
| 4 | 352 | ProcessFileHandler handler = ProcessFile; | ||
353 | | |||
| 4 | 354 | if (handler != null) { | ||
| 4 | 355 | var args = new ScanEventArgs(file); | ||
| 4 | 356 | handler(this, args); | ||
| 4 | 357 | alive_ = args.ContinueRunning; | ||
358 | } | |||
| 4 | 359 | } | ||
360 | | |||
361 | /// <summary> | |||
362 | /// Raise the complete file event | |||
363 | /// </summary> | |||
364 | /// <param name="file">The file name</param> | |||
365 | void OnCompleteFile(string file) | |||
366 | { | |||
| 0 | 367 | CompletedFileHandler handler = CompletedFile; | ||
368 | | |||
| 0 | 369 | if (handler != null) { | ||
| 0 | 370 | var args = new ScanEventArgs(file); | ||
| 0 | 371 | handler(this, args); | ||
| 0 | 372 | alive_ = args.ContinueRunning; | ||
373 | } | |||
| 0 | 374 | } | ||
375 | | |||
376 | /// <summary> | |||
377 | /// Raise the ProcessDirectory event. | |||
378 | /// </summary> | |||
379 | /// <param name="directory">The directory name.</param> | |||
380 | /// <param name="hasMatchingFiles">Flag indicating if the directory has matching files.</param> | |||
381 | void OnProcessDirectory(string directory, bool hasMatchingFiles) | |||
382 | { | |||
| 4 | 383 | EventHandler<DirectoryEventArgs> handler = ProcessDirectory; | ||
384 | | |||
| 4 | 385 | if (handler != null) { | ||
| 0 | 386 | var args = new DirectoryEventArgs(directory, hasMatchingFiles); | ||
| 0 | 387 | handler(this, args); | ||
| 0 | 388 | alive_ = args.ContinueRunning; | ||
389 | } | |||
| 4 | 390 | } | ||
391 | | |||
392 | /// <summary> | |||
393 | /// Scan a directory. | |||
394 | /// </summary> | |||
395 | /// <param name="directory">The base directory to scan.</param> | |||
396 | /// <param name="recurse">True to recurse subdirectories, false to scan a single directory.</param> | |||
397 | public void Scan(string directory, bool recurse) | |||
398 | { | |||
| 4 | 399 | alive_ = true; | ||
| 4 | 400 | ScanDir(directory, recurse); | ||
| 4 | 401 | } | ||
402 | | |||
403 | void ScanDir(string directory, bool recurse) | |||
404 | { | |||
405 | | |||
406 | try { | |||
| 4 | 407 | string[] names = System.IO.Directory.GetFiles(directory); | ||
| 4 | 408 | bool hasMatch = false; | ||
| 28302 | 409 | for (int fileIndex = 0; fileIndex < names.Length; ++fileIndex) { | ||
| 14147 | 410 | if (!fileFilter_.IsMatch(names[fileIndex])) { | ||
| 14143 | 411 | names[fileIndex] = null; | ||
| 14143 | 412 | } else { | ||
| 4 | 413 | hasMatch = true; | ||
414 | } | |||
415 | } | |||
416 | | |||
| 4 | 417 | OnProcessDirectory(directory, hasMatch); | ||
418 | | |||
| 4 | 419 | if (alive_ && hasMatch) { | ||
| 28302 | 420 | foreach (string fileName in names) { | ||
421 | try { | |||
| 14147 | 422 | if (fileName != null) { | ||
| 4 | 423 | OnProcessFile(fileName); | ||
| 4 | 424 | if (!alive_) { | ||
| 0 | 425 | break; | ||
426 | } | |||
427 | } | |||
| 14147 | 428 | } catch (Exception e) { | ||
| 0 | 429 | if (!OnFileFailure(fileName, e)) { | ||
| 0 | 430 | throw; | ||
431 | } | |||
| 0 | 432 | } | ||
433 | } | |||
434 | } | |||
| 4 | 435 | } catch (Exception e) { | ||
| 0 | 436 | if (!OnDirectoryFailure(directory, e)) { | ||
| 0 | 437 | throw; | ||
438 | } | |||
| 0 | 439 | } | ||
440 | | |||
| 4 | 441 | if (alive_ && recurse) { | ||
442 | try { | |||
| 0 | 443 | string[] names = System.IO.Directory.GetDirectories(directory); | ||
| 0 | 444 | foreach (string fulldir in names) { | ||
| 0 | 445 | if ((directoryFilter_ == null) || (directoryFilter_.IsMatch(fulldir))) { | ||
| 0 | 446 | ScanDir(fulldir, true); | ||
| 0 | 447 | if (!alive_) { | ||
448 | break; | |||
449 | } | |||
450 | } | |||
451 | } | |||
| 0 | 452 | } catch (Exception e) { | ||
| 0 | 453 | if (!OnDirectoryFailure(directory, e)) { | ||
| 0 | 454 | throw; | ||
455 | } | |||
| 0 | 456 | } | ||
457 | } | |||
| 4 | 458 | } | ||
459 | | |||
460 | #region Instance Fields | |||
461 | /// <summary> | |||
462 | /// The file filter currently in use. | |||
463 | /// </summary> | |||
464 | IScanFilter fileFilter_; | |||
465 | /// <summary> | |||
466 | /// The directory filter currently in use. | |||
467 | /// </summary> | |||
468 | IScanFilter directoryFilter_; | |||
469 | /// <summary> | |||
470 | /// Flag indicating if scanning should continue running. | |||
471 | /// </summary> | |||
472 | bool alive_; | |||
473 | #endregion | |||
474 | } | |||
475 | } |
| Class: | ICSharpCode.SharpZipLib.GZip.GZip |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\GZip\GZip.cs |
| Covered lines: | 0 |
| Uncovered lines: | 20 |
| Coverable lines: | 20 |
| Total lines: | 66 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| Decompress(...) | 5 | 0 | 0 |
| Compress(...) | 5 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.GZip | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// An example class to demonstrate compression and decompression of GZip streams. | |||
8 | /// </summary> | |||
9 | public static class GZip | |||
10 | { | |||
11 | /// <summary> | |||
12 | /// Decompress the <paramref name="inStream">input</paramref> writing | |||
13 | /// uncompressed data to the <paramref name="outStream">output stream</paramref> | |||
14 | /// </summary> | |||
15 | /// <param name="inStream">The readable stream containing data to decompress.</param> | |||
16 | /// <param name="outStream">The output stream to receive the decompressed data.</param> | |||
17 | /// <param name="isStreamOwner">Both streams are closed on completion if true.</param> | |||
18 | public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner) | |||
19 | { | |||
| 0 | 20 | if (inStream == null || outStream == null) { | ||
| 0 | 21 | throw new Exception("Null Stream"); | ||
22 | } | |||
23 | | |||
24 | try { | |||
| 0 | 25 | using (GZipInputStream bzipInput = new GZipInputStream(inStream)) { | ||
| 0 | 26 | bzipInput.IsStreamOwner = isStreamOwner; | ||
| 0 | 27 | Core.StreamUtils.Copy(bzipInput, outStream, new byte[4096]); | ||
| 0 | 28 | } | ||
29 | } finally { | |||
| 0 | 30 | if (isStreamOwner) { | ||
31 | // inStream is closed by the GZipInputStream if stream owner | |||
| 0 | 32 | outStream.Close(); | ||
33 | } | |||
| 0 | 34 | } | ||
| 0 | 35 | } | ||
36 | | |||
37 | /// <summary> | |||
38 | /// Compress the <paramref name="inStream">input stream</paramref> sending | |||
39 | /// result data to <paramref name="outStream">output stream</paramref> | |||
40 | /// </summary> | |||
41 | /// <param name="inStream">The readable stream to compress.</param> | |||
42 | /// <param name="outStream">The output stream to receive the compressed data.</param> | |||
43 | /// <param name="isStreamOwner">Both streams are closed on completion if true.</param> | |||
44 | /// <param name="level">Block size acts as compression level (1 to 9) with 1 giving | |||
45 | /// the lowest compression and 9 the highest.</param> | |||
46 | public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int level) | |||
47 | { | |||
| 0 | 48 | if (inStream == null || outStream == null) { | ||
| 0 | 49 | throw new Exception("Null Stream"); | ||
50 | } | |||
51 | | |||
52 | try { | |||
| 0 | 53 | using (GZipOutputStream bzipOutput = new GZipOutputStream(outStream, level)) { | ||
| 0 | 54 | bzipOutput.IsStreamOwner = isStreamOwner; | ||
| 0 | 55 | Core.StreamUtils.Copy(inStream, bzipOutput, new byte[4096]); | ||
| 0 | 56 | } | ||
57 | } finally { | |||
| 0 | 58 | if (isStreamOwner) { | ||
59 | // outStream is closed by the GZipOutputStream if stream owner | |||
| 0 | 60 | inStream.Close(); | ||
61 | } | |||
| 0 | 62 | } | ||
| 0 | 63 | } | ||
64 | | |||
65 | } | |||
66 | } |
| Class: | ICSharpCode.SharpZipLib.GZip.GZipConstants |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\GZip\GZipConstants.cs |
| Covered lines: | 0 |
| Uncovered lines: | 2 |
| Coverable lines: | 2 |
| Total lines: | 58 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | namespace ICSharpCode.SharpZipLib.GZip | |||
2 | { | |||
3 | /// <summary> | |||
4 | /// This class contains constants used for gzip. | |||
5 | /// </summary> | |||
6 | sealed public class GZipConstants | |||
7 | { | |||
8 | /// <summary> | |||
9 | /// Magic number found at start of GZIP header | |||
10 | /// </summary> | |||
11 | public const int GZIP_MAGIC = 0x1F8B; | |||
12 | | |||
13 | /* The flag byte is divided into individual bits as follows: | |||
14 | | |||
15 | bit 0 FTEXT | |||
16 | bit 1 FHCRC | |||
17 | bit 2 FEXTRA | |||
18 | bit 3 FNAME | |||
19 | bit 4 FCOMMENT | |||
20 | bit 5 reserved | |||
21 | bit 6 reserved | |||
22 | bit 7 reserved | |||
23 | */ | |||
24 | | |||
25 | /// <summary> | |||
26 | /// Flag bit mask for text | |||
27 | /// </summary> | |||
28 | public const int FTEXT = 0x1; | |||
29 | | |||
30 | /// <summary> | |||
31 | /// Flag bitmask for Crc | |||
32 | /// </summary> | |||
33 | public const int FHCRC = 0x2; | |||
34 | | |||
35 | /// <summary> | |||
36 | /// Flag bit mask for extra | |||
37 | /// </summary> | |||
38 | public const int FEXTRA = 0x4; | |||
39 | | |||
40 | /// <summary> | |||
41 | /// flag bitmask for name | |||
42 | /// </summary> | |||
43 | public const int FNAME = 0x8; | |||
44 | | |||
45 | /// <summary> | |||
46 | /// flag bit mask indicating comment is present | |||
47 | /// </summary> | |||
48 | public const int FCOMMENT = 0x10; | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Initialise default instance. | |||
52 | /// </summary> | |||
53 | /// <remarks>Constructor is private to prevent instances being created.</remarks> | |||
| 0 | 54 | GZipConstants() | ||
55 | { | |||
| 0 | 56 | } | ||
57 | } | |||
58 | } |
| Class: | ICSharpCode.SharpZipLib.GZip.GZipException |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\GZip\GZipException.cs |
| Covered lines: | 0 |
| Uncovered lines: | 8 |
| Coverable lines: | 8 |
| Total lines: | 48 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Runtime.Serialization; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.GZip | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// GZipException represents exceptions specific to GZip classes and code. | |||
8 | /// </summary> | |||
9 | [Serializable] | |||
10 | public class GZipException : SharpZipBaseException | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Deserialization constructor | |||
14 | /// </summary> | |||
15 | /// <param name="info"><see cref="SerializationInfo"/> for this constructor</param> | |||
16 | /// <param name="context"><see cref="StreamingContext"/> for this constructor</param> | |||
17 | protected GZipException(SerializationInfo info, StreamingContext context) | |||
| 0 | 18 | : base(info, context) | ||
19 | { | |||
| 0 | 20 | } | ||
21 | | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="GZipException" />. | |||
24 | /// </summary> | |||
| 0 | 25 | public GZipException() | ||
26 | { | |||
| 0 | 27 | } | ||
28 | | |||
29 | /// <summary> | |||
30 | /// Initialise a new instance of <see cref="GZipException" /> with its message string. | |||
31 | /// </summary> | |||
32 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
33 | public GZipException(string message) | |||
| 0 | 34 | : base(message) | ||
35 | { | |||
| 0 | 36 | } | ||
37 | | |||
38 | /// <summary> | |||
39 | /// Initialise a new instance of <see cref="GZipException" />. | |||
40 | /// </summary> | |||
41 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
42 | /// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param> | |||
43 | public GZipException(string message, Exception innerException) | |||
| 0 | 44 | : base(message, innerException) | ||
45 | { | |||
| 0 | 46 | } | ||
47 | } | |||
48 | } |
| Class: | ICSharpCode.SharpZipLib.GZip.GZipInputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\GZip\GzipInputStream.cs |
| Covered lines: | 4 |
| Uncovered lines: | 104 |
| Coverable lines: | 108 |
| Total lines: | 330 |
| Line coverage: | 3.7% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| Read(...) | 5 | 0 | 0 |
| ReadHeader() | 28 | 0 | 0 |
| ReadFooter() | 5 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
5 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
6 | | |||
7 | namespace ICSharpCode.SharpZipLib.GZip | |||
8 | { | |||
9 | | |||
10 | /// <summary> | |||
11 | /// This filter stream is used to decompress a "GZIP" format stream. | |||
12 | /// The "GZIP" format is described baseInputStream RFC 1952. | |||
13 | /// | |||
14 | /// author of the original java version : John Leuner | |||
15 | /// </summary> | |||
16 | /// <example> This sample shows how to unzip a gzipped file | |||
17 | /// <code> | |||
18 | /// using System; | |||
19 | /// using System.IO; | |||
20 | /// | |||
21 | /// using ICSharpCode.SharpZipLib.Core; | |||
22 | /// using ICSharpCode.SharpZipLib.GZip; | |||
23 | /// | |||
24 | /// class MainClass | |||
25 | /// { | |||
26 | /// public static void Main(string[] args) | |||
27 | /// { | |||
28 | /// using (Stream inStream = new GZipInputStream(File.OpenRead(args[0]))) | |||
29 | /// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { | |||
30 | /// byte[] buffer = new byte[4096]; | |||
31 | /// StreamUtils.Copy(inStream, outStream, buffer); | |||
32 | /// } | |||
33 | /// } | |||
34 | /// } | |||
35 | /// </code> | |||
36 | /// </example> | |||
37 | public class GZipInputStream : InflaterInputStream | |||
38 | { | |||
39 | #region Instance Fields | |||
40 | /// <summary> | |||
41 | /// CRC-32 value for uncompressed data | |||
42 | /// </summary> | |||
43 | protected Crc32 crc; | |||
44 | | |||
45 | /// <summary> | |||
46 | /// Flag to indicate if we've read the GZIP header yet for the current member (block of compressed data). | |||
47 | /// This is tracked per-block as the file is parsed. | |||
48 | /// </summary> | |||
49 | bool readGZIPHeader; | |||
50 | #endregion | |||
51 | | |||
52 | #region Constructors | |||
53 | /// <summary> | |||
54 | /// Creates a GZipInputStream with the default buffer size | |||
55 | /// </summary> | |||
56 | /// <param name="baseInputStream"> | |||
57 | /// The stream to read compressed data from (baseInputStream GZIP format) | |||
58 | /// </param> | |||
59 | public GZipInputStream(Stream baseInputStream) | |||
| 2 | 60 | : this(baseInputStream, 4096) | ||
61 | { | |||
| 2 | 62 | } | ||
63 | | |||
64 | /// <summary> | |||
65 | /// Creates a GZIPInputStream with the specified buffer size | |||
66 | /// </summary> | |||
67 | /// <param name="baseInputStream"> | |||
68 | /// The stream to read compressed data from (baseInputStream GZIP format) | |||
69 | /// </param> | |||
70 | /// <param name="size"> | |||
71 | /// Size of the buffer to use | |||
72 | /// </param> | |||
73 | public GZipInputStream(Stream baseInputStream, int size) | |||
| 2 | 74 | : base(baseInputStream, new Inflater(true), size) | ||
75 | { | |||
| 2 | 76 | } | ||
77 | #endregion | |||
78 | | |||
79 | #region Stream overrides | |||
80 | /// <summary> | |||
81 | /// Reads uncompressed data into an array of bytes | |||
82 | /// </summary> | |||
83 | /// <param name="buffer"> | |||
84 | /// The buffer to read uncompressed data into | |||
85 | /// </param> | |||
86 | /// <param name="offset"> | |||
87 | /// The offset indicating where the data should be placed | |||
88 | /// </param> | |||
89 | /// <param name="count"> | |||
90 | /// The number of uncompressed bytes to be read | |||
91 | /// </param> | |||
92 | /// <returns>Returns the number of bytes actually read.</returns> | |||
93 | public override int Read(byte[] buffer, int offset, int count) | |||
94 | { | |||
95 | // A GZIP file can contain multiple blocks of compressed data, although this is quite rare. | |||
96 | // A compressed block could potentially be empty, so we need to loop until we reach EOF or | |||
97 | // we find data. | |||
98 | while (true) { | |||
99 | | |||
100 | // If we haven't read the header for this block, read it | |||
| 0 | 101 | if (!readGZIPHeader) { | ||
102 | | |||
103 | // Try to read header. If there is no header (0 bytes available), this is EOF. If there is | |||
104 | // an incomplete header, this will throw an exception. | |||
| 0 | 105 | if (!ReadHeader()) { | ||
| 0 | 106 | return 0; | ||
107 | } | |||
108 | } | |||
109 | | |||
110 | // Try to read compressed data | |||
| 0 | 111 | int bytesRead = base.Read(buffer, offset, count); | ||
| 0 | 112 | if (bytesRead > 0) { | ||
| 0 | 113 | crc.Update(buffer, offset, bytesRead); | ||
114 | } | |||
115 | | |||
116 | // If this is the end of stream, read the footer | |||
| 0 | 117 | if (inf.IsFinished) { | ||
| 0 | 118 | ReadFooter(); | ||
119 | } | |||
120 | | |||
| 0 | 121 | if (bytesRead > 0) { | ||
| 0 | 122 | return bytesRead; | ||
123 | } | |||
124 | } | |||
125 | } | |||
126 | #endregion | |||
127 | | |||
128 | #region Support routines | |||
129 | bool ReadHeader() | |||
130 | { | |||
131 | // Initialize CRC for this block | |||
| 0 | 132 | crc = new Crc32(); | ||
133 | | |||
134 | // Make sure there is data in file. We can't rely on ReadLeByte() to fill the buffer, as this could be EOF, | |||
135 | // which is fine, but ReadLeByte() throws an exception if it doesn't find data, so we do this part ourselves. | |||
| 0 | 136 | if (inputBuffer.Available <= 0) { | ||
| 0 | 137 | inputBuffer.Fill(); | ||
| 0 | 138 | if (inputBuffer.Available <= 0) { | ||
139 | // No header, EOF. | |||
| 0 | 140 | return false; | ||
141 | } | |||
142 | } | |||
143 | | |||
144 | // 1. Check the two magic bytes | |||
| 0 | 145 | var headCRC = new Crc32(); | ||
| 0 | 146 | int magic = inputBuffer.ReadLeByte(); | ||
147 | | |||
| 0 | 148 | if (magic < 0) { | ||
| 0 | 149 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
150 | } | |||
151 | | |||
| 0 | 152 | headCRC.Update(magic); | ||
| 0 | 153 | if (magic != (GZipConstants.GZIP_MAGIC >> 8)) { | ||
| 0 | 154 | throw new GZipException("Error GZIP header, first magic byte doesn't match"); | ||
155 | } | |||
156 | | |||
157 | //magic = baseInputStream.ReadByte(); | |||
| 0 | 158 | magic = inputBuffer.ReadLeByte(); | ||
159 | | |||
| 0 | 160 | if (magic < 0) { | ||
| 0 | 161 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
162 | } | |||
163 | | |||
| 0 | 164 | if (magic != (GZipConstants.GZIP_MAGIC & 0xFF)) { | ||
| 0 | 165 | throw new GZipException("Error GZIP header, second magic byte doesn't match"); | ||
166 | } | |||
167 | | |||
| 0 | 168 | headCRC.Update(magic); | ||
169 | | |||
170 | // 2. Check the compression type (must be 8) | |||
| 0 | 171 | int compressionType = inputBuffer.ReadLeByte(); | ||
172 | | |||
| 0 | 173 | if (compressionType < 0) { | ||
| 0 | 174 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
175 | } | |||
176 | | |||
| 0 | 177 | if (compressionType != 8) { | ||
| 0 | 178 | throw new GZipException("Error GZIP header, data not in deflate format"); | ||
179 | } | |||
| 0 | 180 | headCRC.Update(compressionType); | ||
181 | | |||
182 | // 3. Check the flags | |||
| 0 | 183 | int flags = inputBuffer.ReadLeByte(); | ||
| 0 | 184 | if (flags < 0) { | ||
| 0 | 185 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
186 | } | |||
| 0 | 187 | headCRC.Update(flags); | ||
188 | | |||
189 | /* This flag byte is divided into individual bits as follows: | |||
190 | | |||
191 | bit 0 FTEXT | |||
192 | bit 1 FHCRC | |||
193 | bit 2 FEXTRA | |||
194 | bit 3 FNAME | |||
195 | bit 4 FCOMMENT | |||
196 | bit 5 reserved | |||
197 | bit 6 reserved | |||
198 | bit 7 reserved | |||
199 | */ | |||
200 | | |||
201 | // 3.1 Check the reserved bits are zero | |||
202 | | |||
| 0 | 203 | if ((flags & 0xE0) != 0) { | ||
| 0 | 204 | throw new GZipException("Reserved flag bits in GZIP header != 0"); | ||
205 | } | |||
206 | | |||
207 | // 4.-6. Skip the modification time, extra flags, and OS type | |||
| 0 | 208 | for (int i = 0; i < 6; i++) { | ||
| 0 | 209 | int readByte = inputBuffer.ReadLeByte(); | ||
| 0 | 210 | if (readByte < 0) { | ||
| 0 | 211 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
212 | } | |||
| 0 | 213 | headCRC.Update(readByte); | ||
214 | } | |||
215 | | |||
216 | // 7. Read extra field | |||
| 0 | 217 | if ((flags & GZipConstants.FEXTRA) != 0) { | ||
218 | | |||
219 | // XLEN is total length of extra subfields, we will skip them all | |||
220 | int len1, len2; | |||
| 0 | 221 | len1 = inputBuffer.ReadLeByte(); | ||
| 0 | 222 | len2 = inputBuffer.ReadLeByte(); | ||
| 0 | 223 | if ((len1 < 0) || (len2 < 0)) { | ||
| 0 | 224 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
225 | } | |||
| 0 | 226 | headCRC.Update(len1); | ||
| 0 | 227 | headCRC.Update(len2); | ||
228 | | |||
| 0 | 229 | int extraLen = (len2 << 8) | len1; // gzip is LSB first | ||
| 0 | 230 | for (int i = 0; i < extraLen; i++) { | ||
| 0 | 231 | int readByte = inputBuffer.ReadLeByte(); | ||
| 0 | 232 | if (readByte < 0) { | ||
| 0 | 233 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
234 | } | |||
| 0 | 235 | headCRC.Update(readByte); | ||
236 | } | |||
237 | } | |||
238 | | |||
239 | // 8. Read file name | |||
| 0 | 240 | if ((flags & GZipConstants.FNAME) != 0) { | ||
241 | int readByte; | |||
| 0 | 242 | while ((readByte = inputBuffer.ReadLeByte()) > 0) { | ||
| 0 | 243 | headCRC.Update(readByte); | ||
244 | } | |||
245 | | |||
| 0 | 246 | if (readByte < 0) { | ||
| 0 | 247 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
248 | } | |||
| 0 | 249 | headCRC.Update(readByte); | ||
250 | } | |||
251 | | |||
252 | // 9. Read comment | |||
| 0 | 253 | if ((flags & GZipConstants.FCOMMENT) != 0) { | ||
254 | int readByte; | |||
| 0 | 255 | while ((readByte = inputBuffer.ReadLeByte()) > 0) { | ||
| 0 | 256 | headCRC.Update(readByte); | ||
257 | } | |||
258 | | |||
| 0 | 259 | if (readByte < 0) { | ||
| 0 | 260 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
261 | } | |||
262 | | |||
| 0 | 263 | headCRC.Update(readByte); | ||
264 | } | |||
265 | | |||
266 | // 10. Read header CRC | |||
| 0 | 267 | if ((flags & GZipConstants.FHCRC) != 0) { | ||
268 | int tempByte; | |||
| 0 | 269 | int crcval = inputBuffer.ReadLeByte(); | ||
| 0 | 270 | if (crcval < 0) { | ||
| 0 | 271 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
272 | } | |||
273 | | |||
| 0 | 274 | tempByte = inputBuffer.ReadLeByte(); | ||
| 0 | 275 | if (tempByte < 0) { | ||
| 0 | 276 | throw new EndOfStreamException("EOS reading GZIP header"); | ||
277 | } | |||
278 | | |||
| 0 | 279 | crcval = (crcval << 8) | tempByte; | ||
| 0 | 280 | if (crcval != ((int)headCRC.Value & 0xffff)) { | ||
| 0 | 281 | throw new GZipException("Header CRC value mismatch"); | ||
282 | } | |||
283 | } | |||
284 | | |||
| 0 | 285 | readGZIPHeader = true; | ||
| 0 | 286 | return true; | ||
287 | } | |||
288 | | |||
289 | void ReadFooter() | |||
290 | { | |||
| 0 | 291 | byte[] footer = new byte[8]; | ||
292 | | |||
293 | // End of stream; reclaim all bytes from inf, read the final byte count, and reset the inflator | |||
| 0 | 294 | long bytesRead = inf.TotalOut & 0xffffffff; | ||
| 0 | 295 | inputBuffer.Available += inf.RemainingInput; | ||
| 0 | 296 | inf.Reset(); | ||
297 | | |||
298 | // Read footer from inputBuffer | |||
| 0 | 299 | int needed = 8; | ||
| 0 | 300 | while (needed > 0) { | ||
| 0 | 301 | int count = inputBuffer.ReadClearTextBuffer(footer, 8 - needed, needed); | ||
| 0 | 302 | if (count <= 0) { | ||
| 0 | 303 | throw new EndOfStreamException("EOS reading GZIP footer"); | ||
304 | } | |||
| 0 | 305 | needed -= count; // Jewel Jan 16 | ||
306 | } | |||
307 | | |||
308 | // Calculate CRC | |||
| 0 | 309 | int crcval = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8) | ((footer[2] & 0xff) << 16) | (footer[3] << 24); | ||
| 0 | 310 | if (crcval != (int)crc.Value) { | ||
| 0 | 311 | throw new GZipException("GZIP crc sum mismatch, theirs \"" + crcval + "\" and ours \"" + (int)crc.Value); | ||
312 | } | |||
313 | | |||
314 | // NOTE The total here is the original total modulo 2 ^ 32. | |||
| 0 | 315 | uint total = | ||
| 0 | 316 | (uint)((uint)footer[4] & 0xff) | | ||
| 0 | 317 | (uint)(((uint)footer[5] & 0xff) << 8) | | ||
| 0 | 318 | (uint)(((uint)footer[6] & 0xff) << 16) | | ||
| 0 | 319 | (uint)((uint)footer[7] << 24); | ||
320 | | |||
| 0 | 321 | if (bytesRead != total) { | ||
| 0 | 322 | throw new GZipException("Number of bytes mismatch in footer"); | ||
323 | } | |||
324 | | |||
325 | // Mark header read as false so if another header exists, we'll continue reading through the file | |||
| 0 | 326 | readGZIPHeader = false; | ||
| 0 | 327 | } | ||
328 | #endregion | |||
329 | } | |||
330 | } |
| Class: | ICSharpCode.SharpZipLib.GZip.GZipOutputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\GZip\GzipOutputStream.cs |
| Covered lines: | 63 |
| Uncovered lines: | 5 |
| Coverable lines: | 68 |
| Total lines: | 227 |
| Line coverage: | 92.6% |
| Branch coverage: | 81.2% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| Finalize() | 1 | 100 | 100 |
| SetLevel(...) | 2 | 0 | 0 |
| GetLevel() | 1 | 0 | 0 |
| Write(...) | 3 | 100 | 100 |
| Close() | 3 | 100 | 100 |
| Finish() | 3 | 100 | 100 |
| WriteHeader() | 2 | 100 | 66.67 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
5 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
6 | | |||
7 | namespace ICSharpCode.SharpZipLib.GZip | |||
8 | { | |||
9 | /// <summary> | |||
10 | /// This filter stream is used to compress a stream into a "GZIP" stream. | |||
11 | /// The "GZIP" format is described in RFC 1952. | |||
12 | /// | |||
13 | /// author of the original java version : John Leuner | |||
14 | /// </summary> | |||
15 | /// <example> This sample shows how to gzip a file | |||
16 | /// <code> | |||
17 | /// using System; | |||
18 | /// using System.IO; | |||
19 | /// | |||
20 | /// using ICSharpCode.SharpZipLib.GZip; | |||
21 | /// using ICSharpCode.SharpZipLib.Core; | |||
22 | /// | |||
23 | /// class MainClass | |||
24 | /// { | |||
25 | /// public static void Main(string[] args) | |||
26 | /// { | |||
27 | /// using (Stream s = new GZipOutputStream(File.Create(args[0] + ".gz"))) | |||
28 | /// using (FileStream fs = File.OpenRead(args[0])) { | |||
29 | /// byte[] writeData = new byte[4096]; | |||
30 | /// Streamutils.Copy(s, fs, writeData); | |||
31 | /// } | |||
32 | /// } | |||
33 | /// } | |||
34 | /// } | |||
35 | /// </code> | |||
36 | /// </example> | |||
37 | public class GZipOutputStream : DeflaterOutputStream | |||
38 | { | |||
39 | enum OutputState | |||
40 | { | |||
41 | Header, | |||
42 | Footer, | |||
43 | Finished, | |||
44 | Closed, | |||
45 | }; | |||
46 | | |||
47 | #region Instance Fields | |||
48 | /// <summary> | |||
49 | /// CRC-32 value for uncompressed data | |||
50 | /// </summary> | |||
| 9 | 51 | protected Crc32 crc = new Crc32(); | ||
52 | OutputState state_ = OutputState.Header; | |||
53 | #endregion | |||
54 | | |||
55 | #region Constructors | |||
56 | /// <summary> | |||
57 | /// Creates a GzipOutputStream with the default buffer size | |||
58 | /// </summary> | |||
59 | /// <param name="baseOutputStream"> | |||
60 | /// The stream to read data (to be compressed) from | |||
61 | /// </param> | |||
62 | public GZipOutputStream(Stream baseOutputStream) | |||
| 9 | 63 | : this(baseOutputStream, 4096) | ||
64 | { | |||
| 9 | 65 | } | ||
66 | | |||
67 | /// <summary> | |||
68 | /// Creates a GZipOutputStream with the specified buffer size | |||
69 | /// </summary> | |||
70 | /// <param name="baseOutputStream"> | |||
71 | /// The stream to read data (to be compressed) from | |||
72 | /// </param> | |||
73 | /// <param name="size"> | |||
74 | /// Size of the buffer to use | |||
75 | /// </param> | |||
| 9 | 76 | public GZipOutputStream(Stream baseOutputStream, int size) : base(baseOutputStream, new Deflater(Deflater.DEFAULT_CO | ||
77 | { | |||
| 9 | 78 | } | ||
79 | #endregion | |||
80 | | |||
81 | #region Destructor | |||
82 | /// <summary> | |||
83 | /// Ensures that resources are freed and other cleanup operations | |||
84 | /// are performed when the garbage collector reclaims the GZipOutputStream. | |||
85 | /// </summary> | |||
86 | ~GZipOutputStream() | |||
87 | { | |||
| 9 | 88 | Dispose(false); | ||
| 18 | 89 | } | ||
90 | #endregion | |||
91 | | |||
92 | #region Public API | |||
93 | /// <summary> | |||
94 | /// Sets the active compression level (1-9). The new level will be activated | |||
95 | /// immediately. | |||
96 | /// </summary> | |||
97 | /// <param name="level">The compression level to set.</param> | |||
98 | /// <exception cref="ArgumentOutOfRangeException"> | |||
99 | /// Level specified is not supported. | |||
100 | /// </exception> | |||
101 | /// <see cref="Deflater"/> | |||
102 | public void SetLevel(int level) | |||
103 | { | |||
| 0 | 104 | if (level < Deflater.BEST_SPEED) { | ||
| 0 | 105 | throw new ArgumentOutOfRangeException(nameof(level)); | ||
106 | } | |||
| 0 | 107 | deflater_.SetLevel(level); | ||
| 0 | 108 | } | ||
109 | | |||
110 | /// <summary> | |||
111 | /// Get the current compression level. | |||
112 | /// </summary> | |||
113 | /// <returns>The current compression level.</returns> | |||
114 | public int GetLevel() | |||
115 | { | |||
| 0 | 116 | return deflater_.GetLevel(); | ||
117 | } | |||
118 | #endregion | |||
119 | | |||
120 | #region Stream overrides | |||
121 | /// <summary> | |||
122 | /// Write given buffer to output updating crc | |||
123 | /// </summary> | |||
124 | /// <param name="buffer">Buffer to write</param> | |||
125 | /// <param name="offset">Offset of first byte in buf to write</param> | |||
126 | /// <param name="count">Number of bytes to write</param> | |||
127 | public override void Write(byte[] buffer, int offset, int count) | |||
128 | { | |||
| 3 | 129 | if (state_ == OutputState.Header) { | ||
| 1 | 130 | WriteHeader(); | ||
131 | } | |||
132 | | |||
| 3 | 133 | if (state_ != OutputState.Footer) { | ||
| 2 | 134 | throw new InvalidOperationException("Write not permitted in current state"); | ||
135 | } | |||
136 | | |||
| 1 | 137 | crc.Update(buffer, offset, count); | ||
| 1 | 138 | base.Write(buffer, offset, count); | ||
| 1 | 139 | } | ||
140 | | |||
141 | /// <summary> | |||
142 | /// Writes remaining compressed output data to the output stream | |||
143 | /// and closes it. | |||
144 | /// </summary> | |||
145 | public override void Close() | |||
146 | { | |||
147 | try { | |||
| 10 | 148 | Finish(); | ||
| 10 | 149 | } finally { | ||
| 10 | 150 | if (state_ != OutputState.Closed) { | ||
| 8 | 151 | state_ = OutputState.Closed; | ||
| 8 | 152 | if (IsStreamOwner) { | ||
| 7 | 153 | baseOutputStream_.Close(); | ||
154 | } | |||
155 | } | |||
| 10 | 156 | } | ||
| 10 | 157 | } | ||
158 | #endregion | |||
159 | | |||
160 | #region DeflaterOutputStream overrides | |||
161 | /// <summary> | |||
162 | /// Finish compression and write any footer information required to stream | |||
163 | /// </summary> | |||
164 | public override void Finish() | |||
165 | { | |||
166 | // If no data has been written a header should be added. | |||
| 13 | 167 | if (state_ == OutputState.Header) { | ||
| 8 | 168 | WriteHeader(); | ||
169 | } | |||
170 | | |||
| 13 | 171 | if (state_ == OutputState.Footer) { | ||
| 9 | 172 | state_ = OutputState.Finished; | ||
| 9 | 173 | base.Finish(); | ||
174 | | |||
| 9 | 175 | var totalin = (uint)(deflater_.TotalIn & 0xffffffff); | ||
| 9 | 176 | var crcval = (uint)(crc.Value & 0xffffffff); | ||
177 | | |||
178 | byte[] gzipFooter; | |||
179 | | |||
180 | unchecked { | |||
| 9 | 181 | gzipFooter = new byte[] { | ||
| 9 | 182 | (byte) crcval, (byte) (crcval >> 8), | ||
| 9 | 183 | (byte) (crcval >> 16), (byte) (crcval >> 24), | ||
| 9 | 184 | | ||
| 9 | 185 | (byte) totalin, (byte) (totalin >> 8), | ||
| 9 | 186 | (byte) (totalin >> 16), (byte) (totalin >> 24) | ||
| 9 | 187 | }; | ||
188 | } | |||
189 | | |||
| 9 | 190 | baseOutputStream_.Write(gzipFooter, 0, gzipFooter.Length); | ||
191 | } | |||
| 13 | 192 | } | ||
193 | #endregion | |||
194 | | |||
195 | #region Support Routines | |||
196 | void WriteHeader() | |||
197 | { | |||
| 9 | 198 | if (state_ == OutputState.Header) { | ||
| 9 | 199 | state_ = OutputState.Footer; | ||
200 | | |||
| 9 | 201 | var mod_time = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100 | ||
| 9 | 202 | byte[] gzipHeader = { | ||
| 9 | 203 | // The two magic bytes | ||
| 9 | 204 | (byte) (GZipConstants.GZIP_MAGIC >> 8), (byte) (GZipConstants.GZIP_MAGIC & 0xff), | ||
| 9 | 205 | | ||
| 9 | 206 | // The compression type | ||
| 9 | 207 | (byte) Deflater.DEFLATED, | ||
| 9 | 208 | | ||
| 9 | 209 | // The flags (not set) | ||
| 9 | 210 | 0, | ||
| 9 | 211 | | ||
| 9 | 212 | // The modification time | ||
| 9 | 213 | (byte) mod_time, (byte) (mod_time >> 8), | ||
| 9 | 214 | (byte) (mod_time >> 16), (byte) (mod_time >> 24), | ||
| 9 | 215 | | ||
| 9 | 216 | // The extra flags | ||
| 9 | 217 | 0, | ||
| 9 | 218 | | ||
| 9 | 219 | // The OS type (unknown) | ||
| 9 | 220 | (byte) 255 | ||
| 9 | 221 | }; | ||
| 9 | 222 | baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length); | ||
223 | } | |||
| 9 | 224 | } | ||
225 | #endregion | |||
226 | } | |||
227 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.Inflater |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\Inflater.cs |
| Covered lines: | 173 |
| Uncovered lines: | 57 |
| Coverable lines: | 230 |
| Total lines: | 788 |
| Line coverage: | 75.2% |
| Branch coverage: | 59.8% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| .ctor(...) | 3 | 100 | 100 |
| Reset() | 3 | 100 | 66.67 |
| DecodeHeader() | 5 | 73.33 | 66.67 |
| DecodeDict() | 3 | 0 | 0 |
| DecodeHuffman() | 14 | 77.08 | 61.54 |
| DecodeChksum() | 4 | 81.82 | 71.43 |
| Decode() | 24 | 87.27 | 71.43 |
| SetDictionary(...) | 1 | 0 | 0 |
| SetDictionary(...) | 6 | 0 | 0 |
| SetInput(...) | 1 | 0 | 0 |
| SetInput(...) | 1 | 100 | 100 |
| Inflate(...) | 2 | 0 | 0 |
| Inflate(...) | 13 | 84 | 80 |
| .cctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using ICSharpCode.SharpZipLib.Checksum; | |||
3 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// Inflater is used to decompress data that has been compressed according | |||
9 | /// to the "deflate" standard described in rfc1951. | |||
10 | /// | |||
11 | /// By default Zlib (rfc1950) headers and footers are expected in the input. | |||
12 | /// You can use constructor <code> public Inflater(bool noHeader)</code> passing true | |||
13 | /// if there is no Zlib header information | |||
14 | /// | |||
15 | /// The usage is as following. First you have to set some input with | |||
16 | /// <code>SetInput()</code>, then Inflate() it. If inflate doesn't | |||
17 | /// inflate any bytes there may be three reasons: | |||
18 | /// <ul> | |||
19 | /// <li>IsNeedingInput() returns true because the input buffer is empty. | |||
20 | /// You have to provide more input with <code>SetInput()</code>. | |||
21 | /// NOTE: IsNeedingInput() also returns true when, the stream is finished. | |||
22 | /// </li> | |||
23 | /// <li>IsNeedingDictionary() returns true, you have to provide a preset | |||
24 | /// dictionary with <code>SetDictionary()</code>.</li> | |||
25 | /// <li>IsFinished returns true, the inflater has finished.</li> | |||
26 | /// </ul> | |||
27 | /// Once the first output byte is produced, a dictionary will not be | |||
28 | /// needed at a later stage. | |||
29 | /// | |||
30 | /// author of the original java version : John Leuner, Jochen Hoenicke | |||
31 | /// </summary> | |||
32 | public class Inflater | |||
33 | { | |||
34 | #region Constants/Readonly | |||
35 | /// <summary> | |||
36 | /// Copy lengths for literal codes 257..285 | |||
37 | /// </summary> | |||
| 1 | 38 | static readonly int[] CPLENS = { | ||
| 1 | 39 | 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, | ||
| 1 | 40 | 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 | ||
| 1 | 41 | }; | ||
42 | | |||
43 | /// <summary> | |||
44 | /// Extra bits for literal codes 257..285 | |||
45 | /// </summary> | |||
| 1 | 46 | static readonly int[] CPLEXT = { | ||
| 1 | 47 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, | ||
| 1 | 48 | 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 | ||
| 1 | 49 | }; | ||
50 | | |||
51 | /// <summary> | |||
52 | /// Copy offsets for distance codes 0..29 | |||
53 | /// </summary> | |||
| 1 | 54 | static readonly int[] CPDIST = { | ||
| 1 | 55 | 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, | ||
| 1 | 56 | 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, | ||
| 1 | 57 | 8193, 12289, 16385, 24577 | ||
| 1 | 58 | }; | ||
59 | | |||
60 | /// <summary> | |||
61 | /// Extra bits for distance codes | |||
62 | /// </summary> | |||
| 1 | 63 | static readonly int[] CPDEXT = { | ||
| 1 | 64 | 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, | ||
| 1 | 65 | 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, | ||
| 1 | 66 | 12, 12, 13, 13 | ||
| 1 | 67 | }; | ||
68 | | |||
69 | /// <summary> | |||
70 | /// These are the possible states for an inflater | |||
71 | /// </summary> | |||
72 | const int DECODE_HEADER = 0; | |||
73 | const int DECODE_DICT = 1; | |||
74 | const int DECODE_BLOCKS = 2; | |||
75 | const int DECODE_STORED_LEN1 = 3; | |||
76 | const int DECODE_STORED_LEN2 = 4; | |||
77 | const int DECODE_STORED = 5; | |||
78 | const int DECODE_DYN_HEADER = 6; | |||
79 | const int DECODE_HUFFMAN = 7; | |||
80 | const int DECODE_HUFFMAN_LENBITS = 8; | |||
81 | const int DECODE_HUFFMAN_DIST = 9; | |||
82 | const int DECODE_HUFFMAN_DISTBITS = 10; | |||
83 | const int DECODE_CHKSUM = 11; | |||
84 | const int FINISHED = 12; | |||
85 | #endregion | |||
86 | | |||
87 | #region Instance Fields | |||
88 | /// <summary> | |||
89 | /// This variable contains the current state. | |||
90 | /// </summary> | |||
91 | int mode; | |||
92 | | |||
93 | /// <summary> | |||
94 | /// The adler checksum of the dictionary or of the decompressed | |||
95 | /// stream, as it is written in the header resp. footer of the | |||
96 | /// compressed stream. | |||
97 | /// Only valid if mode is DECODE_DICT or DECODE_CHKSUM. | |||
98 | /// </summary> | |||
99 | int readAdler; | |||
100 | | |||
101 | /// <summary> | |||
102 | /// The number of bits needed to complete the current state. This | |||
103 | /// is valid, if mode is DECODE_DICT, DECODE_CHKSUM, | |||
104 | /// DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. | |||
105 | /// </summary> | |||
106 | int neededBits; | |||
107 | int repLength; | |||
108 | int repDist; | |||
109 | int uncomprLen; | |||
110 | | |||
111 | /// <summary> | |||
112 | /// True, if the last block flag was set in the last block of the | |||
113 | /// inflated stream. This means that the stream ends after the | |||
114 | /// current block. | |||
115 | /// </summary> | |||
116 | bool isLastBlock; | |||
117 | | |||
118 | /// <summary> | |||
119 | /// The total number of inflated bytes. | |||
120 | /// </summary> | |||
121 | long totalOut; | |||
122 | | |||
123 | /// <summary> | |||
124 | /// The total number of bytes set with setInput(). This is not the | |||
125 | /// value returned by the TotalIn property, since this also includes the | |||
126 | /// unprocessed input. | |||
127 | /// </summary> | |||
128 | long totalIn; | |||
129 | | |||
130 | /// <summary> | |||
131 | /// This variable stores the noHeader flag that was given to the constructor. | |||
132 | /// True means, that the inflated stream doesn't contain a Zlib header or | |||
133 | /// footer. | |||
134 | /// </summary> | |||
135 | bool noHeader; | |||
136 | readonly StreamManipulator input; | |||
137 | OutputWindow outputWindow; | |||
138 | InflaterDynHeader dynHeader; | |||
139 | InflaterHuffmanTree litlenTree, distTree; | |||
140 | Adler32 adler; | |||
141 | #endregion | |||
142 | | |||
143 | #region Constructors | |||
144 | /// <summary> | |||
145 | /// Creates a new inflater or RFC1951 decompressor | |||
146 | /// RFC1950/Zlib headers and footers will be expected in the input data | |||
147 | /// </summary> | |||
| 3 | 148 | public Inflater() : this(false) | ||
149 | { | |||
| 3 | 150 | } | ||
151 | | |||
152 | /// <summary> | |||
153 | /// Creates a new inflater. | |||
154 | /// </summary> | |||
155 | /// <param name="noHeader"> | |||
156 | /// True if no RFC1950/Zlib header and footer fields are expected in the input data | |||
157 | /// | |||
158 | /// This is used for GZIPed/Zipped input. | |||
159 | /// | |||
160 | /// For compatibility with | |||
161 | /// Sun JDK you should provide one byte of input more than needed in | |||
162 | /// this case. | |||
163 | /// </param> | |||
| 435 | 164 | public Inflater(bool noHeader) | ||
165 | { | |||
| 435 | 166 | this.noHeader = noHeader; | ||
| 435 | 167 | this.adler = new Adler32(); | ||
| 435 | 168 | input = new StreamManipulator(); | ||
| 435 | 169 | outputWindow = new OutputWindow(); | ||
| 435 | 170 | mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER; | ||
| 435 | 171 | } | ||
172 | #endregion | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Resets the inflater so that a new stream can be decompressed. All | |||
176 | /// pending input and output will be discarded. | |||
177 | /// </summary> | |||
178 | public void Reset() | |||
179 | { | |||
| 41 | 180 | mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER; | ||
| 41 | 181 | totalIn = 0; | ||
| 41 | 182 | totalOut = 0; | ||
| 41 | 183 | input.Reset(); | ||
| 41 | 184 | outputWindow.Reset(); | ||
| 41 | 185 | dynHeader = null; | ||
| 41 | 186 | litlenTree = null; | ||
| 41 | 187 | distTree = null; | ||
| 41 | 188 | isLastBlock = false; | ||
| 41 | 189 | adler.Reset(); | ||
| 41 | 190 | } | ||
191 | | |||
192 | /// <summary> | |||
193 | /// Decodes a zlib/RFC1950 header. | |||
194 | /// </summary> | |||
195 | /// <returns> | |||
196 | /// False if more input is needed. | |||
197 | /// </returns> | |||
198 | /// <exception cref="SharpZipBaseException"> | |||
199 | /// The header is invalid. | |||
200 | /// </exception> | |||
201 | private bool DecodeHeader() | |||
202 | { | |||
| 22 | 203 | int header = input.PeekBits(16); | ||
| 22 | 204 | if (header < 0) { | ||
| 11 | 205 | return false; | ||
206 | } | |||
| 11 | 207 | input.DropBits(16); | ||
208 | | |||
209 | // The header is written in "wrong" byte order | |||
| 11 | 210 | header = ((header << 8) | (header >> 8)) & 0xffff; | ||
| 11 | 211 | if (header % 31 != 0) { | ||
| 0 | 212 | throw new SharpZipBaseException("Header checksum illegal"); | ||
213 | } | |||
214 | | |||
| 11 | 215 | if ((header & 0x0f00) != (Deflater.DEFLATED << 8)) { | ||
| 0 | 216 | throw new SharpZipBaseException("Compression Method unknown"); | ||
217 | } | |||
218 | | |||
219 | /* Maximum size of the backwards window in bits. | |||
220 | * We currently ignore this, but we could use it to make the | |||
221 | * inflater window more space efficient. On the other hand the | |||
222 | * full window (15 bits) is needed most times, anyway. | |||
223 | int max_wbits = ((header & 0x7000) >> 12) + 8; | |||
224 | */ | |||
225 | | |||
| 11 | 226 | if ((header & 0x0020) == 0) { // Dictionary flag? | ||
| 11 | 227 | mode = DECODE_BLOCKS; | ||
| 11 | 228 | } else { | ||
| 0 | 229 | mode = DECODE_DICT; | ||
| 0 | 230 | neededBits = 32; | ||
231 | } | |||
| 11 | 232 | return true; | ||
233 | } | |||
234 | | |||
235 | /// <summary> | |||
236 | /// Decodes the dictionary checksum after the deflate header. | |||
237 | /// </summary> | |||
238 | /// <returns> | |||
239 | /// False if more input is needed. | |||
240 | /// </returns> | |||
241 | private bool DecodeDict() | |||
242 | { | |||
| 0 | 243 | while (neededBits > 0) { | ||
| 0 | 244 | int dictByte = input.PeekBits(8); | ||
| 0 | 245 | if (dictByte < 0) { | ||
| 0 | 246 | return false; | ||
247 | } | |||
| 0 | 248 | input.DropBits(8); | ||
| 0 | 249 | readAdler = (readAdler << 8) | dictByte; | ||
| 0 | 250 | neededBits -= 8; | ||
251 | } | |||
| 0 | 252 | return false; | ||
253 | } | |||
254 | | |||
255 | /// <summary> | |||
256 | /// Decodes the huffman encoded symbols in the input stream. | |||
257 | /// </summary> | |||
258 | /// <returns> | |||
259 | /// false if more input is needed, true if output window is | |||
260 | /// full or the current block ends. | |||
261 | /// </returns> | |||
262 | /// <exception cref="SharpZipBaseException"> | |||
263 | /// if deflated stream is invalid. | |||
264 | /// </exception> | |||
265 | private bool DecodeHuffman() | |||
266 | { | |||
| 371 | 267 | int free = outputWindow.GetFreeSpace(); | ||
| 468 | 268 | while (free >= 258) { | ||
269 | int symbol; | |||
| 468 | 270 | switch (mode) { | ||
271 | case DECODE_HUFFMAN: | |||
272 | // This is the inner loop so it is optimized a bit | |||
| 9697 | 273 | while (((symbol = litlenTree.GetSymbol(input)) & ~0xff) == 0) { | ||
| 9229 | 274 | outputWindow.Write(symbol); | ||
| 9229 | 275 | if (--free < 258) { | ||
| 0 | 276 | return true; | ||
277 | } | |||
278 | } | |||
279 | | |||
| 468 | 280 | if (symbol < 257) { | ||
| 371 | 281 | if (symbol < 0) { | ||
| 0 | 282 | return false; | ||
283 | } else { | |||
284 | // symbol == 256: end of block | |||
| 371 | 285 | distTree = null; | ||
| 371 | 286 | litlenTree = null; | ||
| 371 | 287 | mode = DECODE_BLOCKS; | ||
| 371 | 288 | return true; | ||
289 | } | |||
290 | } | |||
291 | | |||
292 | try { | |||
| 97 | 293 | repLength = CPLENS[symbol - 257]; | ||
| 97 | 294 | neededBits = CPLEXT[symbol - 257]; | ||
| 97 | 295 | } catch (Exception) { | ||
| 0 | 296 | throw new SharpZipBaseException("Illegal rep length code"); | ||
297 | } | |||
298 | goto case DECODE_HUFFMAN_LENBITS; // fall through | |||
299 | | |||
300 | case DECODE_HUFFMAN_LENBITS: | |||
| 97 | 301 | if (neededBits > 0) { | ||
| 25 | 302 | mode = DECODE_HUFFMAN_LENBITS; | ||
| 25 | 303 | int i = input.PeekBits(neededBits); | ||
| 25 | 304 | if (i < 0) { | ||
| 0 | 305 | return false; | ||
306 | } | |||
| 25 | 307 | input.DropBits(neededBits); | ||
| 25 | 308 | repLength += i; | ||
309 | } | |||
| 97 | 310 | mode = DECODE_HUFFMAN_DIST; | ||
311 | goto case DECODE_HUFFMAN_DIST; // fall through | |||
312 | | |||
313 | case DECODE_HUFFMAN_DIST: | |||
| 97 | 314 | symbol = distTree.GetSymbol(input); | ||
| 97 | 315 | if (symbol < 0) { | ||
| 0 | 316 | return false; | ||
317 | } | |||
318 | | |||
319 | try { | |||
| 97 | 320 | repDist = CPDIST[symbol]; | ||
| 97 | 321 | neededBits = CPDEXT[symbol]; | ||
| 97 | 322 | } catch (Exception) { | ||
| 0 | 323 | throw new SharpZipBaseException("Illegal rep dist code"); | ||
324 | } | |||
325 | | |||
326 | goto case DECODE_HUFFMAN_DISTBITS; // fall through | |||
327 | | |||
328 | case DECODE_HUFFMAN_DISTBITS: | |||
| 97 | 329 | if (neededBits > 0) { | ||
| 68 | 330 | mode = DECODE_HUFFMAN_DISTBITS; | ||
| 68 | 331 | int i = input.PeekBits(neededBits); | ||
| 68 | 332 | if (i < 0) { | ||
| 0 | 333 | return false; | ||
334 | } | |||
| 68 | 335 | input.DropBits(neededBits); | ||
| 68 | 336 | repDist += i; | ||
337 | } | |||
338 | | |||
| 97 | 339 | outputWindow.Repeat(repLength, repDist); | ||
| 97 | 340 | free -= repLength; | ||
| 97 | 341 | mode = DECODE_HUFFMAN; | ||
| 97 | 342 | break; | ||
343 | | |||
344 | default: | |||
| 0 | 345 | throw new SharpZipBaseException("Inflater unknown mode"); | ||
346 | } | |||
347 | } | |||
| 0 | 348 | return true; | ||
349 | } | |||
350 | | |||
351 | /// <summary> | |||
352 | /// Decodes the adler checksum after the deflate stream. | |||
353 | /// </summary> | |||
354 | /// <returns> | |||
355 | /// false if more input is needed. | |||
356 | /// </returns> | |||
357 | /// <exception cref="SharpZipBaseException"> | |||
358 | /// If checksum doesn't match. | |||
359 | /// </exception> | |||
360 | private bool DecodeChksum() | |||
361 | { | |||
| 5 | 362 | while (neededBits > 0) { | ||
| 4 | 363 | int chkByte = input.PeekBits(8); | ||
| 4 | 364 | if (chkByte < 0) { | ||
| 0 | 365 | return false; | ||
366 | } | |||
| 4 | 367 | input.DropBits(8); | ||
| 4 | 368 | readAdler = (readAdler << 8) | chkByte; | ||
| 4 | 369 | neededBits -= 8; | ||
370 | } | |||
371 | | |||
| 1 | 372 | if ((int)adler.Value != readAdler) { | ||
| 0 | 373 | throw new SharpZipBaseException("Adler chksum doesn't match: " + (int)adler.Value + " vs. " + readAdler); | ||
374 | } | |||
375 | | |||
| 1 | 376 | mode = FINISHED; | ||
| 1 | 377 | return false; | ||
378 | } | |||
379 | | |||
380 | /// <summary> | |||
381 | /// Decodes the deflated stream. | |||
382 | /// </summary> | |||
383 | /// <returns> | |||
384 | /// false if more input is needed, or if finished. | |||
385 | /// </returns> | |||
386 | /// <exception cref="SharpZipBaseException"> | |||
387 | /// if deflated stream is invalid. | |||
388 | /// </exception> | |||
389 | private bool Decode() | |||
390 | { | |||
| 4422 | 391 | switch (mode) { | ||
392 | case DECODE_HEADER: | |||
| 22 | 393 | return DecodeHeader(); | ||
394 | | |||
395 | case DECODE_DICT: | |||
| 0 | 396 | return DecodeDict(); | ||
397 | | |||
398 | case DECODE_CHKSUM: | |||
| 1 | 399 | return DecodeChksum(); | ||
400 | | |||
401 | case DECODE_BLOCKS: | |||
| 1412 | 402 | if (isLastBlock) { | ||
| 387 | 403 | if (noHeader) { | ||
| 376 | 404 | mode = FINISHED; | ||
| 376 | 405 | return false; | ||
406 | } else { | |||
| 11 | 407 | input.SkipToByteBoundary(); | ||
| 11 | 408 | neededBits = 32; | ||
| 11 | 409 | mode = DECODE_CHKSUM; | ||
| 11 | 410 | return true; | ||
411 | } | |||
412 | } | |||
413 | | |||
| 1025 | 414 | int type = input.PeekBits(3); | ||
| 1025 | 415 | if (type < 0) { | ||
| 362 | 416 | return false; | ||
417 | } | |||
| 663 | 418 | input.DropBits(3); | ||
419 | | |||
| 663 | 420 | isLastBlock |= (type & 1) != 0; | ||
| 663 | 421 | switch (type >> 1) { | ||
422 | case DeflaterConstants.STORED_BLOCK: | |||
| 292 | 423 | input.SkipToByteBoundary(); | ||
| 292 | 424 | mode = DECODE_STORED_LEN1; | ||
| 292 | 425 | break; | ||
426 | case DeflaterConstants.STATIC_TREES: | |||
| 370 | 427 | litlenTree = InflaterHuffmanTree.defLitLenTree; | ||
| 370 | 428 | distTree = InflaterHuffmanTree.defDistTree; | ||
| 370 | 429 | mode = DECODE_HUFFMAN; | ||
| 370 | 430 | break; | ||
431 | case DeflaterConstants.DYN_TREES: | |||
| 1 | 432 | dynHeader = new InflaterDynHeader(); | ||
| 1 | 433 | mode = DECODE_DYN_HEADER; | ||
| 1 | 434 | break; | ||
435 | default: | |||
| 0 | 436 | throw new SharpZipBaseException("Unknown block type " + type); | ||
437 | } | |||
| 663 | 438 | return true; | ||
439 | | |||
440 | case DECODE_STORED_LEN1: { | |||
| 292 | 441 | if ((uncomprLen = input.PeekBits(16)) < 0) { | ||
| 0 | 442 | return false; | ||
443 | } | |||
| 292 | 444 | input.DropBits(16); | ||
| 292 | 445 | mode = DECODE_STORED_LEN2; | ||
446 | } | |||
447 | goto case DECODE_STORED_LEN2; // fall through | |||
448 | | |||
449 | case DECODE_STORED_LEN2: { | |||
| 292 | 450 | int nlen = input.PeekBits(16); | ||
| 292 | 451 | if (nlen < 0) { | ||
| 0 | 452 | return false; | ||
453 | } | |||
| 292 | 454 | input.DropBits(16); | ||
| 292 | 455 | if (nlen != (uncomprLen ^ 0xffff)) { | ||
| 0 | 456 | throw new SharpZipBaseException("broken uncompressed block"); | ||
457 | } | |||
| 292 | 458 | mode = DECODE_STORED; | ||
459 | } | |||
460 | goto case DECODE_STORED; // fall through | |||
461 | | |||
462 | case DECODE_STORED: { | |||
| 2280 | 463 | int more = outputWindow.CopyStored(input, uncomprLen); | ||
| 2280 | 464 | uncomprLen -= more; | ||
| 2280 | 465 | if (uncomprLen == 0) { | ||
| 292 | 466 | mode = DECODE_BLOCKS; | ||
| 292 | 467 | return true; | ||
468 | } | |||
| 1988 | 469 | return !input.IsNeedingInput; | ||
470 | } | |||
471 | | |||
472 | case DECODE_DYN_HEADER: | |||
| 1 | 473 | if (!dynHeader.Decode(input)) { | ||
| 0 | 474 | return false; | ||
475 | } | |||
476 | | |||
| 1 | 477 | litlenTree = dynHeader.BuildLitLenTree(); | ||
| 1 | 478 | distTree = dynHeader.BuildDistTree(); | ||
| 1 | 479 | mode = DECODE_HUFFMAN; | ||
480 | goto case DECODE_HUFFMAN; // fall through | |||
481 | | |||
482 | case DECODE_HUFFMAN: | |||
483 | case DECODE_HUFFMAN_LENBITS: | |||
484 | case DECODE_HUFFMAN_DIST: | |||
485 | case DECODE_HUFFMAN_DISTBITS: | |||
| 371 | 486 | return DecodeHuffman(); | ||
487 | | |||
488 | case FINISHED: | |||
| 336 | 489 | return false; | ||
490 | | |||
491 | default: | |||
| 0 | 492 | throw new SharpZipBaseException("Inflater.Decode unknown mode"); | ||
493 | } | |||
494 | } | |||
495 | | |||
496 | /// <summary> | |||
497 | /// Sets the preset dictionary. This should only be called, if | |||
498 | /// needsDictionary() returns true and it should set the same | |||
499 | /// dictionary, that was used for deflating. The getAdler() | |||
500 | /// function returns the checksum of the dictionary needed. | |||
501 | /// </summary> | |||
502 | /// <param name="buffer"> | |||
503 | /// The dictionary. | |||
504 | /// </param> | |||
505 | public void SetDictionary(byte[] buffer) | |||
506 | { | |||
| 0 | 507 | SetDictionary(buffer, 0, buffer.Length); | ||
| 0 | 508 | } | ||
509 | | |||
510 | /// <summary> | |||
511 | /// Sets the preset dictionary. This should only be called, if | |||
512 | /// needsDictionary() returns true and it should set the same | |||
513 | /// dictionary, that was used for deflating. The getAdler() | |||
514 | /// function returns the checksum of the dictionary needed. | |||
515 | /// </summary> | |||
516 | /// <param name="buffer"> | |||
517 | /// The dictionary. | |||
518 | /// </param> | |||
519 | /// <param name="index"> | |||
520 | /// The index into buffer where the dictionary starts. | |||
521 | /// </param> | |||
522 | /// <param name="count"> | |||
523 | /// The number of bytes in the dictionary. | |||
524 | /// </param> | |||
525 | /// <exception cref="System.InvalidOperationException"> | |||
526 | /// No dictionary is needed. | |||
527 | /// </exception> | |||
528 | /// <exception cref="SharpZipBaseException"> | |||
529 | /// The adler checksum for the buffer is invalid | |||
530 | /// </exception> | |||
531 | public void SetDictionary(byte[] buffer, int index, int count) | |||
532 | { | |||
| 0 | 533 | if (buffer == null) { | ||
| 0 | 534 | throw new ArgumentNullException(nameof(buffer)); | ||
535 | } | |||
536 | | |||
| 0 | 537 | if (index < 0) { | ||
| 0 | 538 | throw new ArgumentOutOfRangeException(nameof(index)); | ||
539 | } | |||
540 | | |||
| 0 | 541 | if (count < 0) { | ||
| 0 | 542 | throw new ArgumentOutOfRangeException(nameof(count)); | ||
543 | } | |||
544 | | |||
| 0 | 545 | if (!IsNeedingDictionary) { | ||
| 0 | 546 | throw new InvalidOperationException("Dictionary is not needed"); | ||
547 | } | |||
548 | | |||
| 0 | 549 | adler.Update(buffer, index, count); | ||
550 | | |||
| 0 | 551 | if ((int)adler.Value != readAdler) { | ||
| 0 | 552 | throw new SharpZipBaseException("Wrong adler checksum"); | ||
553 | } | |||
| 0 | 554 | adler.Reset(); | ||
| 0 | 555 | outputWindow.CopyDict(buffer, index, count); | ||
| 0 | 556 | mode = DECODE_BLOCKS; | ||
| 0 | 557 | } | ||
558 | | |||
559 | /// <summary> | |||
560 | /// Sets the input. This should only be called, if needsInput() | |||
561 | /// returns true. | |||
562 | /// </summary> | |||
563 | /// <param name="buffer"> | |||
564 | /// the input. | |||
565 | /// </param> | |||
566 | public void SetInput(byte[] buffer) | |||
567 | { | |||
| 0 | 568 | SetInput(buffer, 0, buffer.Length); | ||
| 0 | 569 | } | ||
570 | | |||
571 | /// <summary> | |||
572 | /// Sets the input. This should only be called, if needsInput() | |||
573 | /// returns true. | |||
574 | /// </summary> | |||
575 | /// <param name="buffer"> | |||
576 | /// The source of input data | |||
577 | /// </param> | |||
578 | /// <param name="index"> | |||
579 | /// The index into buffer where the input starts. | |||
580 | /// </param> | |||
581 | /// <param name="count"> | |||
582 | /// The number of bytes of input to use. | |||
583 | /// </param> | |||
584 | /// <exception cref="System.InvalidOperationException"> | |||
585 | /// No input is needed. | |||
586 | /// </exception> | |||
587 | /// <exception cref="System.ArgumentOutOfRangeException"> | |||
588 | /// The index and/or count are wrong. | |||
589 | /// </exception> | |||
590 | public void SetInput(byte[] buffer, int index, int count) | |||
591 | { | |||
| 1433 | 592 | input.SetInput(buffer, index, count); | ||
| 1433 | 593 | totalIn += (long)count; | ||
| 1433 | 594 | } | ||
595 | | |||
596 | /// <summary> | |||
597 | /// Inflates the compressed stream to the output buffer. If this | |||
598 | /// returns 0, you should check, whether IsNeedingDictionary(), | |||
599 | /// IsNeedingInput() or IsFinished() returns true, to determine why no | |||
600 | /// further output is produced. | |||
601 | /// </summary> | |||
602 | /// <param name="buffer"> | |||
603 | /// the output buffer. | |||
604 | /// </param> | |||
605 | /// <returns> | |||
606 | /// The number of bytes written to the buffer, 0 if no further | |||
607 | /// output can be produced. | |||
608 | /// </returns> | |||
609 | /// <exception cref="System.ArgumentOutOfRangeException"> | |||
610 | /// if buffer has length 0. | |||
611 | /// </exception> | |||
612 | /// <exception cref="System.FormatException"> | |||
613 | /// if deflated stream is invalid. | |||
614 | /// </exception> | |||
615 | public int Inflate(byte[] buffer) | |||
616 | { | |||
| 0 | 617 | if (buffer == null) { | ||
| 0 | 618 | throw new ArgumentNullException(nameof(buffer)); | ||
619 | } | |||
620 | | |||
| 0 | 621 | return Inflate(buffer, 0, buffer.Length); | ||
622 | } | |||
623 | | |||
624 | /// <summary> | |||
625 | /// Inflates the compressed stream to the output buffer. If this | |||
626 | /// returns 0, you should check, whether needsDictionary(), | |||
627 | /// needsInput() or finished() returns true, to determine why no | |||
628 | /// further output is produced. | |||
629 | /// </summary> | |||
630 | /// <param name="buffer"> | |||
631 | /// the output buffer. | |||
632 | /// </param> | |||
633 | /// <param name="offset"> | |||
634 | /// the offset in buffer where storing starts. | |||
635 | /// </param> | |||
636 | /// <param name="count"> | |||
637 | /// the maximum number of bytes to output. | |||
638 | /// </param> | |||
639 | /// <returns> | |||
640 | /// the number of bytes written to the buffer, 0 if no further output can be produced. | |||
641 | /// </returns> | |||
642 | /// <exception cref="System.ArgumentOutOfRangeException"> | |||
643 | /// if count is less than 0. | |||
644 | /// </exception> | |||
645 | /// <exception cref="System.ArgumentOutOfRangeException"> | |||
646 | /// if the index and / or count are wrong. | |||
647 | /// </exception> | |||
648 | /// <exception cref="System.FormatException"> | |||
649 | /// if deflated stream is invalid. | |||
650 | /// </exception> | |||
651 | public int Inflate(byte[] buffer, int offset, int count) | |||
652 | { | |||
| 2212 | 653 | if (buffer == null) { | ||
| 0 | 654 | throw new ArgumentNullException(nameof(buffer)); | ||
655 | } | |||
656 | | |||
| 2212 | 657 | if (count < 0) { | ||
| 0 | 658 | throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative"); | ||
659 | } | |||
660 | | |||
| 2212 | 661 | if (offset < 0) { | ||
| 0 | 662 | throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be negative"); | ||
663 | } | |||
664 | | |||
| 2212 | 665 | if (offset + count > buffer.Length) { | ||
| 0 | 666 | throw new ArgumentException("count exceeds buffer bounds"); | ||
667 | } | |||
668 | | |||
669 | // Special case: count may be zero | |||
| 2212 | 670 | if (count == 0) { | ||
| 20 | 671 | if (!IsFinished) { // -jr- 08-Nov-2003 INFLATE_BUG fix.. | ||
| 20 | 672 | Decode(); | ||
673 | } | |||
| 20 | 674 | return 0; | ||
675 | } | |||
676 | | |||
| 2192 | 677 | int bytesCopied = 0; | ||
678 | | |||
679 | do { | |||
| 4524 | 680 | if (mode != DECODE_CHKSUM) { | ||
681 | /* Don't give away any output, if we are waiting for the | |||
682 | * checksum in the input stream. | |||
683 | * | |||
684 | * With this trick we have always: | |||
685 | * IsNeedingInput() and not IsFinished() | |||
686 | * implies more output can be produced. | |||
687 | */ | |||
| 4523 | 688 | int more = outputWindow.CopyOutput(buffer, offset, count); | ||
| 4523 | 689 | if (more > 0) { | ||
| 1679 | 690 | adler.Update(buffer, offset, more); | ||
| 1679 | 691 | offset += more; | ||
| 1679 | 692 | bytesCopied += more; | ||
| 1679 | 693 | totalOut += (long)more; | ||
| 1679 | 694 | count -= more; | ||
| 1679 | 695 | if (count == 0) { | ||
| 122 | 696 | return bytesCopied; | ||
697 | } | |||
698 | } | |||
699 | } | |||
| 4402 | 700 | } while (Decode() || ((outputWindow.GetAvailable() > 0) && (mode != DECODE_CHKSUM))); | ||
| 2070 | 701 | return bytesCopied; | ||
702 | } | |||
703 | | |||
704 | /// <summary> | |||
705 | /// Returns true, if the input buffer is empty. | |||
706 | /// You should then call setInput(). | |||
707 | /// NOTE: This method also returns true when the stream is finished. | |||
708 | /// </summary> | |||
709 | public bool IsNeedingInput { | |||
710 | get { | |||
| 1367 | 711 | return input.IsNeedingInput; | ||
712 | } | |||
713 | } | |||
714 | | |||
715 | /// <summary> | |||
716 | /// Returns true, if a preset dictionary is needed to inflate the input. | |||
717 | /// </summary> | |||
718 | public bool IsNeedingDictionary { | |||
719 | get { | |||
| 845 | 720 | return mode == DECODE_DICT && neededBits == 0; | ||
721 | } | |||
722 | } | |||
723 | | |||
724 | /// <summary> | |||
725 | /// Returns true, if the inflater has finished. This means, that no | |||
726 | /// input is needed and no output can be produced. | |||
727 | /// </summary> | |||
728 | public bool IsFinished { | |||
729 | get { | |||
| 2106 | 730 | return mode == FINISHED && outputWindow.GetAvailable() == 0; | ||
731 | } | |||
732 | } | |||
733 | | |||
734 | /// <summary> | |||
735 | /// Gets the adler checksum. This is either the checksum of all | |||
736 | /// uncompressed bytes returned by inflate(), or if needsDictionary() | |||
737 | /// returns true (and thus no output was yet produced) this is the | |||
738 | /// adler checksum of the expected dictionary. | |||
739 | /// </summary> | |||
740 | /// <returns> | |||
741 | /// the adler checksum. | |||
742 | /// </returns> | |||
743 | public int Adler { | |||
744 | get { | |||
| 0 | 745 | return IsNeedingDictionary ? readAdler : (int)adler.Value; | ||
746 | } | |||
747 | } | |||
748 | | |||
749 | /// <summary> | |||
750 | /// Gets the total number of output bytes returned by Inflate(). | |||
751 | /// </summary> | |||
752 | /// <returns> | |||
753 | /// the total number of output bytes. | |||
754 | /// </returns> | |||
755 | public long TotalOut { | |||
756 | get { | |||
| 13 | 757 | return totalOut; | ||
758 | } | |||
759 | } | |||
760 | | |||
761 | /// <summary> | |||
762 | /// Gets the total number of processed compressed input bytes. | |||
763 | /// </summary> | |||
764 | /// <returns> | |||
765 | /// The total number of bytes of processed input bytes. | |||
766 | /// </returns> | |||
767 | public long TotalIn { | |||
768 | get { | |||
| 22 | 769 | return totalIn - (long)RemainingInput; | ||
770 | } | |||
771 | } | |||
772 | | |||
773 | /// <summary> | |||
774 | /// Gets the number of unprocessed input bytes. Useful, if the end of the | |||
775 | /// stream is reached and you want to further process the bytes after | |||
776 | /// the deflate stream. | |||
777 | /// </summary> | |||
778 | /// <returns> | |||
779 | /// The number of bytes of the input which have not been processed. | |||
780 | /// </returns> | |||
781 | public int RemainingInput { | |||
782 | // TODO: This should be a long? | |||
783 | get { | |||
| 47 | 784 | return input.AvailableBytes; | ||
785 | } | |||
786 | } | |||
787 | } | |||
788 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.InflaterDynHeader |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\InflaterDynHeader.cs |
| Covered lines: | 63 |
| Uncovered lines: | 10 |
| Coverable lines: | 73 |
| Total lines: | 170 |
| Line coverage: | 86.3% |
| Branch coverage: | 54.2% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| Decode(...) | 21 | 84.13 | 55.56 |
| BuildLitLenTree() | 1 | 100 | 100 |
| BuildDistTree() | 1 | 100 | 100 |
| .cctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
5 | { | |||
6 | class InflaterDynHeader | |||
7 | { | |||
8 | #region Constants | |||
9 | const int LNUM = 0; | |||
10 | const int DNUM = 1; | |||
11 | const int BLNUM = 2; | |||
12 | const int BLLENS = 3; | |||
13 | const int LENS = 4; | |||
14 | const int REPS = 5; | |||
15 | | |||
| 1 | 16 | static readonly int[] repMin = { 3, 3, 11 }; | ||
| 1 | 17 | static readonly int[] repBits = { 2, 3, 7 }; | ||
18 | | |||
| 1 | 19 | static readonly int[] BL_ORDER = | ||
| 1 | 20 | { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; | ||
21 | #endregion | |||
22 | | |||
23 | public bool Decode(StreamManipulator input) | |||
24 | { | |||
25 | decode_loop: | |||
26 | for (;;) { | |||
| 10 | 27 | switch (mode) { | ||
28 | case LNUM: | |||
| 1 | 29 | lnum = input.PeekBits(5); | ||
| 1 | 30 | if (lnum < 0) { | ||
| 0 | 31 | return false; | ||
32 | } | |||
| 1 | 33 | lnum += 257; | ||
| 1 | 34 | input.DropBits(5); | ||
35 | // System.err.println("LNUM: "+lnum); | |||
| 1 | 36 | mode = DNUM; | ||
37 | goto case DNUM; // fall through | |||
38 | case DNUM: | |||
| 1 | 39 | dnum = input.PeekBits(5); | ||
| 1 | 40 | if (dnum < 0) { | ||
| 0 | 41 | return false; | ||
42 | } | |||
| 1 | 43 | dnum++; | ||
| 1 | 44 | input.DropBits(5); | ||
45 | // System.err.println("DNUM: "+dnum); | |||
| 1 | 46 | num = lnum + dnum; | ||
| 1 | 47 | litdistLens = new byte[num]; | ||
| 1 | 48 | mode = BLNUM; | ||
49 | goto case BLNUM; // fall through | |||
50 | case BLNUM: | |||
| 1 | 51 | blnum = input.PeekBits(4); | ||
| 1 | 52 | if (blnum < 0) { | ||
| 0 | 53 | return false; | ||
54 | } | |||
| 1 | 55 | blnum += 4; | ||
| 1 | 56 | input.DropBits(4); | ||
| 1 | 57 | blLens = new byte[19]; | ||
| 1 | 58 | ptr = 0; | ||
59 | // System.err.println("BLNUM: "+blnum); | |||
| 1 | 60 | mode = BLLENS; | ||
| 1 | 61 | goto case BLLENS; // fall through | ||
62 | case BLLENS: | |||
| 19 | 63 | while (ptr < blnum) { | ||
| 18 | 64 | int len = input.PeekBits(3); | ||
| 18 | 65 | if (len < 0) { | ||
| 0 | 66 | return false; | ||
67 | } | |||
| 18 | 68 | input.DropBits(3); | ||
69 | // System.err.println("blLens["+BL_ORDER[ptr]+"]: "+len); | |||
| 18 | 70 | blLens[BL_ORDER[ptr]] = (byte)len; | ||
| 18 | 71 | ptr++; | ||
72 | } | |||
| 1 | 73 | blTree = new InflaterHuffmanTree(blLens); | ||
| 1 | 74 | blLens = null; | ||
| 1 | 75 | ptr = 0; | ||
| 1 | 76 | mode = LENS; | ||
| 1 | 77 | goto case LENS; // fall through | ||
78 | case LENS: { | |||
79 | int symbol; | |||
| 45 | 80 | while (((symbol = blTree.GetSymbol(input)) & ~15) == 0) { | ||
81 | /* Normal case: symbol in [0..15] */ | |||
82 | | |||
83 | // System.err.println("litdistLens["+ptr+"]: "+symbol); | |||
| 36 | 84 | litdistLens[ptr++] = lastLen = (byte)symbol; | ||
85 | | |||
| 36 | 86 | if (ptr == num) { | ||
87 | /* Finished */ | |||
| 1 | 88 | return true; | ||
89 | } | |||
90 | } | |||
91 | | |||
92 | /* need more input ? */ | |||
| 9 | 93 | if (symbol < 0) { | ||
| 0 | 94 | return false; | ||
95 | } | |||
96 | | |||
97 | /* otherwise repeat code */ | |||
| 9 | 98 | if (symbol >= 17) { | ||
99 | /* repeat zero */ | |||
100 | // System.err.println("repeating zero"); | |||
| 9 | 101 | lastLen = 0; | ||
| 9 | 102 | } else { | ||
| 0 | 103 | if (ptr == 0) { | ||
| 0 | 104 | throw new SharpZipBaseException(); | ||
105 | } | |||
106 | } | |||
| 9 | 107 | repSymbol = symbol - 16; | ||
108 | } | |||
| 9 | 109 | mode = REPS; | ||
110 | goto case REPS; // fall through | |||
111 | case REPS: { | |||
| 9 | 112 | int bits = repBits[repSymbol]; | ||
| 9 | 113 | int count = input.PeekBits(bits); | ||
| 9 | 114 | if (count < 0) { | ||
| 0 | 115 | return false; | ||
116 | } | |||
| 9 | 117 | input.DropBits(bits); | ||
| 9 | 118 | count += repMin[repSymbol]; | ||
119 | // System.err.println("litdistLens repeated: "+count); | |||
120 | | |||
| 9 | 121 | if (ptr + count > num) { | ||
| 0 | 122 | throw new SharpZipBaseException(); | ||
123 | } | |||
| 239 | 124 | while (count-- > 0) { | ||
| 230 | 125 | litdistLens[ptr++] = lastLen; | ||
126 | } | |||
127 | | |||
| 9 | 128 | if (ptr == num) { | ||
129 | /* Finished */ | |||
| 0 | 130 | return true; | ||
131 | } | |||
132 | } | |||
| 9 | 133 | mode = LENS; | ||
| 9 | 134 | goto decode_loop; | ||
135 | } | |||
136 | } | |||
137 | } | |||
138 | | |||
139 | public InflaterHuffmanTree BuildLitLenTree() | |||
140 | { | |||
| 1 | 141 | byte[] litlenLens = new byte[lnum]; | ||
| 1 | 142 | Array.Copy(litdistLens, 0, litlenLens, 0, lnum); | ||
| 1 | 143 | return new InflaterHuffmanTree(litlenLens); | ||
144 | } | |||
145 | | |||
146 | public InflaterHuffmanTree BuildDistTree() | |||
147 | { | |||
| 1 | 148 | byte[] distLens = new byte[dnum]; | ||
| 1 | 149 | Array.Copy(litdistLens, lnum, distLens, 0, dnum); | ||
| 1 | 150 | return new InflaterHuffmanTree(distLens); | ||
151 | } | |||
152 | | |||
153 | #region Instance Fields | |||
154 | byte[] blLens; | |||
155 | byte[] litdistLens; | |||
156 | | |||
157 | InflaterHuffmanTree blTree; | |||
158 | | |||
159 | /// <summary> | |||
160 | /// The current decode mode | |||
161 | /// </summary> | |||
162 | int mode; | |||
163 | int lnum, dnum, blnum, num; | |||
164 | int repSymbol; | |||
165 | byte lastLen; | |||
166 | int ptr; | |||
167 | #endregion | |||
168 | | |||
169 | } | |||
170 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.InflaterHuffmanTree |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\InflaterHuffmanTree.cs |
| Covered lines: | 65 |
| Uncovered lines: | 23 |
| Coverable lines: | 88 |
| Total lines: | 193 |
| Line coverage: | 73.8% |
| Branch coverage: | 75% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .cctor() | 6 | 90 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| BuildTree(...) | 12 | 82.69 | 82.61 |
| GetSymbol(...) | 7 | 41.67 | 46.15 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// Huffman tree used for inflation | |||
8 | /// </summary> | |||
9 | public class InflaterHuffmanTree | |||
10 | { | |||
11 | #region Constants | |||
12 | const int MAX_BITLEN = 15; | |||
13 | #endregion | |||
14 | | |||
15 | #region Instance Fields | |||
16 | short[] tree; | |||
17 | #endregion | |||
18 | | |||
19 | /// <summary> | |||
20 | /// Literal length tree | |||
21 | /// </summary> | |||
22 | public static InflaterHuffmanTree defLitLenTree; | |||
23 | | |||
24 | /// <summary> | |||
25 | /// Distance tree | |||
26 | /// </summary> | |||
27 | public static InflaterHuffmanTree defDistTree; | |||
28 | | |||
29 | static InflaterHuffmanTree() | |||
30 | { | |||
31 | try { | |||
| 1 | 32 | byte[] codeLengths = new byte[288]; | ||
| 1 | 33 | int i = 0; | ||
| 145 | 34 | while (i < 144) { | ||
| 144 | 35 | codeLengths[i++] = 8; | ||
36 | } | |||
| 113 | 37 | while (i < 256) { | ||
| 112 | 38 | codeLengths[i++] = 9; | ||
39 | } | |||
| 25 | 40 | while (i < 280) { | ||
| 24 | 41 | codeLengths[i++] = 7; | ||
42 | } | |||
| 9 | 43 | while (i < 288) { | ||
| 8 | 44 | codeLengths[i++] = 8; | ||
45 | } | |||
| 1 | 46 | defLitLenTree = new InflaterHuffmanTree(codeLengths); | ||
47 | | |||
| 1 | 48 | codeLengths = new byte[32]; | ||
| 1 | 49 | i = 0; | ||
| 33 | 50 | while (i < 32) { | ||
| 32 | 51 | codeLengths[i++] = 5; | ||
52 | } | |||
| 1 | 53 | defDistTree = new InflaterHuffmanTree(codeLengths); | ||
| 1 | 54 | } catch (Exception) { | ||
| 0 | 55 | throw new SharpZipBaseException("InflaterHuffmanTree: static tree length illegal"); | ||
56 | } | |||
| 1 | 57 | } | ||
58 | | |||
59 | #region Constructors | |||
60 | /// <summary> | |||
61 | /// Constructs a Huffman tree from the array of code lengths. | |||
62 | /// </summary> | |||
63 | /// <param name = "codeLengths"> | |||
64 | /// the array of code lengths | |||
65 | /// </param> | |||
| 5 | 66 | public InflaterHuffmanTree(byte[] codeLengths) | ||
67 | { | |||
| 5 | 68 | BuildTree(codeLengths); | ||
| 5 | 69 | } | ||
70 | #endregion | |||
71 | | |||
72 | void BuildTree(byte[] codeLengths) | |||
73 | { | |||
| 5 | 74 | int[] blCount = new int[MAX_BITLEN + 1]; | ||
| 5 | 75 | int[] nextCode = new int[MAX_BITLEN + 1]; | ||
76 | | |||
| 1220 | 77 | for (int i = 0; i < codeLengths.Length; i++) { | ||
| 605 | 78 | int bits = codeLengths[i]; | ||
| 605 | 79 | if (bits > 0) { | ||
| 358 | 80 | blCount[bits]++; | ||
81 | } | |||
82 | } | |||
83 | | |||
| 5 | 84 | int code = 0; | ||
| 5 | 85 | int treeSize = 512; | ||
| 160 | 86 | for (int bits = 1; bits <= MAX_BITLEN; bits++) { | ||
| 75 | 87 | nextCode[bits] = code; | ||
| 75 | 88 | code += blCount[bits] << (16 - bits); | ||
| 75 | 89 | if (bits >= 10) { | ||
90 | /* We need an extra table for bit lengths >= 10. */ | |||
| 30 | 91 | int start = nextCode[bits] & 0x1ff80; | ||
| 30 | 92 | int end = code & 0x1ff80; | ||
| 30 | 93 | treeSize += (end - start) >> (16 - bits); | ||
94 | } | |||
95 | } | |||
96 | | |||
97 | /* -jr comment this out! doesnt work for dynamic trees and pkzip 2.04g | |||
98 | if (code != 65536) | |||
99 | { | |||
100 | throw new SharpZipBaseException("Code lengths don't add up properly."); | |||
101 | } | |||
102 | */ | |||
103 | /* Now create and fill the extra tables from longest to shortest | |||
104 | * bit len. This way the sub trees will be aligned. | |||
105 | */ | |||
| 5 | 106 | tree = new short[treeSize]; | ||
| 5 | 107 | int treePtr = 512; | ||
| 70 | 108 | for (int bits = MAX_BITLEN; bits >= 10; bits--) { | ||
| 30 | 109 | int end = code & 0x1ff80; | ||
| 30 | 110 | code -= blCount[bits] << (16 - bits); | ||
| 30 | 111 | int start = code & 0x1ff80; | ||
| 60 | 112 | for (int i = start; i < end; i += 1 << 7) { | ||
| 0 | 113 | tree[DeflaterHuffman.BitReverse(i)] = (short)((-treePtr << 4) | bits); | ||
| 0 | 114 | treePtr += 1 << (bits - 9); | ||
115 | } | |||
116 | } | |||
117 | | |||
| 1220 | 118 | for (int i = 0; i < codeLengths.Length; i++) { | ||
| 605 | 119 | int bits = codeLengths[i]; | ||
| 605 | 120 | if (bits == 0) { | ||
121 | continue; | |||
122 | } | |||
| 358 | 123 | code = nextCode[bits]; | ||
| 358 | 124 | int revcode = DeflaterHuffman.BitReverse(code); | ||
| 358 | 125 | if (bits <= 9) { | ||
126 | do { | |||
| 2560 | 127 | tree[revcode] = (short)((i << 4) | bits); | ||
| 2560 | 128 | revcode += 1 << bits; | ||
| 2560 | 129 | } while (revcode < 512); | ||
| 358 | 130 | } else { | ||
| 0 | 131 | int subTree = tree[revcode & 511]; | ||
| 0 | 132 | int treeLen = 1 << (subTree & 15); | ||
| 0 | 133 | subTree = -(subTree >> 4); | ||
134 | do { | |||
| 0 | 135 | tree[subTree | (revcode >> 9)] = (short)((i << 4) | bits); | ||
| 0 | 136 | revcode += 1 << bits; | ||
| 0 | 137 | } while (revcode < treeLen); | ||
138 | } | |||
| 358 | 139 | nextCode[bits] = code + (1 << (16 - bits)); | ||
140 | } | |||
141 | | |||
| 5 | 142 | } | ||
143 | | |||
144 | /// <summary> | |||
145 | /// Reads the next symbol from input. The symbol is encoded using the | |||
146 | /// huffman tree. | |||
147 | /// </summary> | |||
148 | /// <param name="input"> | |||
149 | /// input the input source. | |||
150 | /// </param> | |||
151 | /// <returns> | |||
152 | /// the next symbol, or -1 if not enough input is available. | |||
153 | /// </returns> | |||
154 | public int GetSymbol(StreamManipulator input) | |||
155 | { | |||
156 | int lookahead, symbol; | |||
| 9839 | 157 | if ((lookahead = input.PeekBits(9)) >= 0) { | ||
| 9816 | 158 | if ((symbol = tree[lookahead]) >= 0) { | ||
| 9816 | 159 | input.DropBits(symbol & 15); | ||
| 9816 | 160 | return symbol >> 4; | ||
161 | } | |||
| 0 | 162 | int subtree = -(symbol >> 4); | ||
| 0 | 163 | int bitlen = symbol & 15; | ||
| 0 | 164 | if ((lookahead = input.PeekBits(bitlen)) >= 0) { | ||
| 0 | 165 | symbol = tree[subtree | (lookahead >> 9)]; | ||
| 0 | 166 | input.DropBits(symbol & 15); | ||
| 0 | 167 | return symbol >> 4; | ||
168 | } else { | |||
| 0 | 169 | int bits = input.AvailableBits; | ||
| 0 | 170 | lookahead = input.PeekBits(bits); | ||
| 0 | 171 | symbol = tree[subtree | (lookahead >> 9)]; | ||
| 0 | 172 | if ((symbol & 15) <= bits) { | ||
| 0 | 173 | input.DropBits(symbol & 15); | ||
| 0 | 174 | return symbol >> 4; | ||
175 | } else { | |||
| 0 | 176 | return -1; | ||
177 | } | |||
178 | } | |||
179 | } else { | |||
| 23 | 180 | int bits = input.AvailableBits; | ||
| 23 | 181 | lookahead = input.PeekBits(bits); | ||
| 23 | 182 | symbol = tree[lookahead]; | ||
| 23 | 183 | if (symbol >= 0 && (symbol & 15) <= bits) { | ||
| 23 | 184 | input.DropBits(symbol & 15); | ||
| 23 | 185 | return symbol >> 4; | ||
186 | } else { | |||
| 0 | 187 | return -1; | ||
188 | } | |||
189 | } | |||
190 | } | |||
191 | } | |||
192 | } | |||
193 | |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputBuffer |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\Streams\InflaterInputStream.cs |
| Covered lines: | 72 |
| Uncovered lines: | 14 |
| Coverable lines: | 86 |
| Total lines: | 662 |
| Line coverage: | 83.7% |
| Branch coverage: | 71% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 2 | 85.71 | 66.67 |
| SetInflaterInput(...) | 2 | 100 | 66.67 |
| Fill() | 4 | 100 | 100 |
| ReadRawBuffer(...) | 1 | 100 | 100 |
| ReadRawBuffer(...) | 5 | 73.33 | 55.56 |
| ReadClearTextBuffer(...) | 5 | 86.67 | 77.78 |
| ReadLeByte() | 3 | 85.71 | 80 |
| ReadLeShort() | 1 | 100 | 100 |
| ReadLeInt() | 1 | 100 | 100 |
| ReadLeLong() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Security.Cryptography; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// An input buffer customised for use by <see cref="InflaterInputStream"/> | |||
9 | /// </summary> | |||
10 | /// <remarks> | |||
11 | /// The buffer supports decryption of incoming data. | |||
12 | /// </remarks> | |||
13 | public class InflaterInputBuffer | |||
14 | { | |||
15 | #region Constructors | |||
16 | /// <summary> | |||
17 | /// Initialise a new instance of <see cref="InflaterInputBuffer"/> with a default buffer size | |||
18 | /// </summary> | |||
19 | /// <param name="stream">The stream to buffer.</param> | |||
| 0 | 20 | public InflaterInputBuffer(Stream stream) : this(stream, 4096) | ||
21 | { | |||
| 0 | 22 | } | ||
23 | | |||
24 | /// <summary> | |||
25 | /// Initialise a new instance of <see cref="InflaterInputBuffer"/> | |||
26 | /// </summary> | |||
27 | /// <param name="stream">The stream to buffer.</param> | |||
28 | /// <param name="bufferSize">The size to use for the buffer</param> | |||
29 | /// <remarks>A minimum buffer size of 1KB is permitted. Lower sizes are treated as 1KB.</remarks> | |||
| 435 | 30 | public InflaterInputBuffer(Stream stream, int bufferSize) | ||
31 | { | |||
| 435 | 32 | inputStream = stream; | ||
| 435 | 33 | if (bufferSize < 1024) { | ||
| 0 | 34 | bufferSize = 1024; | ||
35 | } | |||
| 435 | 36 | rawData = new byte[bufferSize]; | ||
| 435 | 37 | clearText = rawData; | ||
| 435 | 38 | } | ||
39 | #endregion | |||
40 | | |||
41 | /// <summary> | |||
42 | /// Get the length of bytes bytes in the <see cref="RawData"/> | |||
43 | /// </summary> | |||
44 | public int RawLength { | |||
45 | get { | |||
| 0 | 46 | return rawLength; | ||
47 | } | |||
48 | } | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Get the contents of the raw data buffer. | |||
52 | /// </summary> | |||
53 | /// <remarks>This may contain encrypted data.</remarks> | |||
54 | public byte[] RawData { | |||
55 | get { | |||
| 0 | 56 | return rawData; | ||
57 | } | |||
58 | } | |||
59 | | |||
60 | /// <summary> | |||
61 | /// Get the number of useable bytes in <see cref="ClearText"/> | |||
62 | /// </summary> | |||
63 | public int ClearTextLength { | |||
64 | get { | |||
| 0 | 65 | return clearTextLength; | ||
66 | } | |||
67 | } | |||
68 | | |||
69 | /// <summary> | |||
70 | /// Get the contents of the clear text buffer. | |||
71 | /// </summary> | |||
72 | public byte[] ClearText { | |||
73 | get { | |||
| 0 | 74 | return clearText; | ||
75 | } | |||
76 | } | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Get/set the number of bytes available | |||
80 | /// </summary> | |||
81 | public int Available { | |||
| 2837 | 82 | get { return available; } | ||
| 78 | 83 | set { available = value; } | ||
84 | } | |||
85 | | |||
86 | /// <summary> | |||
87 | /// Call <see cref="Inflater.SetInput(byte[], int, int)"/> passing the current clear text buffer contents. | |||
88 | /// </summary> | |||
89 | /// <param name="inflater">The inflater to set input for.</param> | |||
90 | public void SetInflaterInput(Inflater inflater) | |||
91 | { | |||
| 1433 | 92 | if (available > 0) { | ||
| 1433 | 93 | inflater.SetInput(clearText, clearTextLength - available, available); | ||
| 1433 | 94 | available = 0; | ||
95 | } | |||
| 1433 | 96 | } | ||
97 | | |||
98 | /// <summary> | |||
99 | /// Fill the buffer from the underlying input stream. | |||
100 | /// </summary> | |||
101 | public void Fill() | |||
102 | { | |||
| 1448 | 103 | rawLength = 0; | ||
| 1448 | 104 | int toRead = rawData.Length; | ||
105 | | |||
| 2896 | 106 | while (toRead > 0) { | ||
| 1877 | 107 | int count = inputStream.Read(rawData, rawLength, toRead); | ||
| 1877 | 108 | if (count <= 0) { | ||
109 | break; | |||
110 | } | |||
| 1448 | 111 | rawLength += count; | ||
| 1448 | 112 | toRead -= count; | ||
113 | } | |||
114 | | |||
| 1448 | 115 | if (cryptoTransform != null) { | ||
| 264 | 116 | clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0); | ||
| 264 | 117 | } else { | ||
| 1184 | 118 | clearTextLength = rawLength; | ||
119 | } | |||
120 | | |||
| 1448 | 121 | available = clearTextLength; | ||
| 1448 | 122 | } | ||
123 | | |||
124 | /// <summary> | |||
125 | /// Read a buffer directly from the input stream | |||
126 | /// </summary> | |||
127 | /// <param name="buffer">The buffer to fill</param> | |||
128 | /// <returns>Returns the number of bytes read.</returns> | |||
129 | public int ReadRawBuffer(byte[] buffer) | |||
130 | { | |||
| 159 | 131 | return ReadRawBuffer(buffer, 0, buffer.Length); | ||
132 | } | |||
133 | | |||
134 | /// <summary> | |||
135 | /// Read a buffer directly from the input stream | |||
136 | /// </summary> | |||
137 | /// <param name="outBuffer">The buffer to read into</param> | |||
138 | /// <param name="offset">The offset to start reading data into.</param> | |||
139 | /// <param name="length">The number of bytes to read.</param> | |||
140 | /// <returns>Returns the number of bytes read.</returns> | |||
141 | public int ReadRawBuffer(byte[] outBuffer, int offset, int length) | |||
142 | { | |||
| 159 | 143 | if (length < 0) { | ||
| 0 | 144 | throw new ArgumentOutOfRangeException(nameof(length)); | ||
145 | } | |||
146 | | |||
| 159 | 147 | int currentOffset = offset; | ||
| 159 | 148 | int currentLength = length; | ||
149 | | |||
| 318 | 150 | while (currentLength > 0) { | ||
| 159 | 151 | if (available <= 0) { | ||
| 0 | 152 | Fill(); | ||
| 0 | 153 | if (available <= 0) { | ||
| 0 | 154 | return 0; | ||
155 | } | |||
156 | } | |||
| 159 | 157 | int toCopy = Math.Min(currentLength, available); | ||
| 159 | 158 | System.Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy); | ||
| 159 | 159 | currentOffset += toCopy; | ||
| 159 | 160 | currentLength -= toCopy; | ||
| 159 | 161 | available -= toCopy; | ||
162 | } | |||
| 159 | 163 | return length; | ||
164 | } | |||
165 | | |||
166 | /// <summary> | |||
167 | /// Read clear text data from the input stream. | |||
168 | /// </summary> | |||
169 | /// <param name="outBuffer">The buffer to add data to.</param> | |||
170 | /// <param name="offset">The offset to start adding data at.</param> | |||
171 | /// <param name="length">The number of bytes to read.</param> | |||
172 | /// <returns>Returns the number of bytes actually read.</returns> | |||
173 | public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length) | |||
174 | { | |||
| 27 | 175 | if (length < 0) { | ||
| 0 | 176 | throw new ArgumentOutOfRangeException(nameof(length)); | ||
177 | } | |||
178 | | |||
| 27 | 179 | int currentOffset = offset; | ||
| 27 | 180 | int currentLength = length; | ||
181 | | |||
| 78 | 182 | while (currentLength > 0) { | ||
| 51 | 183 | if (available <= 0) { | ||
| 24 | 184 | Fill(); | ||
| 24 | 185 | if (available <= 0) { | ||
| 0 | 186 | return 0; | ||
187 | } | |||
188 | } | |||
189 | | |||
| 51 | 190 | int toCopy = Math.Min(currentLength, available); | ||
| 51 | 191 | Array.Copy(clearText, clearTextLength - (int)available, outBuffer, currentOffset, toCopy); | ||
| 51 | 192 | currentOffset += toCopy; | ||
| 51 | 193 | currentLength -= toCopy; | ||
| 51 | 194 | available -= toCopy; | ||
195 | } | |||
| 27 | 196 | return length; | ||
197 | } | |||
198 | | |||
199 | /// <summary> | |||
200 | /// Read a <see cref="byte"/> from the input stream. | |||
201 | /// </summary> | |||
202 | /// <returns>Returns the byte read.</returns> | |||
203 | public int ReadLeByte() | |||
204 | { | |||
| 2574 | 205 | if (available <= 0) { | ||
| 57 | 206 | Fill(); | ||
| 57 | 207 | if (available <= 0) { | ||
| 0 | 208 | throw new ZipException("EOF in header"); | ||
209 | } | |||
210 | } | |||
| 2574 | 211 | byte result = rawData[rawLength - available]; | ||
| 2574 | 212 | available -= 1; | ||
| 2574 | 213 | return result; | ||
214 | } | |||
215 | | |||
216 | /// <summary> | |||
217 | /// Read an <see cref="short"/> in little endian byte order. | |||
218 | /// </summary> | |||
219 | /// <returns>The short value read case to an int.</returns> | |||
220 | public int ReadLeShort() | |||
221 | { | |||
| 1287 | 222 | return ReadLeByte() | (ReadLeByte() << 8); | ||
223 | } | |||
224 | | |||
225 | /// <summary> | |||
226 | /// Read an <see cref="int"/> in little endian byte order. | |||
227 | /// </summary> | |||
228 | /// <returns>The int value read.</returns> | |||
229 | public int ReadLeInt() | |||
230 | { | |||
| 441 | 231 | return ReadLeShort() | (ReadLeShort() << 16); | ||
232 | } | |||
233 | | |||
234 | /// <summary> | |||
235 | /// Read a <see cref="long"/> in little endian byte order. | |||
236 | /// </summary> | |||
237 | /// <returns>The long value read.</returns> | |||
238 | public long ReadLeLong() | |||
239 | { | |||
| 10 | 240 | return (uint)ReadLeInt() | ((long)ReadLeInt() << 32); | ||
241 | } | |||
242 | | |||
243 | /// <summary> | |||
244 | /// Get/set the <see cref="ICryptoTransform"/> to apply to any data. | |||
245 | /// </summary> | |||
246 | /// <remarks>Set this value to null to have no transform applied.</remarks> | |||
247 | public ICryptoTransform CryptoTransform { | |||
248 | set { | |||
| 102 | 249 | cryptoTransform = value; | ||
| 102 | 250 | if (cryptoTransform != null) { | ||
| 24 | 251 | if (rawData == clearText) { | ||
| 24 | 252 | if (internalClearText == null) { | ||
| 23 | 253 | internalClearText = new byte[rawData.Length]; | ||
254 | } | |||
| 24 | 255 | clearText = internalClearText; | ||
256 | } | |||
| 24 | 257 | clearTextLength = rawLength; | ||
| 24 | 258 | if (available > 0) { | ||
| 24 | 259 | cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available); | ||
260 | } | |||
| 24 | 261 | } else { | ||
| 78 | 262 | clearText = rawData; | ||
| 78 | 263 | clearTextLength = rawLength; | ||
264 | } | |||
| 78 | 265 | } | ||
266 | } | |||
267 | | |||
268 | #region Instance Fields | |||
269 | int rawLength; | |||
270 | byte[] rawData; | |||
271 | | |||
272 | int clearTextLength; | |||
273 | byte[] clearText; | |||
274 | byte[] internalClearText; | |||
275 | | |||
276 | int available; | |||
277 | | |||
278 | ICryptoTransform cryptoTransform; | |||
279 | Stream inputStream; | |||
280 | #endregion | |||
281 | } | |||
282 | | |||
283 | /// <summary> | |||
284 | /// This filter stream is used to decompress data compressed using the "deflate" | |||
285 | /// format. The "deflate" format is described in RFC 1951. | |||
286 | /// | |||
287 | /// This stream may form the basis for other decompression filters, such | |||
288 | /// as the <see cref="ICSharpCode.SharpZipLib.GZip.GZipInputStream">GZipInputStream</see>. | |||
289 | /// | |||
290 | /// Author of the original java version : John Leuner. | |||
291 | /// </summary> | |||
292 | public class InflaterInputStream : Stream | |||
293 | { | |||
294 | #region Constructors | |||
295 | /// <summary> | |||
296 | /// Create an InflaterInputStream with the default decompressor | |||
297 | /// and a default buffer size of 4KB. | |||
298 | /// </summary> | |||
299 | /// <param name = "baseInputStream"> | |||
300 | /// The InputStream to read bytes from | |||
301 | /// </param> | |||
302 | public InflaterInputStream(Stream baseInputStream) | |||
303 | : this(baseInputStream, new Inflater(), 4096) | |||
304 | { | |||
305 | } | |||
306 | | |||
307 | /// <summary> | |||
308 | /// Create an InflaterInputStream with the specified decompressor | |||
309 | /// and a default buffer size of 4KB. | |||
310 | /// </summary> | |||
311 | /// <param name = "baseInputStream"> | |||
312 | /// The source of input data | |||
313 | /// </param> | |||
314 | /// <param name = "inf"> | |||
315 | /// The decompressor used to decompress data read from baseInputStream | |||
316 | /// </param> | |||
317 | public InflaterInputStream(Stream baseInputStream, Inflater inf) | |||
318 | : this(baseInputStream, inf, 4096) | |||
319 | { | |||
320 | } | |||
321 | | |||
322 | /// <summary> | |||
323 | /// Create an InflaterInputStream with the specified decompressor | |||
324 | /// and the specified buffer size. | |||
325 | /// </summary> | |||
326 | /// <param name = "baseInputStream"> | |||
327 | /// The InputStream to read bytes from | |||
328 | /// </param> | |||
329 | /// <param name = "inflater"> | |||
330 | /// The decompressor to use | |||
331 | /// </param> | |||
332 | /// <param name = "bufferSize"> | |||
333 | /// Size of the buffer to use | |||
334 | /// </param> | |||
335 | public InflaterInputStream(Stream baseInputStream, Inflater inflater, int bufferSize) | |||
336 | { | |||
337 | if (baseInputStream == null) { | |||
338 | throw new ArgumentNullException(nameof(baseInputStream)); | |||
339 | } | |||
340 | | |||
341 | if (inflater == null) { | |||
342 | throw new ArgumentNullException(nameof(inflater)); | |||
343 | } | |||
344 | | |||
345 | if (bufferSize <= 0) { | |||
346 | throw new ArgumentOutOfRangeException(nameof(bufferSize)); | |||
347 | } | |||
348 | | |||
349 | this.baseInputStream = baseInputStream; | |||
350 | this.inf = inflater; | |||
351 | | |||
352 | inputBuffer = new InflaterInputBuffer(baseInputStream, bufferSize); | |||
353 | } | |||
354 | | |||
355 | #endregion | |||
356 | | |||
357 | /// <summary> | |||
358 | /// Get/set flag indicating ownership of underlying stream. | |||
359 | /// When the flag is true <see cref="Close"/> will close the underlying stream also. | |||
360 | /// </summary> | |||
361 | /// <remarks> | |||
362 | /// The default value is true. | |||
363 | /// </remarks> | |||
364 | public bool IsStreamOwner { | |||
365 | get { return isStreamOwner; } | |||
366 | set { isStreamOwner = value; } | |||
367 | } | |||
368 | | |||
369 | /// <summary> | |||
370 | /// Skip specified number of bytes of uncompressed data | |||
371 | /// </summary> | |||
372 | /// <param name ="count"> | |||
373 | /// Number of bytes to skip | |||
374 | /// </param> | |||
375 | /// <returns> | |||
376 | /// The number of bytes skipped, zero if the end of | |||
377 | /// stream has been reached | |||
378 | /// </returns> | |||
379 | /// <exception cref="ArgumentOutOfRangeException"> | |||
380 | /// <paramref name="count">The number of bytes</paramref> to skip is less than or equal to zero. | |||
381 | /// </exception> | |||
382 | public long Skip(long count) | |||
383 | { | |||
384 | if (count <= 0) { | |||
385 | throw new ArgumentOutOfRangeException(nameof(count)); | |||
386 | } | |||
387 | | |||
388 | // v0.80 Skip by seeking if underlying stream supports it... | |||
389 | if (baseInputStream.CanSeek) { | |||
390 | baseInputStream.Seek(count, SeekOrigin.Current); | |||
391 | return count; | |||
392 | } else { | |||
393 | int length = 2048; | |||
394 | if (count < length) { | |||
395 | length = (int)count; | |||
396 | } | |||
397 | | |||
398 | byte[] tmp = new byte[length]; | |||
399 | int readCount = 1; | |||
400 | long toSkip = count; | |||
401 | | |||
402 | while ((toSkip > 0) && (readCount > 0)) { | |||
403 | if (toSkip < length) { | |||
404 | length = (int)toSkip; | |||
405 | } | |||
406 | | |||
407 | readCount = baseInputStream.Read(tmp, 0, length); | |||
408 | toSkip -= readCount; | |||
409 | } | |||
410 | | |||
411 | return count - toSkip; | |||
412 | } | |||
413 | } | |||
414 | | |||
415 | /// <summary> | |||
416 | /// Clear any cryptographic state. | |||
417 | /// </summary> | |||
418 | protected void StopDecrypting() | |||
419 | { | |||
420 | inputBuffer.CryptoTransform = null; | |||
421 | } | |||
422 | | |||
423 | /// <summary> | |||
424 | /// Returns 0 once the end of the stream (EOF) has been reached. | |||
425 | /// Otherwise returns 1. | |||
426 | /// </summary> | |||
427 | public virtual int Available { | |||
428 | get { | |||
429 | return inf.IsFinished ? 0 : 1; | |||
430 | } | |||
431 | } | |||
432 | | |||
433 | /// <summary> | |||
434 | /// Fills the buffer with more data to decompress. | |||
435 | /// </summary> | |||
436 | /// <exception cref="SharpZipBaseException"> | |||
437 | /// Stream ends early | |||
438 | /// </exception> | |||
439 | protected void Fill() | |||
440 | { | |||
441 | // Protect against redundant calls | |||
442 | if (inputBuffer.Available <= 0) { | |||
443 | inputBuffer.Fill(); | |||
444 | if (inputBuffer.Available <= 0) { | |||
445 | throw new SharpZipBaseException("Unexpected EOF"); | |||
446 | } | |||
447 | } | |||
448 | inputBuffer.SetInflaterInput(inf); | |||
449 | } | |||
450 | | |||
451 | #region Stream Overrides | |||
452 | /// <summary> | |||
453 | /// Gets a value indicating whether the current stream supports reading | |||
454 | /// </summary> | |||
455 | public override bool CanRead { | |||
456 | get { | |||
457 | return baseInputStream.CanRead; | |||
458 | } | |||
459 | } | |||
460 | | |||
461 | /// <summary> | |||
462 | /// Gets a value of false indicating seeking is not supported for this stream. | |||
463 | /// </summary> | |||
464 | public override bool CanSeek { | |||
465 | get { | |||
466 | return false; | |||
467 | } | |||
468 | } | |||
469 | | |||
470 | /// <summary> | |||
471 | /// Gets a value of false indicating that this stream is not writeable. | |||
472 | /// </summary> | |||
473 | public override bool CanWrite { | |||
474 | get { | |||
475 | return false; | |||
476 | } | |||
477 | } | |||
478 | | |||
479 | /// <summary> | |||
480 | /// A value representing the length of the stream in bytes. | |||
481 | /// </summary> | |||
482 | public override long Length { | |||
483 | get { | |||
484 | return inputBuffer.RawLength; | |||
485 | } | |||
486 | } | |||
487 | | |||
488 | /// <summary> | |||
489 | /// The current position within the stream. | |||
490 | /// Throws a NotSupportedException when attempting to set the position | |||
491 | /// </summary> | |||
492 | /// <exception cref="NotSupportedException">Attempting to set the position</exception> | |||
493 | public override long Position { | |||
494 | get { | |||
495 | return baseInputStream.Position; | |||
496 | } | |||
497 | set { | |||
498 | throw new NotSupportedException("InflaterInputStream Position not supported"); | |||
499 | } | |||
500 | } | |||
501 | | |||
502 | /// <summary> | |||
503 | /// Flushes the baseInputStream | |||
504 | /// </summary> | |||
505 | public override void Flush() | |||
506 | { | |||
507 | baseInputStream.Flush(); | |||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Sets the position within the current stream | |||
512 | /// Always throws a NotSupportedException | |||
513 | /// </summary> | |||
514 | /// <param name="offset">The relative offset to seek to.</param> | |||
515 | /// <param name="origin">The <see cref="SeekOrigin"/> defining where to seek from.</param> | |||
516 | /// <returns>The new position in the stream.</returns> | |||
517 | /// <exception cref="NotSupportedException">Any access</exception> | |||
518 | public override long Seek(long offset, SeekOrigin origin) | |||
519 | { | |||
520 | throw new NotSupportedException("Seek not supported"); | |||
521 | } | |||
522 | | |||
523 | /// <summary> | |||
524 | /// Set the length of the current stream | |||
525 | /// Always throws a NotSupportedException | |||
526 | /// </summary> | |||
527 | /// <param name="value">The new length value for the stream.</param> | |||
528 | /// <exception cref="NotSupportedException">Any access</exception> | |||
529 | public override void SetLength(long value) | |||
530 | { | |||
531 | throw new NotSupportedException("InflaterInputStream SetLength not supported"); | |||
532 | } | |||
533 | | |||
534 | /// <summary> | |||
535 | /// Writes a sequence of bytes to stream and advances the current position | |||
536 | /// This method always throws a NotSupportedException | |||
537 | /// </summary> | |||
538 | /// <param name="buffer">Thew buffer containing data to write.</param> | |||
539 | /// <param name="offset">The offset of the first byte to write.</param> | |||
540 | /// <param name="count">The number of bytes to write.</param> | |||
541 | /// <exception cref="NotSupportedException">Any access</exception> | |||
542 | public override void Write(byte[] buffer, int offset, int count) | |||
543 | { | |||
544 | throw new NotSupportedException("InflaterInputStream Write not supported"); | |||
545 | } | |||
546 | | |||
547 | /// <summary> | |||
548 | /// Writes one byte to the current stream and advances the current position | |||
549 | /// Always throws a NotSupportedException | |||
550 | /// </summary> | |||
551 | /// <param name="value">The byte to write.</param> | |||
552 | /// <exception cref="NotSupportedException">Any access</exception> | |||
553 | public override void WriteByte(byte value) | |||
554 | { | |||
555 | throw new NotSupportedException("InflaterInputStream WriteByte not supported"); | |||
556 | } | |||
557 | | |||
558 | /// <summary> | |||
559 | /// Entry point to begin an asynchronous write. Always throws a NotSupportedException. | |||
560 | /// </summary> | |||
561 | /// <param name="buffer">The buffer to write data from</param> | |||
562 | /// <param name="offset">Offset of first byte to write</param> | |||
563 | /// <param name="count">The maximum number of bytes to write</param> | |||
564 | /// <param name="callback">The method to be called when the asynchronous write operation is completed</param> | |||
565 | /// <param name="state">A user-provided object that distinguishes this particular asynchronous write request from ot | |||
566 | /// <returns>An <see cref="System.IAsyncResult">IAsyncResult</see> that references the asynchronous write</returns> | |||
567 | /// <exception cref="NotSupportedException">Any access</exception> | |||
568 | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) | |||
569 | { | |||
570 | throw new NotSupportedException("InflaterInputStream BeginWrite not supported"); | |||
571 | } | |||
572 | | |||
573 | /// <summary> | |||
574 | /// Closes the input stream. When <see cref="IsStreamOwner"></see> | |||
575 | /// is true the underlying stream is also closed. | |||
576 | /// </summary> | |||
577 | public override void Close() | |||
578 | { | |||
579 | if (!isClosed) { | |||
580 | isClosed = true; | |||
581 | if (isStreamOwner) { | |||
582 | baseInputStream.Close(); | |||
583 | } | |||
584 | } | |||
585 | } | |||
586 | | |||
587 | /// <summary> | |||
588 | /// Reads decompressed data into the provided buffer byte array | |||
589 | /// </summary> | |||
590 | /// <param name ="buffer"> | |||
591 | /// The array to read and decompress data into | |||
592 | /// </param> | |||
593 | /// <param name ="offset"> | |||
594 | /// The offset indicating where the data should be placed | |||
595 | /// </param> | |||
596 | /// <param name ="count"> | |||
597 | /// The number of bytes to decompress | |||
598 | /// </param> | |||
599 | /// <returns>The number of bytes read. Zero signals the end of stream</returns> | |||
600 | /// <exception cref="SharpZipBaseException"> | |||
601 | /// Inflater needs a dictionary | |||
602 | /// </exception> | |||
603 | public override int Read(byte[] buffer, int offset, int count) | |||
604 | { | |||
605 | if (inf.IsNeedingDictionary) { | |||
606 | throw new SharpZipBaseException("Need a dictionary"); | |||
607 | } | |||
608 | | |||
609 | int remainingBytes = count; | |||
610 | while (true) { | |||
611 | int bytesRead = inf.Inflate(buffer, offset, remainingBytes); | |||
612 | offset += bytesRead; | |||
613 | remainingBytes -= bytesRead; | |||
614 | | |||
615 | if (remainingBytes == 0 || inf.IsFinished) { | |||
616 | break; | |||
617 | } | |||
618 | | |||
619 | if (inf.IsNeedingInput) { | |||
620 | Fill(); | |||
621 | } else if (bytesRead == 0) { | |||
622 | throw new ZipException("Dont know what to do"); | |||
623 | } | |||
624 | } | |||
625 | return count - remainingBytes; | |||
626 | } | |||
627 | #endregion | |||
628 | | |||
629 | #region Instance Fields | |||
630 | /// <summary> | |||
631 | /// Decompressor for this stream | |||
632 | /// </summary> | |||
633 | protected Inflater inf; | |||
634 | | |||
635 | /// <summary> | |||
636 | /// <see cref="InflaterInputBuffer">Input buffer</see> for this stream. | |||
637 | /// </summary> | |||
638 | protected InflaterInputBuffer inputBuffer; | |||
639 | | |||
640 | /// <summary> | |||
641 | /// Base stream the inflater reads from. | |||
642 | /// </summary> | |||
643 | private Stream baseInputStream; | |||
644 | | |||
645 | /// <summary> | |||
646 | /// The compressed size | |||
647 | /// </summary> | |||
648 | protected long csize; | |||
649 | | |||
650 | /// <summary> | |||
651 | /// Flag indicating wether this instance has been closed or not. | |||
652 | /// </summary> | |||
653 | bool isClosed; | |||
654 | | |||
655 | /// <summary> | |||
656 | /// Flag indicating wether this instance is designated the stream owner. | |||
657 | /// When closing if this flag is true the underlying stream is closed. | |||
658 | /// </summary> | |||
659 | bool isStreamOwner = true; | |||
660 | #endregion | |||
661 | } | |||
662 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\Streams\InflaterInputStream.cs |
| Covered lines: | 38 |
| Uncovered lines: | 36 |
| Coverable lines: | 74 |
| Total lines: | 662 |
| Line coverage: | 51.3% |
| Branch coverage: | 39.4% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 4 | 75 | 57.14 |
| Skip(...) | 7 | 0 | 0 |
| StopDecrypting() | 1 | 100 | 100 |
| Fill() | 3 | 83.33 | 60 |
| Flush() | 1 | 0 | 0 |
| Seek(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| Write(...) | 1 | 0 | 0 |
| WriteByte(...) | 1 | 0 | 0 |
| BeginWrite(...) | 1 | 0 | 0 |
| Close() | 3 | 100 | 100 |
| Read(...) | 6 | 76.92 | 63.64 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Security.Cryptography; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// An input buffer customised for use by <see cref="InflaterInputStream"/> | |||
9 | /// </summary> | |||
10 | /// <remarks> | |||
11 | /// The buffer supports decryption of incoming data. | |||
12 | /// </remarks> | |||
13 | public class InflaterInputBuffer | |||
14 | { | |||
15 | #region Constructors | |||
16 | /// <summary> | |||
17 | /// Initialise a new instance of <see cref="InflaterInputBuffer"/> with a default buffer size | |||
18 | /// </summary> | |||
19 | /// <param name="stream">The stream to buffer.</param> | |||
20 | public InflaterInputBuffer(Stream stream) : this(stream, 4096) | |||
21 | { | |||
22 | } | |||
23 | | |||
24 | /// <summary> | |||
25 | /// Initialise a new instance of <see cref="InflaterInputBuffer"/> | |||
26 | /// </summary> | |||
27 | /// <param name="stream">The stream to buffer.</param> | |||
28 | /// <param name="bufferSize">The size to use for the buffer</param> | |||
29 | /// <remarks>A minimum buffer size of 1KB is permitted. Lower sizes are treated as 1KB.</remarks> | |||
30 | public InflaterInputBuffer(Stream stream, int bufferSize) | |||
31 | { | |||
32 | inputStream = stream; | |||
33 | if (bufferSize < 1024) { | |||
34 | bufferSize = 1024; | |||
35 | } | |||
36 | rawData = new byte[bufferSize]; | |||
37 | clearText = rawData; | |||
38 | } | |||
39 | #endregion | |||
40 | | |||
41 | /// <summary> | |||
42 | /// Get the length of bytes bytes in the <see cref="RawData"/> | |||
43 | /// </summary> | |||
44 | public int RawLength { | |||
45 | get { | |||
46 | return rawLength; | |||
47 | } | |||
48 | } | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Get the contents of the raw data buffer. | |||
52 | /// </summary> | |||
53 | /// <remarks>This may contain encrypted data.</remarks> | |||
54 | public byte[] RawData { | |||
55 | get { | |||
56 | return rawData; | |||
57 | } | |||
58 | } | |||
59 | | |||
60 | /// <summary> | |||
61 | /// Get the number of useable bytes in <see cref="ClearText"/> | |||
62 | /// </summary> | |||
63 | public int ClearTextLength { | |||
64 | get { | |||
65 | return clearTextLength; | |||
66 | } | |||
67 | } | |||
68 | | |||
69 | /// <summary> | |||
70 | /// Get the contents of the clear text buffer. | |||
71 | /// </summary> | |||
72 | public byte[] ClearText { | |||
73 | get { | |||
74 | return clearText; | |||
75 | } | |||
76 | } | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Get/set the number of bytes available | |||
80 | /// </summary> | |||
81 | public int Available { | |||
82 | get { return available; } | |||
83 | set { available = value; } | |||
84 | } | |||
85 | | |||
86 | /// <summary> | |||
87 | /// Call <see cref="Inflater.SetInput(byte[], int, int)"/> passing the current clear text buffer contents. | |||
88 | /// </summary> | |||
89 | /// <param name="inflater">The inflater to set input for.</param> | |||
90 | public void SetInflaterInput(Inflater inflater) | |||
91 | { | |||
92 | if (available > 0) { | |||
93 | inflater.SetInput(clearText, clearTextLength - available, available); | |||
94 | available = 0; | |||
95 | } | |||
96 | } | |||
97 | | |||
98 | /// <summary> | |||
99 | /// Fill the buffer from the underlying input stream. | |||
100 | /// </summary> | |||
101 | public void Fill() | |||
102 | { | |||
103 | rawLength = 0; | |||
104 | int toRead = rawData.Length; | |||
105 | | |||
106 | while (toRead > 0) { | |||
107 | int count = inputStream.Read(rawData, rawLength, toRead); | |||
108 | if (count <= 0) { | |||
109 | break; | |||
110 | } | |||
111 | rawLength += count; | |||
112 | toRead -= count; | |||
113 | } | |||
114 | | |||
115 | if (cryptoTransform != null) { | |||
116 | clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0); | |||
117 | } else { | |||
118 | clearTextLength = rawLength; | |||
119 | } | |||
120 | | |||
121 | available = clearTextLength; | |||
122 | } | |||
123 | | |||
124 | /// <summary> | |||
125 | /// Read a buffer directly from the input stream | |||
126 | /// </summary> | |||
127 | /// <param name="buffer">The buffer to fill</param> | |||
128 | /// <returns>Returns the number of bytes read.</returns> | |||
129 | public int ReadRawBuffer(byte[] buffer) | |||
130 | { | |||
131 | return ReadRawBuffer(buffer, 0, buffer.Length); | |||
132 | } | |||
133 | | |||
134 | /// <summary> | |||
135 | /// Read a buffer directly from the input stream | |||
136 | /// </summary> | |||
137 | /// <param name="outBuffer">The buffer to read into</param> | |||
138 | /// <param name="offset">The offset to start reading data into.</param> | |||
139 | /// <param name="length">The number of bytes to read.</param> | |||
140 | /// <returns>Returns the number of bytes read.</returns> | |||
141 | public int ReadRawBuffer(byte[] outBuffer, int offset, int length) | |||
142 | { | |||
143 | if (length < 0) { | |||
144 | throw new ArgumentOutOfRangeException(nameof(length)); | |||
145 | } | |||
146 | | |||
147 | int currentOffset = offset; | |||
148 | int currentLength = length; | |||
149 | | |||
150 | while (currentLength > 0) { | |||
151 | if (available <= 0) { | |||
152 | Fill(); | |||
153 | if (available <= 0) { | |||
154 | return 0; | |||
155 | } | |||
156 | } | |||
157 | int toCopy = Math.Min(currentLength, available); | |||
158 | System.Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy); | |||
159 | currentOffset += toCopy; | |||
160 | currentLength -= toCopy; | |||
161 | available -= toCopy; | |||
162 | } | |||
163 | return length; | |||
164 | } | |||
165 | | |||
166 | /// <summary> | |||
167 | /// Read clear text data from the input stream. | |||
168 | /// </summary> | |||
169 | /// <param name="outBuffer">The buffer to add data to.</param> | |||
170 | /// <param name="offset">The offset to start adding data at.</param> | |||
171 | /// <param name="length">The number of bytes to read.</param> | |||
172 | /// <returns>Returns the number of bytes actually read.</returns> | |||
173 | public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length) | |||
174 | { | |||
175 | if (length < 0) { | |||
176 | throw new ArgumentOutOfRangeException(nameof(length)); | |||
177 | } | |||
178 | | |||
179 | int currentOffset = offset; | |||
180 | int currentLength = length; | |||
181 | | |||
182 | while (currentLength > 0) { | |||
183 | if (available <= 0) { | |||
184 | Fill(); | |||
185 | if (available <= 0) { | |||
186 | return 0; | |||
187 | } | |||
188 | } | |||
189 | | |||
190 | int toCopy = Math.Min(currentLength, available); | |||
191 | Array.Copy(clearText, clearTextLength - (int)available, outBuffer, currentOffset, toCopy); | |||
192 | currentOffset += toCopy; | |||
193 | currentLength -= toCopy; | |||
194 | available -= toCopy; | |||
195 | } | |||
196 | return length; | |||
197 | } | |||
198 | | |||
199 | /// <summary> | |||
200 | /// Read a <see cref="byte"/> from the input stream. | |||
201 | /// </summary> | |||
202 | /// <returns>Returns the byte read.</returns> | |||
203 | public int ReadLeByte() | |||
204 | { | |||
205 | if (available <= 0) { | |||
206 | Fill(); | |||
207 | if (available <= 0) { | |||
208 | throw new ZipException("EOF in header"); | |||
209 | } | |||
210 | } | |||
211 | byte result = rawData[rawLength - available]; | |||
212 | available -= 1; | |||
213 | return result; | |||
214 | } | |||
215 | | |||
216 | /// <summary> | |||
217 | /// Read an <see cref="short"/> in little endian byte order. | |||
218 | /// </summary> | |||
219 | /// <returns>The short value read case to an int.</returns> | |||
220 | public int ReadLeShort() | |||
221 | { | |||
222 | return ReadLeByte() | (ReadLeByte() << 8); | |||
223 | } | |||
224 | | |||
225 | /// <summary> | |||
226 | /// Read an <see cref="int"/> in little endian byte order. | |||
227 | /// </summary> | |||
228 | /// <returns>The int value read.</returns> | |||
229 | public int ReadLeInt() | |||
230 | { | |||
231 | return ReadLeShort() | (ReadLeShort() << 16); | |||
232 | } | |||
233 | | |||
234 | /// <summary> | |||
235 | /// Read a <see cref="long"/> in little endian byte order. | |||
236 | /// </summary> | |||
237 | /// <returns>The long value read.</returns> | |||
238 | public long ReadLeLong() | |||
239 | { | |||
240 | return (uint)ReadLeInt() | ((long)ReadLeInt() << 32); | |||
241 | } | |||
242 | | |||
243 | /// <summary> | |||
244 | /// Get/set the <see cref="ICryptoTransform"/> to apply to any data. | |||
245 | /// </summary> | |||
246 | /// <remarks>Set this value to null to have no transform applied.</remarks> | |||
247 | public ICryptoTransform CryptoTransform { | |||
248 | set { | |||
249 | cryptoTransform = value; | |||
250 | if (cryptoTransform != null) { | |||
251 | if (rawData == clearText) { | |||
252 | if (internalClearText == null) { | |||
253 | internalClearText = new byte[rawData.Length]; | |||
254 | } | |||
255 | clearText = internalClearText; | |||
256 | } | |||
257 | clearTextLength = rawLength; | |||
258 | if (available > 0) { | |||
259 | cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available); | |||
260 | } | |||
261 | } else { | |||
262 | clearText = rawData; | |||
263 | clearTextLength = rawLength; | |||
264 | } | |||
265 | } | |||
266 | } | |||
267 | | |||
268 | #region Instance Fields | |||
269 | int rawLength; | |||
270 | byte[] rawData; | |||
271 | | |||
272 | int clearTextLength; | |||
273 | byte[] clearText; | |||
274 | byte[] internalClearText; | |||
275 | | |||
276 | int available; | |||
277 | | |||
278 | ICryptoTransform cryptoTransform; | |||
279 | Stream inputStream; | |||
280 | #endregion | |||
281 | } | |||
282 | | |||
283 | /// <summary> | |||
284 | /// This filter stream is used to decompress data compressed using the "deflate" | |||
285 | /// format. The "deflate" format is described in RFC 1951. | |||
286 | /// | |||
287 | /// This stream may form the basis for other decompression filters, such | |||
288 | /// as the <see cref="ICSharpCode.SharpZipLib.GZip.GZipInputStream">GZipInputStream</see>. | |||
289 | /// | |||
290 | /// Author of the original java version : John Leuner. | |||
291 | /// </summary> | |||
292 | public class InflaterInputStream : Stream | |||
293 | { | |||
294 | #region Constructors | |||
295 | /// <summary> | |||
296 | /// Create an InflaterInputStream with the default decompressor | |||
297 | /// and a default buffer size of 4KB. | |||
298 | /// </summary> | |||
299 | /// <param name = "baseInputStream"> | |||
300 | /// The InputStream to read bytes from | |||
301 | /// </param> | |||
302 | public InflaterInputStream(Stream baseInputStream) | |||
| 3 | 303 | : this(baseInputStream, new Inflater(), 4096) | ||
304 | { | |||
| 3 | 305 | } | ||
306 | | |||
307 | /// <summary> | |||
308 | /// Create an InflaterInputStream with the specified decompressor | |||
309 | /// and a default buffer size of 4KB. | |||
310 | /// </summary> | |||
311 | /// <param name = "baseInputStream"> | |||
312 | /// The source of input data | |||
313 | /// </param> | |||
314 | /// <param name = "inf"> | |||
315 | /// The decompressor used to decompress data read from baseInputStream | |||
316 | /// </param> | |||
317 | public InflaterInputStream(Stream baseInputStream, Inflater inf) | |||
| 430 | 318 | : this(baseInputStream, inf, 4096) | ||
319 | { | |||
| 430 | 320 | } | ||
321 | | |||
322 | /// <summary> | |||
323 | /// Create an InflaterInputStream with the specified decompressor | |||
324 | /// and the specified buffer size. | |||
325 | /// </summary> | |||
326 | /// <param name = "baseInputStream"> | |||
327 | /// The InputStream to read bytes from | |||
328 | /// </param> | |||
329 | /// <param name = "inflater"> | |||
330 | /// The decompressor to use | |||
331 | /// </param> | |||
332 | /// <param name = "bufferSize"> | |||
333 | /// Size of the buffer to use | |||
334 | /// </param> | |||
| 435 | 335 | public InflaterInputStream(Stream baseInputStream, Inflater inflater, int bufferSize) | ||
336 | { | |||
| 435 | 337 | if (baseInputStream == null) { | ||
| 0 | 338 | throw new ArgumentNullException(nameof(baseInputStream)); | ||
339 | } | |||
340 | | |||
| 435 | 341 | if (inflater == null) { | ||
| 0 | 342 | throw new ArgumentNullException(nameof(inflater)); | ||
343 | } | |||
344 | | |||
| 435 | 345 | if (bufferSize <= 0) { | ||
| 0 | 346 | throw new ArgumentOutOfRangeException(nameof(bufferSize)); | ||
347 | } | |||
348 | | |||
| 435 | 349 | this.baseInputStream = baseInputStream; | ||
| 435 | 350 | this.inf = inflater; | ||
351 | | |||
| 435 | 352 | inputBuffer = new InflaterInputBuffer(baseInputStream, bufferSize); | ||
| 435 | 353 | } | ||
354 | | |||
355 | #endregion | |||
356 | | |||
357 | /// <summary> | |||
358 | /// Get/set flag indicating ownership of underlying stream. | |||
359 | /// When the flag is true <see cref="Close"/> will close the underlying stream also. | |||
360 | /// </summary> | |||
361 | /// <remarks> | |||
362 | /// The default value is true. | |||
363 | /// </remarks> | |||
364 | public bool IsStreamOwner { | |||
| 0 | 365 | get { return isStreamOwner; } | ||
| 4 | 366 | set { isStreamOwner = value; } | ||
367 | } | |||
368 | | |||
369 | /// <summary> | |||
370 | /// Skip specified number of bytes of uncompressed data | |||
371 | /// </summary> | |||
372 | /// <param name ="count"> | |||
373 | /// Number of bytes to skip | |||
374 | /// </param> | |||
375 | /// <returns> | |||
376 | /// The number of bytes skipped, zero if the end of | |||
377 | /// stream has been reached | |||
378 | /// </returns> | |||
379 | /// <exception cref="ArgumentOutOfRangeException"> | |||
380 | /// <paramref name="count">The number of bytes</paramref> to skip is less than or equal to zero. | |||
381 | /// </exception> | |||
382 | public long Skip(long count) | |||
383 | { | |||
| 0 | 384 | if (count <= 0) { | ||
| 0 | 385 | throw new ArgumentOutOfRangeException(nameof(count)); | ||
386 | } | |||
387 | | |||
388 | // v0.80 Skip by seeking if underlying stream supports it... | |||
| 0 | 389 | if (baseInputStream.CanSeek) { | ||
| 0 | 390 | baseInputStream.Seek(count, SeekOrigin.Current); | ||
| 0 | 391 | return count; | ||
392 | } else { | |||
| 0 | 393 | int length = 2048; | ||
| 0 | 394 | if (count < length) { | ||
| 0 | 395 | length = (int)count; | ||
396 | } | |||
397 | | |||
| 0 | 398 | byte[] tmp = new byte[length]; | ||
| 0 | 399 | int readCount = 1; | ||
| 0 | 400 | long toSkip = count; | ||
401 | | |||
| 0 | 402 | while ((toSkip > 0) && (readCount > 0)) { | ||
| 0 | 403 | if (toSkip < length) { | ||
| 0 | 404 | length = (int)toSkip; | ||
405 | } | |||
406 | | |||
| 0 | 407 | readCount = baseInputStream.Read(tmp, 0, length); | ||
| 0 | 408 | toSkip -= readCount; | ||
409 | } | |||
410 | | |||
| 0 | 411 | return count - toSkip; | ||
412 | } | |||
413 | } | |||
414 | | |||
415 | /// <summary> | |||
416 | /// Clear any cryptographic state. | |||
417 | /// </summary> | |||
418 | protected void StopDecrypting() | |||
419 | { | |||
| 32 | 420 | inputBuffer.CryptoTransform = null; | ||
| 32 | 421 | } | ||
422 | | |||
423 | /// <summary> | |||
424 | /// Returns 0 once the end of the stream (EOF) has been reached. | |||
425 | /// Otherwise returns 1. | |||
426 | /// </summary> | |||
427 | public virtual int Available { | |||
428 | get { | |||
| 0 | 429 | return inf.IsFinished ? 0 : 1; | ||
430 | } | |||
431 | } | |||
432 | | |||
433 | /// <summary> | |||
434 | /// Fills the buffer with more data to decompress. | |||
435 | /// </summary> | |||
436 | /// <exception cref="SharpZipBaseException"> | |||
437 | /// Stream ends early | |||
438 | /// </exception> | |||
439 | protected void Fill() | |||
440 | { | |||
441 | // Protect against redundant calls | |||
| 1367 | 442 | if (inputBuffer.Available <= 0) { | ||
| 1367 | 443 | inputBuffer.Fill(); | ||
| 1367 | 444 | if (inputBuffer.Available <= 0) { | ||
| 0 | 445 | throw new SharpZipBaseException("Unexpected EOF"); | ||
446 | } | |||
447 | } | |||
| 1367 | 448 | inputBuffer.SetInflaterInput(inf); | ||
| 1367 | 449 | } | ||
450 | | |||
451 | #region Stream Overrides | |||
452 | /// <summary> | |||
453 | /// Gets a value indicating whether the current stream supports reading | |||
454 | /// </summary> | |||
455 | public override bool CanRead { | |||
456 | get { | |||
| 8 | 457 | return baseInputStream.CanRead; | ||
458 | } | |||
459 | } | |||
460 | | |||
461 | /// <summary> | |||
462 | /// Gets a value of false indicating seeking is not supported for this stream. | |||
463 | /// </summary> | |||
464 | public override bool CanSeek { | |||
465 | get { | |||
| 2 | 466 | return false; | ||
467 | } | |||
468 | } | |||
469 | | |||
470 | /// <summary> | |||
471 | /// Gets a value of false indicating that this stream is not writeable. | |||
472 | /// </summary> | |||
473 | public override bool CanWrite { | |||
474 | get { | |||
| 0 | 475 | return false; | ||
476 | } | |||
477 | } | |||
478 | | |||
479 | /// <summary> | |||
480 | /// A value representing the length of the stream in bytes. | |||
481 | /// </summary> | |||
482 | public override long Length { | |||
483 | get { | |||
| 0 | 484 | return inputBuffer.RawLength; | ||
485 | } | |||
486 | } | |||
487 | | |||
488 | /// <summary> | |||
489 | /// The current position within the stream. | |||
490 | /// Throws a NotSupportedException when attempting to set the position | |||
491 | /// </summary> | |||
492 | /// <exception cref="NotSupportedException">Attempting to set the position</exception> | |||
493 | public override long Position { | |||
494 | get { | |||
| 0 | 495 | return baseInputStream.Position; | ||
496 | } | |||
497 | set { | |||
| 0 | 498 | throw new NotSupportedException("InflaterInputStream Position not supported"); | ||
499 | } | |||
500 | } | |||
501 | | |||
502 | /// <summary> | |||
503 | /// Flushes the baseInputStream | |||
504 | /// </summary> | |||
505 | public override void Flush() | |||
506 | { | |||
| 0 | 507 | baseInputStream.Flush(); | ||
| 0 | 508 | } | ||
509 | | |||
510 | /// <summary> | |||
511 | /// Sets the position within the current stream | |||
512 | /// Always throws a NotSupportedException | |||
513 | /// </summary> | |||
514 | /// <param name="offset">The relative offset to seek to.</param> | |||
515 | /// <param name="origin">The <see cref="SeekOrigin"/> defining where to seek from.</param> | |||
516 | /// <returns>The new position in the stream.</returns> | |||
517 | /// <exception cref="NotSupportedException">Any access</exception> | |||
518 | public override long Seek(long offset, SeekOrigin origin) | |||
519 | { | |||
| 0 | 520 | throw new NotSupportedException("Seek not supported"); | ||
521 | } | |||
522 | | |||
523 | /// <summary> | |||
524 | /// Set the length of the current stream | |||
525 | /// Always throws a NotSupportedException | |||
526 | /// </summary> | |||
527 | /// <param name="value">The new length value for the stream.</param> | |||
528 | /// <exception cref="NotSupportedException">Any access</exception> | |||
529 | public override void SetLength(long value) | |||
530 | { | |||
| 0 | 531 | throw new NotSupportedException("InflaterInputStream SetLength not supported"); | ||
532 | } | |||
533 | | |||
534 | /// <summary> | |||
535 | /// Writes a sequence of bytes to stream and advances the current position | |||
536 | /// This method always throws a NotSupportedException | |||
537 | /// </summary> | |||
538 | /// <param name="buffer">Thew buffer containing data to write.</param> | |||
539 | /// <param name="offset">The offset of the first byte to write.</param> | |||
540 | /// <param name="count">The number of bytes to write.</param> | |||
541 | /// <exception cref="NotSupportedException">Any access</exception> | |||
542 | public override void Write(byte[] buffer, int offset, int count) | |||
543 | { | |||
| 0 | 544 | throw new NotSupportedException("InflaterInputStream Write not supported"); | ||
545 | } | |||
546 | | |||
547 | /// <summary> | |||
548 | /// Writes one byte to the current stream and advances the current position | |||
549 | /// Always throws a NotSupportedException | |||
550 | /// </summary> | |||
551 | /// <param name="value">The byte to write.</param> | |||
552 | /// <exception cref="NotSupportedException">Any access</exception> | |||
553 | public override void WriteByte(byte value) | |||
554 | { | |||
| 0 | 555 | throw new NotSupportedException("InflaterInputStream WriteByte not supported"); | ||
556 | } | |||
557 | | |||
558 | /// <summary> | |||
559 | /// Entry point to begin an asynchronous write. Always throws a NotSupportedException. | |||
560 | /// </summary> | |||
561 | /// <param name="buffer">The buffer to write data from</param> | |||
562 | /// <param name="offset">Offset of first byte to write</param> | |||
563 | /// <param name="count">The maximum number of bytes to write</param> | |||
564 | /// <param name="callback">The method to be called when the asynchronous write operation is completed</param> | |||
565 | /// <param name="state">A user-provided object that distinguishes this particular asynchronous write request from ot | |||
566 | /// <returns>An <see cref="System.IAsyncResult">IAsyncResult</see> that references the asynchronous write</returns> | |||
567 | /// <exception cref="NotSupportedException">Any access</exception> | |||
568 | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) | |||
569 | { | |||
| 0 | 570 | throw new NotSupportedException("InflaterInputStream BeginWrite not supported"); | ||
571 | } | |||
572 | | |||
573 | /// <summary> | |||
574 | /// Closes the input stream. When <see cref="IsStreamOwner"></see> | |||
575 | /// is true the underlying stream is also closed. | |||
576 | /// </summary> | |||
577 | public override void Close() | |||
578 | { | |||
| 392 | 579 | if (!isClosed) { | ||
| 383 | 580 | isClosed = true; | ||
| 383 | 581 | if (isStreamOwner) { | ||
| 381 | 582 | baseInputStream.Close(); | ||
583 | } | |||
584 | } | |||
| 392 | 585 | } | ||
586 | | |||
587 | /// <summary> | |||
588 | /// Reads decompressed data into the provided buffer byte array | |||
589 | /// </summary> | |||
590 | /// <param name ="buffer"> | |||
591 | /// The array to read and decompress data into | |||
592 | /// </param> | |||
593 | /// <param name ="offset"> | |||
594 | /// The offset indicating where the data should be placed | |||
595 | /// </param> | |||
596 | /// <param name ="count"> | |||
597 | /// The number of bytes to decompress | |||
598 | /// </param> | |||
599 | /// <returns>The number of bytes read. Zero signals the end of stream</returns> | |||
600 | /// <exception cref="SharpZipBaseException"> | |||
601 | /// Inflater needs a dictionary | |||
602 | /// </exception> | |||
603 | public override int Read(byte[] buffer, int offset, int count) | |||
604 | { | |||
| 845 | 605 | if (inf.IsNeedingDictionary) { | ||
| 0 | 606 | throw new SharpZipBaseException("Need a dictionary"); | ||
607 | } | |||
608 | | |||
| 845 | 609 | int remainingBytes = count; | ||
610 | while (true) { | |||
| 2212 | 611 | int bytesRead = inf.Inflate(buffer, offset, remainingBytes); | ||
| 2212 | 612 | offset += bytesRead; | ||
| 2212 | 613 | remainingBytes -= bytesRead; | ||
614 | | |||
| 2212 | 615 | if (remainingBytes == 0 || inf.IsFinished) { | ||
616 | break; | |||
617 | } | |||
618 | | |||
| 1367 | 619 | if (inf.IsNeedingInput) { | ||
| 1367 | 620 | Fill(); | ||
| 1367 | 621 | } else if (bytesRead == 0) { | ||
| 0 | 622 | throw new ZipException("Dont know what to do"); | ||
623 | } | |||
624 | } | |||
| 845 | 625 | return count - remainingBytes; | ||
626 | } | |||
627 | #endregion | |||
628 | | |||
629 | #region Instance Fields | |||
630 | /// <summary> | |||
631 | /// Decompressor for this stream | |||
632 | /// </summary> | |||
633 | protected Inflater inf; | |||
634 | | |||
635 | /// <summary> | |||
636 | /// <see cref="InflaterInputBuffer">Input buffer</see> for this stream. | |||
637 | /// </summary> | |||
638 | protected InflaterInputBuffer inputBuffer; | |||
639 | | |||
640 | /// <summary> | |||
641 | /// Base stream the inflater reads from. | |||
642 | /// </summary> | |||
643 | private Stream baseInputStream; | |||
644 | | |||
645 | /// <summary> | |||
646 | /// The compressed size | |||
647 | /// </summary> | |||
648 | protected long csize; | |||
649 | | |||
650 | /// <summary> | |||
651 | /// Flag indicating wether this instance has been closed or not. | |||
652 | /// </summary> | |||
653 | bool isClosed; | |||
654 | | |||
655 | /// <summary> | |||
656 | /// Flag indicating wether this instance is designated the stream owner. | |||
657 | /// When closing if this flag is true the underlying stream is closed. | |||
658 | /// </summary> | |||
| 435 | 659 | bool isStreamOwner = true; | ||
660 | #endregion | |||
661 | } | |||
662 | } |
| Class: | ICSharpCode.SharpZipLib.Tar.InvalidHeaderException |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\InvalidHeaderException.cs |
| Covered lines: | 0 |
| Uncovered lines: | 8 |
| Coverable lines: | 8 |
| Total lines: | 51 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Runtime.Serialization; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Tar | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// This exception is used to indicate that there is a problem | |||
8 | /// with a TAR archive header. | |||
9 | /// </summary> | |||
10 | [Serializable] | |||
11 | public class InvalidHeaderException : TarException | |||
12 | { | |||
13 | | |||
14 | /// <summary> | |||
15 | /// Deserialization constructor | |||
16 | /// </summary> | |||
17 | /// <param name="information"><see cref="SerializationInfo"/> for this constructor</param> | |||
18 | /// <param name="context"><see cref="StreamingContext"/> for this constructor</param> | |||
19 | protected InvalidHeaderException(SerializationInfo information, StreamingContext context) | |||
| 0 | 20 | : base(information, context) | ||
21 | | |||
22 | { | |||
| 0 | 23 | } | ||
24 | | |||
25 | /// <summary> | |||
26 | /// Initialise a new instance of the InvalidHeaderException class. | |||
27 | /// </summary> | |||
| 0 | 28 | public InvalidHeaderException() | ||
29 | { | |||
| 0 | 30 | } | ||
31 | | |||
32 | /// <summary> | |||
33 | /// Initialises a new instance of the InvalidHeaderException class with a specified message. | |||
34 | /// </summary> | |||
35 | /// <param name="message">Message describing the exception cause.</param> | |||
36 | public InvalidHeaderException(string message) | |||
| 0 | 37 | : base(message) | ||
38 | { | |||
| 0 | 39 | } | ||
40 | | |||
41 | /// <summary> | |||
42 | /// Initialise a new instance of InvalidHeaderException | |||
43 | /// </summary> | |||
44 | /// <param name="message">Message describing the problem.</param> | |||
45 | /// <param name="exception">The exception that is the cause of the current exception.</param> | |||
46 | public InvalidHeaderException(string message, Exception exception) | |||
| 0 | 47 | : base(message, exception) | ||
48 | { | |||
| 0 | 49 | } | ||
50 | } | |||
51 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.KeysRequiredEventArgs |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs |
| Covered lines: | 0 |
| Uncovered lines: | 10 |
| Coverable lines: | 10 |
| Total lines: | 4263 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.IO; | |||
4 | using System.Text; | |||
5 | using System.Globalization; | |||
6 | using System.Security.Cryptography; | |||
7 | using ICSharpCode.SharpZipLib.Encryption; | |||
8 | using ICSharpCode.SharpZipLib.Core; | |||
9 | using ICSharpCode.SharpZipLib.Checksum; | |||
10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
11 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
12 | | |||
13 | namespace ICSharpCode.SharpZipLib.Zip | |||
14 | { | |||
15 | #region Keys Required Event Args | |||
16 | /// <summary> | |||
17 | /// Arguments used with KeysRequiredEvent | |||
18 | /// </summary> | |||
19 | public class KeysRequiredEventArgs : EventArgs | |||
20 | { | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
24 | /// </summary> | |||
25 | /// <param name="name">The name of the file for which keys are required.</param> | |||
| 0 | 26 | public KeysRequiredEventArgs(string name) | ||
27 | { | |||
| 0 | 28 | fileName = name; | ||
| 0 | 29 | } | ||
30 | | |||
31 | /// <summary> | |||
32 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
33 | /// </summary> | |||
34 | /// <param name="name">The name of the file for which keys are required.</param> | |||
35 | /// <param name="keyValue">The current key value.</param> | |||
| 0 | 36 | public KeysRequiredEventArgs(string name, byte[] keyValue) | ||
37 | { | |||
| 0 | 38 | fileName = name; | ||
| 0 | 39 | key = keyValue; | ||
| 0 | 40 | } | ||
41 | | |||
42 | #endregion | |||
43 | #region Properties | |||
44 | /// <summary> | |||
45 | /// Gets the name of the file for which keys are required. | |||
46 | /// </summary> | |||
47 | public string FileName { | |||
| 0 | 48 | get { return fileName; } | ||
49 | } | |||
50 | | |||
51 | /// <summary> | |||
52 | /// Gets or sets the key value | |||
53 | /// </summary> | |||
54 | public byte[] Key { | |||
| 0 | 55 | get { return key; } | ||
| 0 | 56 | set { key = value; } | ||
57 | } | |||
58 | #endregion | |||
59 | | |||
60 | #region Instance Fields | |||
61 | string fileName; | |||
62 | byte[] key; | |||
63 | #endregion | |||
64 | } | |||
65 | #endregion | |||
66 | | |||
67 | #region Test Definitions | |||
68 | /// <summary> | |||
69 | /// The strategy to apply to testing. | |||
70 | /// </summary> | |||
71 | public enum TestStrategy | |||
72 | { | |||
73 | /// <summary> | |||
74 | /// Find the first error only. | |||
75 | /// </summary> | |||
76 | FindFirstError, | |||
77 | /// <summary> | |||
78 | /// Find all possible errors. | |||
79 | /// </summary> | |||
80 | FindAllErrors, | |||
81 | } | |||
82 | | |||
83 | /// <summary> | |||
84 | /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. | |||
85 | /// </summary> | |||
86 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
87 | public enum TestOperation | |||
88 | { | |||
89 | /// <summary> | |||
90 | /// Setting up testing. | |||
91 | /// </summary> | |||
92 | Initialising, | |||
93 | | |||
94 | /// <summary> | |||
95 | /// Testing an individual entries header | |||
96 | /// </summary> | |||
97 | EntryHeader, | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Testing an individual entries data | |||
101 | /// </summary> | |||
102 | EntryData, | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Testing an individual entry has completed. | |||
106 | /// </summary> | |||
107 | EntryComplete, | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Running miscellaneous tests | |||
111 | /// </summary> | |||
112 | MiscellaneousTests, | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Testing is complete | |||
116 | /// </summary> | |||
117 | Complete, | |||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. | |||
122 | /// </summary> | |||
123 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
124 | public class TestStatus | |||
125 | { | |||
126 | #region Constructors | |||
127 | /// <summary> | |||
128 | /// Initialise a new instance of <see cref="TestStatus"/> | |||
129 | /// </summary> | |||
130 | /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> | |||
131 | public TestStatus(ZipFile file) | |||
132 | { | |||
133 | file_ = file; | |||
134 | } | |||
135 | #endregion | |||
136 | | |||
137 | #region Properties | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Get the current <see cref="TestOperation"/> in progress. | |||
141 | /// </summary> | |||
142 | public TestOperation Operation { | |||
143 | get { return operation_; } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get the <see cref="ZipFile"/> this status is applicable to. | |||
148 | /// </summary> | |||
149 | public ZipFile File { | |||
150 | get { return file_; } | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Get the current/last entry tested. | |||
155 | /// </summary> | |||
156 | public ZipEntry Entry { | |||
157 | get { return entry_; } | |||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Get the number of errors detected so far. | |||
162 | /// </summary> | |||
163 | public int ErrorCount { | |||
164 | get { return errorCount_; } | |||
165 | } | |||
166 | | |||
167 | /// <summary> | |||
168 | /// Get the number of bytes tested so far for the current entry. | |||
169 | /// </summary> | |||
170 | public long BytesTested { | |||
171 | get { return bytesTested_; } | |||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Get a value indicating wether the last entry test was valid. | |||
176 | /// </summary> | |||
177 | public bool EntryValid { | |||
178 | get { return entryValid_; } | |||
179 | } | |||
180 | #endregion | |||
181 | | |||
182 | #region Internal API | |||
183 | internal void AddError() | |||
184 | { | |||
185 | errorCount_++; | |||
186 | entryValid_ = false; | |||
187 | } | |||
188 | | |||
189 | internal void SetOperation(TestOperation operation) | |||
190 | { | |||
191 | operation_ = operation; | |||
192 | } | |||
193 | | |||
194 | internal void SetEntry(ZipEntry entry) | |||
195 | { | |||
196 | entry_ = entry; | |||
197 | entryValid_ = true; | |||
198 | bytesTested_ = 0; | |||
199 | } | |||
200 | | |||
201 | internal void SetBytesTested(long value) | |||
202 | { | |||
203 | bytesTested_ = value; | |||
204 | } | |||
205 | #endregion | |||
206 | | |||
207 | #region Instance Fields | |||
208 | ZipFile file_; | |||
209 | ZipEntry entry_; | |||
210 | bool entryValid_; | |||
211 | int errorCount_; | |||
212 | long bytesTested_; | |||
213 | TestOperation operation_; | |||
214 | #endregion | |||
215 | } | |||
216 | | |||
217 | /// <summary> | |||
218 | /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if | |||
219 | /// </summary> | |||
220 | /// <remarks>If the message is non-null an error has occured. If the message is null | |||
221 | /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> | |||
222 | public delegate void ZipTestResultHandler(TestStatus status, string message); | |||
223 | #endregion | |||
224 | | |||
225 | #region Update Definitions | |||
226 | /// <summary> | |||
227 | /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. | |||
228 | /// </summary> | |||
229 | public enum FileUpdateMode | |||
230 | { | |||
231 | /// <summary> | |||
232 | /// Perform all updates on temporary files ensuring that the original file is saved. | |||
233 | /// </summary> | |||
234 | Safe, | |||
235 | /// <summary> | |||
236 | /// Update the archive directly, which is faster but less safe. | |||
237 | /// </summary> | |||
238 | Direct, | |||
239 | } | |||
240 | #endregion | |||
241 | | |||
242 | #region ZipFile Class | |||
243 | /// <summary> | |||
244 | /// This class represents a Zip archive. You can ask for the contained | |||
245 | /// entries, or get an input stream for a file entry. The entry is | |||
246 | /// automatically decompressed. | |||
247 | /// | |||
248 | /// You can also update the archive adding or deleting entries. | |||
249 | /// | |||
250 | /// This class is thread safe for input: You can open input streams for arbitrary | |||
251 | /// entries in different threads. | |||
252 | /// <br/> | |||
253 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
254 | /// </summary> | |||
255 | /// <example> | |||
256 | /// <code> | |||
257 | /// using System; | |||
258 | /// using System.Text; | |||
259 | /// using System.Collections; | |||
260 | /// using System.IO; | |||
261 | /// | |||
262 | /// using ICSharpCode.SharpZipLib.Zip; | |||
263 | /// | |||
264 | /// class MainClass | |||
265 | /// { | |||
266 | /// static public void Main(string[] args) | |||
267 | /// { | |||
268 | /// using (ZipFile zFile = new ZipFile(args[0])) { | |||
269 | /// Console.WriteLine("Listing of : " + zFile.Name); | |||
270 | /// Console.WriteLine(""); | |||
271 | /// Console.WriteLine("Raw Size Size Date Time Name"); | |||
272 | /// Console.WriteLine("-------- -------- -------- ------ ---------"); | |||
273 | /// foreach (ZipEntry e in zFile) { | |||
274 | /// if ( e.IsFile ) { | |||
275 | /// DateTime d = e.DateTime; | |||
276 | /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, | |||
277 | /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), | |||
278 | /// e.Name); | |||
279 | /// } | |||
280 | /// } | |||
281 | /// } | |||
282 | /// } | |||
283 | /// } | |||
284 | /// </code> | |||
285 | /// </example> | |||
286 | public class ZipFile : IEnumerable, IDisposable | |||
287 | { | |||
288 | #region KeyHandling | |||
289 | | |||
290 | /// <summary> | |||
291 | /// Delegate for handling keys/password setting during compresion/decompression. | |||
292 | /// </summary> | |||
293 | public delegate void KeysRequiredEventHandler( | |||
294 | object sender, | |||
295 | KeysRequiredEventArgs e | |||
296 | ); | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Event handler for handling encryption keys. | |||
300 | /// </summary> | |||
301 | public KeysRequiredEventHandler KeysRequired; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Handles getting of encryption keys when required. | |||
305 | /// </summary> | |||
306 | /// <param name="fileName">The file for which encryption keys are required.</param> | |||
307 | void OnKeysRequired(string fileName) | |||
308 | { | |||
309 | if (KeysRequired != null) { | |||
310 | var krea = new KeysRequiredEventArgs(fileName, key); | |||
311 | KeysRequired(this, krea); | |||
312 | key = krea.Key; | |||
313 | } | |||
314 | } | |||
315 | | |||
316 | /// <summary> | |||
317 | /// Get/set the encryption key value. | |||
318 | /// </summary> | |||
319 | byte[] Key { | |||
320 | get { return key; } | |||
321 | set { key = value; } | |||
322 | } | |||
323 | | |||
324 | /// <summary> | |||
325 | /// Password to be used for encrypting/decrypting files. | |||
326 | /// </summary> | |||
327 | /// <remarks>Set to null if no password is required.</remarks> | |||
328 | public string Password { | |||
329 | set { | |||
330 | if (string.IsNullOrEmpty(value)) { | |||
331 | key = null; | |||
332 | } else { | |||
333 | rawPassword_ = value; | |||
334 | key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); | |||
335 | } | |||
336 | } | |||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Get a value indicating wether encryption keys are currently available. | |||
341 | /// </summary> | |||
342 | bool HaveKeys { | |||
343 | get { return key != null; } | |||
344 | } | |||
345 | #endregion | |||
346 | | |||
347 | #region Constructors | |||
348 | /// <summary> | |||
349 | /// Opens a Zip file with the given name for reading. | |||
350 | /// </summary> | |||
351 | /// <param name="name">The name of the file to open.</param> | |||
352 | /// <exception cref="ArgumentNullException">The argument supplied is null.</exception> | |||
353 | /// <exception cref="IOException"> | |||
354 | /// An i/o error occurs | |||
355 | /// </exception> | |||
356 | /// <exception cref="ZipException"> | |||
357 | /// The file doesn't contain a valid zip archive. | |||
358 | /// </exception> | |||
359 | public ZipFile(string name) | |||
360 | { | |||
361 | if (name == null) { | |||
362 | throw new ArgumentNullException(nameof(name)); | |||
363 | } | |||
364 | | |||
365 | name_ = name; | |||
366 | | |||
367 | baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
368 | isStreamOwner = true; | |||
369 | | |||
370 | try { | |||
371 | ReadEntries(); | |||
372 | } catch { | |||
373 | DisposeInternal(true); | |||
374 | throw; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Opens a Zip file reading the given <see cref="FileStream"/>. | |||
380 | /// </summary> | |||
381 | /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> | |||
382 | /// <exception cref="ArgumentNullException">The supplied argument is null.</exception> | |||
383 | /// <exception cref="IOException"> | |||
384 | /// An i/o error occurs. | |||
385 | /// </exception> | |||
386 | /// <exception cref="ZipException"> | |||
387 | /// The file doesn't contain a valid zip archive. | |||
388 | /// </exception> | |||
389 | public ZipFile(FileStream file) | |||
390 | { | |||
391 | if (file == null) { | |||
392 | throw new ArgumentNullException(nameof(file)); | |||
393 | } | |||
394 | | |||
395 | if (!file.CanSeek) { | |||
396 | throw new ArgumentException("Stream is not seekable", nameof(file)); | |||
397 | } | |||
398 | | |||
399 | baseStream_ = file; | |||
400 | name_ = file.Name; | |||
401 | isStreamOwner = true; | |||
402 | | |||
403 | try { | |||
404 | ReadEntries(); | |||
405 | } catch { | |||
406 | DisposeInternal(true); | |||
407 | throw; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Opens a Zip file reading the given <see cref="Stream"/>. | |||
413 | /// </summary> | |||
414 | /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> | |||
415 | /// <exception cref="IOException"> | |||
416 | /// An i/o error occurs | |||
417 | /// </exception> | |||
418 | /// <exception cref="ZipException"> | |||
419 | /// The stream doesn't contain a valid zip archive.<br/> | |||
420 | /// </exception> | |||
421 | /// <exception cref="ArgumentException"> | |||
422 | /// The <see cref="Stream">stream</see> doesnt support seeking. | |||
423 | /// </exception> | |||
424 | /// <exception cref="ArgumentNullException"> | |||
425 | /// The <see cref="Stream">stream</see> argument is null. | |||
426 | /// </exception> | |||
427 | public ZipFile(Stream stream) | |||
428 | { | |||
429 | if (stream == null) { | |||
430 | throw new ArgumentNullException(nameof(stream)); | |||
431 | } | |||
432 | | |||
433 | if (!stream.CanSeek) { | |||
434 | throw new ArgumentException("Stream is not seekable", nameof(stream)); | |||
435 | } | |||
436 | | |||
437 | baseStream_ = stream; | |||
438 | isStreamOwner = true; | |||
439 | | |||
440 | if (baseStream_.Length > 0) { | |||
441 | try { | |||
442 | ReadEntries(); | |||
443 | } catch { | |||
444 | DisposeInternal(true); | |||
445 | throw; | |||
446 | } | |||
447 | } else { | |||
448 | entries_ = new ZipEntry[0]; | |||
449 | isNewArchive_ = true; | |||
450 | } | |||
451 | } | |||
452 | | |||
453 | /// <summary> | |||
454 | /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. | |||
455 | /// </summary> | |||
456 | internal ZipFile() | |||
457 | { | |||
458 | entries_ = new ZipEntry[0]; | |||
459 | isNewArchive_ = true; | |||
460 | } | |||
461 | | |||
462 | #endregion | |||
463 | | |||
464 | #region Destructors and Closing | |||
465 | /// <summary> | |||
466 | /// Finalize this instance. | |||
467 | /// </summary> | |||
468 | ~ZipFile() | |||
469 | { | |||
470 | Dispose(false); | |||
471 | } | |||
472 | | |||
473 | /// <summary> | |||
474 | /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying | |||
475 | /// Once closed, no further instance methods should be called. | |||
476 | /// </summary> | |||
477 | /// <exception cref="System.IO.IOException"> | |||
478 | /// An i/o error occurs. | |||
479 | /// </exception> | |||
480 | public void Close() | |||
481 | { | |||
482 | DisposeInternal(true); | |||
483 | GC.SuppressFinalize(this); | |||
484 | } | |||
485 | | |||
486 | #endregion | |||
487 | | |||
488 | #region Creators | |||
489 | /// <summary> | |||
490 | /// Create a new <see cref="ZipFile"/> whose data will be stored in a file. | |||
491 | /// </summary> | |||
492 | /// <param name="fileName">The name of the archive to create.</param> | |||
493 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
494 | /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception> | |||
495 | public static ZipFile Create(string fileName) | |||
496 | { | |||
497 | if (fileName == null) { | |||
498 | throw new ArgumentNullException(nameof(fileName)); | |||
499 | } | |||
500 | | |||
501 | FileStream fs = File.Create(fileName); | |||
502 | | |||
503 | var result = new ZipFile(); | |||
504 | result.name_ = fileName; | |||
505 | result.baseStream_ = fs; | |||
506 | result.isStreamOwner = true; | |||
507 | return result; | |||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. | |||
512 | /// </summary> | |||
513 | /// <param name="outStream">The stream providing data storage.</param> | |||
514 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
515 | /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception> | |||
516 | /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception> | |||
517 | public static ZipFile Create(Stream outStream) | |||
518 | { | |||
519 | if (outStream == null) { | |||
520 | throw new ArgumentNullException(nameof(outStream)); | |||
521 | } | |||
522 | | |||
523 | if (!outStream.CanWrite) { | |||
524 | throw new ArgumentException("Stream is not writeable", nameof(outStream)); | |||
525 | } | |||
526 | | |||
527 | if (!outStream.CanSeek) { | |||
528 | throw new ArgumentException("Stream is not seekable", nameof(outStream)); | |||
529 | } | |||
530 | | |||
531 | var result = new ZipFile(); | |||
532 | result.baseStream_ = outStream; | |||
533 | return result; | |||
534 | } | |||
535 | | |||
536 | #endregion | |||
537 | | |||
538 | #region Properties | |||
539 | /// <summary> | |||
540 | /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. | |||
541 | /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. | |||
542 | /// </summary> | |||
543 | /// <remarks> | |||
544 | /// The default value is true in all cases. | |||
545 | /// </remarks> | |||
546 | public bool IsStreamOwner { | |||
547 | get { return isStreamOwner; } | |||
548 | set { isStreamOwner = value; } | |||
549 | } | |||
550 | | |||
551 | /// <summary> | |||
552 | /// Get a value indicating wether | |||
553 | /// this archive is embedded in another file or not. | |||
554 | /// </summary> | |||
555 | public bool IsEmbeddedArchive { | |||
556 | // Not strictly correct in all circumstances currently | |||
557 | get { return offsetOfFirstEntry > 0; } | |||
558 | } | |||
559 | | |||
560 | /// <summary> | |||
561 | /// Get a value indicating that this archive is a new one. | |||
562 | /// </summary> | |||
563 | public bool IsNewArchive { | |||
564 | get { return isNewArchive_; } | |||
565 | } | |||
566 | | |||
567 | /// <summary> | |||
568 | /// Gets the comment for the zip file. | |||
569 | /// </summary> | |||
570 | public string ZipFileComment { | |||
571 | get { return comment_; } | |||
572 | } | |||
573 | | |||
574 | /// <summary> | |||
575 | /// Gets the name of this zip file. | |||
576 | /// </summary> | |||
577 | public string Name { | |||
578 | get { return name_; } | |||
579 | } | |||
580 | | |||
581 | /// <summary> | |||
582 | /// Gets the number of entries in this zip file. | |||
583 | /// </summary> | |||
584 | /// <exception cref="InvalidOperationException"> | |||
585 | /// The Zip file has been closed. | |||
586 | /// </exception> | |||
587 | [Obsolete("Use the Count property instead")] | |||
588 | public int Size { | |||
589 | get { | |||
590 | return entries_.Length; | |||
591 | } | |||
592 | } | |||
593 | | |||
594 | /// <summary> | |||
595 | /// Get the number of entries contained in this <see cref="ZipFile"/>. | |||
596 | /// </summary> | |||
597 | public long Count { | |||
598 | get { | |||
599 | return entries_.Length; | |||
600 | } | |||
601 | } | |||
602 | | |||
603 | /// <summary> | |||
604 | /// Indexer property for ZipEntries | |||
605 | /// </summary> | |||
606 | [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] | |||
607 | public ZipEntry this[int index] { | |||
608 | get { | |||
609 | return (ZipEntry)entries_[index].Clone(); | |||
610 | } | |||
611 | } | |||
612 | | |||
613 | #endregion | |||
614 | | |||
615 | #region Input Handling | |||
616 | /// <summary> | |||
617 | /// Gets an enumerator for the Zip entries in this Zip file. | |||
618 | /// </summary> | |||
619 | /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> | |||
620 | /// <exception cref="ObjectDisposedException"> | |||
621 | /// The Zip file has been closed. | |||
622 | /// </exception> | |||
623 | public IEnumerator GetEnumerator() | |||
624 | { | |||
625 | if (isDisposed_) { | |||
626 | throw new ObjectDisposedException("ZipFile"); | |||
627 | } | |||
628 | | |||
629 | return new ZipEntryEnumerator(entries_); | |||
630 | } | |||
631 | | |||
632 | /// <summary> | |||
633 | /// Return the index of the entry with a matching name | |||
634 | /// </summary> | |||
635 | /// <param name="name">Entry name to find</param> | |||
636 | /// <param name="ignoreCase">If true the comparison is case insensitive</param> | |||
637 | /// <returns>The index position of the matching entry or -1 if not found</returns> | |||
638 | /// <exception cref="ObjectDisposedException"> | |||
639 | /// The Zip file has been closed. | |||
640 | /// </exception> | |||
641 | public int FindEntry(string name, bool ignoreCase) | |||
642 | { | |||
643 | if (isDisposed_) { | |||
644 | throw new ObjectDisposedException("ZipFile"); | |||
645 | } | |||
646 | | |||
647 | // TODO: This will be slow as the next ice age for huge archives! | |||
648 | for (int i = 0; i < entries_.Length; i++) { | |||
649 | if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) { | |||
650 | return i; | |||
651 | } | |||
652 | } | |||
653 | return -1; | |||
654 | } | |||
655 | | |||
656 | /// <summary> | |||
657 | /// Searches for a zip entry in this archive with the given name. | |||
658 | /// String comparisons are case insensitive | |||
659 | /// </summary> | |||
660 | /// <param name="name"> | |||
661 | /// The name to find. May contain directory components separated by slashes ('/'). | |||
662 | /// </param> | |||
663 | /// <returns> | |||
664 | /// A clone of the zip entry, or null if no entry with that name exists. | |||
665 | /// </returns> | |||
666 | /// <exception cref="ObjectDisposedException"> | |||
667 | /// The Zip file has been closed. | |||
668 | /// </exception> | |||
669 | public ZipEntry GetEntry(string name) | |||
670 | { | |||
671 | if (isDisposed_) { | |||
672 | throw new ObjectDisposedException("ZipFile"); | |||
673 | } | |||
674 | | |||
675 | int index = FindEntry(name, true); | |||
676 | return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; | |||
677 | } | |||
678 | | |||
679 | /// <summary> | |||
680 | /// Gets an input stream for reading the given zip entry data in an uncompressed form. | |||
681 | /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). | |||
682 | /// </summary> | |||
683 | /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> | |||
684 | /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> | |||
685 | /// <exception cref="ObjectDisposedException"> | |||
686 | /// The ZipFile has already been closed | |||
687 | /// </exception> | |||
688 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
689 | /// The compression method for the entry is unknown | |||
690 | /// </exception> | |||
691 | /// <exception cref="IndexOutOfRangeException"> | |||
692 | /// The entry is not found in the ZipFile | |||
693 | /// </exception> | |||
694 | public Stream GetInputStream(ZipEntry entry) | |||
695 | { | |||
696 | if (entry == null) { | |||
697 | throw new ArgumentNullException(nameof(entry)); | |||
698 | } | |||
699 | | |||
700 | if (isDisposed_) { | |||
701 | throw new ObjectDisposedException("ZipFile"); | |||
702 | } | |||
703 | | |||
704 | long index = entry.ZipFileIndex; | |||
705 | if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { | |||
706 | index = FindEntry(entry.Name, true); | |||
707 | if (index < 0) { | |||
708 | throw new ZipException("Entry cannot be found"); | |||
709 | } | |||
710 | } | |||
711 | return GetInputStream(index); | |||
712 | } | |||
713 | | |||
714 | /// <summary> | |||
715 | /// Creates an input stream reading a zip entry | |||
716 | /// </summary> | |||
717 | /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> | |||
718 | /// <returns> | |||
719 | /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> | |||
720 | /// </returns> | |||
721 | /// <exception cref="ObjectDisposedException"> | |||
722 | /// The ZipFile has already been closed | |||
723 | /// </exception> | |||
724 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
725 | /// The compression method for the entry is unknown | |||
726 | /// </exception> | |||
727 | /// <exception cref="IndexOutOfRangeException"> | |||
728 | /// The entry is not found in the ZipFile | |||
729 | /// </exception> | |||
730 | public Stream GetInputStream(long entryIndex) | |||
731 | { | |||
732 | if (isDisposed_) { | |||
733 | throw new ObjectDisposedException("ZipFile"); | |||
734 | } | |||
735 | | |||
736 | long start = LocateEntry(entries_[entryIndex]); | |||
737 | CompressionMethod method = entries_[entryIndex].CompressionMethod; | |||
738 | Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); | |||
739 | | |||
740 | if (entries_[entryIndex].IsCrypted == true) { | |||
741 | result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); | |||
742 | if (result == null) { | |||
743 | throw new ZipException("Unable to decrypt this entry"); | |||
744 | } | |||
745 | } | |||
746 | | |||
747 | switch (method) { | |||
748 | case CompressionMethod.Stored: | |||
749 | // read as is. | |||
750 | break; | |||
751 | | |||
752 | case CompressionMethod.Deflated: | |||
753 | // No need to worry about ownership and closing as underlying stream close does nothing. | |||
754 | result = new InflaterInputStream(result, new Inflater(true)); | |||
755 | break; | |||
756 | | |||
757 | default: | |||
758 | throw new ZipException("Unsupported compression method " + method); | |||
759 | } | |||
760 | | |||
761 | return result; | |||
762 | } | |||
763 | | |||
764 | #endregion | |||
765 | | |||
766 | #region Archive Testing | |||
767 | /// <summary> | |||
768 | /// Test an archive for integrity/validity | |||
769 | /// </summary> | |||
770 | /// <param name="testData">Perform low level data Crc check</param> | |||
771 | /// <returns>true if all tests pass, false otherwise</returns> | |||
772 | /// <remarks>Testing will terminate on the first error found.</remarks> | |||
773 | public bool TestArchive(bool testData) | |||
774 | { | |||
775 | return TestArchive(testData, TestStrategy.FindFirstError, null); | |||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Test an archive for integrity/validity | |||
780 | /// </summary> | |||
781 | /// <param name="testData">Perform low level data Crc check</param> | |||
782 | /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> | |||
783 | /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> | |||
784 | /// <returns>true if all tests pass, false otherwise</returns> | |||
785 | /// <exception cref="ObjectDisposedException">The object has already been closed.</exception> | |||
786 | public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) | |||
787 | { | |||
788 | if (isDisposed_) { | |||
789 | throw new ObjectDisposedException("ZipFile"); | |||
790 | } | |||
791 | | |||
792 | var status = new TestStatus(this); | |||
793 | | |||
794 | if (resultHandler != null) { | |||
795 | resultHandler(status, null); | |||
796 | } | |||
797 | | |||
798 | HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; | |||
799 | | |||
800 | bool testing = true; | |||
801 | | |||
802 | try { | |||
803 | int entryIndex = 0; | |||
804 | | |||
805 | while (testing && (entryIndex < Count)) { | |||
806 | if (resultHandler != null) { | |||
807 | status.SetEntry(this[entryIndex]); | |||
808 | status.SetOperation(TestOperation.EntryHeader); | |||
809 | resultHandler(status, null); | |||
810 | } | |||
811 | | |||
812 | try { | |||
813 | TestLocalHeader(this[entryIndex], test); | |||
814 | } catch (ZipException ex) { | |||
815 | status.AddError(); | |||
816 | | |||
817 | if (resultHandler != null) { | |||
818 | resultHandler(status, | |||
819 | string.Format("Exception during test - '{0}'", ex.Message)); | |||
820 | } | |||
821 | | |||
822 | testing &= strategy != TestStrategy.FindFirstError; | |||
823 | } | |||
824 | | |||
825 | if (testing && testData && this[entryIndex].IsFile) { | |||
826 | if (resultHandler != null) { | |||
827 | status.SetOperation(TestOperation.EntryData); | |||
828 | resultHandler(status, null); | |||
829 | } | |||
830 | | |||
831 | var crc = new Crc32(); | |||
832 | | |||
833 | using (Stream entryStream = this.GetInputStream(this[entryIndex])) { | |||
834 | | |||
835 | byte[] buffer = new byte[4096]; | |||
836 | long totalBytes = 0; | |||
837 | int bytesRead; | |||
838 | while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { | |||
839 | crc.Update(buffer, 0, bytesRead); | |||
840 | | |||
841 | if (resultHandler != null) { | |||
842 | totalBytes += bytesRead; | |||
843 | status.SetBytesTested(totalBytes); | |||
844 | resultHandler(status, null); | |||
845 | } | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | if (this[entryIndex].Crc != crc.Value) { | |||
850 | status.AddError(); | |||
851 | | |||
852 | if (resultHandler != null) { | |||
853 | resultHandler(status, "CRC mismatch"); | |||
854 | } | |||
855 | | |||
856 | testing &= strategy != TestStrategy.FindFirstError; | |||
857 | } | |||
858 | | |||
859 | if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
860 | var helper = new ZipHelperStream(baseStream_); | |||
861 | var data = new DescriptorData(); | |||
862 | helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); | |||
863 | if (this[entryIndex].Crc != data.Crc) { | |||
864 | status.AddError(); | |||
865 | } | |||
866 | | |||
867 | if (this[entryIndex].CompressedSize != data.CompressedSize) { | |||
868 | status.AddError(); | |||
869 | } | |||
870 | | |||
871 | if (this[entryIndex].Size != data.Size) { | |||
872 | status.AddError(); | |||
873 | } | |||
874 | } | |||
875 | } | |||
876 | | |||
877 | if (resultHandler != null) { | |||
878 | status.SetOperation(TestOperation.EntryComplete); | |||
879 | resultHandler(status, null); | |||
880 | } | |||
881 | | |||
882 | entryIndex += 1; | |||
883 | } | |||
884 | | |||
885 | if (resultHandler != null) { | |||
886 | status.SetOperation(TestOperation.MiscellaneousTests); | |||
887 | resultHandler(status, null); | |||
888 | } | |||
889 | | |||
890 | // TODO: the 'Corrina Johns' test where local headers are missing from | |||
891 | // the central directory. They are therefore invisible to many archivers. | |||
892 | } catch (Exception ex) { | |||
893 | status.AddError(); | |||
894 | | |||
895 | if (resultHandler != null) { | |||
896 | resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); | |||
897 | } | |||
898 | } | |||
899 | | |||
900 | if (resultHandler != null) { | |||
901 | status.SetOperation(TestOperation.Complete); | |||
902 | status.SetEntry(null); | |||
903 | resultHandler(status, null); | |||
904 | } | |||
905 | | |||
906 | return (status.ErrorCount == 0); | |||
907 | } | |||
908 | | |||
909 | [Flags] | |||
910 | enum HeaderTest | |||
911 | { | |||
912 | Extract = 0x01, // Check that this header represents an entry whose data can be extracted | |||
913 | Header = 0x02, // Check that this header contents are valid | |||
914 | } | |||
915 | | |||
916 | /// <summary> | |||
917 | /// Test a local header against that provided from the central directory | |||
918 | /// </summary> | |||
919 | /// <param name="entry"> | |||
920 | /// The entry to test against | |||
921 | /// </param> | |||
922 | /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> | |||
923 | /// <returns>The offset of the entries data in the file</returns> | |||
924 | long TestLocalHeader(ZipEntry entry, HeaderTest tests) | |||
925 | { | |||
926 | lock (baseStream_) { | |||
927 | bool testHeader = (tests & HeaderTest.Header) != 0; | |||
928 | bool testData = (tests & HeaderTest.Extract) != 0; | |||
929 | | |||
930 | baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); | |||
931 | if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { | |||
932 | throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset) | |||
933 | } | |||
934 | | |||
935 | var extractVersion = (short)(ReadLEUshort() & 0x00ff); | |||
936 | var localFlags = (short)ReadLEUshort(); | |||
937 | var compressionMethod = (short)ReadLEUshort(); | |||
938 | var fileTime = (short)ReadLEUshort(); | |||
939 | var fileDate = (short)ReadLEUshort(); | |||
940 | uint crcValue = ReadLEUint(); | |||
941 | long compressedSize = ReadLEUint(); | |||
942 | long size = ReadLEUint(); | |||
943 | int storedNameLength = ReadLEUshort(); | |||
944 | int extraDataLength = ReadLEUshort(); | |||
945 | | |||
946 | byte[] nameData = new byte[storedNameLength]; | |||
947 | StreamUtils.ReadFully(baseStream_, nameData); | |||
948 | | |||
949 | byte[] extraData = new byte[extraDataLength]; | |||
950 | StreamUtils.ReadFully(baseStream_, extraData); | |||
951 | | |||
952 | var localExtraData = new ZipExtraData(extraData); | |||
953 | | |||
954 | // Extra data / zip64 checks | |||
955 | if (localExtraData.Find(1)) { | |||
956 | // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 | |||
957 | // and size or compressedSize = MaxValue, due to rogue creators. | |||
958 | | |||
959 | size = localExtraData.ReadLong(); | |||
960 | compressedSize = localExtraData.ReadLong(); | |||
961 | | |||
962 | if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
963 | // These may be valid if patched later | |||
964 | if ((size != -1) && (size != entry.Size)) { | |||
965 | throw new ZipException("Size invalid for descriptor"); | |||
966 | } | |||
967 | | |||
968 | if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) { | |||
969 | throw new ZipException("Compressed size invalid for descriptor"); | |||
970 | } | |||
971 | } | |||
972 | } else { | |||
973 | // No zip64 extra data but entry requires it. | |||
974 | if ((extractVersion >= ZipConstants.VersionZip64) && | |||
975 | (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) { | |||
976 | throw new ZipException("Required Zip64 extended information missing"); | |||
977 | } | |||
978 | } | |||
979 | | |||
980 | if (testData) { | |||
981 | if (entry.IsFile) { | |||
982 | if (!entry.IsCompressionMethodSupported()) { | |||
983 | throw new ZipException("Compression method not supported"); | |||
984 | } | |||
985 | | |||
986 | if ((extractVersion > ZipConstants.VersionMadeBy) | |||
987 | || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { | |||
988 | throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract | |||
989 | } | |||
990 | | |||
991 | if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance | |||
992 | throw new ZipException("The library does not support the zip version required to extract this entry"); | |||
993 | } | |||
994 | } | |||
995 | } | |||
996 | | |||
997 | if (testHeader) { | |||
998 | if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. | |||
999 | (extractVersion != 10) && | |||
1000 | (extractVersion != 11) && | |||
1001 | (extractVersion != 20) && | |||
1002 | (extractVersion != 21) && | |||
1003 | (extractVersion != 25) && | |||
1004 | (extractVersion != 27) && | |||
1005 | (extractVersion != 45) && | |||
1006 | (extractVersion != 46) && | |||
1007 | (extractVersion != 50) && | |||
1008 | (extractVersion != 51) && | |||
1009 | (extractVersion != 52) && | |||
1010 | (extractVersion != 61) && | |||
1011 | (extractVersion != 62) && | |||
1012 | (extractVersion != 63) | |||
1013 | ) { | |||
1014 | throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersi | |||
1015 | } | |||
1016 | | |||
1017 | // Local entry flags dont have reserved bit set on. | |||
1018 | if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R | |||
1019 | throw new ZipException("Reserved bit flags cannot be set."); | |||
1020 | } | |||
1021 | | |||
1022 | // Encryption requires extract version >= 20 | |||
1023 | if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { | |||
1024 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0}) | |||
1025 | } | |||
1026 | | |||
1027 | // Strong encryption requires encryption flag to be set and extract version >= 50. | |||
1028 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1029 | if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) { | |||
1030 | throw new ZipException("Strong encryption flag set but encryption flag is not set"); | |||
1031 | } | |||
1032 | | |||
1033 | if (extractVersion < 50) { | |||
1034 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0 | |||
1035 | } | |||
1036 | } | |||
1037 | | |||
1038 | // Patched entries require extract version >= 27 | |||
1039 | if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { | |||
1040 | throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); | |||
1041 | } | |||
1042 | | |||
1043 | // Central header flags match local entry flags. | |||
1044 | if (localFlags != entry.Flags) { | |||
1045 | throw new ZipException("Central header/local header flags mismatch"); | |||
1046 | } | |||
1047 | | |||
1048 | // Central header compression method matches local entry | |||
1049 | if (entry.CompressionMethod != (CompressionMethod)compressionMethod) { | |||
1050 | throw new ZipException("Central header/local header compression method mismatch"); | |||
1051 | } | |||
1052 | | |||
1053 | if (entry.Version != extractVersion) { | |||
1054 | throw new ZipException("Extract version mismatch"); | |||
1055 | } | |||
1056 | | |||
1057 | // Strong encryption and extract version match | |||
1058 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1059 | if (extractVersion < 62) { | |||
1060 | throw new ZipException("Strong encryption flag set but version not high enough"); | |||
1061 | } | |||
1062 | } | |||
1063 | | |||
1064 | if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) { | |||
1065 | if ((fileTime != 0) || (fileDate != 0)) { | |||
1066 | throw new ZipException("Header masked set but date/time values non-zero"); | |||
1067 | } | |||
1068 | } | |||
1069 | | |||
1070 | if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) { | |||
1071 | if (crcValue != (uint)entry.Crc) { | |||
1072 | throw new ZipException("Central header/local header crc mismatch"); | |||
1073 | } | |||
1074 | } | |||
1075 | | |||
1076 | // Crc valid for empty entry. | |||
1077 | // This will also apply to streamed entries where size isnt known and the header cant be patched | |||
1078 | if ((size == 0) && (compressedSize == 0)) { | |||
1079 | if (crcValue != 0) { | |||
1080 | throw new ZipException("Invalid CRC for empty entry"); | |||
1081 | } | |||
1082 | } | |||
1083 | | |||
1084 | // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS str | |||
1085 | // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably | |||
1086 | if (entry.Name.Length > storedNameLength) { | |||
1087 | throw new ZipException("File name length mismatch"); | |||
1088 | } | |||
1089 | | |||
1090 | // Name data has already been read convert it and compare. | |||
1091 | string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); | |||
1092 | | |||
1093 | // Central directory and local entry name match | |||
1094 | if (localName != entry.Name) { | |||
1095 | throw new ZipException("Central header and local header file name mismatch"); | |||
1096 | } | |||
1097 | | |||
1098 | // Directories have zero actual size but can have compressed size | |||
1099 | if (entry.IsDirectory) { | |||
1100 | if (size > 0) { | |||
1101 | throw new ZipException("Directory cannot have size"); | |||
1102 | } | |||
1103 | | |||
1104 | // There may be other cases where the compressed size can be greater than this? | |||
1105 | // If so until details are known we will be strict. | |||
1106 | if (entry.IsCrypted) { | |||
1107 | if (compressedSize > ZipConstants.CryptoHeaderSize + 2) { | |||
1108 | throw new ZipException("Directory compressed size invalid"); | |||
1109 | } | |||
1110 | } else if (compressedSize > 2) { | |||
1111 | // When not compressed the directory size can validly be 2 bytes | |||
1112 | // if the true size wasnt known when data was originally being written. | |||
1113 | // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes | |||
1114 | throw new ZipException("Directory compressed size invalid"); | |||
1115 | } | |||
1116 | } | |||
1117 | | |||
1118 | if (!ZipNameTransform.IsValidName(localName, true)) { | |||
1119 | throw new ZipException("Name is invalid"); | |||
1120 | } | |||
1121 | } | |||
1122 | | |||
1123 | // Tests that apply to both data and header. | |||
1124 | | |||
1125 | // Size can be verified only if it is known in the local header. | |||
1126 | // it will always be known in the central header. | |||
1127 | if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || | |||
1128 | ((size > 0 || compressedSize > 0) && entry.Size > 0)) { | |||
1129 | | |||
1130 | if ((size != 0) | |||
1131 | && (size != entry.Size)) { | |||
1132 | throw new ZipException( | |||
1133 | string.Format("Size mismatch between central header({0}) and local header({1})", | |||
1134 | entry.Size, size)); | |||
1135 | } | |||
1136 | | |||
1137 | if ((compressedSize != 0) | |||
1138 | && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) { | |||
1139 | throw new ZipException( | |||
1140 | string.Format("Compressed size mismatch between central header({0}) and local header({1})", | |||
1141 | entry.CompressedSize, compressedSize)); | |||
1142 | } | |||
1143 | } | |||
1144 | | |||
1145 | int extraLength = storedNameLength + extraDataLength; | |||
1146 | return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; | |||
1147 | } | |||
1148 | } | |||
1149 | | |||
1150 | #endregion | |||
1151 | | |||
1152 | #region Updating | |||
1153 | | |||
1154 | const int DefaultBufferSize = 4096; | |||
1155 | | |||
1156 | /// <summary> | |||
1157 | /// The kind of update to apply. | |||
1158 | /// </summary> | |||
1159 | enum UpdateCommand | |||
1160 | { | |||
1161 | Copy, // Copy original file contents. | |||
1162 | Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. | |||
1163 | Add, // Add a new file to the archive. | |||
1164 | } | |||
1165 | | |||
1166 | #region Properties | |||
1167 | /// <summary> | |||
1168 | /// Get / set the <see cref="INameTransform"/> to apply to names when updating. | |||
1169 | /// </summary> | |||
1170 | public INameTransform NameTransform { | |||
1171 | get { | |||
1172 | return updateEntryFactory_.NameTransform; | |||
1173 | } | |||
1174 | | |||
1175 | set { | |||
1176 | updateEntryFactory_.NameTransform = value; | |||
1177 | } | |||
1178 | } | |||
1179 | | |||
1180 | /// <summary> | |||
1181 | /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values | |||
1182 | /// during updates. | |||
1183 | /// </summary> | |||
1184 | public IEntryFactory EntryFactory { | |||
1185 | get { | |||
1186 | return updateEntryFactory_; | |||
1187 | } | |||
1188 | | |||
1189 | set { | |||
1190 | if (value == null) { | |||
1191 | updateEntryFactory_ = new ZipEntryFactory(); | |||
1192 | } else { | |||
1193 | updateEntryFactory_ = value; | |||
1194 | } | |||
1195 | } | |||
1196 | } | |||
1197 | | |||
1198 | /// <summary> | |||
1199 | /// Get /set the buffer size to be used when updating this zip file. | |||
1200 | /// </summary> | |||
1201 | public int BufferSize { | |||
1202 | get { return bufferSize_; } | |||
1203 | set { | |||
1204 | if (value < 1024) { | |||
1205 | throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); | |||
1206 | } | |||
1207 | | |||
1208 | if (bufferSize_ != value) { | |||
1209 | bufferSize_ = value; | |||
1210 | copyBuffer_ = null; | |||
1211 | } | |||
1212 | } | |||
1213 | } | |||
1214 | | |||
1215 | /// <summary> | |||
1216 | /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. | |||
1217 | /// </summary> | |||
1218 | public bool IsUpdating { | |||
1219 | get { return updates_ != null; } | |||
1220 | } | |||
1221 | | |||
1222 | /// <summary> | |||
1223 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
1224 | /// </summary> | |||
1225 | public UseZip64 UseZip64 { | |||
1226 | get { return useZip64_; } | |||
1227 | set { useZip64_ = value; } | |||
1228 | } | |||
1229 | | |||
1230 | #endregion | |||
1231 | | |||
1232 | #region Immediate updating | |||
1233 | // TBD: Direct form of updating | |||
1234 | // | |||
1235 | // public void Update(IEntryMatcher deleteMatcher) | |||
1236 | // { | |||
1237 | // } | |||
1238 | // | |||
1239 | // public void Update(IScanner addScanner) | |||
1240 | // { | |||
1241 | // } | |||
1242 | #endregion | |||
1243 | | |||
1244 | #region Deferred Updating | |||
1245 | /// <summary> | |||
1246 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1247 | /// </summary> | |||
1248 | /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</p | |||
1249 | /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param | |||
1250 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1251 | /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception> | |||
1252 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1253 | public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) | |||
1254 | { | |||
1255 | if (archiveStorage == null) { | |||
1256 | throw new ArgumentNullException(nameof(archiveStorage)); | |||
1257 | } | |||
1258 | | |||
1259 | if (dataSource == null) { | |||
1260 | throw new ArgumentNullException(nameof(dataSource)); | |||
1261 | } | |||
1262 | | |||
1263 | if (isDisposed_) { | |||
1264 | throw new ObjectDisposedException("ZipFile"); | |||
1265 | } | |||
1266 | | |||
1267 | if (IsEmbeddedArchive) { | |||
1268 | throw new ZipException("Cannot update embedded/SFX archives"); | |||
1269 | } | |||
1270 | | |||
1271 | archiveStorage_ = archiveStorage; | |||
1272 | updateDataSource_ = dataSource; | |||
1273 | | |||
1274 | // NOTE: the baseStream_ may not currently support writing or seeking. | |||
1275 | | |||
1276 | updateIndex_ = new Hashtable(); | |||
1277 | | |||
1278 | updates_ = new ArrayList(entries_.Length); | |||
1279 | foreach (ZipEntry entry in entries_) { | |||
1280 | int index = updates_.Add(new ZipUpdate(entry)); | |||
1281 | updateIndex_.Add(entry.Name, index); | |||
1282 | } | |||
1283 | | |||
1284 | // We must sort by offset before using offset's calculated sizes | |||
1285 | updates_.Sort(new UpdateComparer()); | |||
1286 | | |||
1287 | int idx = 0; | |||
1288 | foreach (ZipUpdate update in updates_) { | |||
1289 | //If last entry, there is no next entry offset to use | |||
1290 | if (idx == updates_.Count - 1) | |||
1291 | break; | |||
1292 | | |||
1293 | update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; | |||
1294 | idx++; | |||
1295 | } | |||
1296 | updateCount_ = updates_.Count; | |||
1297 | | |||
1298 | contentsEdited_ = false; | |||
1299 | commentEdited_ = false; | |||
1300 | newComment_ = null; | |||
1301 | } | |||
1302 | | |||
1303 | /// <summary> | |||
1304 | /// Begin updating to this <see cref="ZipFile"/> archive. | |||
1305 | /// </summary> | |||
1306 | /// <param name="archiveStorage">The storage to use during the update.</param> | |||
1307 | public void BeginUpdate(IArchiveStorage archiveStorage) | |||
1308 | { | |||
1309 | BeginUpdate(archiveStorage, new DynamicDiskDataSource()); | |||
1310 | } | |||
1311 | | |||
1312 | /// <summary> | |||
1313 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1314 | /// </summary> | |||
1315 | /// <seealso cref="BeginUpdate(IArchiveStorage)"/> | |||
1316 | /// <seealso cref="CommitUpdate"></seealso> | |||
1317 | /// <seealso cref="AbortUpdate"></seealso> | |||
1318 | public void BeginUpdate() | |||
1319 | { | |||
1320 | if (Name == null) { | |||
1321 | BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); | |||
1322 | } else { | |||
1323 | BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); | |||
1324 | } | |||
1325 | } | |||
1326 | | |||
1327 | /// <summary> | |||
1328 | /// Commit current updates, updating this archive. | |||
1329 | /// </summary> | |||
1330 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1331 | /// <seealso cref="AbortUpdate"></seealso> | |||
1332 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1333 | public void CommitUpdate() | |||
1334 | { | |||
1335 | if (isDisposed_) { | |||
1336 | throw new ObjectDisposedException("ZipFile"); | |||
1337 | } | |||
1338 | | |||
1339 | CheckUpdating(); | |||
1340 | | |||
1341 | try { | |||
1342 | updateIndex_.Clear(); | |||
1343 | updateIndex_ = null; | |||
1344 | | |||
1345 | if (contentsEdited_) { | |||
1346 | RunUpdates(); | |||
1347 | } else if (commentEdited_) { | |||
1348 | UpdateCommentOnly(); | |||
1349 | } else { | |||
1350 | // Create an empty archive if none existed originally. | |||
1351 | if (entries_.Length == 0) { | |||
1352 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
1353 | using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) { | |||
1354 | zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); | |||
1355 | } | |||
1356 | } | |||
1357 | } | |||
1358 | | |||
1359 | } finally { | |||
1360 | PostUpdateCleanup(); | |||
1361 | } | |||
1362 | } | |||
1363 | | |||
1364 | /// <summary> | |||
1365 | /// Abort updating leaving the archive unchanged. | |||
1366 | /// </summary> | |||
1367 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1368 | /// <seealso cref="CommitUpdate"></seealso> | |||
1369 | public void AbortUpdate() | |||
1370 | { | |||
1371 | PostUpdateCleanup(); | |||
1372 | } | |||
1373 | | |||
1374 | /// <summary> | |||
1375 | /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. | |||
1376 | /// </summary> | |||
1377 | /// <param name="comment">The comment to record.</param> | |||
1378 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1379 | public void SetComment(string comment) | |||
1380 | { | |||
1381 | if (isDisposed_) { | |||
1382 | throw new ObjectDisposedException("ZipFile"); | |||
1383 | } | |||
1384 | | |||
1385 | CheckUpdating(); | |||
1386 | | |||
1387 | newComment_ = new ZipString(comment); | |||
1388 | | |||
1389 | if (newComment_.RawLength > 0xffff) { | |||
1390 | newComment_ = null; | |||
1391 | throw new ZipException("Comment length exceeds maximum - 65535"); | |||
1392 | } | |||
1393 | | |||
1394 | // We dont take account of the original and current comment appearing to be the same | |||
1395 | // as encoding may be different. | |||
1396 | commentEdited_ = true; | |||
1397 | } | |||
1398 | | |||
1399 | #endregion | |||
1400 | | |||
1401 | #region Adding Entries | |||
1402 | | |||
1403 | void AddUpdate(ZipUpdate update) | |||
1404 | { | |||
1405 | contentsEdited_ = true; | |||
1406 | | |||
1407 | int index = FindExistingUpdate(update.Entry.Name); | |||
1408 | | |||
1409 | if (index >= 0) { | |||
1410 | if (updates_[index] == null) { | |||
1411 | updateCount_ += 1; | |||
1412 | } | |||
1413 | | |||
1414 | // Direct replacement is faster than delete and add. | |||
1415 | updates_[index] = update; | |||
1416 | } else { | |||
1417 | index = updates_.Add(update); | |||
1418 | updateCount_ += 1; | |||
1419 | updateIndex_.Add(update.Entry.Name, index); | |||
1420 | } | |||
1421 | } | |||
1422 | | |||
1423 | /// <summary> | |||
1424 | /// Add a new entry to the archive. | |||
1425 | /// </summary> | |||
1426 | /// <param name="fileName">The name of the file to add.</param> | |||
1427 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1428 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> | |||
1429 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1430 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1431 | /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception> | |||
1432 | public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) | |||
1433 | { | |||
1434 | if (fileName == null) { | |||
1435 | throw new ArgumentNullException(nameof(fileName)); | |||
1436 | } | |||
1437 | | |||
1438 | if (isDisposed_) { | |||
1439 | throw new ObjectDisposedException("ZipFile"); | |||
1440 | } | |||
1441 | | |||
1442 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1443 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1444 | } | |||
1445 | | |||
1446 | CheckUpdating(); | |||
1447 | contentsEdited_ = true; | |||
1448 | | |||
1449 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1450 | entry.IsUnicodeText = useUnicodeText; | |||
1451 | entry.CompressionMethod = compressionMethod; | |||
1452 | | |||
1453 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1454 | } | |||
1455 | | |||
1456 | /// <summary> | |||
1457 | /// Add a new entry to the archive. | |||
1458 | /// </summary> | |||
1459 | /// <param name="fileName">The name of the file to add.</param> | |||
1460 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1461 | /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception> | |||
1462 | /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception> | |||
1463 | public void Add(string fileName, CompressionMethod compressionMethod) | |||
1464 | { | |||
1465 | if (fileName == null) { | |||
1466 | throw new ArgumentNullException(nameof(fileName)); | |||
1467 | } | |||
1468 | | |||
1469 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1470 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1471 | } | |||
1472 | | |||
1473 | CheckUpdating(); | |||
1474 | contentsEdited_ = true; | |||
1475 | | |||
1476 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1477 | entry.CompressionMethod = compressionMethod; | |||
1478 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1479 | } | |||
1480 | | |||
1481 | /// <summary> | |||
1482 | /// Add a file to the archive. | |||
1483 | /// </summary> | |||
1484 | /// <param name="fileName">The name of the file to add.</param> | |||
1485 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1486 | public void Add(string fileName) | |||
1487 | { | |||
1488 | if (fileName == null) { | |||
1489 | throw new ArgumentNullException(nameof(fileName)); | |||
1490 | } | |||
1491 | | |||
1492 | CheckUpdating(); | |||
1493 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); | |||
1494 | } | |||
1495 | | |||
1496 | /// <summary> | |||
1497 | /// Add a file to the archive. | |||
1498 | /// </summary> | |||
1499 | /// <param name="fileName">The name of the file to add.</param> | |||
1500 | /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param> | |||
1501 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1502 | public void Add(string fileName, string entryName) | |||
1503 | { | |||
1504 | if (fileName == null) { | |||
1505 | throw new ArgumentNullException(nameof(fileName)); | |||
1506 | } | |||
1507 | | |||
1508 | if (entryName == null) { | |||
1509 | throw new ArgumentNullException(nameof(entryName)); | |||
1510 | } | |||
1511 | | |||
1512 | CheckUpdating(); | |||
1513 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); | |||
1514 | } | |||
1515 | | |||
1516 | | |||
1517 | /// <summary> | |||
1518 | /// Add a file entry with data. | |||
1519 | /// </summary> | |||
1520 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1521 | /// <param name="entryName">The name to give to the entry.</param> | |||
1522 | public void Add(IStaticDataSource dataSource, string entryName) | |||
1523 | { | |||
1524 | if (dataSource == null) { | |||
1525 | throw new ArgumentNullException(nameof(dataSource)); | |||
1526 | } | |||
1527 | | |||
1528 | if (entryName == null) { | |||
1529 | throw new ArgumentNullException(nameof(entryName)); | |||
1530 | } | |||
1531 | | |||
1532 | CheckUpdating(); | |||
1533 | AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); | |||
1534 | } | |||
1535 | | |||
1536 | /// <summary> | |||
1537 | /// Add a file entry with data. | |||
1538 | /// </summary> | |||
1539 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1540 | /// <param name="entryName">The name to give to the entry.</param> | |||
1541 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1542 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
1543 | { | |||
1544 | if (dataSource == null) { | |||
1545 | throw new ArgumentNullException(nameof(dataSource)); | |||
1546 | } | |||
1547 | | |||
1548 | if (entryName == null) { | |||
1549 | throw new ArgumentNullException(nameof(entryName)); | |||
1550 | } | |||
1551 | | |||
1552 | CheckUpdating(); | |||
1553 | | |||
1554 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1555 | entry.CompressionMethod = compressionMethod; | |||
1556 | | |||
1557 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1558 | } | |||
1559 | | |||
1560 | /// <summary> | |||
1561 | /// Add a file entry with data. | |||
1562 | /// </summary> | |||
1563 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1564 | /// <param name="entryName">The name to give to the entry.</param> | |||
1565 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1566 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> | |||
1567 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicode | |||
1568 | { | |||
1569 | if (dataSource == null) { | |||
1570 | throw new ArgumentNullException(nameof(dataSource)); | |||
1571 | } | |||
1572 | | |||
1573 | if (entryName == null) { | |||
1574 | throw new ArgumentNullException(nameof(entryName)); | |||
1575 | } | |||
1576 | | |||
1577 | CheckUpdating(); | |||
1578 | | |||
1579 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1580 | entry.IsUnicodeText = useUnicodeText; | |||
1581 | entry.CompressionMethod = compressionMethod; | |||
1582 | | |||
1583 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1584 | } | |||
1585 | | |||
1586 | /// <summary> | |||
1587 | /// Add a <see cref="ZipEntry"/> that contains no data. | |||
1588 | /// </summary> | |||
1589 | /// <param name="entry">The entry to add.</param> | |||
1590 | /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> | |||
1591 | public void Add(ZipEntry entry) | |||
1592 | { | |||
1593 | if (entry == null) { | |||
1594 | throw new ArgumentNullException(nameof(entry)); | |||
1595 | } | |||
1596 | | |||
1597 | CheckUpdating(); | |||
1598 | | |||
1599 | if ((entry.Size != 0) || (entry.CompressedSize != 0)) { | |||
1600 | throw new ZipException("Entry cannot have any data"); | |||
1601 | } | |||
1602 | | |||
1603 | AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); | |||
1604 | } | |||
1605 | | |||
1606 | /// <summary> | |||
1607 | /// Add a directory entry to the archive. | |||
1608 | /// </summary> | |||
1609 | /// <param name="directoryName">The directory to add.</param> | |||
1610 | public void AddDirectory(string directoryName) | |||
1611 | { | |||
1612 | if (directoryName == null) { | |||
1613 | throw new ArgumentNullException(nameof(directoryName)); | |||
1614 | } | |||
1615 | | |||
1616 | CheckUpdating(); | |||
1617 | | |||
1618 | ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); | |||
1619 | AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); | |||
1620 | } | |||
1621 | | |||
1622 | #endregion | |||
1623 | | |||
1624 | #region Modifying Entries | |||
1625 | /* Modify not yet ready for public consumption. | |||
1626 | Direct modification of an entry should not overwrite original data before its read. | |||
1627 | Safe mode is trivial in this sense. | |||
1628 | public void Modify(ZipEntry original, ZipEntry updated) | |||
1629 | { | |||
1630 | if ( original == null ) { | |||
1631 | throw new ArgumentNullException("original"); | |||
1632 | } | |||
1633 | | |||
1634 | if ( updated == null ) { | |||
1635 | throw new ArgumentNullException("updated"); | |||
1636 | } | |||
1637 | | |||
1638 | CheckUpdating(); | |||
1639 | contentsEdited_ = true; | |||
1640 | updates_.Add(new ZipUpdate(original, updated)); | |||
1641 | } | |||
1642 | */ | |||
1643 | #endregion | |||
1644 | | |||
1645 | #region Deleting Entries | |||
1646 | /// <summary> | |||
1647 | /// Delete an entry by name | |||
1648 | /// </summary> | |||
1649 | /// <param name="fileName">The filename to delete</param> | |||
1650 | /// <returns>True if the entry was found and deleted; false otherwise.</returns> | |||
1651 | public bool Delete(string fileName) | |||
1652 | { | |||
1653 | if (fileName == null) { | |||
1654 | throw new ArgumentNullException(nameof(fileName)); | |||
1655 | } | |||
1656 | | |||
1657 | CheckUpdating(); | |||
1658 | | |||
1659 | bool result = false; | |||
1660 | int index = FindExistingUpdate(fileName); | |||
1661 | if ((index >= 0) && (updates_[index] != null)) { | |||
1662 | result = true; | |||
1663 | contentsEdited_ = true; | |||
1664 | updates_[index] = null; | |||
1665 | updateCount_ -= 1; | |||
1666 | } else { | |||
1667 | throw new ZipException("Cannot find entry to delete"); | |||
1668 | } | |||
1669 | return result; | |||
1670 | } | |||
1671 | | |||
1672 | /// <summary> | |||
1673 | /// Delete a <see cref="ZipEntry"/> from the archive. | |||
1674 | /// </summary> | |||
1675 | /// <param name="entry">The entry to delete.</param> | |||
1676 | public void Delete(ZipEntry entry) | |||
1677 | { | |||
1678 | if (entry == null) { | |||
1679 | throw new ArgumentNullException(nameof(entry)); | |||
1680 | } | |||
1681 | | |||
1682 | CheckUpdating(); | |||
1683 | | |||
1684 | int index = FindExistingUpdate(entry); | |||
1685 | if (index >= 0) { | |||
1686 | contentsEdited_ = true; | |||
1687 | updates_[index] = null; | |||
1688 | updateCount_ -= 1; | |||
1689 | } else { | |||
1690 | throw new ZipException("Cannot find entry to delete"); | |||
1691 | } | |||
1692 | } | |||
1693 | | |||
1694 | #endregion | |||
1695 | | |||
1696 | #region Update Support | |||
1697 | | |||
1698 | #region Writing Values/Headers | |||
1699 | void WriteLEShort(int value) | |||
1700 | { | |||
1701 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1702 | baseStream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
1703 | } | |||
1704 | | |||
1705 | /// <summary> | |||
1706 | /// Write an unsigned short in little endian byte order. | |||
1707 | /// </summary> | |||
1708 | void WriteLEUshort(ushort value) | |||
1709 | { | |||
1710 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1711 | baseStream_.WriteByte((byte)(value >> 8)); | |||
1712 | } | |||
1713 | | |||
1714 | /// <summary> | |||
1715 | /// Write an int in little endian byte order. | |||
1716 | /// </summary> | |||
1717 | void WriteLEInt(int value) | |||
1718 | { | |||
1719 | WriteLEShort(value & 0xffff); | |||
1720 | WriteLEShort(value >> 16); | |||
1721 | } | |||
1722 | | |||
1723 | /// <summary> | |||
1724 | /// Write an unsigned int in little endian byte order. | |||
1725 | /// </summary> | |||
1726 | void WriteLEUint(uint value) | |||
1727 | { | |||
1728 | WriteLEUshort((ushort)(value & 0xffff)); | |||
1729 | WriteLEUshort((ushort)(value >> 16)); | |||
1730 | } | |||
1731 | | |||
1732 | /// <summary> | |||
1733 | /// Write a long in little endian byte order. | |||
1734 | /// </summary> | |||
1735 | void WriteLeLong(long value) | |||
1736 | { | |||
1737 | WriteLEInt((int)(value & 0xffffffff)); | |||
1738 | WriteLEInt((int)(value >> 32)); | |||
1739 | } | |||
1740 | | |||
1741 | void WriteLEUlong(ulong value) | |||
1742 | { | |||
1743 | WriteLEUint((uint)(value & 0xffffffff)); | |||
1744 | WriteLEUint((uint)(value >> 32)); | |||
1745 | } | |||
1746 | | |||
1747 | void WriteLocalEntryHeader(ZipUpdate update) | |||
1748 | { | |||
1749 | ZipEntry entry = update.OutEntry; | |||
1750 | | |||
1751 | // TODO: Local offset will require adjusting for multi-disk zip files. | |||
1752 | entry.Offset = baseStream_.Position; | |||
1753 | | |||
1754 | // TODO: Need to clear any entry flags that dont make sense or throw an exception here. | |||
1755 | if (update.Command != UpdateCommand.Copy) { | |||
1756 | if (entry.CompressionMethod == CompressionMethod.Deflated) { | |||
1757 | if (entry.Size == 0) { | |||
1758 | // No need to compress - no data. | |||
1759 | entry.CompressedSize = entry.Size; | |||
1760 | entry.Crc = 0; | |||
1761 | entry.CompressionMethod = CompressionMethod.Stored; | |||
1762 | } | |||
1763 | } else if (entry.CompressionMethod == CompressionMethod.Stored) { | |||
1764 | entry.Flags &= ~(int)GeneralBitFlags.Descriptor; | |||
1765 | } | |||
1766 | | |||
1767 | if (HaveKeys) { | |||
1768 | entry.IsCrypted = true; | |||
1769 | if (entry.Crc < 0) { | |||
1770 | entry.Flags |= (int)GeneralBitFlags.Descriptor; | |||
1771 | } | |||
1772 | } else { | |||
1773 | entry.IsCrypted = false; | |||
1774 | } | |||
1775 | | |||
1776 | switch (useZip64_) { | |||
1777 | case UseZip64.Dynamic: | |||
1778 | if (entry.Size < 0) { | |||
1779 | entry.ForceZip64(); | |||
1780 | } | |||
1781 | break; | |||
1782 | | |||
1783 | case UseZip64.On: | |||
1784 | entry.ForceZip64(); | |||
1785 | break; | |||
1786 | | |||
1787 | case UseZip64.Off: | |||
1788 | // Do nothing. The entry itself may be using Zip64 independantly. | |||
1789 | break; | |||
1790 | } | |||
1791 | } | |||
1792 | | |||
1793 | // Write the local file header | |||
1794 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
1795 | | |||
1796 | WriteLEShort(entry.Version); | |||
1797 | WriteLEShort(entry.Flags); | |||
1798 | | |||
1799 | WriteLEShort((byte)entry.CompressionMethod); | |||
1800 | WriteLEInt((int)entry.DosTime); | |||
1801 | | |||
1802 | if (!entry.HasCrc) { | |||
1803 | // Note patch address for updating CRC later. | |||
1804 | update.CrcPatchOffset = baseStream_.Position; | |||
1805 | WriteLEInt((int)0); | |||
1806 | } else { | |||
1807 | WriteLEInt(unchecked((int)entry.Crc)); | |||
1808 | } | |||
1809 | | |||
1810 | if (entry.LocalHeaderRequiresZip64) { | |||
1811 | WriteLEInt(-1); | |||
1812 | WriteLEInt(-1); | |||
1813 | } else { | |||
1814 | if ((entry.CompressedSize < 0) || (entry.Size < 0)) { | |||
1815 | update.SizePatchOffset = baseStream_.Position; | |||
1816 | } | |||
1817 | | |||
1818 | WriteLEInt((int)entry.CompressedSize); | |||
1819 | WriteLEInt((int)entry.Size); | |||
1820 | } | |||
1821 | | |||
1822 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1823 | | |||
1824 | if (name.Length > 0xFFFF) { | |||
1825 | throw new ZipException("Entry name too long."); | |||
1826 | } | |||
1827 | | |||
1828 | var ed = new ZipExtraData(entry.ExtraData); | |||
1829 | | |||
1830 | if (entry.LocalHeaderRequiresZip64) { | |||
1831 | ed.StartNewEntry(); | |||
1832 | | |||
1833 | // Local entry header always includes size and compressed size. | |||
1834 | // NOTE the order of these fields is reversed when compared to the normal headers! | |||
1835 | ed.AddLeLong(entry.Size); | |||
1836 | ed.AddLeLong(entry.CompressedSize); | |||
1837 | ed.AddNewEntry(1); | |||
1838 | } else { | |||
1839 | ed.Delete(1); | |||
1840 | } | |||
1841 | | |||
1842 | entry.ExtraData = ed.GetEntryData(); | |||
1843 | | |||
1844 | WriteLEShort(name.Length); | |||
1845 | WriteLEShort(entry.ExtraData.Length); | |||
1846 | | |||
1847 | if (name.Length > 0) { | |||
1848 | baseStream_.Write(name, 0, name.Length); | |||
1849 | } | |||
1850 | | |||
1851 | if (entry.LocalHeaderRequiresZip64) { | |||
1852 | if (!ed.Find(1)) { | |||
1853 | throw new ZipException("Internal error cannot find extra data"); | |||
1854 | } | |||
1855 | | |||
1856 | update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; | |||
1857 | } | |||
1858 | | |||
1859 | if (entry.ExtraData.Length > 0) { | |||
1860 | baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); | |||
1861 | } | |||
1862 | } | |||
1863 | | |||
1864 | int WriteCentralDirectoryHeader(ZipEntry entry) | |||
1865 | { | |||
1866 | if (entry.CompressedSize < 0) { | |||
1867 | throw new ZipException("Attempt to write central directory entry with unknown csize"); | |||
1868 | } | |||
1869 | | |||
1870 | if (entry.Size < 0) { | |||
1871 | throw new ZipException("Attempt to write central directory entry with unknown size"); | |||
1872 | } | |||
1873 | | |||
1874 | if (entry.Crc < 0) { | |||
1875 | throw new ZipException("Attempt to write central directory entry with unknown crc"); | |||
1876 | } | |||
1877 | | |||
1878 | // Write the central file header | |||
1879 | WriteLEInt(ZipConstants.CentralHeaderSignature); | |||
1880 | | |||
1881 | // Version made by | |||
1882 | WriteLEShort(ZipConstants.VersionMadeBy); | |||
1883 | | |||
1884 | // Version required to extract | |||
1885 | WriteLEShort(entry.Version); | |||
1886 | | |||
1887 | WriteLEShort(entry.Flags); | |||
1888 | | |||
1889 | unchecked { | |||
1890 | WriteLEShort((byte)entry.CompressionMethod); | |||
1891 | WriteLEInt((int)entry.DosTime); | |||
1892 | WriteLEInt((int)entry.Crc); | |||
1893 | } | |||
1894 | | |||
1895 | if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { | |||
1896 | WriteLEInt(-1); | |||
1897 | } else { | |||
1898 | WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); | |||
1899 | } | |||
1900 | | |||
1901 | if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { | |||
1902 | WriteLEInt(-1); | |||
1903 | } else { | |||
1904 | WriteLEInt((int)entry.Size); | |||
1905 | } | |||
1906 | | |||
1907 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1908 | | |||
1909 | if (name.Length > 0xFFFF) { | |||
1910 | throw new ZipException("Entry name is too long."); | |||
1911 | } | |||
1912 | | |||
1913 | WriteLEShort(name.Length); | |||
1914 | | |||
1915 | // Central header extra data is different to local header version so regenerate. | |||
1916 | var ed = new ZipExtraData(entry.ExtraData); | |||
1917 | | |||
1918 | if (entry.CentralHeaderRequiresZip64) { | |||
1919 | ed.StartNewEntry(); | |||
1920 | | |||
1921 | if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1922 | ed.AddLeLong(entry.Size); | |||
1923 | } | |||
1924 | | |||
1925 | if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1926 | ed.AddLeLong(entry.CompressedSize); | |||
1927 | } | |||
1928 | | |||
1929 | if (entry.Offset >= 0xffffffff) { | |||
1930 | ed.AddLeLong(entry.Offset); | |||
1931 | } | |||
1932 | | |||
1933 | // Number of disk on which this file starts isnt supported and is never written here. | |||
1934 | ed.AddNewEntry(1); | |||
1935 | } else { | |||
1936 | // Should have already be done when local header was added. | |||
1937 | ed.Delete(1); | |||
1938 | } | |||
1939 | | |||
1940 | byte[] centralExtraData = ed.GetEntryData(); | |||
1941 | | |||
1942 | WriteLEShort(centralExtraData.Length); | |||
1943 | WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); | |||
1944 | | |||
1945 | WriteLEShort(0); // disk number | |||
1946 | WriteLEShort(0); // internal file attributes | |||
1947 | | |||
1948 | // External file attributes... | |||
1949 | if (entry.ExternalFileAttributes != -1) { | |||
1950 | WriteLEInt(entry.ExternalFileAttributes); | |||
1951 | } else { | |||
1952 | if (entry.IsDirectory) { | |||
1953 | WriteLEUint(16); | |||
1954 | } else { | |||
1955 | WriteLEUint(0); | |||
1956 | } | |||
1957 | } | |||
1958 | | |||
1959 | if (entry.Offset >= 0xffffffff) { | |||
1960 | WriteLEUint(0xffffffff); | |||
1961 | } else { | |||
1962 | WriteLEUint((uint)(int)entry.Offset); | |||
1963 | } | |||
1964 | | |||
1965 | if (name.Length > 0) { | |||
1966 | baseStream_.Write(name, 0, name.Length); | |||
1967 | } | |||
1968 | | |||
1969 | if (centralExtraData.Length > 0) { | |||
1970 | baseStream_.Write(centralExtraData, 0, centralExtraData.Length); | |||
1971 | } | |||
1972 | | |||
1973 | byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; | |||
1974 | | |||
1975 | if (rawComment.Length > 0) { | |||
1976 | baseStream_.Write(rawComment, 0, rawComment.Length); | |||
1977 | } | |||
1978 | | |||
1979 | return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; | |||
1980 | } | |||
1981 | #endregion | |||
1982 | | |||
1983 | void PostUpdateCleanup() | |||
1984 | { | |||
1985 | updateDataSource_ = null; | |||
1986 | updates_ = null; | |||
1987 | updateIndex_ = null; | |||
1988 | | |||
1989 | if (archiveStorage_ != null) { | |||
1990 | archiveStorage_.Dispose(); | |||
1991 | archiveStorage_ = null; | |||
1992 | } | |||
1993 | } | |||
1994 | | |||
1995 | string GetTransformedFileName(string name) | |||
1996 | { | |||
1997 | INameTransform transform = NameTransform; | |||
1998 | return (transform != null) ? | |||
1999 | transform.TransformFile(name) : | |||
2000 | name; | |||
2001 | } | |||
2002 | | |||
2003 | string GetTransformedDirectoryName(string name) | |||
2004 | { | |||
2005 | INameTransform transform = NameTransform; | |||
2006 | return (transform != null) ? | |||
2007 | transform.TransformDirectory(name) : | |||
2008 | name; | |||
2009 | } | |||
2010 | | |||
2011 | /// <summary> | |||
2012 | /// Get a raw memory buffer. | |||
2013 | /// </summary> | |||
2014 | /// <returns>Returns a raw memory buffer.</returns> | |||
2015 | byte[] GetBuffer() | |||
2016 | { | |||
2017 | if (copyBuffer_ == null) { | |||
2018 | copyBuffer_ = new byte[bufferSize_]; | |||
2019 | } | |||
2020 | return copyBuffer_; | |||
2021 | } | |||
2022 | | |||
2023 | void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) | |||
2024 | { | |||
2025 | int bytesToCopy = GetDescriptorSize(update); | |||
2026 | | |||
2027 | if (bytesToCopy > 0) { | |||
2028 | byte[] buffer = GetBuffer(); | |||
2029 | | |||
2030 | while (bytesToCopy > 0) { | |||
2031 | int readSize = Math.Min(buffer.Length, bytesToCopy); | |||
2032 | | |||
2033 | int bytesRead = source.Read(buffer, 0, readSize); | |||
2034 | if (bytesRead > 0) { | |||
2035 | dest.Write(buffer, 0, bytesRead); | |||
2036 | bytesToCopy -= bytesRead; | |||
2037 | } else { | |||
2038 | throw new ZipException("Unxpected end of stream"); | |||
2039 | } | |||
2040 | } | |||
2041 | } | |||
2042 | } | |||
2043 | | |||
2044 | void CopyBytes(ZipUpdate update, Stream destination, Stream source, | |||
2045 | long bytesToCopy, bool updateCrc) | |||
2046 | { | |||
2047 | if (destination == source) { | |||
2048 | throw new InvalidOperationException("Destination and source are the same"); | |||
2049 | } | |||
2050 | | |||
2051 | // NOTE: Compressed size is updated elsewhere. | |||
2052 | var crc = new Crc32(); | |||
2053 | byte[] buffer = GetBuffer(); | |||
2054 | | |||
2055 | long targetBytes = bytesToCopy; | |||
2056 | long totalBytesRead = 0; | |||
2057 | | |||
2058 | int bytesRead; | |||
2059 | do { | |||
2060 | int readSize = buffer.Length; | |||
2061 | | |||
2062 | if (bytesToCopy < readSize) { | |||
2063 | readSize = (int)bytesToCopy; | |||
2064 | } | |||
2065 | | |||
2066 | bytesRead = source.Read(buffer, 0, readSize); | |||
2067 | if (bytesRead > 0) { | |||
2068 | if (updateCrc) { | |||
2069 | crc.Update(buffer, 0, bytesRead); | |||
2070 | } | |||
2071 | destination.Write(buffer, 0, bytesRead); | |||
2072 | bytesToCopy -= bytesRead; | |||
2073 | totalBytesRead += bytesRead; | |||
2074 | } | |||
2075 | } | |||
2076 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2077 | | |||
2078 | if (totalBytesRead != targetBytes) { | |||
2079 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2080 | } | |||
2081 | | |||
2082 | if (updateCrc) { | |||
2083 | update.OutEntry.Crc = crc.Value; | |||
2084 | } | |||
2085 | } | |||
2086 | | |||
2087 | /// <summary> | |||
2088 | /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. | |||
2089 | /// </summary> | |||
2090 | /// <param name="update">The update to get the size for.</param> | |||
2091 | /// <returns>The descriptor size, zero if there isnt one.</returns> | |||
2092 | int GetDescriptorSize(ZipUpdate update) | |||
2093 | { | |||
2094 | int result = 0; | |||
2095 | if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
2096 | result = ZipConstants.DataDescriptorSize - 4; | |||
2097 | if (update.Entry.LocalHeaderRequiresZip64) { | |||
2098 | result = ZipConstants.Zip64DataDescriptorSize - 4; | |||
2099 | } | |||
2100 | } | |||
2101 | return result; | |||
2102 | } | |||
2103 | | |||
2104 | void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) | |||
2105 | { | |||
2106 | int bytesToCopy = GetDescriptorSize(update); | |||
2107 | | |||
2108 | while (bytesToCopy > 0) { | |||
2109 | var readSize = (int)bytesToCopy; | |||
2110 | byte[] buffer = GetBuffer(); | |||
2111 | | |||
2112 | stream.Position = sourcePosition; | |||
2113 | int bytesRead = stream.Read(buffer, 0, readSize); | |||
2114 | if (bytesRead > 0) { | |||
2115 | stream.Position = destinationPosition; | |||
2116 | stream.Write(buffer, 0, bytesRead); | |||
2117 | bytesToCopy -= bytesRead; | |||
2118 | destinationPosition += bytesRead; | |||
2119 | sourcePosition += bytesRead; | |||
2120 | } else { | |||
2121 | throw new ZipException("Unxpected end of stream"); | |||
2122 | } | |||
2123 | } | |||
2124 | } | |||
2125 | | |||
2126 | void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou | |||
2127 | { | |||
2128 | long bytesToCopy = update.Entry.CompressedSize; | |||
2129 | | |||
2130 | // NOTE: Compressed size is updated elsewhere. | |||
2131 | var crc = new Crc32(); | |||
2132 | byte[] buffer = GetBuffer(); | |||
2133 | | |||
2134 | long targetBytes = bytesToCopy; | |||
2135 | long totalBytesRead = 0; | |||
2136 | | |||
2137 | int bytesRead; | |||
2138 | do { | |||
2139 | int readSize = buffer.Length; | |||
2140 | | |||
2141 | if (bytesToCopy < readSize) { | |||
2142 | readSize = (int)bytesToCopy; | |||
2143 | } | |||
2144 | | |||
2145 | stream.Position = sourcePosition; | |||
2146 | bytesRead = stream.Read(buffer, 0, readSize); | |||
2147 | if (bytesRead > 0) { | |||
2148 | if (updateCrc) { | |||
2149 | crc.Update(buffer, 0, bytesRead); | |||
2150 | } | |||
2151 | stream.Position = destinationPosition; | |||
2152 | stream.Write(buffer, 0, bytesRead); | |||
2153 | | |||
2154 | destinationPosition += bytesRead; | |||
2155 | sourcePosition += bytesRead; | |||
2156 | bytesToCopy -= bytesRead; | |||
2157 | totalBytesRead += bytesRead; | |||
2158 | } | |||
2159 | } | |||
2160 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2161 | | |||
2162 | if (totalBytesRead != targetBytes) { | |||
2163 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2164 | } | |||
2165 | | |||
2166 | if (updateCrc) { | |||
2167 | update.OutEntry.Crc = crc.Value; | |||
2168 | } | |||
2169 | } | |||
2170 | | |||
2171 | int FindExistingUpdate(ZipEntry entry) | |||
2172 | { | |||
2173 | int result = -1; | |||
2174 | string convertedName = GetTransformedFileName(entry.Name); | |||
2175 | | |||
2176 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2177 | result = (int)updateIndex_[convertedName]; | |||
2178 | } | |||
2179 | /* | |||
2180 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2181 | // for CF? | |||
2182 | for (int index = 0; index < updates_.Count; ++index) | |||
2183 | { | |||
2184 | ZipUpdate zu = ( ZipUpdate )updates_[index]; | |||
2185 | if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && | |||
2186 | (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { | |||
2187 | result = index; | |||
2188 | break; | |||
2189 | } | |||
2190 | } | |||
2191 | */ | |||
2192 | return result; | |||
2193 | } | |||
2194 | | |||
2195 | int FindExistingUpdate(string fileName) | |||
2196 | { | |||
2197 | int result = -1; | |||
2198 | | |||
2199 | string convertedName = GetTransformedFileName(fileName); | |||
2200 | | |||
2201 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2202 | result = (int)updateIndex_[convertedName]; | |||
2203 | } | |||
2204 | | |||
2205 | /* | |||
2206 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2207 | // for CF? | |||
2208 | for ( int index = 0; index < updates_.Count; ++index ) { | |||
2209 | if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, | |||
2210 | true, CultureInfo.InvariantCulture) == 0 ) { | |||
2211 | result = index; | |||
2212 | break; | |||
2213 | } | |||
2214 | } | |||
2215 | */ | |||
2216 | | |||
2217 | return result; | |||
2218 | } | |||
2219 | | |||
2220 | /// <summary> | |||
2221 | /// Get an output stream for the specified <see cref="ZipEntry"/> | |||
2222 | /// </summary> | |||
2223 | /// <param name="entry">The entry to get an output stream for.</param> | |||
2224 | /// <returns>The output stream obtained for the entry.</returns> | |||
2225 | Stream GetOutputStream(ZipEntry entry) | |||
2226 | { | |||
2227 | Stream result = baseStream_; | |||
2228 | | |||
2229 | if (entry.IsCrypted == true) { | |||
2230 | result = CreateAndInitEncryptionStream(result, entry); | |||
2231 | } | |||
2232 | | |||
2233 | switch (entry.CompressionMethod) { | |||
2234 | case CompressionMethod.Stored: | |||
2235 | result = new UncompressedStream(result); | |||
2236 | break; | |||
2237 | | |||
2238 | case CompressionMethod.Deflated: | |||
2239 | var dos = new DeflaterOutputStream(result, new Deflater(9, true)); | |||
2240 | dos.IsStreamOwner = false; | |||
2241 | result = dos; | |||
2242 | break; | |||
2243 | | |||
2244 | default: | |||
2245 | throw new ZipException("Unknown compression method " + entry.CompressionMethod); | |||
2246 | } | |||
2247 | return result; | |||
2248 | } | |||
2249 | | |||
2250 | void AddEntry(ZipFile workFile, ZipUpdate update) | |||
2251 | { | |||
2252 | Stream source = null; | |||
2253 | | |||
2254 | if (update.Entry.IsFile) { | |||
2255 | source = update.GetSource(); | |||
2256 | | |||
2257 | if (source == null) { | |||
2258 | source = updateDataSource_.GetSource(update.Entry, update.Filename); | |||
2259 | } | |||
2260 | } | |||
2261 | | |||
2262 | if (source != null) { | |||
2263 | using (source) { | |||
2264 | long sourceStreamLength = source.Length; | |||
2265 | if (update.OutEntry.Size < 0) { | |||
2266 | update.OutEntry.Size = sourceStreamLength; | |||
2267 | } else { | |||
2268 | // Check for errant entries. | |||
2269 | if (update.OutEntry.Size != sourceStreamLength) { | |||
2270 | throw new ZipException("Entry size/stream size mismatch"); | |||
2271 | } | |||
2272 | } | |||
2273 | | |||
2274 | workFile.WriteLocalEntryHeader(update); | |||
2275 | | |||
2276 | long dataStart = workFile.baseStream_.Position; | |||
2277 | | |||
2278 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2279 | CopyBytes(update, output, source, sourceStreamLength, true); | |||
2280 | } | |||
2281 | | |||
2282 | long dataEnd = workFile.baseStream_.Position; | |||
2283 | update.OutEntry.CompressedSize = dataEnd - dataStart; | |||
2284 | | |||
2285 | if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) { | |||
2286 | var helper = new ZipHelperStream(workFile.baseStream_); | |||
2287 | helper.WriteDataDescriptor(update.OutEntry); | |||
2288 | } | |||
2289 | } | |||
2290 | } else { | |||
2291 | workFile.WriteLocalEntryHeader(update); | |||
2292 | update.OutEntry.CompressedSize = 0; | |||
2293 | } | |||
2294 | | |||
2295 | } | |||
2296 | | |||
2297 | void ModifyEntry(ZipFile workFile, ZipUpdate update) | |||
2298 | { | |||
2299 | workFile.WriteLocalEntryHeader(update); | |||
2300 | long dataStart = workFile.baseStream_.Position; | |||
2301 | | |||
2302 | // TODO: This is slow if the changes don't effect the data!! | |||
2303 | if (update.Entry.IsFile && (update.Filename != null)) { | |||
2304 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2305 | using (Stream source = this.GetInputStream(update.Entry)) { | |||
2306 | CopyBytes(update, output, source, source.Length, true); | |||
2307 | } | |||
2308 | } | |||
2309 | } | |||
2310 | | |||
2311 | long dataEnd = workFile.baseStream_.Position; | |||
2312 | update.Entry.CompressedSize = dataEnd - dataStart; | |||
2313 | } | |||
2314 | | |||
2315 | void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) | |||
2316 | { | |||
2317 | bool skipOver = false || update.Entry.Offset == destinationPosition; | |||
2318 | | |||
2319 | if (!skipOver) { | |||
2320 | baseStream_.Position = destinationPosition; | |||
2321 | workFile.WriteLocalEntryHeader(update); | |||
2322 | destinationPosition = baseStream_.Position; | |||
2323 | } | |||
2324 | | |||
2325 | long sourcePosition = 0; | |||
2326 | | |||
2327 | const int NameLengthOffset = 26; | |||
2328 | | |||
2329 | // TODO: Add base for SFX friendly handling | |||
2330 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2331 | | |||
2332 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2333 | | |||
2334 | // Clumsy way of handling retrieving the original name and extra data length for now. | |||
2335 | // TODO: Stop re-reading name and data length in CopyEntryDirect. | |||
2336 | uint nameLength = ReadLEUshort(); | |||
2337 | uint extraLength = ReadLEUshort(); | |||
2338 | | |||
2339 | sourcePosition = baseStream_.Position + nameLength + extraLength; | |||
2340 | | |||
2341 | if (skipOver) { | |||
2342 | if (update.OffsetBasedSize != -1) | |||
2343 | destinationPosition += update.OffsetBasedSize; | |||
2344 | else | |||
2345 | // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) ar | |||
2346 | // WinZip produces a warning on these entries: | |||
2347 | // "caution: value of lrec.csize (compressed size) changed from ..." | |||
2348 | destinationPosition += | |||
2349 | (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size | |||
2350 | update.Entry.CompressedSize + GetDescriptorSize(update); | |||
2351 | } else { | |||
2352 | if (update.Entry.CompressedSize > 0) { | |||
2353 | CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); | |||
2354 | } | |||
2355 | CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); | |||
2356 | } | |||
2357 | } | |||
2358 | | |||
2359 | void CopyEntry(ZipFile workFile, ZipUpdate update) | |||
2360 | { | |||
2361 | workFile.WriteLocalEntryHeader(update); | |||
2362 | | |||
2363 | if (update.Entry.CompressedSize > 0) { | |||
2364 | const int NameLengthOffset = 26; | |||
2365 | | |||
2366 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2367 | | |||
2368 | // TODO: This wont work for SFX files! | |||
2369 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2370 | | |||
2371 | uint nameLength = ReadLEUshort(); | |||
2372 | uint extraLength = ReadLEUshort(); | |||
2373 | | |||
2374 | baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); | |||
2375 | | |||
2376 | CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); | |||
2377 | } | |||
2378 | CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); | |||
2379 | } | |||
2380 | | |||
2381 | void Reopen(Stream source) | |||
2382 | { | |||
2383 | if (source == null) { | |||
2384 | throw new ZipException("Failed to reopen archive - no source"); | |||
2385 | } | |||
2386 | | |||
2387 | isNewArchive_ = false; | |||
2388 | baseStream_ = source; | |||
2389 | ReadEntries(); | |||
2390 | } | |||
2391 | | |||
2392 | void Reopen() | |||
2393 | { | |||
2394 | if (Name == null) { | |||
2395 | throw new InvalidOperationException("Name is not known cannot Reopen"); | |||
2396 | } | |||
2397 | | |||
2398 | Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); | |||
2399 | } | |||
2400 | | |||
2401 | void UpdateCommentOnly() | |||
2402 | { | |||
2403 | long baseLength = baseStream_.Length; | |||
2404 | | |||
2405 | ZipHelperStream updateFile = null; | |||
2406 | | |||
2407 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2408 | Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); | |||
2409 | updateFile = new ZipHelperStream(copyStream); | |||
2410 | updateFile.IsStreamOwner = true; | |||
2411 | | |||
2412 | baseStream_.Close(); | |||
2413 | baseStream_ = null; | |||
2414 | } else { | |||
2415 | if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2416 | // TODO: archiveStorage wasnt originally intended for this use. | |||
2417 | // Need to revisit this to tidy up handling as archive storage currently doesnt | |||
2418 | // handle the original stream well. | |||
2419 | // The problem is when using an existing zip archive with an in memory archive storage. | |||
2420 | // The open stream wont support writing but the memory storage should open the same file not an in memory one. | |||
2421 | | |||
2422 | // Need to tidy up the archive storage interface and contract basically. | |||
2423 | baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); | |||
2424 | updateFile = new ZipHelperStream(baseStream_); | |||
2425 | } else { | |||
2426 | baseStream_.Close(); | |||
2427 | baseStream_ = null; | |||
2428 | updateFile = new ZipHelperStream(Name); | |||
2429 | } | |||
2430 | } | |||
2431 | | |||
2432 | using (updateFile) { | |||
2433 | long locatedCentralDirOffset = | |||
2434 | updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2435 | baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2436 | if (locatedCentralDirOffset < 0) { | |||
2437 | throw new ZipException("Cannot find central directory"); | |||
2438 | } | |||
2439 | | |||
2440 | const int CentralHeaderCommentSizeOffset = 16; | |||
2441 | updateFile.Position += CentralHeaderCommentSizeOffset; | |||
2442 | | |||
2443 | byte[] rawComment = newComment_.RawComment; | |||
2444 | | |||
2445 | updateFile.WriteLEShort(rawComment.Length); | |||
2446 | updateFile.Write(rawComment, 0, rawComment.Length); | |||
2447 | updateFile.SetLength(updateFile.Position); | |||
2448 | } | |||
2449 | | |||
2450 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2451 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2452 | } else { | |||
2453 | ReadEntries(); | |||
2454 | } | |||
2455 | } | |||
2456 | | |||
2457 | /// <summary> | |||
2458 | /// Class used to sort updates. | |||
2459 | /// </summary> | |||
2460 | class UpdateComparer : IComparer | |||
2461 | { | |||
2462 | /// <summary> | |||
2463 | /// Compares two objects and returns a value indicating whether one is | |||
2464 | /// less than, equal to or greater than the other. | |||
2465 | /// </summary> | |||
2466 | /// <param name="x">First object to compare</param> | |||
2467 | /// <param name="y">Second object to compare.</param> | |||
2468 | /// <returns>Compare result.</returns> | |||
2469 | public int Compare( | |||
2470 | object x, | |||
2471 | object y) | |||
2472 | { | |||
2473 | var zx = x as ZipUpdate; | |||
2474 | var zy = y as ZipUpdate; | |||
2475 | | |||
2476 | int result; | |||
2477 | | |||
2478 | if (zx == null) { | |||
2479 | if (zy == null) { | |||
2480 | result = 0; | |||
2481 | } else { | |||
2482 | result = -1; | |||
2483 | } | |||
2484 | } else if (zy == null) { | |||
2485 | result = 1; | |||
2486 | } else { | |||
2487 | int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2488 | int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2489 | | |||
2490 | result = xCmdValue - yCmdValue; | |||
2491 | if (result == 0) { | |||
2492 | long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; | |||
2493 | if (offsetDiff < 0) { | |||
2494 | result = -1; | |||
2495 | } else if (offsetDiff == 0) { | |||
2496 | result = 0; | |||
2497 | } else { | |||
2498 | result = 1; | |||
2499 | } | |||
2500 | } | |||
2501 | } | |||
2502 | return result; | |||
2503 | } | |||
2504 | } | |||
2505 | | |||
2506 | void RunUpdates() | |||
2507 | { | |||
2508 | long sizeEntries = 0; | |||
2509 | long endOfStream = 0; | |||
2510 | bool directUpdate = false; | |||
2511 | long destinationPosition = 0; // NOT SFX friendly | |||
2512 | | |||
2513 | ZipFile workFile; | |||
2514 | | |||
2515 | if (IsNewArchive) { | |||
2516 | workFile = this; | |||
2517 | workFile.baseStream_.Position = 0; | |||
2518 | directUpdate = true; | |||
2519 | } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2520 | workFile = this; | |||
2521 | workFile.baseStream_.Position = 0; | |||
2522 | directUpdate = true; | |||
2523 | | |||
2524 | // Sort the updates by offset within copies/modifies, then adds. | |||
2525 | // This ensures that data required by copies will not be overwritten. | |||
2526 | updates_.Sort(new UpdateComparer()); | |||
2527 | } else { | |||
2528 | workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); | |||
2529 | workFile.UseZip64 = UseZip64; | |||
2530 | | |||
2531 | if (key != null) { | |||
2532 | workFile.key = (byte[])key.Clone(); | |||
2533 | } | |||
2534 | } | |||
2535 | | |||
2536 | try { | |||
2537 | foreach (ZipUpdate update in updates_) { | |||
2538 | if (update != null) { | |||
2539 | switch (update.Command) { | |||
2540 | case UpdateCommand.Copy: | |||
2541 | if (directUpdate) { | |||
2542 | CopyEntryDirect(workFile, update, ref destinationPosition); | |||
2543 | } else { | |||
2544 | CopyEntry(workFile, update); | |||
2545 | } | |||
2546 | break; | |||
2547 | | |||
2548 | case UpdateCommand.Modify: | |||
2549 | // TODO: Direct modifying of an entry will take some legwork. | |||
2550 | ModifyEntry(workFile, update); | |||
2551 | break; | |||
2552 | | |||
2553 | case UpdateCommand.Add: | |||
2554 | if (!IsNewArchive && directUpdate) { | |||
2555 | workFile.baseStream_.Position = destinationPosition; | |||
2556 | } | |||
2557 | | |||
2558 | AddEntry(workFile, update); | |||
2559 | | |||
2560 | if (directUpdate) { | |||
2561 | destinationPosition = workFile.baseStream_.Position; | |||
2562 | } | |||
2563 | break; | |||
2564 | } | |||
2565 | } | |||
2566 | } | |||
2567 | | |||
2568 | if (!IsNewArchive && directUpdate) { | |||
2569 | workFile.baseStream_.Position = destinationPosition; | |||
2570 | } | |||
2571 | | |||
2572 | long centralDirOffset = workFile.baseStream_.Position; | |||
2573 | | |||
2574 | foreach (ZipUpdate update in updates_) { | |||
2575 | if (update != null) { | |||
2576 | sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); | |||
2577 | } | |||
2578 | } | |||
2579 | | |||
2580 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
2581 | using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) { | |||
2582 | zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); | |||
2583 | } | |||
2584 | | |||
2585 | endOfStream = workFile.baseStream_.Position; | |||
2586 | | |||
2587 | // And now patch entries... | |||
2588 | foreach (ZipUpdate update in updates_) { | |||
2589 | if (update != null) { | |||
2590 | // If the size of the entry is zero leave the crc as 0 as well. | |||
2591 | // The calculated crc will be all bits on... | |||
2592 | if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { | |||
2593 | workFile.baseStream_.Position = update.CrcPatchOffset; | |||
2594 | workFile.WriteLEInt((int)update.OutEntry.Crc); | |||
2595 | } | |||
2596 | | |||
2597 | if (update.SizePatchOffset > 0) { | |||
2598 | workFile.baseStream_.Position = update.SizePatchOffset; | |||
2599 | if (update.OutEntry.LocalHeaderRequiresZip64) { | |||
2600 | workFile.WriteLeLong(update.OutEntry.Size); | |||
2601 | workFile.WriteLeLong(update.OutEntry.CompressedSize); | |||
2602 | } else { | |||
2603 | workFile.WriteLEInt((int)update.OutEntry.CompressedSize); | |||
2604 | workFile.WriteLEInt((int)update.OutEntry.Size); | |||
2605 | } | |||
2606 | } | |||
2607 | } | |||
2608 | } | |||
2609 | } catch { | |||
2610 | workFile.Close(); | |||
2611 | if (!directUpdate && (workFile.Name != null)) { | |||
2612 | File.Delete(workFile.Name); | |||
2613 | } | |||
2614 | throw; | |||
2615 | } | |||
2616 | | |||
2617 | if (directUpdate) { | |||
2618 | workFile.baseStream_.SetLength(endOfStream); | |||
2619 | workFile.baseStream_.Flush(); | |||
2620 | isNewArchive_ = false; | |||
2621 | ReadEntries(); | |||
2622 | } else { | |||
2623 | baseStream_.Close(); | |||
2624 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2625 | } | |||
2626 | } | |||
2627 | | |||
2628 | void CheckUpdating() | |||
2629 | { | |||
2630 | if (updates_ == null) { | |||
2631 | throw new InvalidOperationException("BeginUpdate has not been called"); | |||
2632 | } | |||
2633 | } | |||
2634 | | |||
2635 | #endregion | |||
2636 | | |||
2637 | #region ZipUpdate class | |||
2638 | /// <summary> | |||
2639 | /// Represents a pending update to a Zip file. | |||
2640 | /// </summary> | |||
2641 | class ZipUpdate | |||
2642 | { | |||
2643 | #region Constructors | |||
2644 | public ZipUpdate(string fileName, ZipEntry entry) | |||
2645 | { | |||
2646 | command_ = UpdateCommand.Add; | |||
2647 | entry_ = entry; | |||
2648 | filename_ = fileName; | |||
2649 | } | |||
2650 | | |||
2651 | [Obsolete] | |||
2652 | public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) | |||
2653 | { | |||
2654 | command_ = UpdateCommand.Add; | |||
2655 | entry_ = new ZipEntry(entryName); | |||
2656 | entry_.CompressionMethod = compressionMethod; | |||
2657 | filename_ = fileName; | |||
2658 | } | |||
2659 | | |||
2660 | [Obsolete] | |||
2661 | public ZipUpdate(string fileName, string entryName) | |||
2662 | : this(fileName, entryName, CompressionMethod.Deflated) | |||
2663 | { | |||
2664 | // Do nothing. | |||
2665 | } | |||
2666 | | |||
2667 | [Obsolete] | |||
2668 | public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
2669 | { | |||
2670 | command_ = UpdateCommand.Add; | |||
2671 | entry_ = new ZipEntry(entryName); | |||
2672 | entry_.CompressionMethod = compressionMethod; | |||
2673 | dataSource_ = dataSource; | |||
2674 | } | |||
2675 | | |||
2676 | public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) | |||
2677 | { | |||
2678 | command_ = UpdateCommand.Add; | |||
2679 | entry_ = entry; | |||
2680 | dataSource_ = dataSource; | |||
2681 | } | |||
2682 | | |||
2683 | public ZipUpdate(ZipEntry original, ZipEntry updated) | |||
2684 | { | |||
2685 | throw new ZipException("Modify not currently supported"); | |||
2686 | /* | |||
2687 | command_ = UpdateCommand.Modify; | |||
2688 | entry_ = ( ZipEntry )original.Clone(); | |||
2689 | outEntry_ = ( ZipEntry )updated.Clone(); | |||
2690 | */ | |||
2691 | } | |||
2692 | | |||
2693 | public ZipUpdate(UpdateCommand command, ZipEntry entry) | |||
2694 | { | |||
2695 | command_ = command; | |||
2696 | entry_ = (ZipEntry)entry.Clone(); | |||
2697 | } | |||
2698 | | |||
2699 | | |||
2700 | /// <summary> | |||
2701 | /// Copy an existing entry. | |||
2702 | /// </summary> | |||
2703 | /// <param name="entry">The existing entry to copy.</param> | |||
2704 | public ZipUpdate(ZipEntry entry) | |||
2705 | : this(UpdateCommand.Copy, entry) | |||
2706 | { | |||
2707 | // Do nothing. | |||
2708 | } | |||
2709 | #endregion | |||
2710 | | |||
2711 | /// <summary> | |||
2712 | /// Get the <see cref="ZipEntry"/> for this update. | |||
2713 | /// </summary> | |||
2714 | /// <remarks>This is the source or original entry.</remarks> | |||
2715 | public ZipEntry Entry { | |||
2716 | get { return entry_; } | |||
2717 | } | |||
2718 | | |||
2719 | /// <summary> | |||
2720 | /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. | |||
2721 | /// </summary> | |||
2722 | public ZipEntry OutEntry { | |||
2723 | get { | |||
2724 | if (outEntry_ == null) { | |||
2725 | outEntry_ = (ZipEntry)entry_.Clone(); | |||
2726 | } | |||
2727 | | |||
2728 | return outEntry_; | |||
2729 | } | |||
2730 | } | |||
2731 | | |||
2732 | /// <summary> | |||
2733 | /// Get the command for this update. | |||
2734 | /// </summary> | |||
2735 | public UpdateCommand Command { | |||
2736 | get { return command_; } | |||
2737 | } | |||
2738 | | |||
2739 | /// <summary> | |||
2740 | /// Get the filename if any for this update. Null if none exists. | |||
2741 | /// </summary> | |||
2742 | public string Filename { | |||
2743 | get { return filename_; } | |||
2744 | } | |||
2745 | | |||
2746 | /// <summary> | |||
2747 | /// Get/set the location of the size patch for this update. | |||
2748 | /// </summary> | |||
2749 | public long SizePatchOffset { | |||
2750 | get { return sizePatchOffset_; } | |||
2751 | set { sizePatchOffset_ = value; } | |||
2752 | } | |||
2753 | | |||
2754 | /// <summary> | |||
2755 | /// Get /set the location of the crc patch for this update. | |||
2756 | /// </summary> | |||
2757 | public long CrcPatchOffset { | |||
2758 | get { return crcPatchOffset_; } | |||
2759 | set { crcPatchOffset_ = value; } | |||
2760 | } | |||
2761 | | |||
2762 | /// <summary> | |||
2763 | /// Get/set the size calculated by offset. | |||
2764 | /// Specifically, the difference between this and next entry's starting offset. | |||
2765 | /// </summary> | |||
2766 | public long OffsetBasedSize { | |||
2767 | get { return _offsetBasedSize; } | |||
2768 | set { _offsetBasedSize = value; } | |||
2769 | } | |||
2770 | | |||
2771 | public Stream GetSource() | |||
2772 | { | |||
2773 | Stream result = null; | |||
2774 | if (dataSource_ != null) { | |||
2775 | result = dataSource_.GetSource(); | |||
2776 | } | |||
2777 | | |||
2778 | return result; | |||
2779 | } | |||
2780 | | |||
2781 | #region Instance Fields | |||
2782 | ZipEntry entry_; | |||
2783 | ZipEntry outEntry_; | |||
2784 | UpdateCommand command_; | |||
2785 | IStaticDataSource dataSource_; | |||
2786 | string filename_; | |||
2787 | long sizePatchOffset_ = -1; | |||
2788 | long crcPatchOffset_ = -1; | |||
2789 | long _offsetBasedSize = -1; | |||
2790 | #endregion | |||
2791 | } | |||
2792 | | |||
2793 | #endregion | |||
2794 | #endregion | |||
2795 | | |||
2796 | #region Disposing | |||
2797 | | |||
2798 | #region IDisposable Members | |||
2799 | void IDisposable.Dispose() | |||
2800 | { | |||
2801 | Close(); | |||
2802 | } | |||
2803 | #endregion | |||
2804 | | |||
2805 | void DisposeInternal(bool disposing) | |||
2806 | { | |||
2807 | if (!isDisposed_) { | |||
2808 | isDisposed_ = true; | |||
2809 | entries_ = new ZipEntry[0]; | |||
2810 | | |||
2811 | if (IsStreamOwner && (baseStream_ != null)) { | |||
2812 | lock (baseStream_) { | |||
2813 | baseStream_.Close(); | |||
2814 | } | |||
2815 | } | |||
2816 | | |||
2817 | PostUpdateCleanup(); | |||
2818 | } | |||
2819 | } | |||
2820 | | |||
2821 | /// <summary> | |||
2822 | /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. | |||
2823 | /// </summary> | |||
2824 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
2825 | /// false to release only unmanaged resources.</param> | |||
2826 | protected virtual void Dispose(bool disposing) | |||
2827 | { | |||
2828 | DisposeInternal(disposing); | |||
2829 | } | |||
2830 | | |||
2831 | #endregion | |||
2832 | | |||
2833 | #region Internal routines | |||
2834 | #region Reading | |||
2835 | /// <summary> | |||
2836 | /// Read an unsigned short in little endian byte order. | |||
2837 | /// </summary> | |||
2838 | /// <returns>Returns the value read.</returns> | |||
2839 | /// <exception cref="EndOfStreamException"> | |||
2840 | /// The stream ends prematurely | |||
2841 | /// </exception> | |||
2842 | ushort ReadLEUshort() | |||
2843 | { | |||
2844 | int data1 = baseStream_.ReadByte(); | |||
2845 | | |||
2846 | if (data1 < 0) { | |||
2847 | throw new EndOfStreamException("End of stream"); | |||
2848 | } | |||
2849 | | |||
2850 | int data2 = baseStream_.ReadByte(); | |||
2851 | | |||
2852 | if (data2 < 0) { | |||
2853 | throw new EndOfStreamException("End of stream"); | |||
2854 | } | |||
2855 | | |||
2856 | | |||
2857 | return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); | |||
2858 | } | |||
2859 | | |||
2860 | /// <summary> | |||
2861 | /// Read a uint in little endian byte order. | |||
2862 | /// </summary> | |||
2863 | /// <returns>Returns the value read.</returns> | |||
2864 | /// <exception cref="IOException"> | |||
2865 | /// An i/o error occurs. | |||
2866 | /// </exception> | |||
2867 | /// <exception cref="System.IO.EndOfStreamException"> | |||
2868 | /// The file ends prematurely | |||
2869 | /// </exception> | |||
2870 | uint ReadLEUint() | |||
2871 | { | |||
2872 | return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); | |||
2873 | } | |||
2874 | | |||
2875 | ulong ReadLEUlong() | |||
2876 | { | |||
2877 | return ReadLEUint() | ((ulong)ReadLEUint() << 32); | |||
2878 | } | |||
2879 | | |||
2880 | #endregion | |||
2881 | // NOTE this returns the offset of the first byte after the signature. | |||
2882 | long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
2883 | { | |||
2884 | using (ZipHelperStream les = new ZipHelperStream(baseStream_)) { | |||
2885 | return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); | |||
2886 | } | |||
2887 | } | |||
2888 | | |||
2889 | /// <summary> | |||
2890 | /// Search for and read the central directory of a zip file filling the entries array. | |||
2891 | /// </summary> | |||
2892 | /// <exception cref="System.IO.IOException"> | |||
2893 | /// An i/o error occurs. | |||
2894 | /// </exception> | |||
2895 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
2896 | /// The central directory is malformed or cannot be found | |||
2897 | /// </exception> | |||
2898 | void ReadEntries() | |||
2899 | { | |||
2900 | // Search for the End Of Central Directory. When a zip comment is | |||
2901 | // present the directory will start earlier | |||
2902 | // | |||
2903 | // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. | |||
2904 | // This should be compatible with both SFX and ZIP files but has only been tested for Zip files | |||
2905 | // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then | |||
2906 | // this could be invalid. | |||
2907 | // Could also speed this up by reading memory in larger blocks. | |||
2908 | | |||
2909 | if (baseStream_.CanSeek == false) { | |||
2910 | throw new ZipException("ZipFile stream must be seekable"); | |||
2911 | } | |||
2912 | | |||
2913 | long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2914 | baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2915 | | |||
2916 | if (locatedEndOfCentralDir < 0) { | |||
2917 | throw new ZipException("Cannot find central directory"); | |||
2918 | } | |||
2919 | | |||
2920 | // Read end of central directory record | |||
2921 | ushort thisDiskNumber = ReadLEUshort(); | |||
2922 | ushort startCentralDirDisk = ReadLEUshort(); | |||
2923 | ulong entriesForThisDisk = ReadLEUshort(); | |||
2924 | ulong entriesForWholeCentralDir = ReadLEUshort(); | |||
2925 | ulong centralDirSize = ReadLEUint(); | |||
2926 | long offsetOfCentralDir = ReadLEUint(); | |||
2927 | uint commentSize = ReadLEUshort(); | |||
2928 | | |||
2929 | if (commentSize > 0) { | |||
2930 | byte[] comment = new byte[commentSize]; | |||
2931 | | |||
2932 | StreamUtils.ReadFully(baseStream_, comment); | |||
2933 | comment_ = ZipConstants.ConvertToString(comment); | |||
2934 | } else { | |||
2935 | comment_ = string.Empty; | |||
2936 | } | |||
2937 | | |||
2938 | bool isZip64 = false; | |||
2939 | | |||
2940 | // Check if zip64 header information is required. | |||
2941 | if ((thisDiskNumber == 0xffff) || | |||
2942 | (startCentralDirDisk == 0xffff) || | |||
2943 | (entriesForThisDisk == 0xffff) || | |||
2944 | (entriesForWholeCentralDir == 0xffff) || | |||
2945 | (centralDirSize == 0xffffffff) || | |||
2946 | (offsetOfCentralDir == 0xffffffff)) { | |||
2947 | isZip64 = true; | |||
2948 | | |||
2949 | long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, | |||
2950 | if (offset < 0) { | |||
2951 | throw new ZipException("Cannot find Zip64 locator"); | |||
2952 | } | |||
2953 | | |||
2954 | // number of the disk with the start of the zip64 end of central directory 4 bytes | |||
2955 | // relative offset of the zip64 end of central directory record 8 bytes | |||
2956 | // total number of disks 4 bytes | |||
2957 | ReadLEUint(); // startDisk64 is not currently used | |||
2958 | ulong offset64 = ReadLEUlong(); | |||
2959 | uint totalDisks = ReadLEUint(); | |||
2960 | | |||
2961 | baseStream_.Position = (long)offset64; | |||
2962 | long sig64 = ReadLEUint(); | |||
2963 | | |||
2964 | if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { | |||
2965 | throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); | |||
2966 | } | |||
2967 | | |||
2968 | // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. | |||
2969 | ulong recordSize = ReadLEUlong(); | |||
2970 | int versionMadeBy = ReadLEUshort(); | |||
2971 | int versionToExtract = ReadLEUshort(); | |||
2972 | uint thisDisk = ReadLEUint(); | |||
2973 | uint centralDirDisk = ReadLEUint(); | |||
2974 | entriesForThisDisk = ReadLEUlong(); | |||
2975 | entriesForWholeCentralDir = ReadLEUlong(); | |||
2976 | centralDirSize = ReadLEUlong(); | |||
2977 | offsetOfCentralDir = (long)ReadLEUlong(); | |||
2978 | | |||
2979 | // NOTE: zip64 extensible data sector (variable size) is ignored. | |||
2980 | } | |||
2981 | | |||
2982 | entries_ = new ZipEntry[entriesForThisDisk]; | |||
2983 | | |||
2984 | // SFX/embedded support, find the offset of the first entry vis the start of the stream | |||
2985 | // This applies to Zip files that are appended to the end of an SFX stub. | |||
2986 | // Or are appended as a resource to an executable. | |||
2987 | // Zip files created by some archivers have the offsets altered to reflect the true offsets | |||
2988 | // and so dont require any adjustment here... | |||
2989 | // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? | |||
2990 | if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) { | |||
2991 | offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); | |||
2992 | if (offsetOfFirstEntry <= 0) { | |||
2993 | throw new ZipException("Invalid embedded zip archive"); | |||
2994 | } | |||
2995 | } | |||
2996 | | |||
2997 | baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); | |||
2998 | | |||
2999 | for (ulong i = 0; i < entriesForThisDisk; i++) { | |||
3000 | if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { | |||
3001 | throw new ZipException("Wrong Central Directory signature"); | |||
3002 | } | |||
3003 | | |||
3004 | int versionMadeBy = ReadLEUshort(); | |||
3005 | int versionToExtract = ReadLEUshort(); | |||
3006 | int bitFlags = ReadLEUshort(); | |||
3007 | int method = ReadLEUshort(); | |||
3008 | uint dostime = ReadLEUint(); | |||
3009 | uint crc = ReadLEUint(); | |||
3010 | var csize = (long)ReadLEUint(); | |||
3011 | var size = (long)ReadLEUint(); | |||
3012 | int nameLen = ReadLEUshort(); | |||
3013 | int extraLen = ReadLEUshort(); | |||
3014 | int commentLen = ReadLEUshort(); | |||
3015 | | |||
3016 | int diskStartNo = ReadLEUshort(); // Not currently used | |||
3017 | int internalAttributes = ReadLEUshort(); // Not currently used | |||
3018 | | |||
3019 | uint externalAttributes = ReadLEUint(); | |||
3020 | long offset = ReadLEUint(); | |||
3021 | | |||
3022 | byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; | |||
3023 | | |||
3024 | StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); | |||
3025 | string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); | |||
3026 | | |||
3027 | var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); | |||
3028 | entry.Crc = crc & 0xffffffffL; | |||
3029 | entry.Size = size & 0xffffffffL; | |||
3030 | entry.CompressedSize = csize & 0xffffffffL; | |||
3031 | entry.Flags = bitFlags; | |||
3032 | entry.DosTime = (uint)dostime; | |||
3033 | entry.ZipFileIndex = (long)i; | |||
3034 | entry.Offset = offset; | |||
3035 | entry.ExternalFileAttributes = (int)externalAttributes; | |||
3036 | | |||
3037 | if ((bitFlags & 8) == 0) { | |||
3038 | entry.CryptoCheckValue = (byte)(crc >> 24); | |||
3039 | } else { | |||
3040 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | |||
3041 | } | |||
3042 | | |||
3043 | if (extraLen > 0) { | |||
3044 | byte[] extra = new byte[extraLen]; | |||
3045 | StreamUtils.ReadFully(baseStream_, extra); | |||
3046 | entry.ExtraData = extra; | |||
3047 | } | |||
3048 | | |||
3049 | entry.ProcessExtraData(false); | |||
3050 | | |||
3051 | if (commentLen > 0) { | |||
3052 | StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); | |||
3053 | entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); | |||
3054 | } | |||
3055 | | |||
3056 | entries_[i] = entry; | |||
3057 | } | |||
3058 | } | |||
3059 | | |||
3060 | /// <summary> | |||
3061 | /// Locate the data for a given entry. | |||
3062 | /// </summary> | |||
3063 | /// <returns> | |||
3064 | /// The start offset of the data. | |||
3065 | /// </returns> | |||
3066 | /// <exception cref="System.IO.EndOfStreamException"> | |||
3067 | /// The stream ends prematurely | |||
3068 | /// </exception> | |||
3069 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
3070 | /// The local header signature is invalid, the entry and central header file name lengths are different | |||
3071 | /// or the local and entry compression methods dont match | |||
3072 | /// </exception> | |||
3073 | long LocateEntry(ZipEntry entry) | |||
3074 | { | |||
3075 | return TestLocalHeader(entry, HeaderTest.Extract); | |||
3076 | } | |||
3077 | | |||
3078 | Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) | |||
3079 | { | |||
3080 | CryptoStream result = null; | |||
3081 | | |||
3082 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3083 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3084 | var classicManaged = new PkzipClassicManaged(); | |||
3085 | | |||
3086 | OnKeysRequired(entry.Name); | |||
3087 | if (HaveKeys == false) { | |||
3088 | throw new ZipException("No password available for encrypted stream"); | |||
3089 | } | |||
3090 | | |||
3091 | result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); | |||
3092 | CheckClassicPassword(result, entry); | |||
3093 | } else { | |||
3094 | if (entry.Version == ZipConstants.VERSION_AES) { | |||
3095 | // | |||
3096 | OnKeysRequired(entry.Name); | |||
3097 | if (HaveKeys == false) { | |||
3098 | throw new ZipException("No password available for AES encrypted stream"); | |||
3099 | } | |||
3100 | int saltLen = entry.AESSaltLen; | |||
3101 | byte[] saltBytes = new byte[saltLen]; | |||
3102 | int saltIn = baseStream.Read(saltBytes, 0, saltLen); | |||
3103 | if (saltIn != saltLen) | |||
3104 | throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); | |||
3105 | // | |||
3106 | byte[] pwdVerifyRead = new byte[2]; | |||
3107 | baseStream.Read(pwdVerifyRead, 0, 2); | |||
3108 | int blockSize = entry.AESKeySize / 8; // bits to bytes | |||
3109 | | |||
3110 | var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); | |||
3111 | byte[] pwdVerifyCalc = decryptor.PwdVerifier; | |||
3112 | if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) | |||
3113 | throw new ZipException("Invalid password for AES"); | |||
3114 | result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); | |||
3115 | } else { | |||
3116 | throw new ZipException("Decryption method not supported"); | |||
3117 | } | |||
3118 | } | |||
3119 | | |||
3120 | return result; | |||
3121 | } | |||
3122 | | |||
3123 | Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) | |||
3124 | { | |||
3125 | CryptoStream result = null; | |||
3126 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3127 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3128 | var classicManaged = new PkzipClassicManaged(); | |||
3129 | | |||
3130 | OnKeysRequired(entry.Name); | |||
3131 | if (HaveKeys == false) { | |||
3132 | throw new ZipException("No password available for encrypted stream"); | |||
3133 | } | |||
3134 | | |||
3135 | // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream | |||
3136 | // which doesnt do this. | |||
3137 | result = new CryptoStream(new UncompressedStream(baseStream), | |||
3138 | classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); | |||
3139 | | |||
3140 | if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { | |||
3141 | WriteEncryptionHeader(result, entry.DosTime << 16); | |||
3142 | } else { | |||
3143 | WriteEncryptionHeader(result, entry.Crc); | |||
3144 | } | |||
3145 | } | |||
3146 | return result; | |||
3147 | } | |||
3148 | | |||
3149 | static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) | |||
3150 | { | |||
3151 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3152 | StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); | |||
3153 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | |||
3154 | throw new ZipException("Invalid password"); | |||
3155 | } | |||
3156 | } | |||
3157 | | |||
3158 | static void WriteEncryptionHeader(Stream stream, long crcValue) | |||
3159 | { | |||
3160 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3161 | var rnd = new Random(); | |||
3162 | rnd.NextBytes(cryptBuffer); | |||
3163 | cryptBuffer[11] = (byte)(crcValue >> 24); | |||
3164 | stream.Write(cryptBuffer, 0, cryptBuffer.Length); | |||
3165 | } | |||
3166 | | |||
3167 | #endregion | |||
3168 | | |||
3169 | #region Instance Fields | |||
3170 | bool isDisposed_; | |||
3171 | string name_; | |||
3172 | string comment_; | |||
3173 | string rawPassword_; | |||
3174 | Stream baseStream_; | |||
3175 | bool isStreamOwner; | |||
3176 | long offsetOfFirstEntry; | |||
3177 | ZipEntry[] entries_; | |||
3178 | byte[] key; | |||
3179 | bool isNewArchive_; | |||
3180 | | |||
3181 | // Default is dynamic which is not backwards compatible and can cause problems | |||
3182 | // with XP's built in compression which cant read Zip64 archives. | |||
3183 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
3184 | // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. | |||
3185 | UseZip64 useZip64_ = UseZip64.Dynamic; | |||
3186 | | |||
3187 | #region Zip Update Instance Fields | |||
3188 | ArrayList updates_; | |||
3189 | long updateCount_; // Count is managed manually as updates_ can contain nulls! | |||
3190 | Hashtable updateIndex_; | |||
3191 | IArchiveStorage archiveStorage_; | |||
3192 | IDynamicDataSource updateDataSource_; | |||
3193 | bool contentsEdited_; | |||
3194 | int bufferSize_ = DefaultBufferSize; | |||
3195 | byte[] copyBuffer_; | |||
3196 | ZipString newComment_; | |||
3197 | bool commentEdited_; | |||
3198 | IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); | |||
3199 | #endregion | |||
3200 | #endregion | |||
3201 | | |||
3202 | #region Support Classes | |||
3203 | /// <summary> | |||
3204 | /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. | |||
3205 | /// </summary> | |||
3206 | class ZipString | |||
3207 | { | |||
3208 | #region Constructors | |||
3209 | /// <summary> | |||
3210 | /// Initialise a <see cref="ZipString"/> with a string. | |||
3211 | /// </summary> | |||
3212 | /// <param name="comment">The textual string form.</param> | |||
3213 | public ZipString(string comment) | |||
3214 | { | |||
3215 | comment_ = comment; | |||
3216 | isSourceString_ = true; | |||
3217 | } | |||
3218 | | |||
3219 | /// <summary> | |||
3220 | /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. | |||
3221 | /// </summary> | |||
3222 | /// <param name="rawString"></param> | |||
3223 | public ZipString(byte[] rawString) | |||
3224 | { | |||
3225 | rawComment_ = rawString; | |||
3226 | } | |||
3227 | #endregion | |||
3228 | | |||
3229 | /// <summary> | |||
3230 | /// Get a value indicating the original source of data for this instance. | |||
3231 | /// True if the source was a string; false if the source was binary data. | |||
3232 | /// </summary> | |||
3233 | public bool IsSourceString { | |||
3234 | get { return isSourceString_; } | |||
3235 | } | |||
3236 | | |||
3237 | /// <summary> | |||
3238 | /// Get the length of the comment when represented as raw bytes. | |||
3239 | /// </summary> | |||
3240 | public int RawLength { | |||
3241 | get { | |||
3242 | MakeBytesAvailable(); | |||
3243 | return rawComment_.Length; | |||
3244 | } | |||
3245 | } | |||
3246 | | |||
3247 | /// <summary> | |||
3248 | /// Get the comment in its 'raw' form as plain bytes. | |||
3249 | /// </summary> | |||
3250 | public byte[] RawComment { | |||
3251 | get { | |||
3252 | MakeBytesAvailable(); | |||
3253 | return (byte[])rawComment_.Clone(); | |||
3254 | } | |||
3255 | } | |||
3256 | | |||
3257 | /// <summary> | |||
3258 | /// Reset the comment to its initial state. | |||
3259 | /// </summary> | |||
3260 | public void Reset() | |||
3261 | { | |||
3262 | if (isSourceString_) { | |||
3263 | rawComment_ = null; | |||
3264 | } else { | |||
3265 | comment_ = null; | |||
3266 | } | |||
3267 | } | |||
3268 | | |||
3269 | void MakeTextAvailable() | |||
3270 | { | |||
3271 | if (comment_ == null) { | |||
3272 | comment_ = ZipConstants.ConvertToString(rawComment_); | |||
3273 | } | |||
3274 | } | |||
3275 | | |||
3276 | void MakeBytesAvailable() | |||
3277 | { | |||
3278 | if (rawComment_ == null) { | |||
3279 | rawComment_ = ZipConstants.ConvertToArray(comment_); | |||
3280 | } | |||
3281 | } | |||
3282 | | |||
3283 | /// <summary> | |||
3284 | /// Implicit conversion of comment to a string. | |||
3285 | /// </summary> | |||
3286 | /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> | |||
3287 | /// <returns>The textual equivalent for the input value.</returns> | |||
3288 | static public implicit operator string(ZipString zipString) | |||
3289 | { | |||
3290 | zipString.MakeTextAvailable(); | |||
3291 | return zipString.comment_; | |||
3292 | } | |||
3293 | | |||
3294 | #region Instance Fields | |||
3295 | string comment_; | |||
3296 | byte[] rawComment_; | |||
3297 | bool isSourceString_; | |||
3298 | #endregion | |||
3299 | } | |||
3300 | | |||
3301 | /// <summary> | |||
3302 | /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> | |||
3303 | /// </summary> | |||
3304 | class ZipEntryEnumerator : IEnumerator | |||
3305 | { | |||
3306 | #region Constructors | |||
3307 | public ZipEntryEnumerator(ZipEntry[] entries) | |||
3308 | { | |||
3309 | array = entries; | |||
3310 | } | |||
3311 | | |||
3312 | #endregion | |||
3313 | #region IEnumerator Members | |||
3314 | public object Current { | |||
3315 | get { | |||
3316 | return array[index]; | |||
3317 | } | |||
3318 | } | |||
3319 | | |||
3320 | public void Reset() | |||
3321 | { | |||
3322 | index = -1; | |||
3323 | } | |||
3324 | | |||
3325 | public bool MoveNext() | |||
3326 | { | |||
3327 | return (++index < array.Length); | |||
3328 | } | |||
3329 | #endregion | |||
3330 | #region Instance Fields | |||
3331 | ZipEntry[] array; | |||
3332 | int index = -1; | |||
3333 | #endregion | |||
3334 | } | |||
3335 | | |||
3336 | /// <summary> | |||
3337 | /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data | |||
3338 | /// to and flush, but cannot read, seek or do anything else to. | |||
3339 | /// </summary> | |||
3340 | class UncompressedStream : Stream | |||
3341 | { | |||
3342 | #region Constructors | |||
3343 | public UncompressedStream(Stream baseStream) | |||
3344 | { | |||
3345 | baseStream_ = baseStream; | |||
3346 | } | |||
3347 | | |||
3348 | #endregion | |||
3349 | | |||
3350 | /// <summary> | |||
3351 | /// Close this stream instance. | |||
3352 | /// </summary> | |||
3353 | public override void Close() | |||
3354 | { | |||
3355 | // Do nothing | |||
3356 | } | |||
3357 | | |||
3358 | /// <summary> | |||
3359 | /// Gets a value indicating whether the current stream supports reading. | |||
3360 | /// </summary> | |||
3361 | public override bool CanRead { | |||
3362 | get { | |||
3363 | return false; | |||
3364 | } | |||
3365 | } | |||
3366 | | |||
3367 | /// <summary> | |||
3368 | /// Write any buffered data to underlying storage. | |||
3369 | /// </summary> | |||
3370 | public override void Flush() | |||
3371 | { | |||
3372 | baseStream_.Flush(); | |||
3373 | } | |||
3374 | | |||
3375 | /// <summary> | |||
3376 | /// Gets a value indicating whether the current stream supports writing. | |||
3377 | /// </summary> | |||
3378 | public override bool CanWrite { | |||
3379 | get { | |||
3380 | return baseStream_.CanWrite; | |||
3381 | } | |||
3382 | } | |||
3383 | | |||
3384 | /// <summary> | |||
3385 | /// Gets a value indicating whether the current stream supports seeking. | |||
3386 | /// </summary> | |||
3387 | public override bool CanSeek { | |||
3388 | get { | |||
3389 | return false; | |||
3390 | } | |||
3391 | } | |||
3392 | | |||
3393 | /// <summary> | |||
3394 | /// Get the length in bytes of the stream. | |||
3395 | /// </summary> | |||
3396 | public override long Length { | |||
3397 | get { | |||
3398 | return 0; | |||
3399 | } | |||
3400 | } | |||
3401 | | |||
3402 | /// <summary> | |||
3403 | /// Gets or sets the position within the current stream. | |||
3404 | /// </summary> | |||
3405 | public override long Position { | |||
3406 | get { | |||
3407 | return baseStream_.Position; | |||
3408 | } | |||
3409 | set { | |||
3410 | throw new NotImplementedException(); | |||
3411 | } | |||
3412 | } | |||
3413 | | |||
3414 | /// <summary> | |||
3415 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3416 | /// </summary> | |||
3417 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3418 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3419 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3420 | /// <returns> | |||
3421 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3422 | /// </returns> | |||
3423 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3424 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3425 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3426 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3427 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3428 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3429 | public override int Read(byte[] buffer, int offset, int count) | |||
3430 | { | |||
3431 | return 0; | |||
3432 | } | |||
3433 | | |||
3434 | /// <summary> | |||
3435 | /// Sets the position within the current stream. | |||
3436 | /// </summary> | |||
3437 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3438 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3439 | /// <returns> | |||
3440 | /// The new position within the current stream. | |||
3441 | /// </returns> | |||
3442 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3443 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3444 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3445 | public override long Seek(long offset, SeekOrigin origin) | |||
3446 | { | |||
3447 | return 0; | |||
3448 | } | |||
3449 | | |||
3450 | /// <summary> | |||
3451 | /// Sets the length of the current stream. | |||
3452 | /// </summary> | |||
3453 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3454 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3455 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3456 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3457 | public override void SetLength(long value) | |||
3458 | { | |||
3459 | } | |||
3460 | | |||
3461 | /// <summary> | |||
3462 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3463 | /// </summary> | |||
3464 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3465 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3466 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3467 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3468 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3469 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3470 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3471 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3472 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3473 | public override void Write(byte[] buffer, int offset, int count) | |||
3474 | { | |||
3475 | baseStream_.Write(buffer, offset, count); | |||
3476 | } | |||
3477 | | |||
3478 | readonly | |||
3479 | | |||
3480 | #region Instance Fields | |||
3481 | Stream baseStream_; | |||
3482 | #endregion | |||
3483 | } | |||
3484 | | |||
3485 | /// <summary> | |||
3486 | /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> | |||
3487 | /// whose data is only a part or subsection of a file. | |||
3488 | /// </summary> | |||
3489 | class PartialInputStream : Stream | |||
3490 | { | |||
3491 | #region Constructors | |||
3492 | /// <summary> | |||
3493 | /// Initialise a new instance of the <see cref="PartialInputStream"/> class. | |||
3494 | /// </summary> | |||
3495 | /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param> | |||
3496 | /// <param name="start">The start of the partial data.</param> | |||
3497 | /// <param name="length">The length of the partial data.</param> | |||
3498 | public PartialInputStream(ZipFile zipFile, long start, long length) | |||
3499 | { | |||
3500 | start_ = start; | |||
3501 | length_ = length; | |||
3502 | | |||
3503 | // Although this is the only time the zipfile is used | |||
3504 | // keeping a reference here prevents premature closure of | |||
3505 | // this zip file and thus the baseStream_. | |||
3506 | | |||
3507 | // Code like this will cause apparently random failures depending | |||
3508 | // on the size of the files and when garbage is collected. | |||
3509 | // | |||
3510 | // ZipFile z = new ZipFile (stream); | |||
3511 | // Stream reader = z.GetInputStream(0); | |||
3512 | // uses reader here.... | |||
3513 | zipFile_ = zipFile; | |||
3514 | baseStream_ = zipFile_.baseStream_; | |||
3515 | readPos_ = start; | |||
3516 | end_ = start + length; | |||
3517 | } | |||
3518 | #endregion | |||
3519 | | |||
3520 | /// <summary> | |||
3521 | /// Read a byte from this stream. | |||
3522 | /// </summary> | |||
3523 | /// <returns>Returns the byte read or -1 on end of stream.</returns> | |||
3524 | public override int ReadByte() | |||
3525 | { | |||
3526 | if (readPos_ >= end_) { | |||
3527 | // -1 is the correct value at end of stream. | |||
3528 | return -1; | |||
3529 | } | |||
3530 | | |||
3531 | lock (baseStream_) { | |||
3532 | baseStream_.Seek(readPos_++, SeekOrigin.Begin); | |||
3533 | return baseStream_.ReadByte(); | |||
3534 | } | |||
3535 | } | |||
3536 | | |||
3537 | /// <summary> | |||
3538 | /// Close this <see cref="PartialInputStream">partial input stream</see>. | |||
3539 | /// </summary> | |||
3540 | /// <remarks> | |||
3541 | /// The underlying stream is not closed. Close the parent ZipFile class to do that. | |||
3542 | /// </remarks> | |||
3543 | public override void Close() | |||
3544 | { | |||
3545 | // Do nothing at all! | |||
3546 | } | |||
3547 | | |||
3548 | /// <summary> | |||
3549 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3550 | /// </summary> | |||
3551 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3552 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3553 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3554 | /// <returns> | |||
3555 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3556 | /// </returns> | |||
3557 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3558 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3559 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3560 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3561 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3562 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3563 | public override int Read(byte[] buffer, int offset, int count) | |||
3564 | { | |||
3565 | lock (baseStream_) { | |||
3566 | if (count > end_ - readPos_) { | |||
3567 | count = (int)(end_ - readPos_); | |||
3568 | if (count == 0) { | |||
3569 | return 0; | |||
3570 | } | |||
3571 | } | |||
3572 | // Protect against Stream implementations that throw away their buffer on every Seek | |||
3573 | // (for example, Mono FileStream) | |||
3574 | if (baseStream_.Position != readPos_) { | |||
3575 | baseStream_.Seek(readPos_, SeekOrigin.Begin); | |||
3576 | } | |||
3577 | int readCount = baseStream_.Read(buffer, offset, count); | |||
3578 | if (readCount > 0) { | |||
3579 | readPos_ += readCount; | |||
3580 | } | |||
3581 | return readCount; | |||
3582 | } | |||
3583 | } | |||
3584 | | |||
3585 | /// <summary> | |||
3586 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3587 | /// </summary> | |||
3588 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3589 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3590 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3591 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3592 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3593 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3594 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3595 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3596 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3597 | public override void Write(byte[] buffer, int offset, int count) | |||
3598 | { | |||
3599 | throw new NotSupportedException(); | |||
3600 | } | |||
3601 | | |||
3602 | /// <summary> | |||
3603 | /// When overridden in a derived class, sets the length of the current stream. | |||
3604 | /// </summary> | |||
3605 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3606 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3607 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3608 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3609 | public override void SetLength(long value) | |||
3610 | { | |||
3611 | throw new NotSupportedException(); | |||
3612 | } | |||
3613 | | |||
3614 | /// <summary> | |||
3615 | /// When overridden in a derived class, sets the position within the current stream. | |||
3616 | /// </summary> | |||
3617 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3618 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3619 | /// <returns> | |||
3620 | /// The new position within the current stream. | |||
3621 | /// </returns> | |||
3622 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3623 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3624 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3625 | public override long Seek(long offset, SeekOrigin origin) | |||
3626 | { | |||
3627 | long newPos = readPos_; | |||
3628 | | |||
3629 | switch (origin) { | |||
3630 | case SeekOrigin.Begin: | |||
3631 | newPos = start_ + offset; | |||
3632 | break; | |||
3633 | | |||
3634 | case SeekOrigin.Current: | |||
3635 | newPos = readPos_ + offset; | |||
3636 | break; | |||
3637 | | |||
3638 | case SeekOrigin.End: | |||
3639 | newPos = end_ + offset; | |||
3640 | break; | |||
3641 | } | |||
3642 | | |||
3643 | if (newPos < start_) { | |||
3644 | throw new ArgumentException("Negative position is invalid"); | |||
3645 | } | |||
3646 | | |||
3647 | if (newPos >= end_) { | |||
3648 | throw new IOException("Cannot seek past end"); | |||
3649 | } | |||
3650 | readPos_ = newPos; | |||
3651 | return readPos_; | |||
3652 | } | |||
3653 | | |||
3654 | /// <summary> | |||
3655 | /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | |||
3656 | /// </summary> | |||
3657 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3658 | public override void Flush() | |||
3659 | { | |||
3660 | // Nothing to do. | |||
3661 | } | |||
3662 | | |||
3663 | /// <summary> | |||
3664 | /// Gets or sets the position within the current stream. | |||
3665 | /// </summary> | |||
3666 | /// <value></value> | |||
3667 | /// <returns>The current position within the stream.</returns> | |||
3668 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3669 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> | |||
3670 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3671 | public override long Position { | |||
3672 | get { return readPos_ - start_; } | |||
3673 | set { | |||
3674 | long newPos = start_ + value; | |||
3675 | | |||
3676 | if (newPos < start_) { | |||
3677 | throw new ArgumentException("Negative position is invalid"); | |||
3678 | } | |||
3679 | | |||
3680 | if (newPos >= end_) { | |||
3681 | throw new InvalidOperationException("Cannot seek past end"); | |||
3682 | } | |||
3683 | readPos_ = newPos; | |||
3684 | } | |||
3685 | } | |||
3686 | | |||
3687 | /// <summary> | |||
3688 | /// Gets the length in bytes of the stream. | |||
3689 | /// </summary> | |||
3690 | /// <value></value> | |||
3691 | /// <returns>A long value representing the length of the stream in bytes.</returns> | |||
3692 | /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </excep | |||
3693 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3694 | public override long Length { | |||
3695 | get { return length_; } | |||
3696 | } | |||
3697 | | |||
3698 | /// <summary> | |||
3699 | /// Gets a value indicating whether the current stream supports writing. | |||
3700 | /// </summary> | |||
3701 | /// <value>false</value> | |||
3702 | /// <returns>true if the stream supports writing; otherwise, false.</returns> | |||
3703 | public override bool CanWrite { | |||
3704 | get { return false; } | |||
3705 | } | |||
3706 | | |||
3707 | /// <summary> | |||
3708 | /// Gets a value indicating whether the current stream supports seeking. | |||
3709 | /// </summary> | |||
3710 | /// <value>true</value> | |||
3711 | /// <returns>true if the stream supports seeking; otherwise, false.</returns> | |||
3712 | public override bool CanSeek { | |||
3713 | get { return true; } | |||
3714 | } | |||
3715 | | |||
3716 | /// <summary> | |||
3717 | /// Gets a value indicating whether the current stream supports reading. | |||
3718 | /// </summary> | |||
3719 | /// <value>true.</value> | |||
3720 | /// <returns>true if the stream supports reading; otherwise, false.</returns> | |||
3721 | public override bool CanRead { | |||
3722 | get { return true; } | |||
3723 | } | |||
3724 | | |||
3725 | /// <summary> | |||
3726 | /// Gets a value that determines whether the current stream can time out. | |||
3727 | /// </summary> | |||
3728 | /// <value></value> | |||
3729 | /// <returns>A value that determines whether the current stream can time out.</returns> | |||
3730 | public override bool CanTimeout { | |||
3731 | get { return baseStream_.CanTimeout; } | |||
3732 | } | |||
3733 | #region Instance Fields | |||
3734 | ZipFile zipFile_; | |||
3735 | Stream baseStream_; | |||
3736 | long start_; | |||
3737 | long length_; | |||
3738 | long readPos_; | |||
3739 | long end_; | |||
3740 | #endregion | |||
3741 | } | |||
3742 | #endregion | |||
3743 | } | |||
3744 | | |||
3745 | #endregion | |||
3746 | | |||
3747 | #region DataSources | |||
3748 | /// <summary> | |||
3749 | /// Provides a static way to obtain a source of data for an entry. | |||
3750 | /// </summary> | |||
3751 | public interface IStaticDataSource | |||
3752 | { | |||
3753 | /// <summary> | |||
3754 | /// Get a source of data by creating a new stream. | |||
3755 | /// </summary> | |||
3756 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3757 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3758 | Stream GetSource(); | |||
3759 | } | |||
3760 | | |||
3761 | /// <summary> | |||
3762 | /// Represents a source of data that can dynamically provide | |||
3763 | /// multiple <see cref="Stream">data sources</see> based on the parameters passed. | |||
3764 | /// </summary> | |||
3765 | public interface IDynamicDataSource | |||
3766 | { | |||
3767 | /// <summary> | |||
3768 | /// Get a data source. | |||
3769 | /// </summary> | |||
3770 | /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> | |||
3771 | /// <param name="name">The name for data if known.</param> | |||
3772 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3773 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3774 | Stream GetSource(ZipEntry entry, string name); | |||
3775 | } | |||
3776 | | |||
3777 | /// <summary> | |||
3778 | /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. | |||
3779 | /// </summary> | |||
3780 | public class StaticDiskDataSource : IStaticDataSource | |||
3781 | { | |||
3782 | /// <summary> | |||
3783 | /// Initialise a new instnace of <see cref="StaticDiskDataSource"/> | |||
3784 | /// </summary> | |||
3785 | /// <param name="fileName">The name of the file to obtain data from.</param> | |||
3786 | public StaticDiskDataSource(string fileName) | |||
3787 | { | |||
3788 | fileName_ = fileName; | |||
3789 | } | |||
3790 | | |||
3791 | #region IDataSource Members | |||
3792 | | |||
3793 | /// <summary> | |||
3794 | /// Get a <see cref="Stream"/> providing data. | |||
3795 | /// </summary> | |||
3796 | /// <returns>Returns a <see cref="Stream"/> provising data.</returns> | |||
3797 | public Stream GetSource() | |||
3798 | { | |||
3799 | return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3800 | } | |||
3801 | | |||
3802 | readonly | |||
3803 | | |||
3804 | #endregion | |||
3805 | #region Instance Fields | |||
3806 | string fileName_; | |||
3807 | #endregion | |||
3808 | } | |||
3809 | | |||
3810 | | |||
3811 | /// <summary> | |||
3812 | /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. | |||
3813 | /// </summary> | |||
3814 | public class DynamicDiskDataSource : IDynamicDataSource | |||
3815 | { | |||
3816 | | |||
3817 | #region IDataSource Members | |||
3818 | /// <summary> | |||
3819 | /// Get a <see cref="Stream"/> providing data for an entry. | |||
3820 | /// </summary> | |||
3821 | /// <param name="entry">The entry to provide data for.</param> | |||
3822 | /// <param name="name">The file name for data if known.</param> | |||
3823 | /// <returns>Returns a stream providing data; or null if not available</returns> | |||
3824 | public Stream GetSource(ZipEntry entry, string name) | |||
3825 | { | |||
3826 | Stream result = null; | |||
3827 | | |||
3828 | if (name != null) { | |||
3829 | result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3830 | } | |||
3831 | | |||
3832 | return result; | |||
3833 | } | |||
3834 | | |||
3835 | #endregion | |||
3836 | } | |||
3837 | | |||
3838 | #endregion | |||
3839 | | |||
3840 | #region Archive Storage | |||
3841 | /// <summary> | |||
3842 | /// Defines facilities for data storage when updating Zip Archives. | |||
3843 | /// </summary> | |||
3844 | public interface IArchiveStorage | |||
3845 | { | |||
3846 | /// <summary> | |||
3847 | /// Get the <see cref="FileUpdateMode"/> to apply during updates. | |||
3848 | /// </summary> | |||
3849 | FileUpdateMode UpdateMode { get; } | |||
3850 | | |||
3851 | /// <summary> | |||
3852 | /// Get an empty <see cref="Stream"/> that can be used for temporary output. | |||
3853 | /// </summary> | |||
3854 | /// <returns>Returns a temporary output <see cref="Stream"/></returns> | |||
3855 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3856 | Stream GetTemporaryOutput(); | |||
3857 | | |||
3858 | /// <summary> | |||
3859 | /// Convert a temporary output stream to a final stream. | |||
3860 | /// </summary> | |||
3861 | /// <returns>The resulting final <see cref="Stream"/></returns> | |||
3862 | /// <seealso cref="GetTemporaryOutput"/> | |||
3863 | Stream ConvertTemporaryToFinal(); | |||
3864 | | |||
3865 | /// <summary> | |||
3866 | /// Make a temporary copy of the original stream. | |||
3867 | /// </summary> | |||
3868 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
3869 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3870 | Stream MakeTemporaryCopy(Stream stream); | |||
3871 | | |||
3872 | /// <summary> | |||
3873 | /// Return a stream suitable for performing direct updates on the original source. | |||
3874 | /// </summary> | |||
3875 | /// <param name="stream">The current stream.</param> | |||
3876 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3877 | /// <remarks>This may be the current stream passed.</remarks> | |||
3878 | Stream OpenForDirectUpdate(Stream stream); | |||
3879 | | |||
3880 | /// <summary> | |||
3881 | /// Dispose of this instance. | |||
3882 | /// </summary> | |||
3883 | void Dispose(); | |||
3884 | } | |||
3885 | | |||
3886 | /// <summary> | |||
3887 | /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. | |||
3888 | /// </summary> | |||
3889 | abstract public class BaseArchiveStorage : IArchiveStorage | |||
3890 | { | |||
3891 | #region Constructors | |||
3892 | /// <summary> | |||
3893 | /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. | |||
3894 | /// </summary> | |||
3895 | /// <param name="updateMode">The update mode.</param> | |||
3896 | protected BaseArchiveStorage(FileUpdateMode updateMode) | |||
3897 | { | |||
3898 | updateMode_ = updateMode; | |||
3899 | } | |||
3900 | #endregion | |||
3901 | | |||
3902 | #region IArchiveStorage Members | |||
3903 | | |||
3904 | /// <summary> | |||
3905 | /// Gets a temporary output <see cref="Stream"/> | |||
3906 | /// </summary> | |||
3907 | /// <returns>Returns the temporary output stream.</returns> | |||
3908 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3909 | public abstract Stream GetTemporaryOutput(); | |||
3910 | | |||
3911 | /// <summary> | |||
3912 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
3913 | /// </summary> | |||
3914 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
3915 | /// the final storage for the archive.</returns> | |||
3916 | /// <seealso cref="GetTemporaryOutput"/> | |||
3917 | public abstract Stream ConvertTemporaryToFinal(); | |||
3918 | | |||
3919 | /// <summary> | |||
3920 | /// Make a temporary copy of a <see cref="Stream"/>. | |||
3921 | /// </summary> | |||
3922 | /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> | |||
3923 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3924 | public abstract Stream MakeTemporaryCopy(Stream stream); | |||
3925 | | |||
3926 | /// <summary> | |||
3927 | /// Return a stream suitable for performing direct updates on the original source. | |||
3928 | /// </summary> | |||
3929 | /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> | |||
3930 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3931 | public abstract Stream OpenForDirectUpdate(Stream stream); | |||
3932 | | |||
3933 | /// <summary> | |||
3934 | /// Disposes this instance. | |||
3935 | /// </summary> | |||
3936 | public abstract void Dispose(); | |||
3937 | | |||
3938 | /// <summary> | |||
3939 | /// Gets the update mode applicable. | |||
3940 | /// </summary> | |||
3941 | /// <value>The update mode.</value> | |||
3942 | public FileUpdateMode UpdateMode { | |||
3943 | get { | |||
3944 | return updateMode_; | |||
3945 | } | |||
3946 | } | |||
3947 | | |||
3948 | #endregion | |||
3949 | | |||
3950 | #region Instance Fields | |||
3951 | FileUpdateMode updateMode_; | |||
3952 | #endregion | |||
3953 | } | |||
3954 | | |||
3955 | /// <summary> | |||
3956 | /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. | |||
3957 | /// </summary> | |||
3958 | public class DiskArchiveStorage : BaseArchiveStorage | |||
3959 | { | |||
3960 | #region Constructors | |||
3961 | /// <summary> | |||
3962 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3963 | /// </summary> | |||
3964 | /// <param name="file">The file.</param> | |||
3965 | /// <param name="updateMode">The update mode.</param> | |||
3966 | public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) | |||
3967 | : base(updateMode) | |||
3968 | { | |||
3969 | if (file.Name == null) { | |||
3970 | throw new ZipException("Cant handle non file archives"); | |||
3971 | } | |||
3972 | | |||
3973 | fileName_ = file.Name; | |||
3974 | } | |||
3975 | | |||
3976 | /// <summary> | |||
3977 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3978 | /// </summary> | |||
3979 | /// <param name="file">The file.</param> | |||
3980 | public DiskArchiveStorage(ZipFile file) | |||
3981 | : this(file, FileUpdateMode.Safe) | |||
3982 | { | |||
3983 | } | |||
3984 | #endregion | |||
3985 | | |||
3986 | #region IArchiveStorage Members | |||
3987 | | |||
3988 | /// <summary> | |||
3989 | /// Gets a temporary output <see cref="Stream"/> for performing updates on. | |||
3990 | /// </summary> | |||
3991 | /// <returns>Returns the temporary output stream.</returns> | |||
3992 | public override Stream GetTemporaryOutput() | |||
3993 | { | |||
3994 | if (temporaryName_ != null) { | |||
3995 | temporaryName_ = GetTempFileName(temporaryName_, true); | |||
3996 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
3997 | } else { | |||
3998 | // Determine where to place files based on internal strategy. | |||
3999 | // Currently this is always done in system temp directory. | |||
4000 | temporaryName_ = Path.GetTempFileName(); | |||
4001 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
4002 | } | |||
4003 | | |||
4004 | return temporaryStream_; | |||
4005 | } | |||
4006 | | |||
4007 | /// <summary> | |||
4008 | /// Converts a temporary <see cref="Stream"/> to its final form. | |||
4009 | /// </summary> | |||
4010 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4011 | /// the final storage for the archive.</returns> | |||
4012 | public override Stream ConvertTemporaryToFinal() | |||
4013 | { | |||
4014 | if (temporaryStream_ == null) { | |||
4015 | throw new ZipException("No temporary stream has been created"); | |||
4016 | } | |||
4017 | | |||
4018 | Stream result = null; | |||
4019 | | |||
4020 | string moveTempName = GetTempFileName(fileName_, false); | |||
4021 | bool newFileCreated = false; | |||
4022 | | |||
4023 | try { | |||
4024 | temporaryStream_.Close(); | |||
4025 | File.Move(fileName_, moveTempName); | |||
4026 | File.Move(temporaryName_, fileName_); | |||
4027 | newFileCreated = true; | |||
4028 | File.Delete(moveTempName); | |||
4029 | | |||
4030 | result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
4031 | } catch (Exception) { | |||
4032 | result = null; | |||
4033 | | |||
4034 | // Try to roll back changes... | |||
4035 | if (!newFileCreated) { | |||
4036 | File.Move(moveTempName, fileName_); | |||
4037 | File.Delete(temporaryName_); | |||
4038 | } | |||
4039 | | |||
4040 | throw; | |||
4041 | } | |||
4042 | | |||
4043 | return result; | |||
4044 | } | |||
4045 | | |||
4046 | /// <summary> | |||
4047 | /// Make a temporary copy of a stream. | |||
4048 | /// </summary> | |||
4049 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4050 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4051 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4052 | { | |||
4053 | stream.Close(); | |||
4054 | | |||
4055 | temporaryName_ = GetTempFileName(fileName_, true); | |||
4056 | File.Copy(fileName_, temporaryName_, true); | |||
4057 | | |||
4058 | temporaryStream_ = new FileStream(temporaryName_, | |||
4059 | FileMode.Open, | |||
4060 | FileAccess.ReadWrite); | |||
4061 | return temporaryStream_; | |||
4062 | } | |||
4063 | | |||
4064 | /// <summary> | |||
4065 | /// Return a stream suitable for performing direct updates on the original source. | |||
4066 | /// </summary> | |||
4067 | /// <param name="stream">The current stream.</param> | |||
4068 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4069 | /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks> | |||
4070 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4071 | { | |||
4072 | Stream result; | |||
4073 | if ((stream == null) || !stream.CanWrite) { | |||
4074 | if (stream != null) { | |||
4075 | stream.Close(); | |||
4076 | } | |||
4077 | | |||
4078 | result = new FileStream(fileName_, | |||
4079 | FileMode.Open, | |||
4080 | FileAccess.ReadWrite); | |||
4081 | } else { | |||
4082 | result = stream; | |||
4083 | } | |||
4084 | | |||
4085 | return result; | |||
4086 | } | |||
4087 | | |||
4088 | /// <summary> | |||
4089 | /// Disposes this instance. | |||
4090 | /// </summary> | |||
4091 | public override void Dispose() | |||
4092 | { | |||
4093 | if (temporaryStream_ != null) { | |||
4094 | temporaryStream_.Close(); | |||
4095 | } | |||
4096 | } | |||
4097 | | |||
4098 | #endregion | |||
4099 | | |||
4100 | #region Internal routines | |||
4101 | static string GetTempFileName(string original, bool makeTempFile) | |||
4102 | { | |||
4103 | string result = null; | |||
4104 | | |||
4105 | if (original == null) { | |||
4106 | result = Path.GetTempFileName(); | |||
4107 | } else { | |||
4108 | int counter = 0; | |||
4109 | int suffixSeed = DateTime.Now.Second; | |||
4110 | | |||
4111 | while (result == null) { | |||
4112 | counter += 1; | |||
4113 | string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); | |||
4114 | if (!File.Exists(newName)) { | |||
4115 | if (makeTempFile) { | |||
4116 | try { | |||
4117 | // Try and create the file. | |||
4118 | using (FileStream stream = File.Create(newName)) { | |||
4119 | } | |||
4120 | result = newName; | |||
4121 | } catch { | |||
4122 | suffixSeed = DateTime.Now.Second; | |||
4123 | } | |||
4124 | } else { | |||
4125 | result = newName; | |||
4126 | } | |||
4127 | } | |||
4128 | } | |||
4129 | } | |||
4130 | return result; | |||
4131 | } | |||
4132 | #endregion | |||
4133 | | |||
4134 | #region Instance Fields | |||
4135 | Stream temporaryStream_; | |||
4136 | string fileName_; | |||
4137 | string temporaryName_; | |||
4138 | #endregion | |||
4139 | } | |||
4140 | | |||
4141 | /// <summary> | |||
4142 | /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. | |||
4143 | /// </summary> | |||
4144 | public class MemoryArchiveStorage : BaseArchiveStorage | |||
4145 | { | |||
4146 | #region Constructors | |||
4147 | /// <summary> | |||
4148 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4149 | /// </summary> | |||
4150 | public MemoryArchiveStorage() | |||
4151 | : base(FileUpdateMode.Direct) | |||
4152 | { | |||
4153 | } | |||
4154 | | |||
4155 | /// <summary> | |||
4156 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4157 | /// </summary> | |||
4158 | /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> | |||
4159 | /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> | |||
4160 | public MemoryArchiveStorage(FileUpdateMode updateMode) | |||
4161 | : base(updateMode) | |||
4162 | { | |||
4163 | } | |||
4164 | | |||
4165 | #endregion | |||
4166 | | |||
4167 | #region Properties | |||
4168 | /// <summary> | |||
4169 | /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. | |||
4170 | /// </summary> | |||
4171 | public MemoryStream FinalStream { | |||
4172 | get { return finalStream_; } | |||
4173 | } | |||
4174 | | |||
4175 | #endregion | |||
4176 | | |||
4177 | #region IArchiveStorage Members | |||
4178 | | |||
4179 | /// <summary> | |||
4180 | /// Gets the temporary output <see cref="Stream"/> | |||
4181 | /// </summary> | |||
4182 | /// <returns>Returns the temporary output stream.</returns> | |||
4183 | public override Stream GetTemporaryOutput() | |||
4184 | { | |||
4185 | temporaryStream_ = new MemoryStream(); | |||
4186 | return temporaryStream_; | |||
4187 | } | |||
4188 | | |||
4189 | /// <summary> | |||
4190 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
4191 | /// </summary> | |||
4192 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4193 | /// the final storage for the archive.</returns> | |||
4194 | public override Stream ConvertTemporaryToFinal() | |||
4195 | { | |||
4196 | if (temporaryStream_ == null) { | |||
4197 | throw new ZipException("No temporary stream has been created"); | |||
4198 | } | |||
4199 | | |||
4200 | finalStream_ = new MemoryStream(temporaryStream_.ToArray()); | |||
4201 | return finalStream_; | |||
4202 | } | |||
4203 | | |||
4204 | /// <summary> | |||
4205 | /// Make a temporary copy of the original stream. | |||
4206 | /// </summary> | |||
4207 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4208 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4209 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4210 | { | |||
4211 | temporaryStream_ = new MemoryStream(); | |||
4212 | stream.Position = 0; | |||
4213 | StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); | |||
4214 | return temporaryStream_; | |||
4215 | } | |||
4216 | | |||
4217 | /// <summary> | |||
4218 | /// Return a stream suitable for performing direct updates on the original source. | |||
4219 | /// </summary> | |||
4220 | /// <param name="stream">The original source stream</param> | |||
4221 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4222 | /// <remarks>If the <paramref name="stream"/> passed is not null this is used; | |||
4223 | /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> | |||
4224 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4225 | { | |||
4226 | Stream result; | |||
4227 | if ((stream == null) || !stream.CanWrite) { | |||
4228 | | |||
4229 | result = new MemoryStream(); | |||
4230 | | |||
4231 | if (stream != null) { | |||
4232 | stream.Position = 0; | |||
4233 | StreamUtils.Copy(stream, result, new byte[4096]); | |||
4234 | | |||
4235 | stream.Close(); | |||
4236 | } | |||
4237 | } else { | |||
4238 | result = stream; | |||
4239 | } | |||
4240 | | |||
4241 | return result; | |||
4242 | } | |||
4243 | | |||
4244 | /// <summary> | |||
4245 | /// Disposes this instance. | |||
4246 | /// </summary> | |||
4247 | public override void Dispose() | |||
4248 | { | |||
4249 | if (temporaryStream_ != null) { | |||
4250 | temporaryStream_.Close(); | |||
4251 | } | |||
4252 | } | |||
4253 | | |||
4254 | #endregion | |||
4255 | | |||
4256 | #region Instance Fields | |||
4257 | MemoryStream temporaryStream_; | |||
4258 | MemoryStream finalStream_; | |||
4259 | #endregion | |||
4260 | } | |||
4261 | | |||
4262 | #endregion | |||
4263 | } |
| Class: | ICSharpCode.SharpZipLib.Lzw.LzwConstants |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Lzw\LzwConstants.cs |
| Covered lines: | 0 |
| Uncovered lines: | 2 |
| Coverable lines: | 2 |
| Total lines: | 61 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | namespace ICSharpCode.SharpZipLib.Lzw | |||
2 | { | |||
3 | /// <summary> | |||
4 | /// This class contains constants used for LZW | |||
5 | /// </summary> | |||
6 | sealed public class LzwConstants | |||
7 | { | |||
8 | /// <summary> | |||
9 | /// Magic number found at start of LZW header: 0x1f 0x9d | |||
10 | /// </summary> | |||
11 | public const int MAGIC = 0x1f9d; | |||
12 | | |||
13 | /// <summary> | |||
14 | /// Maximum number of bits per code | |||
15 | /// </summary> | |||
16 | public const int MAX_BITS = 16; | |||
17 | | |||
18 | /* 3rd header byte: | |||
19 | * bit 0..4 Number of compression bits | |||
20 | * bit 5 Extended header | |||
21 | * bit 6 Free | |||
22 | * bit 7 Block mode | |||
23 | */ | |||
24 | | |||
25 | /// <summary> | |||
26 | /// Mask for 'number of compression bits' | |||
27 | /// </summary> | |||
28 | public const int BIT_MASK = 0x1f; | |||
29 | | |||
30 | /// <summary> | |||
31 | /// Indicates the presence of a fourth header byte | |||
32 | /// </summary> | |||
33 | public const int EXTENDED_MASK = 0x20; | |||
34 | //public const int FREE_MASK = 0x40; | |||
35 | | |||
36 | /// <summary> | |||
37 | /// Reserved bits | |||
38 | /// </summary> | |||
39 | public const int RESERVED_MASK = 0x60; | |||
40 | | |||
41 | /// <summary> | |||
42 | /// Block compression: if table is full and compression rate is dropping, | |||
43 | /// clear the dictionary. | |||
44 | /// </summary> | |||
45 | public const int BLOCK_MODE_MASK = 0x80; | |||
46 | | |||
47 | /// <summary> | |||
48 | /// LZW file header size (in bytes) | |||
49 | /// </summary> | |||
50 | public const int HDR_SIZE = 3; | |||
51 | | |||
52 | /// <summary> | |||
53 | /// Initial number of bits per code | |||
54 | /// </summary> | |||
55 | public const int INIT_BITS = 9; | |||
56 | | |||
| 0 | 57 | LzwConstants() | ||
58 | { | |||
| 0 | 59 | } | ||
60 | } | |||
61 | } |
| Class: | ICSharpCode.SharpZipLib.Lzw.LzwException |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Lzw\LzwException.cs |
| Covered lines: | 0 |
| Uncovered lines: | 8 |
| Coverable lines: | 8 |
| Total lines: | 48 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Runtime.Serialization; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Lzw | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// LzwException represents exceptions specific to LZW classes and code. | |||
8 | /// </summary> | |||
9 | [Serializable] | |||
10 | public class LzwException : SharpZipBaseException | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Deserialization constructor | |||
14 | /// </summary> | |||
15 | /// <param name="info"><see cref="SerializationInfo"/> for this constructor</param> | |||
16 | /// <param name="context"><see cref="StreamingContext"/> for this constructor</param> | |||
17 | protected LzwException(SerializationInfo info, StreamingContext context) | |||
| 0 | 18 | : base(info, context) | ||
19 | { | |||
| 0 | 20 | } | ||
21 | | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="LzwException" />. | |||
24 | /// </summary> | |||
| 0 | 25 | public LzwException() | ||
26 | { | |||
| 0 | 27 | } | ||
28 | | |||
29 | /// <summary> | |||
30 | /// Initialise a new instance of <see cref="LzwException" /> with its message string. | |||
31 | /// </summary> | |||
32 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
33 | public LzwException(string message) | |||
| 0 | 34 | : base(message) | ||
35 | { | |||
| 0 | 36 | } | ||
37 | | |||
38 | /// <summary> | |||
39 | /// Initialise a new instance of <see cref="LzwException" />. | |||
40 | /// </summary> | |||
41 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
42 | /// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param> | |||
43 | public LzwException(string message, Exception innerException) | |||
| 0 | 44 | : base(message, innerException) | ||
45 | { | |||
| 0 | 46 | } | ||
47 | } | |||
48 | } |
| Class: | ICSharpCode.SharpZipLib.Lzw.LzwInputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Lzw\LzwInputStream.cs |
| Covered lines: | 0 |
| Uncovered lines: | 194 |
| Coverable lines: | 194 |
| Total lines: | 559 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| ReadByte() | 2 | 0 | 0 |
| Read(...) | 25 | 0 | 0 |
| ResetBuf(...) | 1 | 0 | 0 |
| Fill() | 2 | 0 | 0 |
| ParseHeader() | 9 | 0 | 0 |
| Flush() | 1 | 0 | 0 |
| Seek(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| Write(...) | 1 | 0 | 0 |
| WriteByte(...) | 1 | 0 | 0 |
| BeginWrite(...) | 1 | 0 | 0 |
| Close() | 3 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Lzw | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// This filter stream is used to decompress a LZW format stream. | |||
8 | /// Specifically, a stream that uses the LZC compression method. | |||
9 | /// This file format is usually associated with the .Z file extension. | |||
10 | /// | |||
11 | /// See http://en.wikipedia.org/wiki/Compress | |||
12 | /// See http://wiki.wxwidgets.org/Development:_Z_File_Format | |||
13 | /// | |||
14 | /// The file header consists of 3 (or optionally 4) bytes. The first two bytes | |||
15 | /// contain the magic marker "0x1f 0x9d", followed by a byte of flags. | |||
16 | /// | |||
17 | /// Based on Java code by Ronald Tschalar, which in turn was based on the unlzw.c | |||
18 | /// code in the gzip package. | |||
19 | /// </summary> | |||
20 | /// <example> This sample shows how to unzip a compressed file | |||
21 | /// <code> | |||
22 | /// using System; | |||
23 | /// using System.IO; | |||
24 | /// | |||
25 | /// using ICSharpCode.SharpZipLib.Core; | |||
26 | /// using ICSharpCode.SharpZipLib.LZW; | |||
27 | /// | |||
28 | /// class MainClass | |||
29 | /// { | |||
30 | /// public static void Main(string[] args) | |||
31 | /// { | |||
32 | /// using (Stream inStream = new LzwInputStream(File.OpenRead(args[0]))) | |||
33 | /// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { | |||
34 | /// byte[] buffer = new byte[4096]; | |||
35 | /// StreamUtils.Copy(inStream, outStream, buffer); | |||
36 | /// // OR | |||
37 | /// inStream.Read(buffer, 0, buffer.Length); | |||
38 | /// // now do something with the buffer | |||
39 | /// } | |||
40 | /// } | |||
41 | /// } | |||
42 | /// </code> | |||
43 | /// </example> | |||
44 | public class LzwInputStream : Stream | |||
45 | { | |||
46 | /// <summary> | |||
47 | /// Get/set flag indicating ownership of underlying stream. | |||
48 | /// When the flag is true <see cref="Close"/> will close the underlying stream also. | |||
49 | /// </summary> | |||
50 | /// <remarks> | |||
51 | /// The default value is true. | |||
52 | /// </remarks> | |||
53 | public bool IsStreamOwner { | |||
| 0 | 54 | get { return isStreamOwner; } | ||
| 0 | 55 | set { isStreamOwner = value; } | ||
56 | } | |||
57 | | |||
58 | /// <summary> | |||
59 | /// Creates a LzwInputStream | |||
60 | /// </summary> | |||
61 | /// <param name="baseInputStream"> | |||
62 | /// The stream to read compressed data from (baseInputStream LZW format) | |||
63 | /// </param> | |||
| 0 | 64 | public LzwInputStream(Stream baseInputStream) | ||
65 | { | |||
| 0 | 66 | this.baseInputStream = baseInputStream; | ||
| 0 | 67 | } | ||
68 | | |||
69 | /// <summary> | |||
70 | /// See <see cref="System.IO.Stream.ReadByte"/> | |||
71 | /// </summary> | |||
72 | /// <returns></returns> | |||
73 | public override int ReadByte() | |||
74 | { | |||
| 0 | 75 | int b = Read(one, 0, 1); | ||
| 0 | 76 | if (b == 1) | ||
| 0 | 77 | return (one[0] & 0xff); | ||
| 0 | 78 | return -1; | ||
79 | } | |||
80 | | |||
81 | /// <summary> | |||
82 | /// Reads decompressed data into the provided buffer byte array | |||
83 | /// </summary> | |||
84 | /// <param name ="buffer"> | |||
85 | /// The array to read and decompress data into | |||
86 | /// </param> | |||
87 | /// <param name ="offset"> | |||
88 | /// The offset indicating where the data should be placed | |||
89 | /// </param> | |||
90 | /// <param name ="count"> | |||
91 | /// The number of bytes to decompress | |||
92 | /// </param> | |||
93 | /// <returns>The number of bytes read. Zero signals the end of stream</returns> | |||
94 | public override int Read(byte[] buffer, int offset, int count) | |||
95 | { | |||
| 0 | 96 | if (!headerParsed) | ||
| 0 | 97 | ParseHeader(); | ||
98 | | |||
| 0 | 99 | if (eof) | ||
| 0 | 100 | return 0; | ||
101 | | |||
| 0 | 102 | int start = offset; | ||
103 | | |||
104 | /* Using local copies of various variables speeds things up by as | |||
105 | * much as 30% in Java! Performance not tested in C#. | |||
106 | */ | |||
| 0 | 107 | int[] lTabPrefix = tabPrefix; | ||
| 0 | 108 | byte[] lTabSuffix = tabSuffix; | ||
| 0 | 109 | byte[] lStack = stack; | ||
| 0 | 110 | int lNBits = nBits; | ||
| 0 | 111 | int lMaxCode = maxCode; | ||
| 0 | 112 | int lMaxMaxCode = maxMaxCode; | ||
| 0 | 113 | int lBitMask = bitMask; | ||
| 0 | 114 | int lOldCode = oldCode; | ||
| 0 | 115 | byte lFinChar = finChar; | ||
| 0 | 116 | int lStackP = stackP; | ||
| 0 | 117 | int lFreeEnt = freeEnt; | ||
| 0 | 118 | byte[] lData = data; | ||
| 0 | 119 | int lBitPos = bitPos; | ||
120 | | |||
121 | | |||
122 | // empty stack if stuff still left | |||
| 0 | 123 | int sSize = lStack.Length - lStackP; | ||
| 0 | 124 | if (sSize > 0) { | ||
| 0 | 125 | int num = (sSize >= count) ? count : sSize; | ||
| 0 | 126 | Array.Copy(lStack, lStackP, buffer, offset, num); | ||
| 0 | 127 | offset += num; | ||
| 0 | 128 | count -= num; | ||
| 0 | 129 | lStackP += num; | ||
130 | } | |||
131 | | |||
| 0 | 132 | if (count == 0) { | ||
| 0 | 133 | stackP = lStackP; | ||
| 0 | 134 | return offset - start; | ||
135 | } | |||
136 | | |||
137 | | |||
138 | // loop, filling local buffer until enough data has been decompressed | |||
139 | MainLoop: | |||
140 | do { | |||
| 0 | 141 | if (end < EXTRA) { | ||
| 0 | 142 | Fill(); | ||
143 | } | |||
144 | | |||
| 0 | 145 | int bitIn = (got > 0) ? (end - end % lNBits) << 3 : | ||
| 0 | 146 | (end << 3) - (lNBits - 1); | ||
147 | | |||
| 0 | 148 | while (lBitPos < bitIn) { | ||
149 | #region A | |||
150 | // handle 1-byte reads correctly | |||
| 0 | 151 | if (count == 0) { | ||
| 0 | 152 | nBits = lNBits; | ||
| 0 | 153 | maxCode = lMaxCode; | ||
| 0 | 154 | maxMaxCode = lMaxMaxCode; | ||
| 0 | 155 | bitMask = lBitMask; | ||
| 0 | 156 | oldCode = lOldCode; | ||
| 0 | 157 | finChar = lFinChar; | ||
| 0 | 158 | stackP = lStackP; | ||
| 0 | 159 | freeEnt = lFreeEnt; | ||
| 0 | 160 | bitPos = lBitPos; | ||
161 | | |||
| 0 | 162 | return offset - start; | ||
163 | } | |||
164 | | |||
165 | // check for code-width expansion | |||
| 0 | 166 | if (lFreeEnt > lMaxCode) { | ||
| 0 | 167 | int nBytes = lNBits << 3; | ||
| 0 | 168 | lBitPos = (lBitPos - 1) + | ||
| 0 | 169 | nBytes - (lBitPos - 1 + nBytes) % nBytes; | ||
170 | | |||
| 0 | 171 | lNBits++; | ||
| 0 | 172 | lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : | ||
| 0 | 173 | (1 << lNBits) - 1; | ||
174 | | |||
| 0 | 175 | lBitMask = (1 << lNBits) - 1; | ||
| 0 | 176 | lBitPos = ResetBuf(lBitPos); | ||
| 0 | 177 | goto MainLoop; | ||
178 | } | |||
179 | #endregion | |||
180 | | |||
181 | #region B | |||
182 | // read next code | |||
| 0 | 183 | int pos = lBitPos >> 3; | ||
| 0 | 184 | int code = (((lData[pos] & 0xFF) | | ||
| 0 | 185 | ((lData[pos + 1] & 0xFF) << 8) | | ||
| 0 | 186 | ((lData[pos + 2] & 0xFF) << 16)) >> | ||
| 0 | 187 | (lBitPos & 0x7)) & lBitMask; | ||
188 | | |||
| 0 | 189 | lBitPos += lNBits; | ||
190 | | |||
191 | // handle first iteration | |||
| 0 | 192 | if (lOldCode == -1) { | ||
| 0 | 193 | if (code >= 256) | ||
| 0 | 194 | throw new LzwException("corrupt input: " + code + " > 255"); | ||
195 | | |||
| 0 | 196 | lFinChar = (byte)(lOldCode = code); | ||
| 0 | 197 | buffer[offset++] = lFinChar; | ||
| 0 | 198 | count--; | ||
| 0 | 199 | continue; | ||
200 | } | |||
201 | | |||
202 | // handle CLEAR code | |||
| 0 | 203 | if (code == TBL_CLEAR && blockMode) { | ||
| 0 | 204 | Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length); | ||
| 0 | 205 | lFreeEnt = TBL_FIRST - 1; | ||
206 | | |||
| 0 | 207 | int nBytes = lNBits << 3; | ||
| 0 | 208 | lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; | ||
| 0 | 209 | lNBits = LzwConstants.INIT_BITS; | ||
| 0 | 210 | lMaxCode = (1 << lNBits) - 1; | ||
| 0 | 211 | lBitMask = lMaxCode; | ||
212 | | |||
213 | // Code tables reset | |||
214 | | |||
| 0 | 215 | lBitPos = ResetBuf(lBitPos); | ||
| 0 | 216 | goto MainLoop; | ||
217 | } | |||
218 | #endregion | |||
219 | | |||
220 | #region C | |||
221 | // setup | |||
| 0 | 222 | int inCode = code; | ||
| 0 | 223 | lStackP = lStack.Length; | ||
224 | | |||
225 | // Handle KwK case | |||
| 0 | 226 | if (code >= lFreeEnt) { | ||
| 0 | 227 | if (code > lFreeEnt) { | ||
| 0 | 228 | throw new LzwException("corrupt input: code=" + code + | ||
| 0 | 229 | ", freeEnt=" + lFreeEnt); | ||
230 | } | |||
231 | | |||
| 0 | 232 | lStack[--lStackP] = lFinChar; | ||
| 0 | 233 | code = lOldCode; | ||
234 | } | |||
235 | | |||
236 | // Generate output characters in reverse order | |||
| 0 | 237 | while (code >= 256) { | ||
| 0 | 238 | lStack[--lStackP] = lTabSuffix[code]; | ||
| 0 | 239 | code = lTabPrefix[code]; | ||
240 | } | |||
241 | | |||
| 0 | 242 | lFinChar = lTabSuffix[code]; | ||
| 0 | 243 | buffer[offset++] = lFinChar; | ||
| 0 | 244 | count--; | ||
245 | | |||
246 | // And put them out in forward order | |||
| 0 | 247 | sSize = lStack.Length - lStackP; | ||
| 0 | 248 | int num = (sSize >= count) ? count : sSize; | ||
| 0 | 249 | Array.Copy(lStack, lStackP, buffer, offset, num); | ||
| 0 | 250 | offset += num; | ||
| 0 | 251 | count -= num; | ||
| 0 | 252 | lStackP += num; | ||
253 | #endregion | |||
254 | | |||
255 | #region D | |||
256 | // generate new entry in table | |||
| 0 | 257 | if (lFreeEnt < lMaxMaxCode) { | ||
| 0 | 258 | lTabPrefix[lFreeEnt] = lOldCode; | ||
| 0 | 259 | lTabSuffix[lFreeEnt] = lFinChar; | ||
| 0 | 260 | lFreeEnt++; | ||
261 | } | |||
262 | | |||
263 | // Remember previous code | |||
| 0 | 264 | lOldCode = inCode; | ||
265 | | |||
266 | // if output buffer full, then return | |||
| 0 | 267 | if (count == 0) { | ||
| 0 | 268 | nBits = lNBits; | ||
| 0 | 269 | maxCode = lMaxCode; | ||
| 0 | 270 | bitMask = lBitMask; | ||
| 0 | 271 | oldCode = lOldCode; | ||
| 0 | 272 | finChar = lFinChar; | ||
| 0 | 273 | stackP = lStackP; | ||
| 0 | 274 | freeEnt = lFreeEnt; | ||
| 0 | 275 | bitPos = lBitPos; | ||
276 | | |||
| 0 | 277 | return offset - start; | ||
278 | } | |||
279 | #endregion | |||
280 | } // while | |||
281 | | |||
| 0 | 282 | lBitPos = ResetBuf(lBitPos); | ||
283 | | |||
| 0 | 284 | } while (got > 0); // do..while | ||
285 | | |||
| 0 | 286 | nBits = lNBits; | ||
| 0 | 287 | maxCode = lMaxCode; | ||
| 0 | 288 | bitMask = lBitMask; | ||
| 0 | 289 | oldCode = lOldCode; | ||
| 0 | 290 | finChar = lFinChar; | ||
| 0 | 291 | stackP = lStackP; | ||
| 0 | 292 | freeEnt = lFreeEnt; | ||
| 0 | 293 | bitPos = lBitPos; | ||
294 | | |||
| 0 | 295 | eof = true; | ||
| 0 | 296 | return offset - start; | ||
297 | } | |||
298 | | |||
299 | /// <summary> | |||
300 | /// Moves the unread data in the buffer to the beginning and resets | |||
301 | /// the pointers. | |||
302 | /// </summary> | |||
303 | /// <param name="bitPosition"></param> | |||
304 | /// <returns></returns> | |||
305 | private int ResetBuf(int bitPosition) | |||
306 | { | |||
| 0 | 307 | int pos = bitPosition >> 3; | ||
| 0 | 308 | Array.Copy(data, pos, data, 0, end - pos); | ||
| 0 | 309 | end -= pos; | ||
| 0 | 310 | return 0; | ||
311 | } | |||
312 | | |||
313 | | |||
314 | private void Fill() | |||
315 | { | |||
| 0 | 316 | got = baseInputStream.Read(data, end, data.Length - 1 - end); | ||
| 0 | 317 | if (got > 0) { | ||
| 0 | 318 | end += got; | ||
319 | } | |||
| 0 | 320 | } | ||
321 | | |||
322 | | |||
323 | private void ParseHeader() | |||
324 | { | |||
| 0 | 325 | headerParsed = true; | ||
326 | | |||
| 0 | 327 | byte[] hdr = new byte[LzwConstants.HDR_SIZE]; | ||
328 | | |||
| 0 | 329 | int result = baseInputStream.Read(hdr, 0, hdr.Length); | ||
330 | | |||
331 | // Check the magic marker | |||
| 0 | 332 | if (result < 0) | ||
| 0 | 333 | throw new LzwException("Failed to read LZW header"); | ||
334 | | |||
| 0 | 335 | if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) { | ||
| 0 | 336 | throw new LzwException(String.Format( | ||
| 0 | 337 | "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", | ||
| 0 | 338 | hdr[0], hdr[1])); | ||
339 | } | |||
340 | | |||
341 | // Check the 3rd header byte | |||
| 0 | 342 | blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; | ||
| 0 | 343 | maxBits = hdr[2] & LzwConstants.BIT_MASK; | ||
344 | | |||
| 0 | 345 | if (maxBits > LzwConstants.MAX_BITS) { | ||
| 0 | 346 | throw new LzwException("Stream compressed with " + maxBits + | ||
| 0 | 347 | " bits, but decompression can only handle " + | ||
| 0 | 348 | LzwConstants.MAX_BITS + " bits."); | ||
349 | } | |||
350 | | |||
| 0 | 351 | if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) { | ||
| 0 | 352 | throw new LzwException("Unsupported bits set in the header."); | ||
353 | } | |||
354 | | |||
355 | // Initialize variables | |||
| 0 | 356 | maxMaxCode = 1 << maxBits; | ||
| 0 | 357 | nBits = LzwConstants.INIT_BITS; | ||
| 0 | 358 | maxCode = (1 << nBits) - 1; | ||
| 0 | 359 | bitMask = maxCode; | ||
| 0 | 360 | oldCode = -1; | ||
| 0 | 361 | finChar = 0; | ||
| 0 | 362 | freeEnt = blockMode ? TBL_FIRST : 256; | ||
363 | | |||
| 0 | 364 | tabPrefix = new int[1 << maxBits]; | ||
| 0 | 365 | tabSuffix = new byte[1 << maxBits]; | ||
| 0 | 366 | stack = new byte[1 << maxBits]; | ||
| 0 | 367 | stackP = stack.Length; | ||
368 | | |||
| 0 | 369 | for (int idx = 255; idx >= 0; idx--) | ||
| 0 | 370 | tabSuffix[idx] = (byte)idx; | ||
| 0 | 371 | } | ||
372 | | |||
373 | #region Stream Overrides | |||
374 | /// <summary> | |||
375 | /// Gets a value indicating whether the current stream supports reading | |||
376 | /// </summary> | |||
377 | public override bool CanRead { | |||
378 | get { | |||
| 0 | 379 | return baseInputStream.CanRead; | ||
380 | } | |||
381 | } | |||
382 | | |||
383 | /// <summary> | |||
384 | /// Gets a value of false indicating seeking is not supported for this stream. | |||
385 | /// </summary> | |||
386 | public override bool CanSeek { | |||
387 | get { | |||
| 0 | 388 | return false; | ||
389 | } | |||
390 | } | |||
391 | | |||
392 | /// <summary> | |||
393 | /// Gets a value of false indicating that this stream is not writeable. | |||
394 | /// </summary> | |||
395 | public override bool CanWrite { | |||
396 | get { | |||
| 0 | 397 | return false; | ||
398 | } | |||
399 | } | |||
400 | | |||
401 | /// <summary> | |||
402 | /// A value representing the length of the stream in bytes. | |||
403 | /// </summary> | |||
404 | public override long Length { | |||
405 | get { | |||
| 0 | 406 | return got; | ||
407 | } | |||
408 | } | |||
409 | | |||
410 | /// <summary> | |||
411 | /// The current position within the stream. | |||
412 | /// Throws a NotSupportedException when attempting to set the position | |||
413 | /// </summary> | |||
414 | /// <exception cref="NotSupportedException">Attempting to set the position</exception> | |||
415 | public override long Position { | |||
416 | get { | |||
| 0 | 417 | return baseInputStream.Position; | ||
418 | } | |||
419 | set { | |||
| 0 | 420 | throw new NotSupportedException("InflaterInputStream Position not supported"); | ||
421 | } | |||
422 | } | |||
423 | | |||
424 | /// <summary> | |||
425 | /// Flushes the baseInputStream | |||
426 | /// </summary> | |||
427 | public override void Flush() | |||
428 | { | |||
| 0 | 429 | baseInputStream.Flush(); | ||
| 0 | 430 | } | ||
431 | | |||
432 | /// <summary> | |||
433 | /// Sets the position within the current stream | |||
434 | /// Always throws a NotSupportedException | |||
435 | /// </summary> | |||
436 | /// <param name="offset">The relative offset to seek to.</param> | |||
437 | /// <param name="origin">The <see cref="SeekOrigin"/> defining where to seek from.</param> | |||
438 | /// <returns>The new position in the stream.</returns> | |||
439 | /// <exception cref="NotSupportedException">Any access</exception> | |||
440 | public override long Seek(long offset, SeekOrigin origin) | |||
441 | { | |||
| 0 | 442 | throw new NotSupportedException("Seek not supported"); | ||
443 | } | |||
444 | | |||
445 | /// <summary> | |||
446 | /// Set the length of the current stream | |||
447 | /// Always throws a NotSupportedException | |||
448 | /// </summary> | |||
449 | /// <param name="value">The new length value for the stream.</param> | |||
450 | /// <exception cref="NotSupportedException">Any access</exception> | |||
451 | public override void SetLength(long value) | |||
452 | { | |||
| 0 | 453 | throw new NotSupportedException("InflaterInputStream SetLength not supported"); | ||
454 | } | |||
455 | | |||
456 | /// <summary> | |||
457 | /// Writes a sequence of bytes to stream and advances the current position | |||
458 | /// This method always throws a NotSupportedException | |||
459 | /// </summary> | |||
460 | /// <param name="buffer">Thew buffer containing data to write.</param> | |||
461 | /// <param name="offset">The offset of the first byte to write.</param> | |||
462 | /// <param name="count">The number of bytes to write.</param> | |||
463 | /// <exception cref="NotSupportedException">Any access</exception> | |||
464 | public override void Write(byte[] buffer, int offset, int count) | |||
465 | { | |||
| 0 | 466 | throw new NotSupportedException("InflaterInputStream Write not supported"); | ||
467 | } | |||
468 | | |||
469 | /// <summary> | |||
470 | /// Writes one byte to the current stream and advances the current position | |||
471 | /// Always throws a NotSupportedException | |||
472 | /// </summary> | |||
473 | /// <param name="value">The byte to write.</param> | |||
474 | /// <exception cref="NotSupportedException">Any access</exception> | |||
475 | public override void WriteByte(byte value) | |||
476 | { | |||
| 0 | 477 | throw new NotSupportedException("InflaterInputStream WriteByte not supported"); | ||
478 | } | |||
479 | | |||
480 | /// <summary> | |||
481 | /// Entry point to begin an asynchronous write. Always throws a NotSupportedException. | |||
482 | /// </summary> | |||
483 | /// <param name="buffer">The buffer to write data from</param> | |||
484 | /// <param name="offset">Offset of first byte to write</param> | |||
485 | /// <param name="count">The maximum number of bytes to write</param> | |||
486 | /// <param name="callback">The method to be called when the asynchronous write operation is completed</param> | |||
487 | /// <param name="state">A user-provided object that distinguishes this particular asynchronous write request from ot | |||
488 | /// <returns>An <see cref="System.IAsyncResult">IAsyncResult</see> that references the asynchronous write</returns> | |||
489 | /// <exception cref="NotSupportedException">Any access</exception> | |||
490 | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) | |||
491 | { | |||
| 0 | 492 | throw new NotSupportedException("InflaterInputStream BeginWrite not supported"); | ||
493 | } | |||
494 | | |||
495 | /// <summary> | |||
496 | /// Closes the input stream. When <see cref="IsStreamOwner"></see> | |||
497 | /// is true the underlying stream is also closed. | |||
498 | /// </summary> | |||
499 | public override void Close() | |||
500 | { | |||
| 0 | 501 | if (!isClosed) { | ||
| 0 | 502 | isClosed = true; | ||
| 0 | 503 | if (isStreamOwner) { | ||
| 0 | 504 | baseInputStream.Close(); | ||
505 | } | |||
506 | } | |||
| 0 | 507 | } | ||
508 | | |||
509 | #endregion | |||
510 | | |||
511 | #region Instance Fields | |||
512 | | |||
513 | Stream baseInputStream; | |||
514 | | |||
515 | /// <summary> | |||
516 | /// Flag indicating wether this instance is designated the stream owner. | |||
517 | /// When closing if this flag is true the underlying stream is closed. | |||
518 | /// </summary> | |||
| 0 | 519 | bool isStreamOwner = true; | ||
520 | | |||
521 | /// <summary> | |||
522 | /// Flag indicating wether this instance has been closed or not. | |||
523 | /// </summary> | |||
524 | bool isClosed; | |||
525 | | |||
| 0 | 526 | readonly byte[] one = new byte[1]; | ||
527 | bool headerParsed; | |||
528 | | |||
529 | // string table stuff | |||
530 | private const int TBL_CLEAR = 0x100; | |||
531 | private const int TBL_FIRST = TBL_CLEAR + 1; | |||
532 | | |||
533 | private int[] tabPrefix; | |||
534 | private byte[] tabSuffix; | |||
| 0 | 535 | private readonly int[] zeros = new int[256]; | ||
536 | private byte[] stack; | |||
537 | | |||
538 | // various state | |||
539 | private bool blockMode; | |||
540 | private int nBits; | |||
541 | private int maxBits; | |||
542 | private int maxMaxCode; | |||
543 | private int maxCode; | |||
544 | private int bitMask; | |||
545 | private int oldCode; | |||
546 | private byte finChar; | |||
547 | private int stackP; | |||
548 | private int freeEnt; | |||
549 | | |||
550 | // input buffer | |||
| 0 | 551 | private readonly byte[] data = new byte[1024 * 8]; | ||
552 | private int bitPos; | |||
553 | private int end; | |||
554 | int got; | |||
555 | private bool eof; | |||
556 | private const int EXTRA = 64; | |||
557 | #endregion | |||
558 | } | |||
559 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.MemoryArchiveStorage |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs |
| Covered lines: | 15 |
| Uncovered lines: | 12 |
| Coverable lines: | 27 |
| Total lines: | 4263 |
| Line coverage: | 55.5% |
| Branch coverage: | 50% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| GetTemporaryOutput() | 1 | 100 | 100 |
| ConvertTemporaryToFinal() | 2 | 75 | 66.67 |
| MakeTemporaryCopy(...) | 1 | 0 | 0 |
| OpenForDirectUpdate(...) | 4 | 33.33 | 42.86 |
| Dispose() | 2 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.IO; | |||
4 | using System.Text; | |||
5 | using System.Globalization; | |||
6 | using System.Security.Cryptography; | |||
7 | using ICSharpCode.SharpZipLib.Encryption; | |||
8 | using ICSharpCode.SharpZipLib.Core; | |||
9 | using ICSharpCode.SharpZipLib.Checksum; | |||
10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
11 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
12 | | |||
13 | namespace ICSharpCode.SharpZipLib.Zip | |||
14 | { | |||
15 | #region Keys Required Event Args | |||
16 | /// <summary> | |||
17 | /// Arguments used with KeysRequiredEvent | |||
18 | /// </summary> | |||
19 | public class KeysRequiredEventArgs : EventArgs | |||
20 | { | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
24 | /// </summary> | |||
25 | /// <param name="name">The name of the file for which keys are required.</param> | |||
26 | public KeysRequiredEventArgs(string name) | |||
27 | { | |||
28 | fileName = name; | |||
29 | } | |||
30 | | |||
31 | /// <summary> | |||
32 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
33 | /// </summary> | |||
34 | /// <param name="name">The name of the file for which keys are required.</param> | |||
35 | /// <param name="keyValue">The current key value.</param> | |||
36 | public KeysRequiredEventArgs(string name, byte[] keyValue) | |||
37 | { | |||
38 | fileName = name; | |||
39 | key = keyValue; | |||
40 | } | |||
41 | | |||
42 | #endregion | |||
43 | #region Properties | |||
44 | /// <summary> | |||
45 | /// Gets the name of the file for which keys are required. | |||
46 | /// </summary> | |||
47 | public string FileName { | |||
48 | get { return fileName; } | |||
49 | } | |||
50 | | |||
51 | /// <summary> | |||
52 | /// Gets or sets the key value | |||
53 | /// </summary> | |||
54 | public byte[] Key { | |||
55 | get { return key; } | |||
56 | set { key = value; } | |||
57 | } | |||
58 | #endregion | |||
59 | | |||
60 | #region Instance Fields | |||
61 | string fileName; | |||
62 | byte[] key; | |||
63 | #endregion | |||
64 | } | |||
65 | #endregion | |||
66 | | |||
67 | #region Test Definitions | |||
68 | /// <summary> | |||
69 | /// The strategy to apply to testing. | |||
70 | /// </summary> | |||
71 | public enum TestStrategy | |||
72 | { | |||
73 | /// <summary> | |||
74 | /// Find the first error only. | |||
75 | /// </summary> | |||
76 | FindFirstError, | |||
77 | /// <summary> | |||
78 | /// Find all possible errors. | |||
79 | /// </summary> | |||
80 | FindAllErrors, | |||
81 | } | |||
82 | | |||
83 | /// <summary> | |||
84 | /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. | |||
85 | /// </summary> | |||
86 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
87 | public enum TestOperation | |||
88 | { | |||
89 | /// <summary> | |||
90 | /// Setting up testing. | |||
91 | /// </summary> | |||
92 | Initialising, | |||
93 | | |||
94 | /// <summary> | |||
95 | /// Testing an individual entries header | |||
96 | /// </summary> | |||
97 | EntryHeader, | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Testing an individual entries data | |||
101 | /// </summary> | |||
102 | EntryData, | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Testing an individual entry has completed. | |||
106 | /// </summary> | |||
107 | EntryComplete, | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Running miscellaneous tests | |||
111 | /// </summary> | |||
112 | MiscellaneousTests, | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Testing is complete | |||
116 | /// </summary> | |||
117 | Complete, | |||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. | |||
122 | /// </summary> | |||
123 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
124 | public class TestStatus | |||
125 | { | |||
126 | #region Constructors | |||
127 | /// <summary> | |||
128 | /// Initialise a new instance of <see cref="TestStatus"/> | |||
129 | /// </summary> | |||
130 | /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> | |||
131 | public TestStatus(ZipFile file) | |||
132 | { | |||
133 | file_ = file; | |||
134 | } | |||
135 | #endregion | |||
136 | | |||
137 | #region Properties | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Get the current <see cref="TestOperation"/> in progress. | |||
141 | /// </summary> | |||
142 | public TestOperation Operation { | |||
143 | get { return operation_; } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get the <see cref="ZipFile"/> this status is applicable to. | |||
148 | /// </summary> | |||
149 | public ZipFile File { | |||
150 | get { return file_; } | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Get the current/last entry tested. | |||
155 | /// </summary> | |||
156 | public ZipEntry Entry { | |||
157 | get { return entry_; } | |||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Get the number of errors detected so far. | |||
162 | /// </summary> | |||
163 | public int ErrorCount { | |||
164 | get { return errorCount_; } | |||
165 | } | |||
166 | | |||
167 | /// <summary> | |||
168 | /// Get the number of bytes tested so far for the current entry. | |||
169 | /// </summary> | |||
170 | public long BytesTested { | |||
171 | get { return bytesTested_; } | |||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Get a value indicating wether the last entry test was valid. | |||
176 | /// </summary> | |||
177 | public bool EntryValid { | |||
178 | get { return entryValid_; } | |||
179 | } | |||
180 | #endregion | |||
181 | | |||
182 | #region Internal API | |||
183 | internal void AddError() | |||
184 | { | |||
185 | errorCount_++; | |||
186 | entryValid_ = false; | |||
187 | } | |||
188 | | |||
189 | internal void SetOperation(TestOperation operation) | |||
190 | { | |||
191 | operation_ = operation; | |||
192 | } | |||
193 | | |||
194 | internal void SetEntry(ZipEntry entry) | |||
195 | { | |||
196 | entry_ = entry; | |||
197 | entryValid_ = true; | |||
198 | bytesTested_ = 0; | |||
199 | } | |||
200 | | |||
201 | internal void SetBytesTested(long value) | |||
202 | { | |||
203 | bytesTested_ = value; | |||
204 | } | |||
205 | #endregion | |||
206 | | |||
207 | #region Instance Fields | |||
208 | ZipFile file_; | |||
209 | ZipEntry entry_; | |||
210 | bool entryValid_; | |||
211 | int errorCount_; | |||
212 | long bytesTested_; | |||
213 | TestOperation operation_; | |||
214 | #endregion | |||
215 | } | |||
216 | | |||
217 | /// <summary> | |||
218 | /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if | |||
219 | /// </summary> | |||
220 | /// <remarks>If the message is non-null an error has occured. If the message is null | |||
221 | /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> | |||
222 | public delegate void ZipTestResultHandler(TestStatus status, string message); | |||
223 | #endregion | |||
224 | | |||
225 | #region Update Definitions | |||
226 | /// <summary> | |||
227 | /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. | |||
228 | /// </summary> | |||
229 | public enum FileUpdateMode | |||
230 | { | |||
231 | /// <summary> | |||
232 | /// Perform all updates on temporary files ensuring that the original file is saved. | |||
233 | /// </summary> | |||
234 | Safe, | |||
235 | /// <summary> | |||
236 | /// Update the archive directly, which is faster but less safe. | |||
237 | /// </summary> | |||
238 | Direct, | |||
239 | } | |||
240 | #endregion | |||
241 | | |||
242 | #region ZipFile Class | |||
243 | /// <summary> | |||
244 | /// This class represents a Zip archive. You can ask for the contained | |||
245 | /// entries, or get an input stream for a file entry. The entry is | |||
246 | /// automatically decompressed. | |||
247 | /// | |||
248 | /// You can also update the archive adding or deleting entries. | |||
249 | /// | |||
250 | /// This class is thread safe for input: You can open input streams for arbitrary | |||
251 | /// entries in different threads. | |||
252 | /// <br/> | |||
253 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
254 | /// </summary> | |||
255 | /// <example> | |||
256 | /// <code> | |||
257 | /// using System; | |||
258 | /// using System.Text; | |||
259 | /// using System.Collections; | |||
260 | /// using System.IO; | |||
261 | /// | |||
262 | /// using ICSharpCode.SharpZipLib.Zip; | |||
263 | /// | |||
264 | /// class MainClass | |||
265 | /// { | |||
266 | /// static public void Main(string[] args) | |||
267 | /// { | |||
268 | /// using (ZipFile zFile = new ZipFile(args[0])) { | |||
269 | /// Console.WriteLine("Listing of : " + zFile.Name); | |||
270 | /// Console.WriteLine(""); | |||
271 | /// Console.WriteLine("Raw Size Size Date Time Name"); | |||
272 | /// Console.WriteLine("-------- -------- -------- ------ ---------"); | |||
273 | /// foreach (ZipEntry e in zFile) { | |||
274 | /// if ( e.IsFile ) { | |||
275 | /// DateTime d = e.DateTime; | |||
276 | /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, | |||
277 | /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), | |||
278 | /// e.Name); | |||
279 | /// } | |||
280 | /// } | |||
281 | /// } | |||
282 | /// } | |||
283 | /// } | |||
284 | /// </code> | |||
285 | /// </example> | |||
286 | public class ZipFile : IEnumerable, IDisposable | |||
287 | { | |||
288 | #region KeyHandling | |||
289 | | |||
290 | /// <summary> | |||
291 | /// Delegate for handling keys/password setting during compresion/decompression. | |||
292 | /// </summary> | |||
293 | public delegate void KeysRequiredEventHandler( | |||
294 | object sender, | |||
295 | KeysRequiredEventArgs e | |||
296 | ); | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Event handler for handling encryption keys. | |||
300 | /// </summary> | |||
301 | public KeysRequiredEventHandler KeysRequired; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Handles getting of encryption keys when required. | |||
305 | /// </summary> | |||
306 | /// <param name="fileName">The file for which encryption keys are required.</param> | |||
307 | void OnKeysRequired(string fileName) | |||
308 | { | |||
309 | if (KeysRequired != null) { | |||
310 | var krea = new KeysRequiredEventArgs(fileName, key); | |||
311 | KeysRequired(this, krea); | |||
312 | key = krea.Key; | |||
313 | } | |||
314 | } | |||
315 | | |||
316 | /// <summary> | |||
317 | /// Get/set the encryption key value. | |||
318 | /// </summary> | |||
319 | byte[] Key { | |||
320 | get { return key; } | |||
321 | set { key = value; } | |||
322 | } | |||
323 | | |||
324 | /// <summary> | |||
325 | /// Password to be used for encrypting/decrypting files. | |||
326 | /// </summary> | |||
327 | /// <remarks>Set to null if no password is required.</remarks> | |||
328 | public string Password { | |||
329 | set { | |||
330 | if (string.IsNullOrEmpty(value)) { | |||
331 | key = null; | |||
332 | } else { | |||
333 | rawPassword_ = value; | |||
334 | key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); | |||
335 | } | |||
336 | } | |||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Get a value indicating wether encryption keys are currently available. | |||
341 | /// </summary> | |||
342 | bool HaveKeys { | |||
343 | get { return key != null; } | |||
344 | } | |||
345 | #endregion | |||
346 | | |||
347 | #region Constructors | |||
348 | /// <summary> | |||
349 | /// Opens a Zip file with the given name for reading. | |||
350 | /// </summary> | |||
351 | /// <param name="name">The name of the file to open.</param> | |||
352 | /// <exception cref="ArgumentNullException">The argument supplied is null.</exception> | |||
353 | /// <exception cref="IOException"> | |||
354 | /// An i/o error occurs | |||
355 | /// </exception> | |||
356 | /// <exception cref="ZipException"> | |||
357 | /// The file doesn't contain a valid zip archive. | |||
358 | /// </exception> | |||
359 | public ZipFile(string name) | |||
360 | { | |||
361 | if (name == null) { | |||
362 | throw new ArgumentNullException(nameof(name)); | |||
363 | } | |||
364 | | |||
365 | name_ = name; | |||
366 | | |||
367 | baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
368 | isStreamOwner = true; | |||
369 | | |||
370 | try { | |||
371 | ReadEntries(); | |||
372 | } catch { | |||
373 | DisposeInternal(true); | |||
374 | throw; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Opens a Zip file reading the given <see cref="FileStream"/>. | |||
380 | /// </summary> | |||
381 | /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> | |||
382 | /// <exception cref="ArgumentNullException">The supplied argument is null.</exception> | |||
383 | /// <exception cref="IOException"> | |||
384 | /// An i/o error occurs. | |||
385 | /// </exception> | |||
386 | /// <exception cref="ZipException"> | |||
387 | /// The file doesn't contain a valid zip archive. | |||
388 | /// </exception> | |||
389 | public ZipFile(FileStream file) | |||
390 | { | |||
391 | if (file == null) { | |||
392 | throw new ArgumentNullException(nameof(file)); | |||
393 | } | |||
394 | | |||
395 | if (!file.CanSeek) { | |||
396 | throw new ArgumentException("Stream is not seekable", nameof(file)); | |||
397 | } | |||
398 | | |||
399 | baseStream_ = file; | |||
400 | name_ = file.Name; | |||
401 | isStreamOwner = true; | |||
402 | | |||
403 | try { | |||
404 | ReadEntries(); | |||
405 | } catch { | |||
406 | DisposeInternal(true); | |||
407 | throw; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Opens a Zip file reading the given <see cref="Stream"/>. | |||
413 | /// </summary> | |||
414 | /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> | |||
415 | /// <exception cref="IOException"> | |||
416 | /// An i/o error occurs | |||
417 | /// </exception> | |||
418 | /// <exception cref="ZipException"> | |||
419 | /// The stream doesn't contain a valid zip archive.<br/> | |||
420 | /// </exception> | |||
421 | /// <exception cref="ArgumentException"> | |||
422 | /// The <see cref="Stream">stream</see> doesnt support seeking. | |||
423 | /// </exception> | |||
424 | /// <exception cref="ArgumentNullException"> | |||
425 | /// The <see cref="Stream">stream</see> argument is null. | |||
426 | /// </exception> | |||
427 | public ZipFile(Stream stream) | |||
428 | { | |||
429 | if (stream == null) { | |||
430 | throw new ArgumentNullException(nameof(stream)); | |||
431 | } | |||
432 | | |||
433 | if (!stream.CanSeek) { | |||
434 | throw new ArgumentException("Stream is not seekable", nameof(stream)); | |||
435 | } | |||
436 | | |||
437 | baseStream_ = stream; | |||
438 | isStreamOwner = true; | |||
439 | | |||
440 | if (baseStream_.Length > 0) { | |||
441 | try { | |||
442 | ReadEntries(); | |||
443 | } catch { | |||
444 | DisposeInternal(true); | |||
445 | throw; | |||
446 | } | |||
447 | } else { | |||
448 | entries_ = new ZipEntry[0]; | |||
449 | isNewArchive_ = true; | |||
450 | } | |||
451 | } | |||
452 | | |||
453 | /// <summary> | |||
454 | /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. | |||
455 | /// </summary> | |||
456 | internal ZipFile() | |||
457 | { | |||
458 | entries_ = new ZipEntry[0]; | |||
459 | isNewArchive_ = true; | |||
460 | } | |||
461 | | |||
462 | #endregion | |||
463 | | |||
464 | #region Destructors and Closing | |||
465 | /// <summary> | |||
466 | /// Finalize this instance. | |||
467 | /// </summary> | |||
468 | ~ZipFile() | |||
469 | { | |||
470 | Dispose(false); | |||
471 | } | |||
472 | | |||
473 | /// <summary> | |||
474 | /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying | |||
475 | /// Once closed, no further instance methods should be called. | |||
476 | /// </summary> | |||
477 | /// <exception cref="System.IO.IOException"> | |||
478 | /// An i/o error occurs. | |||
479 | /// </exception> | |||
480 | public void Close() | |||
481 | { | |||
482 | DisposeInternal(true); | |||
483 | GC.SuppressFinalize(this); | |||
484 | } | |||
485 | | |||
486 | #endregion | |||
487 | | |||
488 | #region Creators | |||
489 | /// <summary> | |||
490 | /// Create a new <see cref="ZipFile"/> whose data will be stored in a file. | |||
491 | /// </summary> | |||
492 | /// <param name="fileName">The name of the archive to create.</param> | |||
493 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
494 | /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception> | |||
495 | public static ZipFile Create(string fileName) | |||
496 | { | |||
497 | if (fileName == null) { | |||
498 | throw new ArgumentNullException(nameof(fileName)); | |||
499 | } | |||
500 | | |||
501 | FileStream fs = File.Create(fileName); | |||
502 | | |||
503 | var result = new ZipFile(); | |||
504 | result.name_ = fileName; | |||
505 | result.baseStream_ = fs; | |||
506 | result.isStreamOwner = true; | |||
507 | return result; | |||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. | |||
512 | /// </summary> | |||
513 | /// <param name="outStream">The stream providing data storage.</param> | |||
514 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
515 | /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception> | |||
516 | /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception> | |||
517 | public static ZipFile Create(Stream outStream) | |||
518 | { | |||
519 | if (outStream == null) { | |||
520 | throw new ArgumentNullException(nameof(outStream)); | |||
521 | } | |||
522 | | |||
523 | if (!outStream.CanWrite) { | |||
524 | throw new ArgumentException("Stream is not writeable", nameof(outStream)); | |||
525 | } | |||
526 | | |||
527 | if (!outStream.CanSeek) { | |||
528 | throw new ArgumentException("Stream is not seekable", nameof(outStream)); | |||
529 | } | |||
530 | | |||
531 | var result = new ZipFile(); | |||
532 | result.baseStream_ = outStream; | |||
533 | return result; | |||
534 | } | |||
535 | | |||
536 | #endregion | |||
537 | | |||
538 | #region Properties | |||
539 | /// <summary> | |||
540 | /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. | |||
541 | /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. | |||
542 | /// </summary> | |||
543 | /// <remarks> | |||
544 | /// The default value is true in all cases. | |||
545 | /// </remarks> | |||
546 | public bool IsStreamOwner { | |||
547 | get { return isStreamOwner; } | |||
548 | set { isStreamOwner = value; } | |||
549 | } | |||
550 | | |||
551 | /// <summary> | |||
552 | /// Get a value indicating wether | |||
553 | /// this archive is embedded in another file or not. | |||
554 | /// </summary> | |||
555 | public bool IsEmbeddedArchive { | |||
556 | // Not strictly correct in all circumstances currently | |||
557 | get { return offsetOfFirstEntry > 0; } | |||
558 | } | |||
559 | | |||
560 | /// <summary> | |||
561 | /// Get a value indicating that this archive is a new one. | |||
562 | /// </summary> | |||
563 | public bool IsNewArchive { | |||
564 | get { return isNewArchive_; } | |||
565 | } | |||
566 | | |||
567 | /// <summary> | |||
568 | /// Gets the comment for the zip file. | |||
569 | /// </summary> | |||
570 | public string ZipFileComment { | |||
571 | get { return comment_; } | |||
572 | } | |||
573 | | |||
574 | /// <summary> | |||
575 | /// Gets the name of this zip file. | |||
576 | /// </summary> | |||
577 | public string Name { | |||
578 | get { return name_; } | |||
579 | } | |||
580 | | |||
581 | /// <summary> | |||
582 | /// Gets the number of entries in this zip file. | |||
583 | /// </summary> | |||
584 | /// <exception cref="InvalidOperationException"> | |||
585 | /// The Zip file has been closed. | |||
586 | /// </exception> | |||
587 | [Obsolete("Use the Count property instead")] | |||
588 | public int Size { | |||
589 | get { | |||
590 | return entries_.Length; | |||
591 | } | |||
592 | } | |||
593 | | |||
594 | /// <summary> | |||
595 | /// Get the number of entries contained in this <see cref="ZipFile"/>. | |||
596 | /// </summary> | |||
597 | public long Count { | |||
598 | get { | |||
599 | return entries_.Length; | |||
600 | } | |||
601 | } | |||
602 | | |||
603 | /// <summary> | |||
604 | /// Indexer property for ZipEntries | |||
605 | /// </summary> | |||
606 | [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] | |||
607 | public ZipEntry this[int index] { | |||
608 | get { | |||
609 | return (ZipEntry)entries_[index].Clone(); | |||
610 | } | |||
611 | } | |||
612 | | |||
613 | #endregion | |||
614 | | |||
615 | #region Input Handling | |||
616 | /// <summary> | |||
617 | /// Gets an enumerator for the Zip entries in this Zip file. | |||
618 | /// </summary> | |||
619 | /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> | |||
620 | /// <exception cref="ObjectDisposedException"> | |||
621 | /// The Zip file has been closed. | |||
622 | /// </exception> | |||
623 | public IEnumerator GetEnumerator() | |||
624 | { | |||
625 | if (isDisposed_) { | |||
626 | throw new ObjectDisposedException("ZipFile"); | |||
627 | } | |||
628 | | |||
629 | return new ZipEntryEnumerator(entries_); | |||
630 | } | |||
631 | | |||
632 | /// <summary> | |||
633 | /// Return the index of the entry with a matching name | |||
634 | /// </summary> | |||
635 | /// <param name="name">Entry name to find</param> | |||
636 | /// <param name="ignoreCase">If true the comparison is case insensitive</param> | |||
637 | /// <returns>The index position of the matching entry or -1 if not found</returns> | |||
638 | /// <exception cref="ObjectDisposedException"> | |||
639 | /// The Zip file has been closed. | |||
640 | /// </exception> | |||
641 | public int FindEntry(string name, bool ignoreCase) | |||
642 | { | |||
643 | if (isDisposed_) { | |||
644 | throw new ObjectDisposedException("ZipFile"); | |||
645 | } | |||
646 | | |||
647 | // TODO: This will be slow as the next ice age for huge archives! | |||
648 | for (int i = 0; i < entries_.Length; i++) { | |||
649 | if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) { | |||
650 | return i; | |||
651 | } | |||
652 | } | |||
653 | return -1; | |||
654 | } | |||
655 | | |||
656 | /// <summary> | |||
657 | /// Searches for a zip entry in this archive with the given name. | |||
658 | /// String comparisons are case insensitive | |||
659 | /// </summary> | |||
660 | /// <param name="name"> | |||
661 | /// The name to find. May contain directory components separated by slashes ('/'). | |||
662 | /// </param> | |||
663 | /// <returns> | |||
664 | /// A clone of the zip entry, or null if no entry with that name exists. | |||
665 | /// </returns> | |||
666 | /// <exception cref="ObjectDisposedException"> | |||
667 | /// The Zip file has been closed. | |||
668 | /// </exception> | |||
669 | public ZipEntry GetEntry(string name) | |||
670 | { | |||
671 | if (isDisposed_) { | |||
672 | throw new ObjectDisposedException("ZipFile"); | |||
673 | } | |||
674 | | |||
675 | int index = FindEntry(name, true); | |||
676 | return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; | |||
677 | } | |||
678 | | |||
679 | /// <summary> | |||
680 | /// Gets an input stream for reading the given zip entry data in an uncompressed form. | |||
681 | /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). | |||
682 | /// </summary> | |||
683 | /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> | |||
684 | /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> | |||
685 | /// <exception cref="ObjectDisposedException"> | |||
686 | /// The ZipFile has already been closed | |||
687 | /// </exception> | |||
688 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
689 | /// The compression method for the entry is unknown | |||
690 | /// </exception> | |||
691 | /// <exception cref="IndexOutOfRangeException"> | |||
692 | /// The entry is not found in the ZipFile | |||
693 | /// </exception> | |||
694 | public Stream GetInputStream(ZipEntry entry) | |||
695 | { | |||
696 | if (entry == null) { | |||
697 | throw new ArgumentNullException(nameof(entry)); | |||
698 | } | |||
699 | | |||
700 | if (isDisposed_) { | |||
701 | throw new ObjectDisposedException("ZipFile"); | |||
702 | } | |||
703 | | |||
704 | long index = entry.ZipFileIndex; | |||
705 | if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { | |||
706 | index = FindEntry(entry.Name, true); | |||
707 | if (index < 0) { | |||
708 | throw new ZipException("Entry cannot be found"); | |||
709 | } | |||
710 | } | |||
711 | return GetInputStream(index); | |||
712 | } | |||
713 | | |||
714 | /// <summary> | |||
715 | /// Creates an input stream reading a zip entry | |||
716 | /// </summary> | |||
717 | /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> | |||
718 | /// <returns> | |||
719 | /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> | |||
720 | /// </returns> | |||
721 | /// <exception cref="ObjectDisposedException"> | |||
722 | /// The ZipFile has already been closed | |||
723 | /// </exception> | |||
724 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
725 | /// The compression method for the entry is unknown | |||
726 | /// </exception> | |||
727 | /// <exception cref="IndexOutOfRangeException"> | |||
728 | /// The entry is not found in the ZipFile | |||
729 | /// </exception> | |||
730 | public Stream GetInputStream(long entryIndex) | |||
731 | { | |||
732 | if (isDisposed_) { | |||
733 | throw new ObjectDisposedException("ZipFile"); | |||
734 | } | |||
735 | | |||
736 | long start = LocateEntry(entries_[entryIndex]); | |||
737 | CompressionMethod method = entries_[entryIndex].CompressionMethod; | |||
738 | Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); | |||
739 | | |||
740 | if (entries_[entryIndex].IsCrypted == true) { | |||
741 | result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); | |||
742 | if (result == null) { | |||
743 | throw new ZipException("Unable to decrypt this entry"); | |||
744 | } | |||
745 | } | |||
746 | | |||
747 | switch (method) { | |||
748 | case CompressionMethod.Stored: | |||
749 | // read as is. | |||
750 | break; | |||
751 | | |||
752 | case CompressionMethod.Deflated: | |||
753 | // No need to worry about ownership and closing as underlying stream close does nothing. | |||
754 | result = new InflaterInputStream(result, new Inflater(true)); | |||
755 | break; | |||
756 | | |||
757 | default: | |||
758 | throw new ZipException("Unsupported compression method " + method); | |||
759 | } | |||
760 | | |||
761 | return result; | |||
762 | } | |||
763 | | |||
764 | #endregion | |||
765 | | |||
766 | #region Archive Testing | |||
767 | /// <summary> | |||
768 | /// Test an archive for integrity/validity | |||
769 | /// </summary> | |||
770 | /// <param name="testData">Perform low level data Crc check</param> | |||
771 | /// <returns>true if all tests pass, false otherwise</returns> | |||
772 | /// <remarks>Testing will terminate on the first error found.</remarks> | |||
773 | public bool TestArchive(bool testData) | |||
774 | { | |||
775 | return TestArchive(testData, TestStrategy.FindFirstError, null); | |||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Test an archive for integrity/validity | |||
780 | /// </summary> | |||
781 | /// <param name="testData">Perform low level data Crc check</param> | |||
782 | /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> | |||
783 | /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> | |||
784 | /// <returns>true if all tests pass, false otherwise</returns> | |||
785 | /// <exception cref="ObjectDisposedException">The object has already been closed.</exception> | |||
786 | public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) | |||
787 | { | |||
788 | if (isDisposed_) { | |||
789 | throw new ObjectDisposedException("ZipFile"); | |||
790 | } | |||
791 | | |||
792 | var status = new TestStatus(this); | |||
793 | | |||
794 | if (resultHandler != null) { | |||
795 | resultHandler(status, null); | |||
796 | } | |||
797 | | |||
798 | HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; | |||
799 | | |||
800 | bool testing = true; | |||
801 | | |||
802 | try { | |||
803 | int entryIndex = 0; | |||
804 | | |||
805 | while (testing && (entryIndex < Count)) { | |||
806 | if (resultHandler != null) { | |||
807 | status.SetEntry(this[entryIndex]); | |||
808 | status.SetOperation(TestOperation.EntryHeader); | |||
809 | resultHandler(status, null); | |||
810 | } | |||
811 | | |||
812 | try { | |||
813 | TestLocalHeader(this[entryIndex], test); | |||
814 | } catch (ZipException ex) { | |||
815 | status.AddError(); | |||
816 | | |||
817 | if (resultHandler != null) { | |||
818 | resultHandler(status, | |||
819 | string.Format("Exception during test - '{0}'", ex.Message)); | |||
820 | } | |||
821 | | |||
822 | testing &= strategy != TestStrategy.FindFirstError; | |||
823 | } | |||
824 | | |||
825 | if (testing && testData && this[entryIndex].IsFile) { | |||
826 | if (resultHandler != null) { | |||
827 | status.SetOperation(TestOperation.EntryData); | |||
828 | resultHandler(status, null); | |||
829 | } | |||
830 | | |||
831 | var crc = new Crc32(); | |||
832 | | |||
833 | using (Stream entryStream = this.GetInputStream(this[entryIndex])) { | |||
834 | | |||
835 | byte[] buffer = new byte[4096]; | |||
836 | long totalBytes = 0; | |||
837 | int bytesRead; | |||
838 | while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { | |||
839 | crc.Update(buffer, 0, bytesRead); | |||
840 | | |||
841 | if (resultHandler != null) { | |||
842 | totalBytes += bytesRead; | |||
843 | status.SetBytesTested(totalBytes); | |||
844 | resultHandler(status, null); | |||
845 | } | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | if (this[entryIndex].Crc != crc.Value) { | |||
850 | status.AddError(); | |||
851 | | |||
852 | if (resultHandler != null) { | |||
853 | resultHandler(status, "CRC mismatch"); | |||
854 | } | |||
855 | | |||
856 | testing &= strategy != TestStrategy.FindFirstError; | |||
857 | } | |||
858 | | |||
859 | if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
860 | var helper = new ZipHelperStream(baseStream_); | |||
861 | var data = new DescriptorData(); | |||
862 | helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); | |||
863 | if (this[entryIndex].Crc != data.Crc) { | |||
864 | status.AddError(); | |||
865 | } | |||
866 | | |||
867 | if (this[entryIndex].CompressedSize != data.CompressedSize) { | |||
868 | status.AddError(); | |||
869 | } | |||
870 | | |||
871 | if (this[entryIndex].Size != data.Size) { | |||
872 | status.AddError(); | |||
873 | } | |||
874 | } | |||
875 | } | |||
876 | | |||
877 | if (resultHandler != null) { | |||
878 | status.SetOperation(TestOperation.EntryComplete); | |||
879 | resultHandler(status, null); | |||
880 | } | |||
881 | | |||
882 | entryIndex += 1; | |||
883 | } | |||
884 | | |||
885 | if (resultHandler != null) { | |||
886 | status.SetOperation(TestOperation.MiscellaneousTests); | |||
887 | resultHandler(status, null); | |||
888 | } | |||
889 | | |||
890 | // TODO: the 'Corrina Johns' test where local headers are missing from | |||
891 | // the central directory. They are therefore invisible to many archivers. | |||
892 | } catch (Exception ex) { | |||
893 | status.AddError(); | |||
894 | | |||
895 | if (resultHandler != null) { | |||
896 | resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); | |||
897 | } | |||
898 | } | |||
899 | | |||
900 | if (resultHandler != null) { | |||
901 | status.SetOperation(TestOperation.Complete); | |||
902 | status.SetEntry(null); | |||
903 | resultHandler(status, null); | |||
904 | } | |||
905 | | |||
906 | return (status.ErrorCount == 0); | |||
907 | } | |||
908 | | |||
909 | [Flags] | |||
910 | enum HeaderTest | |||
911 | { | |||
912 | Extract = 0x01, // Check that this header represents an entry whose data can be extracted | |||
913 | Header = 0x02, // Check that this header contents are valid | |||
914 | } | |||
915 | | |||
916 | /// <summary> | |||
917 | /// Test a local header against that provided from the central directory | |||
918 | /// </summary> | |||
919 | /// <param name="entry"> | |||
920 | /// The entry to test against | |||
921 | /// </param> | |||
922 | /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> | |||
923 | /// <returns>The offset of the entries data in the file</returns> | |||
924 | long TestLocalHeader(ZipEntry entry, HeaderTest tests) | |||
925 | { | |||
926 | lock (baseStream_) { | |||
927 | bool testHeader = (tests & HeaderTest.Header) != 0; | |||
928 | bool testData = (tests & HeaderTest.Extract) != 0; | |||
929 | | |||
930 | baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); | |||
931 | if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { | |||
932 | throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset) | |||
933 | } | |||
934 | | |||
935 | var extractVersion = (short)(ReadLEUshort() & 0x00ff); | |||
936 | var localFlags = (short)ReadLEUshort(); | |||
937 | var compressionMethod = (short)ReadLEUshort(); | |||
938 | var fileTime = (short)ReadLEUshort(); | |||
939 | var fileDate = (short)ReadLEUshort(); | |||
940 | uint crcValue = ReadLEUint(); | |||
941 | long compressedSize = ReadLEUint(); | |||
942 | long size = ReadLEUint(); | |||
943 | int storedNameLength = ReadLEUshort(); | |||
944 | int extraDataLength = ReadLEUshort(); | |||
945 | | |||
946 | byte[] nameData = new byte[storedNameLength]; | |||
947 | StreamUtils.ReadFully(baseStream_, nameData); | |||
948 | | |||
949 | byte[] extraData = new byte[extraDataLength]; | |||
950 | StreamUtils.ReadFully(baseStream_, extraData); | |||
951 | | |||
952 | var localExtraData = new ZipExtraData(extraData); | |||
953 | | |||
954 | // Extra data / zip64 checks | |||
955 | if (localExtraData.Find(1)) { | |||
956 | // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 | |||
957 | // and size or compressedSize = MaxValue, due to rogue creators. | |||
958 | | |||
959 | size = localExtraData.ReadLong(); | |||
960 | compressedSize = localExtraData.ReadLong(); | |||
961 | | |||
962 | if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
963 | // These may be valid if patched later | |||
964 | if ((size != -1) && (size != entry.Size)) { | |||
965 | throw new ZipException("Size invalid for descriptor"); | |||
966 | } | |||
967 | | |||
968 | if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) { | |||
969 | throw new ZipException("Compressed size invalid for descriptor"); | |||
970 | } | |||
971 | } | |||
972 | } else { | |||
973 | // No zip64 extra data but entry requires it. | |||
974 | if ((extractVersion >= ZipConstants.VersionZip64) && | |||
975 | (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) { | |||
976 | throw new ZipException("Required Zip64 extended information missing"); | |||
977 | } | |||
978 | } | |||
979 | | |||
980 | if (testData) { | |||
981 | if (entry.IsFile) { | |||
982 | if (!entry.IsCompressionMethodSupported()) { | |||
983 | throw new ZipException("Compression method not supported"); | |||
984 | } | |||
985 | | |||
986 | if ((extractVersion > ZipConstants.VersionMadeBy) | |||
987 | || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { | |||
988 | throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract | |||
989 | } | |||
990 | | |||
991 | if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance | |||
992 | throw new ZipException("The library does not support the zip version required to extract this entry"); | |||
993 | } | |||
994 | } | |||
995 | } | |||
996 | | |||
997 | if (testHeader) { | |||
998 | if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. | |||
999 | (extractVersion != 10) && | |||
1000 | (extractVersion != 11) && | |||
1001 | (extractVersion != 20) && | |||
1002 | (extractVersion != 21) && | |||
1003 | (extractVersion != 25) && | |||
1004 | (extractVersion != 27) && | |||
1005 | (extractVersion != 45) && | |||
1006 | (extractVersion != 46) && | |||
1007 | (extractVersion != 50) && | |||
1008 | (extractVersion != 51) && | |||
1009 | (extractVersion != 52) && | |||
1010 | (extractVersion != 61) && | |||
1011 | (extractVersion != 62) && | |||
1012 | (extractVersion != 63) | |||
1013 | ) { | |||
1014 | throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersi | |||
1015 | } | |||
1016 | | |||
1017 | // Local entry flags dont have reserved bit set on. | |||
1018 | if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R | |||
1019 | throw new ZipException("Reserved bit flags cannot be set."); | |||
1020 | } | |||
1021 | | |||
1022 | // Encryption requires extract version >= 20 | |||
1023 | if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { | |||
1024 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0}) | |||
1025 | } | |||
1026 | | |||
1027 | // Strong encryption requires encryption flag to be set and extract version >= 50. | |||
1028 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1029 | if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) { | |||
1030 | throw new ZipException("Strong encryption flag set but encryption flag is not set"); | |||
1031 | } | |||
1032 | | |||
1033 | if (extractVersion < 50) { | |||
1034 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0 | |||
1035 | } | |||
1036 | } | |||
1037 | | |||
1038 | // Patched entries require extract version >= 27 | |||
1039 | if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { | |||
1040 | throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); | |||
1041 | } | |||
1042 | | |||
1043 | // Central header flags match local entry flags. | |||
1044 | if (localFlags != entry.Flags) { | |||
1045 | throw new ZipException("Central header/local header flags mismatch"); | |||
1046 | } | |||
1047 | | |||
1048 | // Central header compression method matches local entry | |||
1049 | if (entry.CompressionMethod != (CompressionMethod)compressionMethod) { | |||
1050 | throw new ZipException("Central header/local header compression method mismatch"); | |||
1051 | } | |||
1052 | | |||
1053 | if (entry.Version != extractVersion) { | |||
1054 | throw new ZipException("Extract version mismatch"); | |||
1055 | } | |||
1056 | | |||
1057 | // Strong encryption and extract version match | |||
1058 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1059 | if (extractVersion < 62) { | |||
1060 | throw new ZipException("Strong encryption flag set but version not high enough"); | |||
1061 | } | |||
1062 | } | |||
1063 | | |||
1064 | if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) { | |||
1065 | if ((fileTime != 0) || (fileDate != 0)) { | |||
1066 | throw new ZipException("Header masked set but date/time values non-zero"); | |||
1067 | } | |||
1068 | } | |||
1069 | | |||
1070 | if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) { | |||
1071 | if (crcValue != (uint)entry.Crc) { | |||
1072 | throw new ZipException("Central header/local header crc mismatch"); | |||
1073 | } | |||
1074 | } | |||
1075 | | |||
1076 | // Crc valid for empty entry. | |||
1077 | // This will also apply to streamed entries where size isnt known and the header cant be patched | |||
1078 | if ((size == 0) && (compressedSize == 0)) { | |||
1079 | if (crcValue != 0) { | |||
1080 | throw new ZipException("Invalid CRC for empty entry"); | |||
1081 | } | |||
1082 | } | |||
1083 | | |||
1084 | // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS str | |||
1085 | // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably | |||
1086 | if (entry.Name.Length > storedNameLength) { | |||
1087 | throw new ZipException("File name length mismatch"); | |||
1088 | } | |||
1089 | | |||
1090 | // Name data has already been read convert it and compare. | |||
1091 | string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); | |||
1092 | | |||
1093 | // Central directory and local entry name match | |||
1094 | if (localName != entry.Name) { | |||
1095 | throw new ZipException("Central header and local header file name mismatch"); | |||
1096 | } | |||
1097 | | |||
1098 | // Directories have zero actual size but can have compressed size | |||
1099 | if (entry.IsDirectory) { | |||
1100 | if (size > 0) { | |||
1101 | throw new ZipException("Directory cannot have size"); | |||
1102 | } | |||
1103 | | |||
1104 | // There may be other cases where the compressed size can be greater than this? | |||
1105 | // If so until details are known we will be strict. | |||
1106 | if (entry.IsCrypted) { | |||
1107 | if (compressedSize > ZipConstants.CryptoHeaderSize + 2) { | |||
1108 | throw new ZipException("Directory compressed size invalid"); | |||
1109 | } | |||
1110 | } else if (compressedSize > 2) { | |||
1111 | // When not compressed the directory size can validly be 2 bytes | |||
1112 | // if the true size wasnt known when data was originally being written. | |||
1113 | // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes | |||
1114 | throw new ZipException("Directory compressed size invalid"); | |||
1115 | } | |||
1116 | } | |||
1117 | | |||
1118 | if (!ZipNameTransform.IsValidName(localName, true)) { | |||
1119 | throw new ZipException("Name is invalid"); | |||
1120 | } | |||
1121 | } | |||
1122 | | |||
1123 | // Tests that apply to both data and header. | |||
1124 | | |||
1125 | // Size can be verified only if it is known in the local header. | |||
1126 | // it will always be known in the central header. | |||
1127 | if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || | |||
1128 | ((size > 0 || compressedSize > 0) && entry.Size > 0)) { | |||
1129 | | |||
1130 | if ((size != 0) | |||
1131 | && (size != entry.Size)) { | |||
1132 | throw new ZipException( | |||
1133 | string.Format("Size mismatch between central header({0}) and local header({1})", | |||
1134 | entry.Size, size)); | |||
1135 | } | |||
1136 | | |||
1137 | if ((compressedSize != 0) | |||
1138 | && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) { | |||
1139 | throw new ZipException( | |||
1140 | string.Format("Compressed size mismatch between central header({0}) and local header({1})", | |||
1141 | entry.CompressedSize, compressedSize)); | |||
1142 | } | |||
1143 | } | |||
1144 | | |||
1145 | int extraLength = storedNameLength + extraDataLength; | |||
1146 | return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; | |||
1147 | } | |||
1148 | } | |||
1149 | | |||
1150 | #endregion | |||
1151 | | |||
1152 | #region Updating | |||
1153 | | |||
1154 | const int DefaultBufferSize = 4096; | |||
1155 | | |||
1156 | /// <summary> | |||
1157 | /// The kind of update to apply. | |||
1158 | /// </summary> | |||
1159 | enum UpdateCommand | |||
1160 | { | |||
1161 | Copy, // Copy original file contents. | |||
1162 | Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. | |||
1163 | Add, // Add a new file to the archive. | |||
1164 | } | |||
1165 | | |||
1166 | #region Properties | |||
1167 | /// <summary> | |||
1168 | /// Get / set the <see cref="INameTransform"/> to apply to names when updating. | |||
1169 | /// </summary> | |||
1170 | public INameTransform NameTransform { | |||
1171 | get { | |||
1172 | return updateEntryFactory_.NameTransform; | |||
1173 | } | |||
1174 | | |||
1175 | set { | |||
1176 | updateEntryFactory_.NameTransform = value; | |||
1177 | } | |||
1178 | } | |||
1179 | | |||
1180 | /// <summary> | |||
1181 | /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values | |||
1182 | /// during updates. | |||
1183 | /// </summary> | |||
1184 | public IEntryFactory EntryFactory { | |||
1185 | get { | |||
1186 | return updateEntryFactory_; | |||
1187 | } | |||
1188 | | |||
1189 | set { | |||
1190 | if (value == null) { | |||
1191 | updateEntryFactory_ = new ZipEntryFactory(); | |||
1192 | } else { | |||
1193 | updateEntryFactory_ = value; | |||
1194 | } | |||
1195 | } | |||
1196 | } | |||
1197 | | |||
1198 | /// <summary> | |||
1199 | /// Get /set the buffer size to be used when updating this zip file. | |||
1200 | /// </summary> | |||
1201 | public int BufferSize { | |||
1202 | get { return bufferSize_; } | |||
1203 | set { | |||
1204 | if (value < 1024) { | |||
1205 | throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); | |||
1206 | } | |||
1207 | | |||
1208 | if (bufferSize_ != value) { | |||
1209 | bufferSize_ = value; | |||
1210 | copyBuffer_ = null; | |||
1211 | } | |||
1212 | } | |||
1213 | } | |||
1214 | | |||
1215 | /// <summary> | |||
1216 | /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. | |||
1217 | /// </summary> | |||
1218 | public bool IsUpdating { | |||
1219 | get { return updates_ != null; } | |||
1220 | } | |||
1221 | | |||
1222 | /// <summary> | |||
1223 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
1224 | /// </summary> | |||
1225 | public UseZip64 UseZip64 { | |||
1226 | get { return useZip64_; } | |||
1227 | set { useZip64_ = value; } | |||
1228 | } | |||
1229 | | |||
1230 | #endregion | |||
1231 | | |||
1232 | #region Immediate updating | |||
1233 | // TBD: Direct form of updating | |||
1234 | // | |||
1235 | // public void Update(IEntryMatcher deleteMatcher) | |||
1236 | // { | |||
1237 | // } | |||
1238 | // | |||
1239 | // public void Update(IScanner addScanner) | |||
1240 | // { | |||
1241 | // } | |||
1242 | #endregion | |||
1243 | | |||
1244 | #region Deferred Updating | |||
1245 | /// <summary> | |||
1246 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1247 | /// </summary> | |||
1248 | /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</p | |||
1249 | /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param | |||
1250 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1251 | /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception> | |||
1252 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1253 | public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) | |||
1254 | { | |||
1255 | if (archiveStorage == null) { | |||
1256 | throw new ArgumentNullException(nameof(archiveStorage)); | |||
1257 | } | |||
1258 | | |||
1259 | if (dataSource == null) { | |||
1260 | throw new ArgumentNullException(nameof(dataSource)); | |||
1261 | } | |||
1262 | | |||
1263 | if (isDisposed_) { | |||
1264 | throw new ObjectDisposedException("ZipFile"); | |||
1265 | } | |||
1266 | | |||
1267 | if (IsEmbeddedArchive) { | |||
1268 | throw new ZipException("Cannot update embedded/SFX archives"); | |||
1269 | } | |||
1270 | | |||
1271 | archiveStorage_ = archiveStorage; | |||
1272 | updateDataSource_ = dataSource; | |||
1273 | | |||
1274 | // NOTE: the baseStream_ may not currently support writing or seeking. | |||
1275 | | |||
1276 | updateIndex_ = new Hashtable(); | |||
1277 | | |||
1278 | updates_ = new ArrayList(entries_.Length); | |||
1279 | foreach (ZipEntry entry in entries_) { | |||
1280 | int index = updates_.Add(new ZipUpdate(entry)); | |||
1281 | updateIndex_.Add(entry.Name, index); | |||
1282 | } | |||
1283 | | |||
1284 | // We must sort by offset before using offset's calculated sizes | |||
1285 | updates_.Sort(new UpdateComparer()); | |||
1286 | | |||
1287 | int idx = 0; | |||
1288 | foreach (ZipUpdate update in updates_) { | |||
1289 | //If last entry, there is no next entry offset to use | |||
1290 | if (idx == updates_.Count - 1) | |||
1291 | break; | |||
1292 | | |||
1293 | update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; | |||
1294 | idx++; | |||
1295 | } | |||
1296 | updateCount_ = updates_.Count; | |||
1297 | | |||
1298 | contentsEdited_ = false; | |||
1299 | commentEdited_ = false; | |||
1300 | newComment_ = null; | |||
1301 | } | |||
1302 | | |||
1303 | /// <summary> | |||
1304 | /// Begin updating to this <see cref="ZipFile"/> archive. | |||
1305 | /// </summary> | |||
1306 | /// <param name="archiveStorage">The storage to use during the update.</param> | |||
1307 | public void BeginUpdate(IArchiveStorage archiveStorage) | |||
1308 | { | |||
1309 | BeginUpdate(archiveStorage, new DynamicDiskDataSource()); | |||
1310 | } | |||
1311 | | |||
1312 | /// <summary> | |||
1313 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1314 | /// </summary> | |||
1315 | /// <seealso cref="BeginUpdate(IArchiveStorage)"/> | |||
1316 | /// <seealso cref="CommitUpdate"></seealso> | |||
1317 | /// <seealso cref="AbortUpdate"></seealso> | |||
1318 | public void BeginUpdate() | |||
1319 | { | |||
1320 | if (Name == null) { | |||
1321 | BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); | |||
1322 | } else { | |||
1323 | BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); | |||
1324 | } | |||
1325 | } | |||
1326 | | |||
1327 | /// <summary> | |||
1328 | /// Commit current updates, updating this archive. | |||
1329 | /// </summary> | |||
1330 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1331 | /// <seealso cref="AbortUpdate"></seealso> | |||
1332 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1333 | public void CommitUpdate() | |||
1334 | { | |||
1335 | if (isDisposed_) { | |||
1336 | throw new ObjectDisposedException("ZipFile"); | |||
1337 | } | |||
1338 | | |||
1339 | CheckUpdating(); | |||
1340 | | |||
1341 | try { | |||
1342 | updateIndex_.Clear(); | |||
1343 | updateIndex_ = null; | |||
1344 | | |||
1345 | if (contentsEdited_) { | |||
1346 | RunUpdates(); | |||
1347 | } else if (commentEdited_) { | |||
1348 | UpdateCommentOnly(); | |||
1349 | } else { | |||
1350 | // Create an empty archive if none existed originally. | |||
1351 | if (entries_.Length == 0) { | |||
1352 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
1353 | using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) { | |||
1354 | zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); | |||
1355 | } | |||
1356 | } | |||
1357 | } | |||
1358 | | |||
1359 | } finally { | |||
1360 | PostUpdateCleanup(); | |||
1361 | } | |||
1362 | } | |||
1363 | | |||
1364 | /// <summary> | |||
1365 | /// Abort updating leaving the archive unchanged. | |||
1366 | /// </summary> | |||
1367 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1368 | /// <seealso cref="CommitUpdate"></seealso> | |||
1369 | public void AbortUpdate() | |||
1370 | { | |||
1371 | PostUpdateCleanup(); | |||
1372 | } | |||
1373 | | |||
1374 | /// <summary> | |||
1375 | /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. | |||
1376 | /// </summary> | |||
1377 | /// <param name="comment">The comment to record.</param> | |||
1378 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1379 | public void SetComment(string comment) | |||
1380 | { | |||
1381 | if (isDisposed_) { | |||
1382 | throw new ObjectDisposedException("ZipFile"); | |||
1383 | } | |||
1384 | | |||
1385 | CheckUpdating(); | |||
1386 | | |||
1387 | newComment_ = new ZipString(comment); | |||
1388 | | |||
1389 | if (newComment_.RawLength > 0xffff) { | |||
1390 | newComment_ = null; | |||
1391 | throw new ZipException("Comment length exceeds maximum - 65535"); | |||
1392 | } | |||
1393 | | |||
1394 | // We dont take account of the original and current comment appearing to be the same | |||
1395 | // as encoding may be different. | |||
1396 | commentEdited_ = true; | |||
1397 | } | |||
1398 | | |||
1399 | #endregion | |||
1400 | | |||
1401 | #region Adding Entries | |||
1402 | | |||
1403 | void AddUpdate(ZipUpdate update) | |||
1404 | { | |||
1405 | contentsEdited_ = true; | |||
1406 | | |||
1407 | int index = FindExistingUpdate(update.Entry.Name); | |||
1408 | | |||
1409 | if (index >= 0) { | |||
1410 | if (updates_[index] == null) { | |||
1411 | updateCount_ += 1; | |||
1412 | } | |||
1413 | | |||
1414 | // Direct replacement is faster than delete and add. | |||
1415 | updates_[index] = update; | |||
1416 | } else { | |||
1417 | index = updates_.Add(update); | |||
1418 | updateCount_ += 1; | |||
1419 | updateIndex_.Add(update.Entry.Name, index); | |||
1420 | } | |||
1421 | } | |||
1422 | | |||
1423 | /// <summary> | |||
1424 | /// Add a new entry to the archive. | |||
1425 | /// </summary> | |||
1426 | /// <param name="fileName">The name of the file to add.</param> | |||
1427 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1428 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> | |||
1429 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1430 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1431 | /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception> | |||
1432 | public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) | |||
1433 | { | |||
1434 | if (fileName == null) { | |||
1435 | throw new ArgumentNullException(nameof(fileName)); | |||
1436 | } | |||
1437 | | |||
1438 | if (isDisposed_) { | |||
1439 | throw new ObjectDisposedException("ZipFile"); | |||
1440 | } | |||
1441 | | |||
1442 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1443 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1444 | } | |||
1445 | | |||
1446 | CheckUpdating(); | |||
1447 | contentsEdited_ = true; | |||
1448 | | |||
1449 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1450 | entry.IsUnicodeText = useUnicodeText; | |||
1451 | entry.CompressionMethod = compressionMethod; | |||
1452 | | |||
1453 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1454 | } | |||
1455 | | |||
1456 | /// <summary> | |||
1457 | /// Add a new entry to the archive. | |||
1458 | /// </summary> | |||
1459 | /// <param name="fileName">The name of the file to add.</param> | |||
1460 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1461 | /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception> | |||
1462 | /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception> | |||
1463 | public void Add(string fileName, CompressionMethod compressionMethod) | |||
1464 | { | |||
1465 | if (fileName == null) { | |||
1466 | throw new ArgumentNullException(nameof(fileName)); | |||
1467 | } | |||
1468 | | |||
1469 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1470 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1471 | } | |||
1472 | | |||
1473 | CheckUpdating(); | |||
1474 | contentsEdited_ = true; | |||
1475 | | |||
1476 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1477 | entry.CompressionMethod = compressionMethod; | |||
1478 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1479 | } | |||
1480 | | |||
1481 | /// <summary> | |||
1482 | /// Add a file to the archive. | |||
1483 | /// </summary> | |||
1484 | /// <param name="fileName">The name of the file to add.</param> | |||
1485 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1486 | public void Add(string fileName) | |||
1487 | { | |||
1488 | if (fileName == null) { | |||
1489 | throw new ArgumentNullException(nameof(fileName)); | |||
1490 | } | |||
1491 | | |||
1492 | CheckUpdating(); | |||
1493 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); | |||
1494 | } | |||
1495 | | |||
1496 | /// <summary> | |||
1497 | /// Add a file to the archive. | |||
1498 | /// </summary> | |||
1499 | /// <param name="fileName">The name of the file to add.</param> | |||
1500 | /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param> | |||
1501 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1502 | public void Add(string fileName, string entryName) | |||
1503 | { | |||
1504 | if (fileName == null) { | |||
1505 | throw new ArgumentNullException(nameof(fileName)); | |||
1506 | } | |||
1507 | | |||
1508 | if (entryName == null) { | |||
1509 | throw new ArgumentNullException(nameof(entryName)); | |||
1510 | } | |||
1511 | | |||
1512 | CheckUpdating(); | |||
1513 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); | |||
1514 | } | |||
1515 | | |||
1516 | | |||
1517 | /// <summary> | |||
1518 | /// Add a file entry with data. | |||
1519 | /// </summary> | |||
1520 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1521 | /// <param name="entryName">The name to give to the entry.</param> | |||
1522 | public void Add(IStaticDataSource dataSource, string entryName) | |||
1523 | { | |||
1524 | if (dataSource == null) { | |||
1525 | throw new ArgumentNullException(nameof(dataSource)); | |||
1526 | } | |||
1527 | | |||
1528 | if (entryName == null) { | |||
1529 | throw new ArgumentNullException(nameof(entryName)); | |||
1530 | } | |||
1531 | | |||
1532 | CheckUpdating(); | |||
1533 | AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); | |||
1534 | } | |||
1535 | | |||
1536 | /// <summary> | |||
1537 | /// Add a file entry with data. | |||
1538 | /// </summary> | |||
1539 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1540 | /// <param name="entryName">The name to give to the entry.</param> | |||
1541 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1542 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
1543 | { | |||
1544 | if (dataSource == null) { | |||
1545 | throw new ArgumentNullException(nameof(dataSource)); | |||
1546 | } | |||
1547 | | |||
1548 | if (entryName == null) { | |||
1549 | throw new ArgumentNullException(nameof(entryName)); | |||
1550 | } | |||
1551 | | |||
1552 | CheckUpdating(); | |||
1553 | | |||
1554 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1555 | entry.CompressionMethod = compressionMethod; | |||
1556 | | |||
1557 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1558 | } | |||
1559 | | |||
1560 | /// <summary> | |||
1561 | /// Add a file entry with data. | |||
1562 | /// </summary> | |||
1563 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1564 | /// <param name="entryName">The name to give to the entry.</param> | |||
1565 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1566 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> | |||
1567 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicode | |||
1568 | { | |||
1569 | if (dataSource == null) { | |||
1570 | throw new ArgumentNullException(nameof(dataSource)); | |||
1571 | } | |||
1572 | | |||
1573 | if (entryName == null) { | |||
1574 | throw new ArgumentNullException(nameof(entryName)); | |||
1575 | } | |||
1576 | | |||
1577 | CheckUpdating(); | |||
1578 | | |||
1579 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1580 | entry.IsUnicodeText = useUnicodeText; | |||
1581 | entry.CompressionMethod = compressionMethod; | |||
1582 | | |||
1583 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1584 | } | |||
1585 | | |||
1586 | /// <summary> | |||
1587 | /// Add a <see cref="ZipEntry"/> that contains no data. | |||
1588 | /// </summary> | |||
1589 | /// <param name="entry">The entry to add.</param> | |||
1590 | /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> | |||
1591 | public void Add(ZipEntry entry) | |||
1592 | { | |||
1593 | if (entry == null) { | |||
1594 | throw new ArgumentNullException(nameof(entry)); | |||
1595 | } | |||
1596 | | |||
1597 | CheckUpdating(); | |||
1598 | | |||
1599 | if ((entry.Size != 0) || (entry.CompressedSize != 0)) { | |||
1600 | throw new ZipException("Entry cannot have any data"); | |||
1601 | } | |||
1602 | | |||
1603 | AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); | |||
1604 | } | |||
1605 | | |||
1606 | /// <summary> | |||
1607 | /// Add a directory entry to the archive. | |||
1608 | /// </summary> | |||
1609 | /// <param name="directoryName">The directory to add.</param> | |||
1610 | public void AddDirectory(string directoryName) | |||
1611 | { | |||
1612 | if (directoryName == null) { | |||
1613 | throw new ArgumentNullException(nameof(directoryName)); | |||
1614 | } | |||
1615 | | |||
1616 | CheckUpdating(); | |||
1617 | | |||
1618 | ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); | |||
1619 | AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); | |||
1620 | } | |||
1621 | | |||
1622 | #endregion | |||
1623 | | |||
1624 | #region Modifying Entries | |||
1625 | /* Modify not yet ready for public consumption. | |||
1626 | Direct modification of an entry should not overwrite original data before its read. | |||
1627 | Safe mode is trivial in this sense. | |||
1628 | public void Modify(ZipEntry original, ZipEntry updated) | |||
1629 | { | |||
1630 | if ( original == null ) { | |||
1631 | throw new ArgumentNullException("original"); | |||
1632 | } | |||
1633 | | |||
1634 | if ( updated == null ) { | |||
1635 | throw new ArgumentNullException("updated"); | |||
1636 | } | |||
1637 | | |||
1638 | CheckUpdating(); | |||
1639 | contentsEdited_ = true; | |||
1640 | updates_.Add(new ZipUpdate(original, updated)); | |||
1641 | } | |||
1642 | */ | |||
1643 | #endregion | |||
1644 | | |||
1645 | #region Deleting Entries | |||
1646 | /// <summary> | |||
1647 | /// Delete an entry by name | |||
1648 | /// </summary> | |||
1649 | /// <param name="fileName">The filename to delete</param> | |||
1650 | /// <returns>True if the entry was found and deleted; false otherwise.</returns> | |||
1651 | public bool Delete(string fileName) | |||
1652 | { | |||
1653 | if (fileName == null) { | |||
1654 | throw new ArgumentNullException(nameof(fileName)); | |||
1655 | } | |||
1656 | | |||
1657 | CheckUpdating(); | |||
1658 | | |||
1659 | bool result = false; | |||
1660 | int index = FindExistingUpdate(fileName); | |||
1661 | if ((index >= 0) && (updates_[index] != null)) { | |||
1662 | result = true; | |||
1663 | contentsEdited_ = true; | |||
1664 | updates_[index] = null; | |||
1665 | updateCount_ -= 1; | |||
1666 | } else { | |||
1667 | throw new ZipException("Cannot find entry to delete"); | |||
1668 | } | |||
1669 | return result; | |||
1670 | } | |||
1671 | | |||
1672 | /// <summary> | |||
1673 | /// Delete a <see cref="ZipEntry"/> from the archive. | |||
1674 | /// </summary> | |||
1675 | /// <param name="entry">The entry to delete.</param> | |||
1676 | public void Delete(ZipEntry entry) | |||
1677 | { | |||
1678 | if (entry == null) { | |||
1679 | throw new ArgumentNullException(nameof(entry)); | |||
1680 | } | |||
1681 | | |||
1682 | CheckUpdating(); | |||
1683 | | |||
1684 | int index = FindExistingUpdate(entry); | |||
1685 | if (index >= 0) { | |||
1686 | contentsEdited_ = true; | |||
1687 | updates_[index] = null; | |||
1688 | updateCount_ -= 1; | |||
1689 | } else { | |||
1690 | throw new ZipException("Cannot find entry to delete"); | |||
1691 | } | |||
1692 | } | |||
1693 | | |||
1694 | #endregion | |||
1695 | | |||
1696 | #region Update Support | |||
1697 | | |||
1698 | #region Writing Values/Headers | |||
1699 | void WriteLEShort(int value) | |||
1700 | { | |||
1701 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1702 | baseStream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
1703 | } | |||
1704 | | |||
1705 | /// <summary> | |||
1706 | /// Write an unsigned short in little endian byte order. | |||
1707 | /// </summary> | |||
1708 | void WriteLEUshort(ushort value) | |||
1709 | { | |||
1710 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1711 | baseStream_.WriteByte((byte)(value >> 8)); | |||
1712 | } | |||
1713 | | |||
1714 | /// <summary> | |||
1715 | /// Write an int in little endian byte order. | |||
1716 | /// </summary> | |||
1717 | void WriteLEInt(int value) | |||
1718 | { | |||
1719 | WriteLEShort(value & 0xffff); | |||
1720 | WriteLEShort(value >> 16); | |||
1721 | } | |||
1722 | | |||
1723 | /// <summary> | |||
1724 | /// Write an unsigned int in little endian byte order. | |||
1725 | /// </summary> | |||
1726 | void WriteLEUint(uint value) | |||
1727 | { | |||
1728 | WriteLEUshort((ushort)(value & 0xffff)); | |||
1729 | WriteLEUshort((ushort)(value >> 16)); | |||
1730 | } | |||
1731 | | |||
1732 | /// <summary> | |||
1733 | /// Write a long in little endian byte order. | |||
1734 | /// </summary> | |||
1735 | void WriteLeLong(long value) | |||
1736 | { | |||
1737 | WriteLEInt((int)(value & 0xffffffff)); | |||
1738 | WriteLEInt((int)(value >> 32)); | |||
1739 | } | |||
1740 | | |||
1741 | void WriteLEUlong(ulong value) | |||
1742 | { | |||
1743 | WriteLEUint((uint)(value & 0xffffffff)); | |||
1744 | WriteLEUint((uint)(value >> 32)); | |||
1745 | } | |||
1746 | | |||
1747 | void WriteLocalEntryHeader(ZipUpdate update) | |||
1748 | { | |||
1749 | ZipEntry entry = update.OutEntry; | |||
1750 | | |||
1751 | // TODO: Local offset will require adjusting for multi-disk zip files. | |||
1752 | entry.Offset = baseStream_.Position; | |||
1753 | | |||
1754 | // TODO: Need to clear any entry flags that dont make sense or throw an exception here. | |||
1755 | if (update.Command != UpdateCommand.Copy) { | |||
1756 | if (entry.CompressionMethod == CompressionMethod.Deflated) { | |||
1757 | if (entry.Size == 0) { | |||
1758 | // No need to compress - no data. | |||
1759 | entry.CompressedSize = entry.Size; | |||
1760 | entry.Crc = 0; | |||
1761 | entry.CompressionMethod = CompressionMethod.Stored; | |||
1762 | } | |||
1763 | } else if (entry.CompressionMethod == CompressionMethod.Stored) { | |||
1764 | entry.Flags &= ~(int)GeneralBitFlags.Descriptor; | |||
1765 | } | |||
1766 | | |||
1767 | if (HaveKeys) { | |||
1768 | entry.IsCrypted = true; | |||
1769 | if (entry.Crc < 0) { | |||
1770 | entry.Flags |= (int)GeneralBitFlags.Descriptor; | |||
1771 | } | |||
1772 | } else { | |||
1773 | entry.IsCrypted = false; | |||
1774 | } | |||
1775 | | |||
1776 | switch (useZip64_) { | |||
1777 | case UseZip64.Dynamic: | |||
1778 | if (entry.Size < 0) { | |||
1779 | entry.ForceZip64(); | |||
1780 | } | |||
1781 | break; | |||
1782 | | |||
1783 | case UseZip64.On: | |||
1784 | entry.ForceZip64(); | |||
1785 | break; | |||
1786 | | |||
1787 | case UseZip64.Off: | |||
1788 | // Do nothing. The entry itself may be using Zip64 independantly. | |||
1789 | break; | |||
1790 | } | |||
1791 | } | |||
1792 | | |||
1793 | // Write the local file header | |||
1794 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
1795 | | |||
1796 | WriteLEShort(entry.Version); | |||
1797 | WriteLEShort(entry.Flags); | |||
1798 | | |||
1799 | WriteLEShort((byte)entry.CompressionMethod); | |||
1800 | WriteLEInt((int)entry.DosTime); | |||
1801 | | |||
1802 | if (!entry.HasCrc) { | |||
1803 | // Note patch address for updating CRC later. | |||
1804 | update.CrcPatchOffset = baseStream_.Position; | |||
1805 | WriteLEInt((int)0); | |||
1806 | } else { | |||
1807 | WriteLEInt(unchecked((int)entry.Crc)); | |||
1808 | } | |||
1809 | | |||
1810 | if (entry.LocalHeaderRequiresZip64) { | |||
1811 | WriteLEInt(-1); | |||
1812 | WriteLEInt(-1); | |||
1813 | } else { | |||
1814 | if ((entry.CompressedSize < 0) || (entry.Size < 0)) { | |||
1815 | update.SizePatchOffset = baseStream_.Position; | |||
1816 | } | |||
1817 | | |||
1818 | WriteLEInt((int)entry.CompressedSize); | |||
1819 | WriteLEInt((int)entry.Size); | |||
1820 | } | |||
1821 | | |||
1822 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1823 | | |||
1824 | if (name.Length > 0xFFFF) { | |||
1825 | throw new ZipException("Entry name too long."); | |||
1826 | } | |||
1827 | | |||
1828 | var ed = new ZipExtraData(entry.ExtraData); | |||
1829 | | |||
1830 | if (entry.LocalHeaderRequiresZip64) { | |||
1831 | ed.StartNewEntry(); | |||
1832 | | |||
1833 | // Local entry header always includes size and compressed size. | |||
1834 | // NOTE the order of these fields is reversed when compared to the normal headers! | |||
1835 | ed.AddLeLong(entry.Size); | |||
1836 | ed.AddLeLong(entry.CompressedSize); | |||
1837 | ed.AddNewEntry(1); | |||
1838 | } else { | |||
1839 | ed.Delete(1); | |||
1840 | } | |||
1841 | | |||
1842 | entry.ExtraData = ed.GetEntryData(); | |||
1843 | | |||
1844 | WriteLEShort(name.Length); | |||
1845 | WriteLEShort(entry.ExtraData.Length); | |||
1846 | | |||
1847 | if (name.Length > 0) { | |||
1848 | baseStream_.Write(name, 0, name.Length); | |||
1849 | } | |||
1850 | | |||
1851 | if (entry.LocalHeaderRequiresZip64) { | |||
1852 | if (!ed.Find(1)) { | |||
1853 | throw new ZipException("Internal error cannot find extra data"); | |||
1854 | } | |||
1855 | | |||
1856 | update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; | |||
1857 | } | |||
1858 | | |||
1859 | if (entry.ExtraData.Length > 0) { | |||
1860 | baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); | |||
1861 | } | |||
1862 | } | |||
1863 | | |||
1864 | int WriteCentralDirectoryHeader(ZipEntry entry) | |||
1865 | { | |||
1866 | if (entry.CompressedSize < 0) { | |||
1867 | throw new ZipException("Attempt to write central directory entry with unknown csize"); | |||
1868 | } | |||
1869 | | |||
1870 | if (entry.Size < 0) { | |||
1871 | throw new ZipException("Attempt to write central directory entry with unknown size"); | |||
1872 | } | |||
1873 | | |||
1874 | if (entry.Crc < 0) { | |||
1875 | throw new ZipException("Attempt to write central directory entry with unknown crc"); | |||
1876 | } | |||
1877 | | |||
1878 | // Write the central file header | |||
1879 | WriteLEInt(ZipConstants.CentralHeaderSignature); | |||
1880 | | |||
1881 | // Version made by | |||
1882 | WriteLEShort(ZipConstants.VersionMadeBy); | |||
1883 | | |||
1884 | // Version required to extract | |||
1885 | WriteLEShort(entry.Version); | |||
1886 | | |||
1887 | WriteLEShort(entry.Flags); | |||
1888 | | |||
1889 | unchecked { | |||
1890 | WriteLEShort((byte)entry.CompressionMethod); | |||
1891 | WriteLEInt((int)entry.DosTime); | |||
1892 | WriteLEInt((int)entry.Crc); | |||
1893 | } | |||
1894 | | |||
1895 | if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { | |||
1896 | WriteLEInt(-1); | |||
1897 | } else { | |||
1898 | WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); | |||
1899 | } | |||
1900 | | |||
1901 | if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { | |||
1902 | WriteLEInt(-1); | |||
1903 | } else { | |||
1904 | WriteLEInt((int)entry.Size); | |||
1905 | } | |||
1906 | | |||
1907 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1908 | | |||
1909 | if (name.Length > 0xFFFF) { | |||
1910 | throw new ZipException("Entry name is too long."); | |||
1911 | } | |||
1912 | | |||
1913 | WriteLEShort(name.Length); | |||
1914 | | |||
1915 | // Central header extra data is different to local header version so regenerate. | |||
1916 | var ed = new ZipExtraData(entry.ExtraData); | |||
1917 | | |||
1918 | if (entry.CentralHeaderRequiresZip64) { | |||
1919 | ed.StartNewEntry(); | |||
1920 | | |||
1921 | if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1922 | ed.AddLeLong(entry.Size); | |||
1923 | } | |||
1924 | | |||
1925 | if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1926 | ed.AddLeLong(entry.CompressedSize); | |||
1927 | } | |||
1928 | | |||
1929 | if (entry.Offset >= 0xffffffff) { | |||
1930 | ed.AddLeLong(entry.Offset); | |||
1931 | } | |||
1932 | | |||
1933 | // Number of disk on which this file starts isnt supported and is never written here. | |||
1934 | ed.AddNewEntry(1); | |||
1935 | } else { | |||
1936 | // Should have already be done when local header was added. | |||
1937 | ed.Delete(1); | |||
1938 | } | |||
1939 | | |||
1940 | byte[] centralExtraData = ed.GetEntryData(); | |||
1941 | | |||
1942 | WriteLEShort(centralExtraData.Length); | |||
1943 | WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); | |||
1944 | | |||
1945 | WriteLEShort(0); // disk number | |||
1946 | WriteLEShort(0); // internal file attributes | |||
1947 | | |||
1948 | // External file attributes... | |||
1949 | if (entry.ExternalFileAttributes != -1) { | |||
1950 | WriteLEInt(entry.ExternalFileAttributes); | |||
1951 | } else { | |||
1952 | if (entry.IsDirectory) { | |||
1953 | WriteLEUint(16); | |||
1954 | } else { | |||
1955 | WriteLEUint(0); | |||
1956 | } | |||
1957 | } | |||
1958 | | |||
1959 | if (entry.Offset >= 0xffffffff) { | |||
1960 | WriteLEUint(0xffffffff); | |||
1961 | } else { | |||
1962 | WriteLEUint((uint)(int)entry.Offset); | |||
1963 | } | |||
1964 | | |||
1965 | if (name.Length > 0) { | |||
1966 | baseStream_.Write(name, 0, name.Length); | |||
1967 | } | |||
1968 | | |||
1969 | if (centralExtraData.Length > 0) { | |||
1970 | baseStream_.Write(centralExtraData, 0, centralExtraData.Length); | |||
1971 | } | |||
1972 | | |||
1973 | byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; | |||
1974 | | |||
1975 | if (rawComment.Length > 0) { | |||
1976 | baseStream_.Write(rawComment, 0, rawComment.Length); | |||
1977 | } | |||
1978 | | |||
1979 | return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; | |||
1980 | } | |||
1981 | #endregion | |||
1982 | | |||
1983 | void PostUpdateCleanup() | |||
1984 | { | |||
1985 | updateDataSource_ = null; | |||
1986 | updates_ = null; | |||
1987 | updateIndex_ = null; | |||
1988 | | |||
1989 | if (archiveStorage_ != null) { | |||
1990 | archiveStorage_.Dispose(); | |||
1991 | archiveStorage_ = null; | |||
1992 | } | |||
1993 | } | |||
1994 | | |||
1995 | string GetTransformedFileName(string name) | |||
1996 | { | |||
1997 | INameTransform transform = NameTransform; | |||
1998 | return (transform != null) ? | |||
1999 | transform.TransformFile(name) : | |||
2000 | name; | |||
2001 | } | |||
2002 | | |||
2003 | string GetTransformedDirectoryName(string name) | |||
2004 | { | |||
2005 | INameTransform transform = NameTransform; | |||
2006 | return (transform != null) ? | |||
2007 | transform.TransformDirectory(name) : | |||
2008 | name; | |||
2009 | } | |||
2010 | | |||
2011 | /// <summary> | |||
2012 | /// Get a raw memory buffer. | |||
2013 | /// </summary> | |||
2014 | /// <returns>Returns a raw memory buffer.</returns> | |||
2015 | byte[] GetBuffer() | |||
2016 | { | |||
2017 | if (copyBuffer_ == null) { | |||
2018 | copyBuffer_ = new byte[bufferSize_]; | |||
2019 | } | |||
2020 | return copyBuffer_; | |||
2021 | } | |||
2022 | | |||
2023 | void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) | |||
2024 | { | |||
2025 | int bytesToCopy = GetDescriptorSize(update); | |||
2026 | | |||
2027 | if (bytesToCopy > 0) { | |||
2028 | byte[] buffer = GetBuffer(); | |||
2029 | | |||
2030 | while (bytesToCopy > 0) { | |||
2031 | int readSize = Math.Min(buffer.Length, bytesToCopy); | |||
2032 | | |||
2033 | int bytesRead = source.Read(buffer, 0, readSize); | |||
2034 | if (bytesRead > 0) { | |||
2035 | dest.Write(buffer, 0, bytesRead); | |||
2036 | bytesToCopy -= bytesRead; | |||
2037 | } else { | |||
2038 | throw new ZipException("Unxpected end of stream"); | |||
2039 | } | |||
2040 | } | |||
2041 | } | |||
2042 | } | |||
2043 | | |||
2044 | void CopyBytes(ZipUpdate update, Stream destination, Stream source, | |||
2045 | long bytesToCopy, bool updateCrc) | |||
2046 | { | |||
2047 | if (destination == source) { | |||
2048 | throw new InvalidOperationException("Destination and source are the same"); | |||
2049 | } | |||
2050 | | |||
2051 | // NOTE: Compressed size is updated elsewhere. | |||
2052 | var crc = new Crc32(); | |||
2053 | byte[] buffer = GetBuffer(); | |||
2054 | | |||
2055 | long targetBytes = bytesToCopy; | |||
2056 | long totalBytesRead = 0; | |||
2057 | | |||
2058 | int bytesRead; | |||
2059 | do { | |||
2060 | int readSize = buffer.Length; | |||
2061 | | |||
2062 | if (bytesToCopy < readSize) { | |||
2063 | readSize = (int)bytesToCopy; | |||
2064 | } | |||
2065 | | |||
2066 | bytesRead = source.Read(buffer, 0, readSize); | |||
2067 | if (bytesRead > 0) { | |||
2068 | if (updateCrc) { | |||
2069 | crc.Update(buffer, 0, bytesRead); | |||
2070 | } | |||
2071 | destination.Write(buffer, 0, bytesRead); | |||
2072 | bytesToCopy -= bytesRead; | |||
2073 | totalBytesRead += bytesRead; | |||
2074 | } | |||
2075 | } | |||
2076 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2077 | | |||
2078 | if (totalBytesRead != targetBytes) { | |||
2079 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2080 | } | |||
2081 | | |||
2082 | if (updateCrc) { | |||
2083 | update.OutEntry.Crc = crc.Value; | |||
2084 | } | |||
2085 | } | |||
2086 | | |||
2087 | /// <summary> | |||
2088 | /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. | |||
2089 | /// </summary> | |||
2090 | /// <param name="update">The update to get the size for.</param> | |||
2091 | /// <returns>The descriptor size, zero if there isnt one.</returns> | |||
2092 | int GetDescriptorSize(ZipUpdate update) | |||
2093 | { | |||
2094 | int result = 0; | |||
2095 | if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
2096 | result = ZipConstants.DataDescriptorSize - 4; | |||
2097 | if (update.Entry.LocalHeaderRequiresZip64) { | |||
2098 | result = ZipConstants.Zip64DataDescriptorSize - 4; | |||
2099 | } | |||
2100 | } | |||
2101 | return result; | |||
2102 | } | |||
2103 | | |||
2104 | void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) | |||
2105 | { | |||
2106 | int bytesToCopy = GetDescriptorSize(update); | |||
2107 | | |||
2108 | while (bytesToCopy > 0) { | |||
2109 | var readSize = (int)bytesToCopy; | |||
2110 | byte[] buffer = GetBuffer(); | |||
2111 | | |||
2112 | stream.Position = sourcePosition; | |||
2113 | int bytesRead = stream.Read(buffer, 0, readSize); | |||
2114 | if (bytesRead > 0) { | |||
2115 | stream.Position = destinationPosition; | |||
2116 | stream.Write(buffer, 0, bytesRead); | |||
2117 | bytesToCopy -= bytesRead; | |||
2118 | destinationPosition += bytesRead; | |||
2119 | sourcePosition += bytesRead; | |||
2120 | } else { | |||
2121 | throw new ZipException("Unxpected end of stream"); | |||
2122 | } | |||
2123 | } | |||
2124 | } | |||
2125 | | |||
2126 | void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou | |||
2127 | { | |||
2128 | long bytesToCopy = update.Entry.CompressedSize; | |||
2129 | | |||
2130 | // NOTE: Compressed size is updated elsewhere. | |||
2131 | var crc = new Crc32(); | |||
2132 | byte[] buffer = GetBuffer(); | |||
2133 | | |||
2134 | long targetBytes = bytesToCopy; | |||
2135 | long totalBytesRead = 0; | |||
2136 | | |||
2137 | int bytesRead; | |||
2138 | do { | |||
2139 | int readSize = buffer.Length; | |||
2140 | | |||
2141 | if (bytesToCopy < readSize) { | |||
2142 | readSize = (int)bytesToCopy; | |||
2143 | } | |||
2144 | | |||
2145 | stream.Position = sourcePosition; | |||
2146 | bytesRead = stream.Read(buffer, 0, readSize); | |||
2147 | if (bytesRead > 0) { | |||
2148 | if (updateCrc) { | |||
2149 | crc.Update(buffer, 0, bytesRead); | |||
2150 | } | |||
2151 | stream.Position = destinationPosition; | |||
2152 | stream.Write(buffer, 0, bytesRead); | |||
2153 | | |||
2154 | destinationPosition += bytesRead; | |||
2155 | sourcePosition += bytesRead; | |||
2156 | bytesToCopy -= bytesRead; | |||
2157 | totalBytesRead += bytesRead; | |||
2158 | } | |||
2159 | } | |||
2160 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2161 | | |||
2162 | if (totalBytesRead != targetBytes) { | |||
2163 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2164 | } | |||
2165 | | |||
2166 | if (updateCrc) { | |||
2167 | update.OutEntry.Crc = crc.Value; | |||
2168 | } | |||
2169 | } | |||
2170 | | |||
2171 | int FindExistingUpdate(ZipEntry entry) | |||
2172 | { | |||
2173 | int result = -1; | |||
2174 | string convertedName = GetTransformedFileName(entry.Name); | |||
2175 | | |||
2176 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2177 | result = (int)updateIndex_[convertedName]; | |||
2178 | } | |||
2179 | /* | |||
2180 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2181 | // for CF? | |||
2182 | for (int index = 0; index < updates_.Count; ++index) | |||
2183 | { | |||
2184 | ZipUpdate zu = ( ZipUpdate )updates_[index]; | |||
2185 | if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && | |||
2186 | (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { | |||
2187 | result = index; | |||
2188 | break; | |||
2189 | } | |||
2190 | } | |||
2191 | */ | |||
2192 | return result; | |||
2193 | } | |||
2194 | | |||
2195 | int FindExistingUpdate(string fileName) | |||
2196 | { | |||
2197 | int result = -1; | |||
2198 | | |||
2199 | string convertedName = GetTransformedFileName(fileName); | |||
2200 | | |||
2201 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2202 | result = (int)updateIndex_[convertedName]; | |||
2203 | } | |||
2204 | | |||
2205 | /* | |||
2206 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2207 | // for CF? | |||
2208 | for ( int index = 0; index < updates_.Count; ++index ) { | |||
2209 | if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, | |||
2210 | true, CultureInfo.InvariantCulture) == 0 ) { | |||
2211 | result = index; | |||
2212 | break; | |||
2213 | } | |||
2214 | } | |||
2215 | */ | |||
2216 | | |||
2217 | return result; | |||
2218 | } | |||
2219 | | |||
2220 | /// <summary> | |||
2221 | /// Get an output stream for the specified <see cref="ZipEntry"/> | |||
2222 | /// </summary> | |||
2223 | /// <param name="entry">The entry to get an output stream for.</param> | |||
2224 | /// <returns>The output stream obtained for the entry.</returns> | |||
2225 | Stream GetOutputStream(ZipEntry entry) | |||
2226 | { | |||
2227 | Stream result = baseStream_; | |||
2228 | | |||
2229 | if (entry.IsCrypted == true) { | |||
2230 | result = CreateAndInitEncryptionStream(result, entry); | |||
2231 | } | |||
2232 | | |||
2233 | switch (entry.CompressionMethod) { | |||
2234 | case CompressionMethod.Stored: | |||
2235 | result = new UncompressedStream(result); | |||
2236 | break; | |||
2237 | | |||
2238 | case CompressionMethod.Deflated: | |||
2239 | var dos = new DeflaterOutputStream(result, new Deflater(9, true)); | |||
2240 | dos.IsStreamOwner = false; | |||
2241 | result = dos; | |||
2242 | break; | |||
2243 | | |||
2244 | default: | |||
2245 | throw new ZipException("Unknown compression method " + entry.CompressionMethod); | |||
2246 | } | |||
2247 | return result; | |||
2248 | } | |||
2249 | | |||
2250 | void AddEntry(ZipFile workFile, ZipUpdate update) | |||
2251 | { | |||
2252 | Stream source = null; | |||
2253 | | |||
2254 | if (update.Entry.IsFile) { | |||
2255 | source = update.GetSource(); | |||
2256 | | |||
2257 | if (source == null) { | |||
2258 | source = updateDataSource_.GetSource(update.Entry, update.Filename); | |||
2259 | } | |||
2260 | } | |||
2261 | | |||
2262 | if (source != null) { | |||
2263 | using (source) { | |||
2264 | long sourceStreamLength = source.Length; | |||
2265 | if (update.OutEntry.Size < 0) { | |||
2266 | update.OutEntry.Size = sourceStreamLength; | |||
2267 | } else { | |||
2268 | // Check for errant entries. | |||
2269 | if (update.OutEntry.Size != sourceStreamLength) { | |||
2270 | throw new ZipException("Entry size/stream size mismatch"); | |||
2271 | } | |||
2272 | } | |||
2273 | | |||
2274 | workFile.WriteLocalEntryHeader(update); | |||
2275 | | |||
2276 | long dataStart = workFile.baseStream_.Position; | |||
2277 | | |||
2278 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2279 | CopyBytes(update, output, source, sourceStreamLength, true); | |||
2280 | } | |||
2281 | | |||
2282 | long dataEnd = workFile.baseStream_.Position; | |||
2283 | update.OutEntry.CompressedSize = dataEnd - dataStart; | |||
2284 | | |||
2285 | if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) { | |||
2286 | var helper = new ZipHelperStream(workFile.baseStream_); | |||
2287 | helper.WriteDataDescriptor(update.OutEntry); | |||
2288 | } | |||
2289 | } | |||
2290 | } else { | |||
2291 | workFile.WriteLocalEntryHeader(update); | |||
2292 | update.OutEntry.CompressedSize = 0; | |||
2293 | } | |||
2294 | | |||
2295 | } | |||
2296 | | |||
2297 | void ModifyEntry(ZipFile workFile, ZipUpdate update) | |||
2298 | { | |||
2299 | workFile.WriteLocalEntryHeader(update); | |||
2300 | long dataStart = workFile.baseStream_.Position; | |||
2301 | | |||
2302 | // TODO: This is slow if the changes don't effect the data!! | |||
2303 | if (update.Entry.IsFile && (update.Filename != null)) { | |||
2304 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2305 | using (Stream source = this.GetInputStream(update.Entry)) { | |||
2306 | CopyBytes(update, output, source, source.Length, true); | |||
2307 | } | |||
2308 | } | |||
2309 | } | |||
2310 | | |||
2311 | long dataEnd = workFile.baseStream_.Position; | |||
2312 | update.Entry.CompressedSize = dataEnd - dataStart; | |||
2313 | } | |||
2314 | | |||
2315 | void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) | |||
2316 | { | |||
2317 | bool skipOver = false || update.Entry.Offset == destinationPosition; | |||
2318 | | |||
2319 | if (!skipOver) { | |||
2320 | baseStream_.Position = destinationPosition; | |||
2321 | workFile.WriteLocalEntryHeader(update); | |||
2322 | destinationPosition = baseStream_.Position; | |||
2323 | } | |||
2324 | | |||
2325 | long sourcePosition = 0; | |||
2326 | | |||
2327 | const int NameLengthOffset = 26; | |||
2328 | | |||
2329 | // TODO: Add base for SFX friendly handling | |||
2330 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2331 | | |||
2332 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2333 | | |||
2334 | // Clumsy way of handling retrieving the original name and extra data length for now. | |||
2335 | // TODO: Stop re-reading name and data length in CopyEntryDirect. | |||
2336 | uint nameLength = ReadLEUshort(); | |||
2337 | uint extraLength = ReadLEUshort(); | |||
2338 | | |||
2339 | sourcePosition = baseStream_.Position + nameLength + extraLength; | |||
2340 | | |||
2341 | if (skipOver) { | |||
2342 | if (update.OffsetBasedSize != -1) | |||
2343 | destinationPosition += update.OffsetBasedSize; | |||
2344 | else | |||
2345 | // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) ar | |||
2346 | // WinZip produces a warning on these entries: | |||
2347 | // "caution: value of lrec.csize (compressed size) changed from ..." | |||
2348 | destinationPosition += | |||
2349 | (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size | |||
2350 | update.Entry.CompressedSize + GetDescriptorSize(update); | |||
2351 | } else { | |||
2352 | if (update.Entry.CompressedSize > 0) { | |||
2353 | CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); | |||
2354 | } | |||
2355 | CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); | |||
2356 | } | |||
2357 | } | |||
2358 | | |||
2359 | void CopyEntry(ZipFile workFile, ZipUpdate update) | |||
2360 | { | |||
2361 | workFile.WriteLocalEntryHeader(update); | |||
2362 | | |||
2363 | if (update.Entry.CompressedSize > 0) { | |||
2364 | const int NameLengthOffset = 26; | |||
2365 | | |||
2366 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2367 | | |||
2368 | // TODO: This wont work for SFX files! | |||
2369 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2370 | | |||
2371 | uint nameLength = ReadLEUshort(); | |||
2372 | uint extraLength = ReadLEUshort(); | |||
2373 | | |||
2374 | baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); | |||
2375 | | |||
2376 | CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); | |||
2377 | } | |||
2378 | CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); | |||
2379 | } | |||
2380 | | |||
2381 | void Reopen(Stream source) | |||
2382 | { | |||
2383 | if (source == null) { | |||
2384 | throw new ZipException("Failed to reopen archive - no source"); | |||
2385 | } | |||
2386 | | |||
2387 | isNewArchive_ = false; | |||
2388 | baseStream_ = source; | |||
2389 | ReadEntries(); | |||
2390 | } | |||
2391 | | |||
2392 | void Reopen() | |||
2393 | { | |||
2394 | if (Name == null) { | |||
2395 | throw new InvalidOperationException("Name is not known cannot Reopen"); | |||
2396 | } | |||
2397 | | |||
2398 | Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); | |||
2399 | } | |||
2400 | | |||
2401 | void UpdateCommentOnly() | |||
2402 | { | |||
2403 | long baseLength = baseStream_.Length; | |||
2404 | | |||
2405 | ZipHelperStream updateFile = null; | |||
2406 | | |||
2407 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2408 | Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); | |||
2409 | updateFile = new ZipHelperStream(copyStream); | |||
2410 | updateFile.IsStreamOwner = true; | |||
2411 | | |||
2412 | baseStream_.Close(); | |||
2413 | baseStream_ = null; | |||
2414 | } else { | |||
2415 | if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2416 | // TODO: archiveStorage wasnt originally intended for this use. | |||
2417 | // Need to revisit this to tidy up handling as archive storage currently doesnt | |||
2418 | // handle the original stream well. | |||
2419 | // The problem is when using an existing zip archive with an in memory archive storage. | |||
2420 | // The open stream wont support writing but the memory storage should open the same file not an in memory one. | |||
2421 | | |||
2422 | // Need to tidy up the archive storage interface and contract basically. | |||
2423 | baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); | |||
2424 | updateFile = new ZipHelperStream(baseStream_); | |||
2425 | } else { | |||
2426 | baseStream_.Close(); | |||
2427 | baseStream_ = null; | |||
2428 | updateFile = new ZipHelperStream(Name); | |||
2429 | } | |||
2430 | } | |||
2431 | | |||
2432 | using (updateFile) { | |||
2433 | long locatedCentralDirOffset = | |||
2434 | updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2435 | baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2436 | if (locatedCentralDirOffset < 0) { | |||
2437 | throw new ZipException("Cannot find central directory"); | |||
2438 | } | |||
2439 | | |||
2440 | const int CentralHeaderCommentSizeOffset = 16; | |||
2441 | updateFile.Position += CentralHeaderCommentSizeOffset; | |||
2442 | | |||
2443 | byte[] rawComment = newComment_.RawComment; | |||
2444 | | |||
2445 | updateFile.WriteLEShort(rawComment.Length); | |||
2446 | updateFile.Write(rawComment, 0, rawComment.Length); | |||
2447 | updateFile.SetLength(updateFile.Position); | |||
2448 | } | |||
2449 | | |||
2450 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2451 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2452 | } else { | |||
2453 | ReadEntries(); | |||
2454 | } | |||
2455 | } | |||
2456 | | |||
2457 | /// <summary> | |||
2458 | /// Class used to sort updates. | |||
2459 | /// </summary> | |||
2460 | class UpdateComparer : IComparer | |||
2461 | { | |||
2462 | /// <summary> | |||
2463 | /// Compares two objects and returns a value indicating whether one is | |||
2464 | /// less than, equal to or greater than the other. | |||
2465 | /// </summary> | |||
2466 | /// <param name="x">First object to compare</param> | |||
2467 | /// <param name="y">Second object to compare.</param> | |||
2468 | /// <returns>Compare result.</returns> | |||
2469 | public int Compare( | |||
2470 | object x, | |||
2471 | object y) | |||
2472 | { | |||
2473 | var zx = x as ZipUpdate; | |||
2474 | var zy = y as ZipUpdate; | |||
2475 | | |||
2476 | int result; | |||
2477 | | |||
2478 | if (zx == null) { | |||
2479 | if (zy == null) { | |||
2480 | result = 0; | |||
2481 | } else { | |||
2482 | result = -1; | |||
2483 | } | |||
2484 | } else if (zy == null) { | |||
2485 | result = 1; | |||
2486 | } else { | |||
2487 | int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2488 | int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2489 | | |||
2490 | result = xCmdValue - yCmdValue; | |||
2491 | if (result == 0) { | |||
2492 | long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; | |||
2493 | if (offsetDiff < 0) { | |||
2494 | result = -1; | |||
2495 | } else if (offsetDiff == 0) { | |||
2496 | result = 0; | |||
2497 | } else { | |||
2498 | result = 1; | |||
2499 | } | |||
2500 | } | |||
2501 | } | |||
2502 | return result; | |||
2503 | } | |||
2504 | } | |||
2505 | | |||
2506 | void RunUpdates() | |||
2507 | { | |||
2508 | long sizeEntries = 0; | |||
2509 | long endOfStream = 0; | |||
2510 | bool directUpdate = false; | |||
2511 | long destinationPosition = 0; // NOT SFX friendly | |||
2512 | | |||
2513 | ZipFile workFile; | |||
2514 | | |||
2515 | if (IsNewArchive) { | |||
2516 | workFile = this; | |||
2517 | workFile.baseStream_.Position = 0; | |||
2518 | directUpdate = true; | |||
2519 | } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2520 | workFile = this; | |||
2521 | workFile.baseStream_.Position = 0; | |||
2522 | directUpdate = true; | |||
2523 | | |||
2524 | // Sort the updates by offset within copies/modifies, then adds. | |||
2525 | // This ensures that data required by copies will not be overwritten. | |||
2526 | updates_.Sort(new UpdateComparer()); | |||
2527 | } else { | |||
2528 | workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); | |||
2529 | workFile.UseZip64 = UseZip64; | |||
2530 | | |||
2531 | if (key != null) { | |||
2532 | workFile.key = (byte[])key.Clone(); | |||
2533 | } | |||
2534 | } | |||
2535 | | |||
2536 | try { | |||
2537 | foreach (ZipUpdate update in updates_) { | |||
2538 | if (update != null) { | |||
2539 | switch (update.Command) { | |||
2540 | case UpdateCommand.Copy: | |||
2541 | if (directUpdate) { | |||
2542 | CopyEntryDirect(workFile, update, ref destinationPosition); | |||
2543 | } else { | |||
2544 | CopyEntry(workFile, update); | |||
2545 | } | |||
2546 | break; | |||
2547 | | |||
2548 | case UpdateCommand.Modify: | |||
2549 | // TODO: Direct modifying of an entry will take some legwork. | |||
2550 | ModifyEntry(workFile, update); | |||
2551 | break; | |||
2552 | | |||
2553 | case UpdateCommand.Add: | |||
2554 | if (!IsNewArchive && directUpdate) { | |||
2555 | workFile.baseStream_.Position = destinationPosition; | |||
2556 | } | |||
2557 | | |||
2558 | AddEntry(workFile, update); | |||
2559 | | |||
2560 | if (directUpdate) { | |||
2561 | destinationPosition = workFile.baseStream_.Position; | |||
2562 | } | |||
2563 | break; | |||
2564 | } | |||
2565 | } | |||
2566 | } | |||
2567 | | |||
2568 | if (!IsNewArchive && directUpdate) { | |||
2569 | workFile.baseStream_.Position = destinationPosition; | |||
2570 | } | |||
2571 | | |||
2572 | long centralDirOffset = workFile.baseStream_.Position; | |||
2573 | | |||
2574 | foreach (ZipUpdate update in updates_) { | |||
2575 | if (update != null) { | |||
2576 | sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); | |||
2577 | } | |||
2578 | } | |||
2579 | | |||
2580 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
2581 | using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) { | |||
2582 | zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); | |||
2583 | } | |||
2584 | | |||
2585 | endOfStream = workFile.baseStream_.Position; | |||
2586 | | |||
2587 | // And now patch entries... | |||
2588 | foreach (ZipUpdate update in updates_) { | |||
2589 | if (update != null) { | |||
2590 | // If the size of the entry is zero leave the crc as 0 as well. | |||
2591 | // The calculated crc will be all bits on... | |||
2592 | if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { | |||
2593 | workFile.baseStream_.Position = update.CrcPatchOffset; | |||
2594 | workFile.WriteLEInt((int)update.OutEntry.Crc); | |||
2595 | } | |||
2596 | | |||
2597 | if (update.SizePatchOffset > 0) { | |||
2598 | workFile.baseStream_.Position = update.SizePatchOffset; | |||
2599 | if (update.OutEntry.LocalHeaderRequiresZip64) { | |||
2600 | workFile.WriteLeLong(update.OutEntry.Size); | |||
2601 | workFile.WriteLeLong(update.OutEntry.CompressedSize); | |||
2602 | } else { | |||
2603 | workFile.WriteLEInt((int)update.OutEntry.CompressedSize); | |||
2604 | workFile.WriteLEInt((int)update.OutEntry.Size); | |||
2605 | } | |||
2606 | } | |||
2607 | } | |||
2608 | } | |||
2609 | } catch { | |||
2610 | workFile.Close(); | |||
2611 | if (!directUpdate && (workFile.Name != null)) { | |||
2612 | File.Delete(workFile.Name); | |||
2613 | } | |||
2614 | throw; | |||
2615 | } | |||
2616 | | |||
2617 | if (directUpdate) { | |||
2618 | workFile.baseStream_.SetLength(endOfStream); | |||
2619 | workFile.baseStream_.Flush(); | |||
2620 | isNewArchive_ = false; | |||
2621 | ReadEntries(); | |||
2622 | } else { | |||
2623 | baseStream_.Close(); | |||
2624 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2625 | } | |||
2626 | } | |||
2627 | | |||
2628 | void CheckUpdating() | |||
2629 | { | |||
2630 | if (updates_ == null) { | |||
2631 | throw new InvalidOperationException("BeginUpdate has not been called"); | |||
2632 | } | |||
2633 | } | |||
2634 | | |||
2635 | #endregion | |||
2636 | | |||
2637 | #region ZipUpdate class | |||
2638 | /// <summary> | |||
2639 | /// Represents a pending update to a Zip file. | |||
2640 | /// </summary> | |||
2641 | class ZipUpdate | |||
2642 | { | |||
2643 | #region Constructors | |||
2644 | public ZipUpdate(string fileName, ZipEntry entry) | |||
2645 | { | |||
2646 | command_ = UpdateCommand.Add; | |||
2647 | entry_ = entry; | |||
2648 | filename_ = fileName; | |||
2649 | } | |||
2650 | | |||
2651 | [Obsolete] | |||
2652 | public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) | |||
2653 | { | |||
2654 | command_ = UpdateCommand.Add; | |||
2655 | entry_ = new ZipEntry(entryName); | |||
2656 | entry_.CompressionMethod = compressionMethod; | |||
2657 | filename_ = fileName; | |||
2658 | } | |||
2659 | | |||
2660 | [Obsolete] | |||
2661 | public ZipUpdate(string fileName, string entryName) | |||
2662 | : this(fileName, entryName, CompressionMethod.Deflated) | |||
2663 | { | |||
2664 | // Do nothing. | |||
2665 | } | |||
2666 | | |||
2667 | [Obsolete] | |||
2668 | public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
2669 | { | |||
2670 | command_ = UpdateCommand.Add; | |||
2671 | entry_ = new ZipEntry(entryName); | |||
2672 | entry_.CompressionMethod = compressionMethod; | |||
2673 | dataSource_ = dataSource; | |||
2674 | } | |||
2675 | | |||
2676 | public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) | |||
2677 | { | |||
2678 | command_ = UpdateCommand.Add; | |||
2679 | entry_ = entry; | |||
2680 | dataSource_ = dataSource; | |||
2681 | } | |||
2682 | | |||
2683 | public ZipUpdate(ZipEntry original, ZipEntry updated) | |||
2684 | { | |||
2685 | throw new ZipException("Modify not currently supported"); | |||
2686 | /* | |||
2687 | command_ = UpdateCommand.Modify; | |||
2688 | entry_ = ( ZipEntry )original.Clone(); | |||
2689 | outEntry_ = ( ZipEntry )updated.Clone(); | |||
2690 | */ | |||
2691 | } | |||
2692 | | |||
2693 | public ZipUpdate(UpdateCommand command, ZipEntry entry) | |||
2694 | { | |||
2695 | command_ = command; | |||
2696 | entry_ = (ZipEntry)entry.Clone(); | |||
2697 | } | |||
2698 | | |||
2699 | | |||
2700 | /// <summary> | |||
2701 | /// Copy an existing entry. | |||
2702 | /// </summary> | |||
2703 | /// <param name="entry">The existing entry to copy.</param> | |||
2704 | public ZipUpdate(ZipEntry entry) | |||
2705 | : this(UpdateCommand.Copy, entry) | |||
2706 | { | |||
2707 | // Do nothing. | |||
2708 | } | |||
2709 | #endregion | |||
2710 | | |||
2711 | /// <summary> | |||
2712 | /// Get the <see cref="ZipEntry"/> for this update. | |||
2713 | /// </summary> | |||
2714 | /// <remarks>This is the source or original entry.</remarks> | |||
2715 | public ZipEntry Entry { | |||
2716 | get { return entry_; } | |||
2717 | } | |||
2718 | | |||
2719 | /// <summary> | |||
2720 | /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. | |||
2721 | /// </summary> | |||
2722 | public ZipEntry OutEntry { | |||
2723 | get { | |||
2724 | if (outEntry_ == null) { | |||
2725 | outEntry_ = (ZipEntry)entry_.Clone(); | |||
2726 | } | |||
2727 | | |||
2728 | return outEntry_; | |||
2729 | } | |||
2730 | } | |||
2731 | | |||
2732 | /// <summary> | |||
2733 | /// Get the command for this update. | |||
2734 | /// </summary> | |||
2735 | public UpdateCommand Command { | |||
2736 | get { return command_; } | |||
2737 | } | |||
2738 | | |||
2739 | /// <summary> | |||
2740 | /// Get the filename if any for this update. Null if none exists. | |||
2741 | /// </summary> | |||
2742 | public string Filename { | |||
2743 | get { return filename_; } | |||
2744 | } | |||
2745 | | |||
2746 | /// <summary> | |||
2747 | /// Get/set the location of the size patch for this update. | |||
2748 | /// </summary> | |||
2749 | public long SizePatchOffset { | |||
2750 | get { return sizePatchOffset_; } | |||
2751 | set { sizePatchOffset_ = value; } | |||
2752 | } | |||
2753 | | |||
2754 | /// <summary> | |||
2755 | /// Get /set the location of the crc patch for this update. | |||
2756 | /// </summary> | |||
2757 | public long CrcPatchOffset { | |||
2758 | get { return crcPatchOffset_; } | |||
2759 | set { crcPatchOffset_ = value; } | |||
2760 | } | |||
2761 | | |||
2762 | /// <summary> | |||
2763 | /// Get/set the size calculated by offset. | |||
2764 | /// Specifically, the difference between this and next entry's starting offset. | |||
2765 | /// </summary> | |||
2766 | public long OffsetBasedSize { | |||
2767 | get { return _offsetBasedSize; } | |||
2768 | set { _offsetBasedSize = value; } | |||
2769 | } | |||
2770 | | |||
2771 | public Stream GetSource() | |||
2772 | { | |||
2773 | Stream result = null; | |||
2774 | if (dataSource_ != null) { | |||
2775 | result = dataSource_.GetSource(); | |||
2776 | } | |||
2777 | | |||
2778 | return result; | |||
2779 | } | |||
2780 | | |||
2781 | #region Instance Fields | |||
2782 | ZipEntry entry_; | |||
2783 | ZipEntry outEntry_; | |||
2784 | UpdateCommand command_; | |||
2785 | IStaticDataSource dataSource_; | |||
2786 | string filename_; | |||
2787 | long sizePatchOffset_ = -1; | |||
2788 | long crcPatchOffset_ = -1; | |||
2789 | long _offsetBasedSize = -1; | |||
2790 | #endregion | |||
2791 | } | |||
2792 | | |||
2793 | #endregion | |||
2794 | #endregion | |||
2795 | | |||
2796 | #region Disposing | |||
2797 | | |||
2798 | #region IDisposable Members | |||
2799 | void IDisposable.Dispose() | |||
2800 | { | |||
2801 | Close(); | |||
2802 | } | |||
2803 | #endregion | |||
2804 | | |||
2805 | void DisposeInternal(bool disposing) | |||
2806 | { | |||
2807 | if (!isDisposed_) { | |||
2808 | isDisposed_ = true; | |||
2809 | entries_ = new ZipEntry[0]; | |||
2810 | | |||
2811 | if (IsStreamOwner && (baseStream_ != null)) { | |||
2812 | lock (baseStream_) { | |||
2813 | baseStream_.Close(); | |||
2814 | } | |||
2815 | } | |||
2816 | | |||
2817 | PostUpdateCleanup(); | |||
2818 | } | |||
2819 | } | |||
2820 | | |||
2821 | /// <summary> | |||
2822 | /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. | |||
2823 | /// </summary> | |||
2824 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
2825 | /// false to release only unmanaged resources.</param> | |||
2826 | protected virtual void Dispose(bool disposing) | |||
2827 | { | |||
2828 | DisposeInternal(disposing); | |||
2829 | } | |||
2830 | | |||
2831 | #endregion | |||
2832 | | |||
2833 | #region Internal routines | |||
2834 | #region Reading | |||
2835 | /// <summary> | |||
2836 | /// Read an unsigned short in little endian byte order. | |||
2837 | /// </summary> | |||
2838 | /// <returns>Returns the value read.</returns> | |||
2839 | /// <exception cref="EndOfStreamException"> | |||
2840 | /// The stream ends prematurely | |||
2841 | /// </exception> | |||
2842 | ushort ReadLEUshort() | |||
2843 | { | |||
2844 | int data1 = baseStream_.ReadByte(); | |||
2845 | | |||
2846 | if (data1 < 0) { | |||
2847 | throw new EndOfStreamException("End of stream"); | |||
2848 | } | |||
2849 | | |||
2850 | int data2 = baseStream_.ReadByte(); | |||
2851 | | |||
2852 | if (data2 < 0) { | |||
2853 | throw new EndOfStreamException("End of stream"); | |||
2854 | } | |||
2855 | | |||
2856 | | |||
2857 | return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); | |||
2858 | } | |||
2859 | | |||
2860 | /// <summary> | |||
2861 | /// Read a uint in little endian byte order. | |||
2862 | /// </summary> | |||
2863 | /// <returns>Returns the value read.</returns> | |||
2864 | /// <exception cref="IOException"> | |||
2865 | /// An i/o error occurs. | |||
2866 | /// </exception> | |||
2867 | /// <exception cref="System.IO.EndOfStreamException"> | |||
2868 | /// The file ends prematurely | |||
2869 | /// </exception> | |||
2870 | uint ReadLEUint() | |||
2871 | { | |||
2872 | return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); | |||
2873 | } | |||
2874 | | |||
2875 | ulong ReadLEUlong() | |||
2876 | { | |||
2877 | return ReadLEUint() | ((ulong)ReadLEUint() << 32); | |||
2878 | } | |||
2879 | | |||
2880 | #endregion | |||
2881 | // NOTE this returns the offset of the first byte after the signature. | |||
2882 | long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
2883 | { | |||
2884 | using (ZipHelperStream les = new ZipHelperStream(baseStream_)) { | |||
2885 | return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); | |||
2886 | } | |||
2887 | } | |||
2888 | | |||
2889 | /// <summary> | |||
2890 | /// Search for and read the central directory of a zip file filling the entries array. | |||
2891 | /// </summary> | |||
2892 | /// <exception cref="System.IO.IOException"> | |||
2893 | /// An i/o error occurs. | |||
2894 | /// </exception> | |||
2895 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
2896 | /// The central directory is malformed or cannot be found | |||
2897 | /// </exception> | |||
2898 | void ReadEntries() | |||
2899 | { | |||
2900 | // Search for the End Of Central Directory. When a zip comment is | |||
2901 | // present the directory will start earlier | |||
2902 | // | |||
2903 | // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. | |||
2904 | // This should be compatible with both SFX and ZIP files but has only been tested for Zip files | |||
2905 | // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then | |||
2906 | // this could be invalid. | |||
2907 | // Could also speed this up by reading memory in larger blocks. | |||
2908 | | |||
2909 | if (baseStream_.CanSeek == false) { | |||
2910 | throw new ZipException("ZipFile stream must be seekable"); | |||
2911 | } | |||
2912 | | |||
2913 | long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2914 | baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2915 | | |||
2916 | if (locatedEndOfCentralDir < 0) { | |||
2917 | throw new ZipException("Cannot find central directory"); | |||
2918 | } | |||
2919 | | |||
2920 | // Read end of central directory record | |||
2921 | ushort thisDiskNumber = ReadLEUshort(); | |||
2922 | ushort startCentralDirDisk = ReadLEUshort(); | |||
2923 | ulong entriesForThisDisk = ReadLEUshort(); | |||
2924 | ulong entriesForWholeCentralDir = ReadLEUshort(); | |||
2925 | ulong centralDirSize = ReadLEUint(); | |||
2926 | long offsetOfCentralDir = ReadLEUint(); | |||
2927 | uint commentSize = ReadLEUshort(); | |||
2928 | | |||
2929 | if (commentSize > 0) { | |||
2930 | byte[] comment = new byte[commentSize]; | |||
2931 | | |||
2932 | StreamUtils.ReadFully(baseStream_, comment); | |||
2933 | comment_ = ZipConstants.ConvertToString(comment); | |||
2934 | } else { | |||
2935 | comment_ = string.Empty; | |||
2936 | } | |||
2937 | | |||
2938 | bool isZip64 = false; | |||
2939 | | |||
2940 | // Check if zip64 header information is required. | |||
2941 | if ((thisDiskNumber == 0xffff) || | |||
2942 | (startCentralDirDisk == 0xffff) || | |||
2943 | (entriesForThisDisk == 0xffff) || | |||
2944 | (entriesForWholeCentralDir == 0xffff) || | |||
2945 | (centralDirSize == 0xffffffff) || | |||
2946 | (offsetOfCentralDir == 0xffffffff)) { | |||
2947 | isZip64 = true; | |||
2948 | | |||
2949 | long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, | |||
2950 | if (offset < 0) { | |||
2951 | throw new ZipException("Cannot find Zip64 locator"); | |||
2952 | } | |||
2953 | | |||
2954 | // number of the disk with the start of the zip64 end of central directory 4 bytes | |||
2955 | // relative offset of the zip64 end of central directory record 8 bytes | |||
2956 | // total number of disks 4 bytes | |||
2957 | ReadLEUint(); // startDisk64 is not currently used | |||
2958 | ulong offset64 = ReadLEUlong(); | |||
2959 | uint totalDisks = ReadLEUint(); | |||
2960 | | |||
2961 | baseStream_.Position = (long)offset64; | |||
2962 | long sig64 = ReadLEUint(); | |||
2963 | | |||
2964 | if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { | |||
2965 | throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); | |||
2966 | } | |||
2967 | | |||
2968 | // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. | |||
2969 | ulong recordSize = ReadLEUlong(); | |||
2970 | int versionMadeBy = ReadLEUshort(); | |||
2971 | int versionToExtract = ReadLEUshort(); | |||
2972 | uint thisDisk = ReadLEUint(); | |||
2973 | uint centralDirDisk = ReadLEUint(); | |||
2974 | entriesForThisDisk = ReadLEUlong(); | |||
2975 | entriesForWholeCentralDir = ReadLEUlong(); | |||
2976 | centralDirSize = ReadLEUlong(); | |||
2977 | offsetOfCentralDir = (long)ReadLEUlong(); | |||
2978 | | |||
2979 | // NOTE: zip64 extensible data sector (variable size) is ignored. | |||
2980 | } | |||
2981 | | |||
2982 | entries_ = new ZipEntry[entriesForThisDisk]; | |||
2983 | | |||
2984 | // SFX/embedded support, find the offset of the first entry vis the start of the stream | |||
2985 | // This applies to Zip files that are appended to the end of an SFX stub. | |||
2986 | // Or are appended as a resource to an executable. | |||
2987 | // Zip files created by some archivers have the offsets altered to reflect the true offsets | |||
2988 | // and so dont require any adjustment here... | |||
2989 | // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? | |||
2990 | if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) { | |||
2991 | offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); | |||
2992 | if (offsetOfFirstEntry <= 0) { | |||
2993 | throw new ZipException("Invalid embedded zip archive"); | |||
2994 | } | |||
2995 | } | |||
2996 | | |||
2997 | baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); | |||
2998 | | |||
2999 | for (ulong i = 0; i < entriesForThisDisk; i++) { | |||
3000 | if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { | |||
3001 | throw new ZipException("Wrong Central Directory signature"); | |||
3002 | } | |||
3003 | | |||
3004 | int versionMadeBy = ReadLEUshort(); | |||
3005 | int versionToExtract = ReadLEUshort(); | |||
3006 | int bitFlags = ReadLEUshort(); | |||
3007 | int method = ReadLEUshort(); | |||
3008 | uint dostime = ReadLEUint(); | |||
3009 | uint crc = ReadLEUint(); | |||
3010 | var csize = (long)ReadLEUint(); | |||
3011 | var size = (long)ReadLEUint(); | |||
3012 | int nameLen = ReadLEUshort(); | |||
3013 | int extraLen = ReadLEUshort(); | |||
3014 | int commentLen = ReadLEUshort(); | |||
3015 | | |||
3016 | int diskStartNo = ReadLEUshort(); // Not currently used | |||
3017 | int internalAttributes = ReadLEUshort(); // Not currently used | |||
3018 | | |||
3019 | uint externalAttributes = ReadLEUint(); | |||
3020 | long offset = ReadLEUint(); | |||
3021 | | |||
3022 | byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; | |||
3023 | | |||
3024 | StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); | |||
3025 | string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); | |||
3026 | | |||
3027 | var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); | |||
3028 | entry.Crc = crc & 0xffffffffL; | |||
3029 | entry.Size = size & 0xffffffffL; | |||
3030 | entry.CompressedSize = csize & 0xffffffffL; | |||
3031 | entry.Flags = bitFlags; | |||
3032 | entry.DosTime = (uint)dostime; | |||
3033 | entry.ZipFileIndex = (long)i; | |||
3034 | entry.Offset = offset; | |||
3035 | entry.ExternalFileAttributes = (int)externalAttributes; | |||
3036 | | |||
3037 | if ((bitFlags & 8) == 0) { | |||
3038 | entry.CryptoCheckValue = (byte)(crc >> 24); | |||
3039 | } else { | |||
3040 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | |||
3041 | } | |||
3042 | | |||
3043 | if (extraLen > 0) { | |||
3044 | byte[] extra = new byte[extraLen]; | |||
3045 | StreamUtils.ReadFully(baseStream_, extra); | |||
3046 | entry.ExtraData = extra; | |||
3047 | } | |||
3048 | | |||
3049 | entry.ProcessExtraData(false); | |||
3050 | | |||
3051 | if (commentLen > 0) { | |||
3052 | StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); | |||
3053 | entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); | |||
3054 | } | |||
3055 | | |||
3056 | entries_[i] = entry; | |||
3057 | } | |||
3058 | } | |||
3059 | | |||
3060 | /// <summary> | |||
3061 | /// Locate the data for a given entry. | |||
3062 | /// </summary> | |||
3063 | /// <returns> | |||
3064 | /// The start offset of the data. | |||
3065 | /// </returns> | |||
3066 | /// <exception cref="System.IO.EndOfStreamException"> | |||
3067 | /// The stream ends prematurely | |||
3068 | /// </exception> | |||
3069 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
3070 | /// The local header signature is invalid, the entry and central header file name lengths are different | |||
3071 | /// or the local and entry compression methods dont match | |||
3072 | /// </exception> | |||
3073 | long LocateEntry(ZipEntry entry) | |||
3074 | { | |||
3075 | return TestLocalHeader(entry, HeaderTest.Extract); | |||
3076 | } | |||
3077 | | |||
3078 | Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) | |||
3079 | { | |||
3080 | CryptoStream result = null; | |||
3081 | | |||
3082 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3083 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3084 | var classicManaged = new PkzipClassicManaged(); | |||
3085 | | |||
3086 | OnKeysRequired(entry.Name); | |||
3087 | if (HaveKeys == false) { | |||
3088 | throw new ZipException("No password available for encrypted stream"); | |||
3089 | } | |||
3090 | | |||
3091 | result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); | |||
3092 | CheckClassicPassword(result, entry); | |||
3093 | } else { | |||
3094 | if (entry.Version == ZipConstants.VERSION_AES) { | |||
3095 | // | |||
3096 | OnKeysRequired(entry.Name); | |||
3097 | if (HaveKeys == false) { | |||
3098 | throw new ZipException("No password available for AES encrypted stream"); | |||
3099 | } | |||
3100 | int saltLen = entry.AESSaltLen; | |||
3101 | byte[] saltBytes = new byte[saltLen]; | |||
3102 | int saltIn = baseStream.Read(saltBytes, 0, saltLen); | |||
3103 | if (saltIn != saltLen) | |||
3104 | throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); | |||
3105 | // | |||
3106 | byte[] pwdVerifyRead = new byte[2]; | |||
3107 | baseStream.Read(pwdVerifyRead, 0, 2); | |||
3108 | int blockSize = entry.AESKeySize / 8; // bits to bytes | |||
3109 | | |||
3110 | var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); | |||
3111 | byte[] pwdVerifyCalc = decryptor.PwdVerifier; | |||
3112 | if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) | |||
3113 | throw new ZipException("Invalid password for AES"); | |||
3114 | result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); | |||
3115 | } else { | |||
3116 | throw new ZipException("Decryption method not supported"); | |||
3117 | } | |||
3118 | } | |||
3119 | | |||
3120 | return result; | |||
3121 | } | |||
3122 | | |||
3123 | Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) | |||
3124 | { | |||
3125 | CryptoStream result = null; | |||
3126 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3127 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3128 | var classicManaged = new PkzipClassicManaged(); | |||
3129 | | |||
3130 | OnKeysRequired(entry.Name); | |||
3131 | if (HaveKeys == false) { | |||
3132 | throw new ZipException("No password available for encrypted stream"); | |||
3133 | } | |||
3134 | | |||
3135 | // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream | |||
3136 | // which doesnt do this. | |||
3137 | result = new CryptoStream(new UncompressedStream(baseStream), | |||
3138 | classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); | |||
3139 | | |||
3140 | if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { | |||
3141 | WriteEncryptionHeader(result, entry.DosTime << 16); | |||
3142 | } else { | |||
3143 | WriteEncryptionHeader(result, entry.Crc); | |||
3144 | } | |||
3145 | } | |||
3146 | return result; | |||
3147 | } | |||
3148 | | |||
3149 | static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) | |||
3150 | { | |||
3151 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3152 | StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); | |||
3153 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | |||
3154 | throw new ZipException("Invalid password"); | |||
3155 | } | |||
3156 | } | |||
3157 | | |||
3158 | static void WriteEncryptionHeader(Stream stream, long crcValue) | |||
3159 | { | |||
3160 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3161 | var rnd = new Random(); | |||
3162 | rnd.NextBytes(cryptBuffer); | |||
3163 | cryptBuffer[11] = (byte)(crcValue >> 24); | |||
3164 | stream.Write(cryptBuffer, 0, cryptBuffer.Length); | |||
3165 | } | |||
3166 | | |||
3167 | #endregion | |||
3168 | | |||
3169 | #region Instance Fields | |||
3170 | bool isDisposed_; | |||
3171 | string name_; | |||
3172 | string comment_; | |||
3173 | string rawPassword_; | |||
3174 | Stream baseStream_; | |||
3175 | bool isStreamOwner; | |||
3176 | long offsetOfFirstEntry; | |||
3177 | ZipEntry[] entries_; | |||
3178 | byte[] key; | |||
3179 | bool isNewArchive_; | |||
3180 | | |||
3181 | // Default is dynamic which is not backwards compatible and can cause problems | |||
3182 | // with XP's built in compression which cant read Zip64 archives. | |||
3183 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
3184 | // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. | |||
3185 | UseZip64 useZip64_ = UseZip64.Dynamic; | |||
3186 | | |||
3187 | #region Zip Update Instance Fields | |||
3188 | ArrayList updates_; | |||
3189 | long updateCount_; // Count is managed manually as updates_ can contain nulls! | |||
3190 | Hashtable updateIndex_; | |||
3191 | IArchiveStorage archiveStorage_; | |||
3192 | IDynamicDataSource updateDataSource_; | |||
3193 | bool contentsEdited_; | |||
3194 | int bufferSize_ = DefaultBufferSize; | |||
3195 | byte[] copyBuffer_; | |||
3196 | ZipString newComment_; | |||
3197 | bool commentEdited_; | |||
3198 | IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); | |||
3199 | #endregion | |||
3200 | #endregion | |||
3201 | | |||
3202 | #region Support Classes | |||
3203 | /// <summary> | |||
3204 | /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. | |||
3205 | /// </summary> | |||
3206 | class ZipString | |||
3207 | { | |||
3208 | #region Constructors | |||
3209 | /// <summary> | |||
3210 | /// Initialise a <see cref="ZipString"/> with a string. | |||
3211 | /// </summary> | |||
3212 | /// <param name="comment">The textual string form.</param> | |||
3213 | public ZipString(string comment) | |||
3214 | { | |||
3215 | comment_ = comment; | |||
3216 | isSourceString_ = true; | |||
3217 | } | |||
3218 | | |||
3219 | /// <summary> | |||
3220 | /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. | |||
3221 | /// </summary> | |||
3222 | /// <param name="rawString"></param> | |||
3223 | public ZipString(byte[] rawString) | |||
3224 | { | |||
3225 | rawComment_ = rawString; | |||
3226 | } | |||
3227 | #endregion | |||
3228 | | |||
3229 | /// <summary> | |||
3230 | /// Get a value indicating the original source of data for this instance. | |||
3231 | /// True if the source was a string; false if the source was binary data. | |||
3232 | /// </summary> | |||
3233 | public bool IsSourceString { | |||
3234 | get { return isSourceString_; } | |||
3235 | } | |||
3236 | | |||
3237 | /// <summary> | |||
3238 | /// Get the length of the comment when represented as raw bytes. | |||
3239 | /// </summary> | |||
3240 | public int RawLength { | |||
3241 | get { | |||
3242 | MakeBytesAvailable(); | |||
3243 | return rawComment_.Length; | |||
3244 | } | |||
3245 | } | |||
3246 | | |||
3247 | /// <summary> | |||
3248 | /// Get the comment in its 'raw' form as plain bytes. | |||
3249 | /// </summary> | |||
3250 | public byte[] RawComment { | |||
3251 | get { | |||
3252 | MakeBytesAvailable(); | |||
3253 | return (byte[])rawComment_.Clone(); | |||
3254 | } | |||
3255 | } | |||
3256 | | |||
3257 | /// <summary> | |||
3258 | /// Reset the comment to its initial state. | |||
3259 | /// </summary> | |||
3260 | public void Reset() | |||
3261 | { | |||
3262 | if (isSourceString_) { | |||
3263 | rawComment_ = null; | |||
3264 | } else { | |||
3265 | comment_ = null; | |||
3266 | } | |||
3267 | } | |||
3268 | | |||
3269 | void MakeTextAvailable() | |||
3270 | { | |||
3271 | if (comment_ == null) { | |||
3272 | comment_ = ZipConstants.ConvertToString(rawComment_); | |||
3273 | } | |||
3274 | } | |||
3275 | | |||
3276 | void MakeBytesAvailable() | |||
3277 | { | |||
3278 | if (rawComment_ == null) { | |||
3279 | rawComment_ = ZipConstants.ConvertToArray(comment_); | |||
3280 | } | |||
3281 | } | |||
3282 | | |||
3283 | /// <summary> | |||
3284 | /// Implicit conversion of comment to a string. | |||
3285 | /// </summary> | |||
3286 | /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> | |||
3287 | /// <returns>The textual equivalent for the input value.</returns> | |||
3288 | static public implicit operator string(ZipString zipString) | |||
3289 | { | |||
3290 | zipString.MakeTextAvailable(); | |||
3291 | return zipString.comment_; | |||
3292 | } | |||
3293 | | |||
3294 | #region Instance Fields | |||
3295 | string comment_; | |||
3296 | byte[] rawComment_; | |||
3297 | bool isSourceString_; | |||
3298 | #endregion | |||
3299 | } | |||
3300 | | |||
3301 | /// <summary> | |||
3302 | /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> | |||
3303 | /// </summary> | |||
3304 | class ZipEntryEnumerator : IEnumerator | |||
3305 | { | |||
3306 | #region Constructors | |||
3307 | public ZipEntryEnumerator(ZipEntry[] entries) | |||
3308 | { | |||
3309 | array = entries; | |||
3310 | } | |||
3311 | | |||
3312 | #endregion | |||
3313 | #region IEnumerator Members | |||
3314 | public object Current { | |||
3315 | get { | |||
3316 | return array[index]; | |||
3317 | } | |||
3318 | } | |||
3319 | | |||
3320 | public void Reset() | |||
3321 | { | |||
3322 | index = -1; | |||
3323 | } | |||
3324 | | |||
3325 | public bool MoveNext() | |||
3326 | { | |||
3327 | return (++index < array.Length); | |||
3328 | } | |||
3329 | #endregion | |||
3330 | #region Instance Fields | |||
3331 | ZipEntry[] array; | |||
3332 | int index = -1; | |||
3333 | #endregion | |||
3334 | } | |||
3335 | | |||
3336 | /// <summary> | |||
3337 | /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data | |||
3338 | /// to and flush, but cannot read, seek or do anything else to. | |||
3339 | /// </summary> | |||
3340 | class UncompressedStream : Stream | |||
3341 | { | |||
3342 | #region Constructors | |||
3343 | public UncompressedStream(Stream baseStream) | |||
3344 | { | |||
3345 | baseStream_ = baseStream; | |||
3346 | } | |||
3347 | | |||
3348 | #endregion | |||
3349 | | |||
3350 | /// <summary> | |||
3351 | /// Close this stream instance. | |||
3352 | /// </summary> | |||
3353 | public override void Close() | |||
3354 | { | |||
3355 | // Do nothing | |||
3356 | } | |||
3357 | | |||
3358 | /// <summary> | |||
3359 | /// Gets a value indicating whether the current stream supports reading. | |||
3360 | /// </summary> | |||
3361 | public override bool CanRead { | |||
3362 | get { | |||
3363 | return false; | |||
3364 | } | |||
3365 | } | |||
3366 | | |||
3367 | /// <summary> | |||
3368 | /// Write any buffered data to underlying storage. | |||
3369 | /// </summary> | |||
3370 | public override void Flush() | |||
3371 | { | |||
3372 | baseStream_.Flush(); | |||
3373 | } | |||
3374 | | |||
3375 | /// <summary> | |||
3376 | /// Gets a value indicating whether the current stream supports writing. | |||
3377 | /// </summary> | |||
3378 | public override bool CanWrite { | |||
3379 | get { | |||
3380 | return baseStream_.CanWrite; | |||
3381 | } | |||
3382 | } | |||
3383 | | |||
3384 | /// <summary> | |||
3385 | /// Gets a value indicating whether the current stream supports seeking. | |||
3386 | /// </summary> | |||
3387 | public override bool CanSeek { | |||
3388 | get { | |||
3389 | return false; | |||
3390 | } | |||
3391 | } | |||
3392 | | |||
3393 | /// <summary> | |||
3394 | /// Get the length in bytes of the stream. | |||
3395 | /// </summary> | |||
3396 | public override long Length { | |||
3397 | get { | |||
3398 | return 0; | |||
3399 | } | |||
3400 | } | |||
3401 | | |||
3402 | /// <summary> | |||
3403 | /// Gets or sets the position within the current stream. | |||
3404 | /// </summary> | |||
3405 | public override long Position { | |||
3406 | get { | |||
3407 | return baseStream_.Position; | |||
3408 | } | |||
3409 | set { | |||
3410 | throw new NotImplementedException(); | |||
3411 | } | |||
3412 | } | |||
3413 | | |||
3414 | /// <summary> | |||
3415 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3416 | /// </summary> | |||
3417 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3418 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3419 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3420 | /// <returns> | |||
3421 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3422 | /// </returns> | |||
3423 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3424 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3425 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3426 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3427 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3428 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3429 | public override int Read(byte[] buffer, int offset, int count) | |||
3430 | { | |||
3431 | return 0; | |||
3432 | } | |||
3433 | | |||
3434 | /// <summary> | |||
3435 | /// Sets the position within the current stream. | |||
3436 | /// </summary> | |||
3437 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3438 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3439 | /// <returns> | |||
3440 | /// The new position within the current stream. | |||
3441 | /// </returns> | |||
3442 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3443 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3444 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3445 | public override long Seek(long offset, SeekOrigin origin) | |||
3446 | { | |||
3447 | return 0; | |||
3448 | } | |||
3449 | | |||
3450 | /// <summary> | |||
3451 | /// Sets the length of the current stream. | |||
3452 | /// </summary> | |||
3453 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3454 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3455 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3456 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3457 | public override void SetLength(long value) | |||
3458 | { | |||
3459 | } | |||
3460 | | |||
3461 | /// <summary> | |||
3462 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3463 | /// </summary> | |||
3464 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3465 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3466 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3467 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3468 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3469 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3470 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3471 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3472 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3473 | public override void Write(byte[] buffer, int offset, int count) | |||
3474 | { | |||
3475 | baseStream_.Write(buffer, offset, count); | |||
3476 | } | |||
3477 | | |||
3478 | readonly | |||
3479 | | |||
3480 | #region Instance Fields | |||
3481 | Stream baseStream_; | |||
3482 | #endregion | |||
3483 | } | |||
3484 | | |||
3485 | /// <summary> | |||
3486 | /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> | |||
3487 | /// whose data is only a part or subsection of a file. | |||
3488 | /// </summary> | |||
3489 | class PartialInputStream : Stream | |||
3490 | { | |||
3491 | #region Constructors | |||
3492 | /// <summary> | |||
3493 | /// Initialise a new instance of the <see cref="PartialInputStream"/> class. | |||
3494 | /// </summary> | |||
3495 | /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param> | |||
3496 | /// <param name="start">The start of the partial data.</param> | |||
3497 | /// <param name="length">The length of the partial data.</param> | |||
3498 | public PartialInputStream(ZipFile zipFile, long start, long length) | |||
3499 | { | |||
3500 | start_ = start; | |||
3501 | length_ = length; | |||
3502 | | |||
3503 | // Although this is the only time the zipfile is used | |||
3504 | // keeping a reference here prevents premature closure of | |||
3505 | // this zip file and thus the baseStream_. | |||
3506 | | |||
3507 | // Code like this will cause apparently random failures depending | |||
3508 | // on the size of the files and when garbage is collected. | |||
3509 | // | |||
3510 | // ZipFile z = new ZipFile (stream); | |||
3511 | // Stream reader = z.GetInputStream(0); | |||
3512 | // uses reader here.... | |||
3513 | zipFile_ = zipFile; | |||
3514 | baseStream_ = zipFile_.baseStream_; | |||
3515 | readPos_ = start; | |||
3516 | end_ = start + length; | |||
3517 | } | |||
3518 | #endregion | |||
3519 | | |||
3520 | /// <summary> | |||
3521 | /// Read a byte from this stream. | |||
3522 | /// </summary> | |||
3523 | /// <returns>Returns the byte read or -1 on end of stream.</returns> | |||
3524 | public override int ReadByte() | |||
3525 | { | |||
3526 | if (readPos_ >= end_) { | |||
3527 | // -1 is the correct value at end of stream. | |||
3528 | return -1; | |||
3529 | } | |||
3530 | | |||
3531 | lock (baseStream_) { | |||
3532 | baseStream_.Seek(readPos_++, SeekOrigin.Begin); | |||
3533 | return baseStream_.ReadByte(); | |||
3534 | } | |||
3535 | } | |||
3536 | | |||
3537 | /// <summary> | |||
3538 | /// Close this <see cref="PartialInputStream">partial input stream</see>. | |||
3539 | /// </summary> | |||
3540 | /// <remarks> | |||
3541 | /// The underlying stream is not closed. Close the parent ZipFile class to do that. | |||
3542 | /// </remarks> | |||
3543 | public override void Close() | |||
3544 | { | |||
3545 | // Do nothing at all! | |||
3546 | } | |||
3547 | | |||
3548 | /// <summary> | |||
3549 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3550 | /// </summary> | |||
3551 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3552 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3553 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3554 | /// <returns> | |||
3555 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3556 | /// </returns> | |||
3557 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3558 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3559 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3560 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3561 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3562 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3563 | public override int Read(byte[] buffer, int offset, int count) | |||
3564 | { | |||
3565 | lock (baseStream_) { | |||
3566 | if (count > end_ - readPos_) { | |||
3567 | count = (int)(end_ - readPos_); | |||
3568 | if (count == 0) { | |||
3569 | return 0; | |||
3570 | } | |||
3571 | } | |||
3572 | // Protect against Stream implementations that throw away their buffer on every Seek | |||
3573 | // (for example, Mono FileStream) | |||
3574 | if (baseStream_.Position != readPos_) { | |||
3575 | baseStream_.Seek(readPos_, SeekOrigin.Begin); | |||
3576 | } | |||
3577 | int readCount = baseStream_.Read(buffer, offset, count); | |||
3578 | if (readCount > 0) { | |||
3579 | readPos_ += readCount; | |||
3580 | } | |||
3581 | return readCount; | |||
3582 | } | |||
3583 | } | |||
3584 | | |||
3585 | /// <summary> | |||
3586 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3587 | /// </summary> | |||
3588 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3589 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3590 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3591 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3592 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3593 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3594 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3595 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3596 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3597 | public override void Write(byte[] buffer, int offset, int count) | |||
3598 | { | |||
3599 | throw new NotSupportedException(); | |||
3600 | } | |||
3601 | | |||
3602 | /// <summary> | |||
3603 | /// When overridden in a derived class, sets the length of the current stream. | |||
3604 | /// </summary> | |||
3605 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3606 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3607 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3608 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3609 | public override void SetLength(long value) | |||
3610 | { | |||
3611 | throw new NotSupportedException(); | |||
3612 | } | |||
3613 | | |||
3614 | /// <summary> | |||
3615 | /// When overridden in a derived class, sets the position within the current stream. | |||
3616 | /// </summary> | |||
3617 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3618 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3619 | /// <returns> | |||
3620 | /// The new position within the current stream. | |||
3621 | /// </returns> | |||
3622 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3623 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3624 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3625 | public override long Seek(long offset, SeekOrigin origin) | |||
3626 | { | |||
3627 | long newPos = readPos_; | |||
3628 | | |||
3629 | switch (origin) { | |||
3630 | case SeekOrigin.Begin: | |||
3631 | newPos = start_ + offset; | |||
3632 | break; | |||
3633 | | |||
3634 | case SeekOrigin.Current: | |||
3635 | newPos = readPos_ + offset; | |||
3636 | break; | |||
3637 | | |||
3638 | case SeekOrigin.End: | |||
3639 | newPos = end_ + offset; | |||
3640 | break; | |||
3641 | } | |||
3642 | | |||
3643 | if (newPos < start_) { | |||
3644 | throw new ArgumentException("Negative position is invalid"); | |||
3645 | } | |||
3646 | | |||
3647 | if (newPos >= end_) { | |||
3648 | throw new IOException("Cannot seek past end"); | |||
3649 | } | |||
3650 | readPos_ = newPos; | |||
3651 | return readPos_; | |||
3652 | } | |||
3653 | | |||
3654 | /// <summary> | |||
3655 | /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | |||
3656 | /// </summary> | |||
3657 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3658 | public override void Flush() | |||
3659 | { | |||
3660 | // Nothing to do. | |||
3661 | } | |||
3662 | | |||
3663 | /// <summary> | |||
3664 | /// Gets or sets the position within the current stream. | |||
3665 | /// </summary> | |||
3666 | /// <value></value> | |||
3667 | /// <returns>The current position within the stream.</returns> | |||
3668 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3669 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> | |||
3670 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3671 | public override long Position { | |||
3672 | get { return readPos_ - start_; } | |||
3673 | set { | |||
3674 | long newPos = start_ + value; | |||
3675 | | |||
3676 | if (newPos < start_) { | |||
3677 | throw new ArgumentException("Negative position is invalid"); | |||
3678 | } | |||
3679 | | |||
3680 | if (newPos >= end_) { | |||
3681 | throw new InvalidOperationException("Cannot seek past end"); | |||
3682 | } | |||
3683 | readPos_ = newPos; | |||
3684 | } | |||
3685 | } | |||
3686 | | |||
3687 | /// <summary> | |||
3688 | /// Gets the length in bytes of the stream. | |||
3689 | /// </summary> | |||
3690 | /// <value></value> | |||
3691 | /// <returns>A long value representing the length of the stream in bytes.</returns> | |||
3692 | /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </excep | |||
3693 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3694 | public override long Length { | |||
3695 | get { return length_; } | |||
3696 | } | |||
3697 | | |||
3698 | /// <summary> | |||
3699 | /// Gets a value indicating whether the current stream supports writing. | |||
3700 | /// </summary> | |||
3701 | /// <value>false</value> | |||
3702 | /// <returns>true if the stream supports writing; otherwise, false.</returns> | |||
3703 | public override bool CanWrite { | |||
3704 | get { return false; } | |||
3705 | } | |||
3706 | | |||
3707 | /// <summary> | |||
3708 | /// Gets a value indicating whether the current stream supports seeking. | |||
3709 | /// </summary> | |||
3710 | /// <value>true</value> | |||
3711 | /// <returns>true if the stream supports seeking; otherwise, false.</returns> | |||
3712 | public override bool CanSeek { | |||
3713 | get { return true; } | |||
3714 | } | |||
3715 | | |||
3716 | /// <summary> | |||
3717 | /// Gets a value indicating whether the current stream supports reading. | |||
3718 | /// </summary> | |||
3719 | /// <value>true.</value> | |||
3720 | /// <returns>true if the stream supports reading; otherwise, false.</returns> | |||
3721 | public override bool CanRead { | |||
3722 | get { return true; } | |||
3723 | } | |||
3724 | | |||
3725 | /// <summary> | |||
3726 | /// Gets a value that determines whether the current stream can time out. | |||
3727 | /// </summary> | |||
3728 | /// <value></value> | |||
3729 | /// <returns>A value that determines whether the current stream can time out.</returns> | |||
3730 | public override bool CanTimeout { | |||
3731 | get { return baseStream_.CanTimeout; } | |||
3732 | } | |||
3733 | #region Instance Fields | |||
3734 | ZipFile zipFile_; | |||
3735 | Stream baseStream_; | |||
3736 | long start_; | |||
3737 | long length_; | |||
3738 | long readPos_; | |||
3739 | long end_; | |||
3740 | #endregion | |||
3741 | } | |||
3742 | #endregion | |||
3743 | } | |||
3744 | | |||
3745 | #endregion | |||
3746 | | |||
3747 | #region DataSources | |||
3748 | /// <summary> | |||
3749 | /// Provides a static way to obtain a source of data for an entry. | |||
3750 | /// </summary> | |||
3751 | public interface IStaticDataSource | |||
3752 | { | |||
3753 | /// <summary> | |||
3754 | /// Get a source of data by creating a new stream. | |||
3755 | /// </summary> | |||
3756 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3757 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3758 | Stream GetSource(); | |||
3759 | } | |||
3760 | | |||
3761 | /// <summary> | |||
3762 | /// Represents a source of data that can dynamically provide | |||
3763 | /// multiple <see cref="Stream">data sources</see> based on the parameters passed. | |||
3764 | /// </summary> | |||
3765 | public interface IDynamicDataSource | |||
3766 | { | |||
3767 | /// <summary> | |||
3768 | /// Get a data source. | |||
3769 | /// </summary> | |||
3770 | /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> | |||
3771 | /// <param name="name">The name for data if known.</param> | |||
3772 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3773 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3774 | Stream GetSource(ZipEntry entry, string name); | |||
3775 | } | |||
3776 | | |||
3777 | /// <summary> | |||
3778 | /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. | |||
3779 | /// </summary> | |||
3780 | public class StaticDiskDataSource : IStaticDataSource | |||
3781 | { | |||
3782 | /// <summary> | |||
3783 | /// Initialise a new instnace of <see cref="StaticDiskDataSource"/> | |||
3784 | /// </summary> | |||
3785 | /// <param name="fileName">The name of the file to obtain data from.</param> | |||
3786 | public StaticDiskDataSource(string fileName) | |||
3787 | { | |||
3788 | fileName_ = fileName; | |||
3789 | } | |||
3790 | | |||
3791 | #region IDataSource Members | |||
3792 | | |||
3793 | /// <summary> | |||
3794 | /// Get a <see cref="Stream"/> providing data. | |||
3795 | /// </summary> | |||
3796 | /// <returns>Returns a <see cref="Stream"/> provising data.</returns> | |||
3797 | public Stream GetSource() | |||
3798 | { | |||
3799 | return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3800 | } | |||
3801 | | |||
3802 | readonly | |||
3803 | | |||
3804 | #endregion | |||
3805 | #region Instance Fields | |||
3806 | string fileName_; | |||
3807 | #endregion | |||
3808 | } | |||
3809 | | |||
3810 | | |||
3811 | /// <summary> | |||
3812 | /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. | |||
3813 | /// </summary> | |||
3814 | public class DynamicDiskDataSource : IDynamicDataSource | |||
3815 | { | |||
3816 | | |||
3817 | #region IDataSource Members | |||
3818 | /// <summary> | |||
3819 | /// Get a <see cref="Stream"/> providing data for an entry. | |||
3820 | /// </summary> | |||
3821 | /// <param name="entry">The entry to provide data for.</param> | |||
3822 | /// <param name="name">The file name for data if known.</param> | |||
3823 | /// <returns>Returns a stream providing data; or null if not available</returns> | |||
3824 | public Stream GetSource(ZipEntry entry, string name) | |||
3825 | { | |||
3826 | Stream result = null; | |||
3827 | | |||
3828 | if (name != null) { | |||
3829 | result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3830 | } | |||
3831 | | |||
3832 | return result; | |||
3833 | } | |||
3834 | | |||
3835 | #endregion | |||
3836 | } | |||
3837 | | |||
3838 | #endregion | |||
3839 | | |||
3840 | #region Archive Storage | |||
3841 | /// <summary> | |||
3842 | /// Defines facilities for data storage when updating Zip Archives. | |||
3843 | /// </summary> | |||
3844 | public interface IArchiveStorage | |||
3845 | { | |||
3846 | /// <summary> | |||
3847 | /// Get the <see cref="FileUpdateMode"/> to apply during updates. | |||
3848 | /// </summary> | |||
3849 | FileUpdateMode UpdateMode { get; } | |||
3850 | | |||
3851 | /// <summary> | |||
3852 | /// Get an empty <see cref="Stream"/> that can be used for temporary output. | |||
3853 | /// </summary> | |||
3854 | /// <returns>Returns a temporary output <see cref="Stream"/></returns> | |||
3855 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3856 | Stream GetTemporaryOutput(); | |||
3857 | | |||
3858 | /// <summary> | |||
3859 | /// Convert a temporary output stream to a final stream. | |||
3860 | /// </summary> | |||
3861 | /// <returns>The resulting final <see cref="Stream"/></returns> | |||
3862 | /// <seealso cref="GetTemporaryOutput"/> | |||
3863 | Stream ConvertTemporaryToFinal(); | |||
3864 | | |||
3865 | /// <summary> | |||
3866 | /// Make a temporary copy of the original stream. | |||
3867 | /// </summary> | |||
3868 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
3869 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3870 | Stream MakeTemporaryCopy(Stream stream); | |||
3871 | | |||
3872 | /// <summary> | |||
3873 | /// Return a stream suitable for performing direct updates on the original source. | |||
3874 | /// </summary> | |||
3875 | /// <param name="stream">The current stream.</param> | |||
3876 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3877 | /// <remarks>This may be the current stream passed.</remarks> | |||
3878 | Stream OpenForDirectUpdate(Stream stream); | |||
3879 | | |||
3880 | /// <summary> | |||
3881 | /// Dispose of this instance. | |||
3882 | /// </summary> | |||
3883 | void Dispose(); | |||
3884 | } | |||
3885 | | |||
3886 | /// <summary> | |||
3887 | /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. | |||
3888 | /// </summary> | |||
3889 | abstract public class BaseArchiveStorage : IArchiveStorage | |||
3890 | { | |||
3891 | #region Constructors | |||
3892 | /// <summary> | |||
3893 | /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. | |||
3894 | /// </summary> | |||
3895 | /// <param name="updateMode">The update mode.</param> | |||
3896 | protected BaseArchiveStorage(FileUpdateMode updateMode) | |||
3897 | { | |||
3898 | updateMode_ = updateMode; | |||
3899 | } | |||
3900 | #endregion | |||
3901 | | |||
3902 | #region IArchiveStorage Members | |||
3903 | | |||
3904 | /// <summary> | |||
3905 | /// Gets a temporary output <see cref="Stream"/> | |||
3906 | /// </summary> | |||
3907 | /// <returns>Returns the temporary output stream.</returns> | |||
3908 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3909 | public abstract Stream GetTemporaryOutput(); | |||
3910 | | |||
3911 | /// <summary> | |||
3912 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
3913 | /// </summary> | |||
3914 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
3915 | /// the final storage for the archive.</returns> | |||
3916 | /// <seealso cref="GetTemporaryOutput"/> | |||
3917 | public abstract Stream ConvertTemporaryToFinal(); | |||
3918 | | |||
3919 | /// <summary> | |||
3920 | /// Make a temporary copy of a <see cref="Stream"/>. | |||
3921 | /// </summary> | |||
3922 | /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> | |||
3923 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3924 | public abstract Stream MakeTemporaryCopy(Stream stream); | |||
3925 | | |||
3926 | /// <summary> | |||
3927 | /// Return a stream suitable for performing direct updates on the original source. | |||
3928 | /// </summary> | |||
3929 | /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> | |||
3930 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3931 | public abstract Stream OpenForDirectUpdate(Stream stream); | |||
3932 | | |||
3933 | /// <summary> | |||
3934 | /// Disposes this instance. | |||
3935 | /// </summary> | |||
3936 | public abstract void Dispose(); | |||
3937 | | |||
3938 | /// <summary> | |||
3939 | /// Gets the update mode applicable. | |||
3940 | /// </summary> | |||
3941 | /// <value>The update mode.</value> | |||
3942 | public FileUpdateMode UpdateMode { | |||
3943 | get { | |||
3944 | return updateMode_; | |||
3945 | } | |||
3946 | } | |||
3947 | | |||
3948 | #endregion | |||
3949 | | |||
3950 | #region Instance Fields | |||
3951 | FileUpdateMode updateMode_; | |||
3952 | #endregion | |||
3953 | } | |||
3954 | | |||
3955 | /// <summary> | |||
3956 | /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. | |||
3957 | /// </summary> | |||
3958 | public class DiskArchiveStorage : BaseArchiveStorage | |||
3959 | { | |||
3960 | #region Constructors | |||
3961 | /// <summary> | |||
3962 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3963 | /// </summary> | |||
3964 | /// <param name="file">The file.</param> | |||
3965 | /// <param name="updateMode">The update mode.</param> | |||
3966 | public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) | |||
3967 | : base(updateMode) | |||
3968 | { | |||
3969 | if (file.Name == null) { | |||
3970 | throw new ZipException("Cant handle non file archives"); | |||
3971 | } | |||
3972 | | |||
3973 | fileName_ = file.Name; | |||
3974 | } | |||
3975 | | |||
3976 | /// <summary> | |||
3977 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3978 | /// </summary> | |||
3979 | /// <param name="file">The file.</param> | |||
3980 | public DiskArchiveStorage(ZipFile file) | |||
3981 | : this(file, FileUpdateMode.Safe) | |||
3982 | { | |||
3983 | } | |||
3984 | #endregion | |||
3985 | | |||
3986 | #region IArchiveStorage Members | |||
3987 | | |||
3988 | /// <summary> | |||
3989 | /// Gets a temporary output <see cref="Stream"/> for performing updates on. | |||
3990 | /// </summary> | |||
3991 | /// <returns>Returns the temporary output stream.</returns> | |||
3992 | public override Stream GetTemporaryOutput() | |||
3993 | { | |||
3994 | if (temporaryName_ != null) { | |||
3995 | temporaryName_ = GetTempFileName(temporaryName_, true); | |||
3996 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
3997 | } else { | |||
3998 | // Determine where to place files based on internal strategy. | |||
3999 | // Currently this is always done in system temp directory. | |||
4000 | temporaryName_ = Path.GetTempFileName(); | |||
4001 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
4002 | } | |||
4003 | | |||
4004 | return temporaryStream_; | |||
4005 | } | |||
4006 | | |||
4007 | /// <summary> | |||
4008 | /// Converts a temporary <see cref="Stream"/> to its final form. | |||
4009 | /// </summary> | |||
4010 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4011 | /// the final storage for the archive.</returns> | |||
4012 | public override Stream ConvertTemporaryToFinal() | |||
4013 | { | |||
4014 | if (temporaryStream_ == null) { | |||
4015 | throw new ZipException("No temporary stream has been created"); | |||
4016 | } | |||
4017 | | |||
4018 | Stream result = null; | |||
4019 | | |||
4020 | string moveTempName = GetTempFileName(fileName_, false); | |||
4021 | bool newFileCreated = false; | |||
4022 | | |||
4023 | try { | |||
4024 | temporaryStream_.Close(); | |||
4025 | File.Move(fileName_, moveTempName); | |||
4026 | File.Move(temporaryName_, fileName_); | |||
4027 | newFileCreated = true; | |||
4028 | File.Delete(moveTempName); | |||
4029 | | |||
4030 | result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
4031 | } catch (Exception) { | |||
4032 | result = null; | |||
4033 | | |||
4034 | // Try to roll back changes... | |||
4035 | if (!newFileCreated) { | |||
4036 | File.Move(moveTempName, fileName_); | |||
4037 | File.Delete(temporaryName_); | |||
4038 | } | |||
4039 | | |||
4040 | throw; | |||
4041 | } | |||
4042 | | |||
4043 | return result; | |||
4044 | } | |||
4045 | | |||
4046 | /// <summary> | |||
4047 | /// Make a temporary copy of a stream. | |||
4048 | /// </summary> | |||
4049 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4050 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4051 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4052 | { | |||
4053 | stream.Close(); | |||
4054 | | |||
4055 | temporaryName_ = GetTempFileName(fileName_, true); | |||
4056 | File.Copy(fileName_, temporaryName_, true); | |||
4057 | | |||
4058 | temporaryStream_ = new FileStream(temporaryName_, | |||
4059 | FileMode.Open, | |||
4060 | FileAccess.ReadWrite); | |||
4061 | return temporaryStream_; | |||
4062 | } | |||
4063 | | |||
4064 | /// <summary> | |||
4065 | /// Return a stream suitable for performing direct updates on the original source. | |||
4066 | /// </summary> | |||
4067 | /// <param name="stream">The current stream.</param> | |||
4068 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4069 | /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks> | |||
4070 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4071 | { | |||
4072 | Stream result; | |||
4073 | if ((stream == null) || !stream.CanWrite) { | |||
4074 | if (stream != null) { | |||
4075 | stream.Close(); | |||
4076 | } | |||
4077 | | |||
4078 | result = new FileStream(fileName_, | |||
4079 | FileMode.Open, | |||
4080 | FileAccess.ReadWrite); | |||
4081 | } else { | |||
4082 | result = stream; | |||
4083 | } | |||
4084 | | |||
4085 | return result; | |||
4086 | } | |||
4087 | | |||
4088 | /// <summary> | |||
4089 | /// Disposes this instance. | |||
4090 | /// </summary> | |||
4091 | public override void Dispose() | |||
4092 | { | |||
4093 | if (temporaryStream_ != null) { | |||
4094 | temporaryStream_.Close(); | |||
4095 | } | |||
4096 | } | |||
4097 | | |||
4098 | #endregion | |||
4099 | | |||
4100 | #region Internal routines | |||
4101 | static string GetTempFileName(string original, bool makeTempFile) | |||
4102 | { | |||
4103 | string result = null; | |||
4104 | | |||
4105 | if (original == null) { | |||
4106 | result = Path.GetTempFileName(); | |||
4107 | } else { | |||
4108 | int counter = 0; | |||
4109 | int suffixSeed = DateTime.Now.Second; | |||
4110 | | |||
4111 | while (result == null) { | |||
4112 | counter += 1; | |||
4113 | string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); | |||
4114 | if (!File.Exists(newName)) { | |||
4115 | if (makeTempFile) { | |||
4116 | try { | |||
4117 | // Try and create the file. | |||
4118 | using (FileStream stream = File.Create(newName)) { | |||
4119 | } | |||
4120 | result = newName; | |||
4121 | } catch { | |||
4122 | suffixSeed = DateTime.Now.Second; | |||
4123 | } | |||
4124 | } else { | |||
4125 | result = newName; | |||
4126 | } | |||
4127 | } | |||
4128 | } | |||
4129 | } | |||
4130 | return result; | |||
4131 | } | |||
4132 | #endregion | |||
4133 | | |||
4134 | #region Instance Fields | |||
4135 | Stream temporaryStream_; | |||
4136 | string fileName_; | |||
4137 | string temporaryName_; | |||
4138 | #endregion | |||
4139 | } | |||
4140 | | |||
4141 | /// <summary> | |||
4142 | /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. | |||
4143 | /// </summary> | |||
4144 | public class MemoryArchiveStorage : BaseArchiveStorage | |||
4145 | { | |||
4146 | #region Constructors | |||
4147 | /// <summary> | |||
4148 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4149 | /// </summary> | |||
4150 | public MemoryArchiveStorage() | |||
| 36 | 4151 | : base(FileUpdateMode.Direct) | ||
4152 | { | |||
| 36 | 4153 | } | ||
4154 | | |||
4155 | /// <summary> | |||
4156 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4157 | /// </summary> | |||
4158 | /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> | |||
4159 | /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> | |||
4160 | public MemoryArchiveStorage(FileUpdateMode updateMode) | |||
| 1 | 4161 | : base(updateMode) | ||
4162 | { | |||
| 1 | 4163 | } | ||
4164 | | |||
4165 | #endregion | |||
4166 | | |||
4167 | #region Properties | |||
4168 | /// <summary> | |||
4169 | /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. | |||
4170 | /// </summary> | |||
4171 | public MemoryStream FinalStream { | |||
| 0 | 4172 | get { return finalStream_; } | ||
4173 | } | |||
4174 | | |||
4175 | #endregion | |||
4176 | | |||
4177 | #region IArchiveStorage Members | |||
4178 | | |||
4179 | /// <summary> | |||
4180 | /// Gets the temporary output <see cref="Stream"/> | |||
4181 | /// </summary> | |||
4182 | /// <returns>Returns the temporary output stream.</returns> | |||
4183 | public override Stream GetTemporaryOutput() | |||
4184 | { | |||
| 1 | 4185 | temporaryStream_ = new MemoryStream(); | ||
| 1 | 4186 | return temporaryStream_; | ||
4187 | } | |||
4188 | | |||
4189 | /// <summary> | |||
4190 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
4191 | /// </summary> | |||
4192 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4193 | /// the final storage for the archive.</returns> | |||
4194 | public override Stream ConvertTemporaryToFinal() | |||
4195 | { | |||
| 1 | 4196 | if (temporaryStream_ == null) { | ||
| 0 | 4197 | throw new ZipException("No temporary stream has been created"); | ||
4198 | } | |||
4199 | | |||
| 1 | 4200 | finalStream_ = new MemoryStream(temporaryStream_.ToArray()); | ||
| 1 | 4201 | return finalStream_; | ||
4202 | } | |||
4203 | | |||
4204 | /// <summary> | |||
4205 | /// Make a temporary copy of the original stream. | |||
4206 | /// </summary> | |||
4207 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4208 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4209 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4210 | { | |||
| 0 | 4211 | temporaryStream_ = new MemoryStream(); | ||
| 0 | 4212 | stream.Position = 0; | ||
| 0 | 4213 | StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); | ||
| 0 | 4214 | return temporaryStream_; | ||
4215 | } | |||
4216 | | |||
4217 | /// <summary> | |||
4218 | /// Return a stream suitable for performing direct updates on the original source. | |||
4219 | /// </summary> | |||
4220 | /// <param name="stream">The original source stream</param> | |||
4221 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4222 | /// <remarks>If the <paramref name="stream"/> passed is not null this is used; | |||
4223 | /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> | |||
4224 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4225 | { | |||
4226 | Stream result; | |||
| 1 | 4227 | if ((stream == null) || !stream.CanWrite) { | ||
4228 | | |||
| 0 | 4229 | result = new MemoryStream(); | ||
4230 | | |||
| 0 | 4231 | if (stream != null) { | ||
| 0 | 4232 | stream.Position = 0; | ||
| 0 | 4233 | StreamUtils.Copy(stream, result, new byte[4096]); | ||
4234 | | |||
| 0 | 4235 | stream.Close(); | ||
4236 | } | |||
| 0 | 4237 | } else { | ||
| 1 | 4238 | result = stream; | ||
4239 | } | |||
4240 | | |||
| 1 | 4241 | return result; | ||
4242 | } | |||
4243 | | |||
4244 | /// <summary> | |||
4245 | /// Disposes this instance. | |||
4246 | /// </summary> | |||
4247 | public override void Dispose() | |||
4248 | { | |||
| 37 | 4249 | if (temporaryStream_ != null) { | ||
| 1 | 4250 | temporaryStream_.Close(); | ||
4251 | } | |||
| 37 | 4252 | } | ||
4253 | | |||
4254 | #endregion | |||
4255 | | |||
4256 | #region Instance Fields | |||
4257 | MemoryStream temporaryStream_; | |||
4258 | MemoryStream finalStream_; | |||
4259 | #endregion | |||
4260 | } | |||
4261 | | |||
4262 | #endregion | |||
4263 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.NTTaggedData |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipExtraData.cs |
| Covered lines: | 45 |
| Uncovered lines: | 9 |
| Coverable lines: | 54 |
| Total lines: | 896 |
| Line coverage: | 83.3% |
| Branch coverage: | 50% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| SetData(...) | 6 | 88.89 | 57.14 |
| GetData() | 3 | 100 | 100 |
| IsValidValue(...) | 1 | 57.14 | 100 |
| .ctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip | |||
5 | { | |||
6 | // TODO: Sort out wether tagged data is useful and what a good implementation might look like. | |||
7 | // Its just a sketch of an idea at the moment. | |||
8 | | |||
9 | /// <summary> | |||
10 | /// ExtraData tagged value interface. | |||
11 | /// </summary> | |||
12 | public interface ITaggedData | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Get the ID for this tagged data value. | |||
16 | /// </summary> | |||
17 | short TagID { get; } | |||
18 | | |||
19 | /// <summary> | |||
20 | /// Set the contents of this instance from the data passed. | |||
21 | /// </summary> | |||
22 | /// <param name="data">The data to extract contents from.</param> | |||
23 | /// <param name="offset">The offset to begin extracting data from.</param> | |||
24 | /// <param name="count">The number of bytes to extract.</param> | |||
25 | void SetData(byte[] data, int offset, int count); | |||
26 | | |||
27 | /// <summary> | |||
28 | /// Get the data representing this instance. | |||
29 | /// </summary> | |||
30 | /// <returns>Returns the data for this instance.</returns> | |||
31 | byte[] GetData(); | |||
32 | } | |||
33 | | |||
34 | /// <summary> | |||
35 | /// A raw binary tagged value | |||
36 | /// </summary> | |||
37 | public class RawTaggedData : ITaggedData | |||
38 | { | |||
39 | /// <summary> | |||
40 | /// Initialise a new instance. | |||
41 | /// </summary> | |||
42 | /// <param name="tag">The tag ID.</param> | |||
43 | public RawTaggedData(short tag) | |||
44 | { | |||
45 | _tag = tag; | |||
46 | } | |||
47 | | |||
48 | #region ITaggedData Members | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Get the ID for this tagged data value. | |||
52 | /// </summary> | |||
53 | public short TagID { | |||
54 | get { return _tag; } | |||
55 | set { _tag = value; } | |||
56 | } | |||
57 | | |||
58 | /// <summary> | |||
59 | /// Set the data from the raw values provided. | |||
60 | /// </summary> | |||
61 | /// <param name="data">The raw data to extract values from.</param> | |||
62 | /// <param name="offset">The index to start extracting values from.</param> | |||
63 | /// <param name="count">The number of bytes available.</param> | |||
64 | public void SetData(byte[] data, int offset, int count) | |||
65 | { | |||
66 | if (data == null) { | |||
67 | throw new ArgumentNullException(nameof(data)); | |||
68 | } | |||
69 | | |||
70 | _data = new byte[count]; | |||
71 | Array.Copy(data, offset, _data, 0, count); | |||
72 | } | |||
73 | | |||
74 | /// <summary> | |||
75 | /// Get the binary data representing this instance. | |||
76 | /// </summary> | |||
77 | /// <returns>The raw binary data representing this instance.</returns> | |||
78 | public byte[] GetData() | |||
79 | { | |||
80 | return _data; | |||
81 | } | |||
82 | | |||
83 | #endregion | |||
84 | | |||
85 | /// <summary> | |||
86 | /// Get /set the binary data representing this instance. | |||
87 | /// </summary> | |||
88 | /// <returns>The raw binary data representing this instance.</returns> | |||
89 | public byte[] Data { | |||
90 | get { return _data; } | |||
91 | set { _data = value; } | |||
92 | } | |||
93 | | |||
94 | #region Instance Fields | |||
95 | /// <summary> | |||
96 | /// The tag ID for this instance. | |||
97 | /// </summary> | |||
98 | short _tag; | |||
99 | | |||
100 | byte[] _data; | |||
101 | #endregion | |||
102 | } | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Class representing extended unix date time values. | |||
106 | /// </summary> | |||
107 | public class ExtendedUnixData : ITaggedData | |||
108 | { | |||
109 | /// <summary> | |||
110 | /// Flags indicate which values are included in this instance. | |||
111 | /// </summary> | |||
112 | [Flags] | |||
113 | public enum Flags : byte | |||
114 | { | |||
115 | /// <summary> | |||
116 | /// The modification time is included | |||
117 | /// </summary> | |||
118 | ModificationTime = 0x01, | |||
119 | | |||
120 | /// <summary> | |||
121 | /// The access time is included | |||
122 | /// </summary> | |||
123 | AccessTime = 0x02, | |||
124 | | |||
125 | /// <summary> | |||
126 | /// The create time is included. | |||
127 | /// </summary> | |||
128 | CreateTime = 0x04, | |||
129 | } | |||
130 | | |||
131 | #region ITaggedData Members | |||
132 | | |||
133 | /// <summary> | |||
134 | /// Get the ID | |||
135 | /// </summary> | |||
136 | public short TagID { | |||
137 | get { return 0x5455; } | |||
138 | } | |||
139 | | |||
140 | /// <summary> | |||
141 | /// Set the data from the raw values provided. | |||
142 | /// </summary> | |||
143 | /// <param name="data">The raw data to extract values from.</param> | |||
144 | /// <param name="index">The index to start extracting values from.</param> | |||
145 | /// <param name="count">The number of bytes available.</param> | |||
146 | public void SetData(byte[] data, int index, int count) | |||
147 | { | |||
148 | using (MemoryStream ms = new MemoryStream(data, index, count, false)) | |||
149 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
150 | // bit 0 if set, modification time is present | |||
151 | // bit 1 if set, access time is present | |||
152 | // bit 2 if set, creation time is present | |||
153 | | |||
154 | _flags = (Flags)helperStream.ReadByte(); | |||
155 | if (((_flags & Flags.ModificationTime) != 0)) | |||
156 | { | |||
157 | int iTime = helperStream.ReadLEInt(); | |||
158 | | |||
159 | _modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
160 | new TimeSpan(0, 0, 0, iTime, 0); | |||
161 | | |||
162 | // Central-header version is truncated after modification time | |||
163 | if (count <= 5) return; | |||
164 | } | |||
165 | | |||
166 | if ((_flags & Flags.AccessTime) != 0) { | |||
167 | int iTime = helperStream.ReadLEInt(); | |||
168 | | |||
169 | _lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
170 | new TimeSpan(0, 0, 0, iTime, 0); | |||
171 | } | |||
172 | | |||
173 | if ((_flags & Flags.CreateTime) != 0) { | |||
174 | int iTime = helperStream.ReadLEInt(); | |||
175 | | |||
176 | _createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
177 | new TimeSpan(0, 0, 0, iTime, 0); | |||
178 | } | |||
179 | } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Get the binary data representing this instance. | |||
184 | /// </summary> | |||
185 | /// <returns>The raw binary data representing this instance.</returns> | |||
186 | public byte[] GetData() | |||
187 | { | |||
188 | using (MemoryStream ms = new MemoryStream()) | |||
189 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
190 | helperStream.IsStreamOwner = false; | |||
191 | helperStream.WriteByte((byte)_flags); // Flags | |||
192 | if ((_flags & Flags.ModificationTime) != 0) { | |||
193 | TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
194 | var seconds = (int)span.TotalSeconds; | |||
195 | helperStream.WriteLEInt(seconds); | |||
196 | } | |||
197 | if ((_flags & Flags.AccessTime) != 0) { | |||
198 | TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
199 | var seconds = (int)span.TotalSeconds; | |||
200 | helperStream.WriteLEInt(seconds); | |||
201 | } | |||
202 | if ((_flags & Flags.CreateTime) != 0) { | |||
203 | TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
204 | var seconds = (int)span.TotalSeconds; | |||
205 | helperStream.WriteLEInt(seconds); | |||
206 | } | |||
207 | return ms.ToArray(); | |||
208 | } | |||
209 | } | |||
210 | | |||
211 | #endregion | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Test a <see cref="DateTime"> value to see if is valid and can be represented here.</see> | |||
215 | /// </summary> | |||
216 | /// <param name="value">The <see cref="DateTime">value</see> to test.</param> | |||
217 | /// <returns>Returns true if the value is valid and can be represented; false if not.</returns> | |||
218 | /// <remarks>The standard Unix time is a signed integer data type, directly encoding the Unix time number, | |||
219 | /// which is the number of seconds since 1970-01-01. | |||
220 | /// Being 32 bits means the values here cover a range of about 136 years. | |||
221 | /// The minimum representable time is 1901-12-13 20:45:52, | |||
222 | /// and the maximum representable time is 2038-01-19 03:14:07. | |||
223 | /// </remarks> | |||
224 | public static bool IsValidValue(DateTime value) | |||
225 | { | |||
226 | return ((value >= new DateTime(1901, 12, 13, 20, 45, 52)) || | |||
227 | (value <= new DateTime(2038, 1, 19, 03, 14, 07))); | |||
228 | } | |||
229 | | |||
230 | /// <summary> | |||
231 | /// Get /set the Modification Time | |||
232 | /// </summary> | |||
233 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
234 | /// <seealso cref="IsValidValue"></seealso> | |||
235 | public DateTime ModificationTime { | |||
236 | get { return _modificationTime; } | |||
237 | set { | |||
238 | if (!IsValidValue(value)) { | |||
239 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
240 | } | |||
241 | | |||
242 | _flags |= Flags.ModificationTime; | |||
243 | _modificationTime = value; | |||
244 | } | |||
245 | } | |||
246 | | |||
247 | /// <summary> | |||
248 | /// Get / set the Access Time | |||
249 | /// </summary> | |||
250 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
251 | /// <seealso cref="IsValidValue"></seealso> | |||
252 | public DateTime AccessTime { | |||
253 | get { return _lastAccessTime; } | |||
254 | set { | |||
255 | if (!IsValidValue(value)) { | |||
256 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
257 | } | |||
258 | | |||
259 | _flags |= Flags.AccessTime; | |||
260 | _lastAccessTime = value; | |||
261 | } | |||
262 | } | |||
263 | | |||
264 | /// <summary> | |||
265 | /// Get / Set the Create Time | |||
266 | /// </summary> | |||
267 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
268 | /// <seealso cref="IsValidValue"></seealso> | |||
269 | public DateTime CreateTime { | |||
270 | get { return _createTime; } | |||
271 | set { | |||
272 | if (!IsValidValue(value)) { | |||
273 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
274 | } | |||
275 | | |||
276 | _flags |= Flags.CreateTime; | |||
277 | _createTime = value; | |||
278 | } | |||
279 | } | |||
280 | | |||
281 | /// <summary> | |||
282 | /// Get/set the <see cref="Flags">values</see> to include. | |||
283 | /// </summary> | |||
284 | public Flags Include | |||
285 | { | |||
286 | get { return _flags; } | |||
287 | set { _flags = value; } | |||
288 | } | |||
289 | | |||
290 | #region Instance Fields | |||
291 | Flags _flags; | |||
292 | DateTime _modificationTime = new DateTime(1970, 1, 1); | |||
293 | DateTime _lastAccessTime = new DateTime(1970, 1, 1); | |||
294 | DateTime _createTime = new DateTime(1970, 1, 1); | |||
295 | #endregion | |||
296 | } | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Class handling NT date time values. | |||
300 | /// </summary> | |||
301 | public class NTTaggedData : ITaggedData | |||
302 | { | |||
303 | /// <summary> | |||
304 | /// Get the ID for this tagged data value. | |||
305 | /// </summary> | |||
306 | public short TagID { | |||
| 1 | 307 | get { return 10; } | ||
308 | } | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Set the data from the raw values provided. | |||
312 | /// </summary> | |||
313 | /// <param name="data">The raw data to extract values from.</param> | |||
314 | /// <param name="index">The index to start extracting values from.</param> | |||
315 | /// <param name="count">The number of bytes available.</param> | |||
316 | public void SetData(byte[] data, int index, int count) | |||
317 | { | |||
| 1 | 318 | using (MemoryStream ms = new MemoryStream(data, index, count, false)) | ||
| 1 | 319 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | ||
| 1 | 320 | helperStream.ReadLEInt(); // Reserved | ||
| 1 | 321 | while (helperStream.Position < helperStream.Length) { | ||
| 1 | 322 | int ntfsTag = helperStream.ReadLEShort(); | ||
| 1 | 323 | int ntfsLength = helperStream.ReadLEShort(); | ||
| 1 | 324 | if (ntfsTag == 1) { | ||
| 1 | 325 | if (ntfsLength >= 24) { | ||
| 1 | 326 | long lastModificationTicks = helperStream.ReadLELong(); | ||
| 1 | 327 | _lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks); | ||
328 | | |||
| 1 | 329 | long lastAccessTicks = helperStream.ReadLELong(); | ||
| 1 | 330 | _lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks); | ||
331 | | |||
| 1 | 332 | long createTimeTicks = helperStream.ReadLELong(); | ||
| 1 | 333 | _createTime = DateTime.FromFileTimeUtc(createTimeTicks); | ||
334 | } | |||
| 1 | 335 | break; | ||
336 | } else { | |||
337 | // An unknown NTFS tag so simply skip it. | |||
| 0 | 338 | helperStream.Seek(ntfsLength, SeekOrigin.Current); | ||
339 | } | |||
340 | } | |||
| 0 | 341 | } | ||
| 1 | 342 | } | ||
343 | | |||
344 | /// <summary> | |||
345 | /// Get the binary data representing this instance. | |||
346 | /// </summary> | |||
347 | /// <returns>The raw binary data representing this instance.</returns> | |||
348 | public byte[] GetData() | |||
349 | { | |||
| 2 | 350 | using (MemoryStream ms = new MemoryStream()) | ||
| 2 | 351 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | ||
| 2 | 352 | helperStream.IsStreamOwner = false; | ||
| 2 | 353 | helperStream.WriteLEInt(0); // Reserved | ||
| 2 | 354 | helperStream.WriteLEShort(1); // Tag | ||
| 2 | 355 | helperStream.WriteLEShort(24); // Length = 3 x 8. | ||
| 2 | 356 | helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc()); | ||
| 2 | 357 | helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc()); | ||
| 2 | 358 | helperStream.WriteLELong(_createTime.ToFileTimeUtc()); | ||
| 2 | 359 | return ms.ToArray(); | ||
360 | } | |||
| 2 | 361 | } | ||
362 | | |||
363 | /// <summary> | |||
364 | /// Test a <see cref="DateTime"> valuie to see if is valid and can be represented here.</see> | |||
365 | /// </summary> | |||
366 | /// <param name="value">The <see cref="DateTime">value</see> to test.</param> | |||
367 | /// <returns>Returns true if the value is valid and can be represented; false if not.</returns> | |||
368 | /// <remarks> | |||
369 | /// NTFS filetimes are 64-bit unsigned integers, stored in Intel | |||
370 | /// (least significant byte first) byte order. They determine the | |||
371 | /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch", | |||
372 | /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit | |||
373 | /// </remarks> | |||
374 | public static bool IsValidValue(DateTime value) | |||
375 | { | |||
| 3 | 376 | bool result = true; | ||
377 | try { | |||
| 3 | 378 | value.ToFileTimeUtc(); | ||
| 3 | 379 | } catch { | ||
| 0 | 380 | result = false; | ||
| 0 | 381 | } | ||
| 3 | 382 | return result; | ||
383 | } | |||
384 | | |||
385 | /// <summary> | |||
386 | /// Get/set the <see cref="DateTime">last modification time</see>. | |||
387 | /// </summary> | |||
388 | public DateTime LastModificationTime { | |||
| 4 | 389 | get { return _lastModificationTime; } | ||
390 | set { | |||
| 1 | 391 | if (!IsValidValue(value)) { | ||
| 0 | 392 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
393 | } | |||
| 1 | 394 | _lastModificationTime = value; | ||
| 1 | 395 | } | ||
396 | } | |||
397 | | |||
398 | /// <summary> | |||
399 | /// Get /set the <see cref="DateTime">create time</see> | |||
400 | /// </summary> | |||
401 | public DateTime CreateTime { | |||
| 0 | 402 | get { return _createTime; } | ||
403 | set { | |||
| 1 | 404 | if (!IsValidValue(value)) { | ||
| 0 | 405 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
406 | } | |||
| 1 | 407 | _createTime = value; | ||
| 1 | 408 | } | ||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Get /set the <see cref="DateTime">last access time</see>. | |||
413 | /// </summary> | |||
414 | public DateTime LastAccessTime { | |||
| 0 | 415 | get { return _lastAccessTime; } | ||
416 | set { | |||
| 1 | 417 | if (!IsValidValue(value)) { | ||
| 0 | 418 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
419 | } | |||
| 1 | 420 | _lastAccessTime = value; | ||
| 1 | 421 | } | ||
422 | } | |||
423 | | |||
424 | #region Instance Fields | |||
| 1 | 425 | DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0); | ||
| 1 | 426 | DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0); | ||
| 1 | 427 | DateTime _createTime = DateTime.FromFileTimeUtc(0); | ||
428 | #endregion | |||
429 | } | |||
430 | | |||
431 | /// <summary> | |||
432 | /// A factory that creates <see cref="ITaggedData">tagged data</see> instances. | |||
433 | /// </summary> | |||
434 | interface ITaggedDataFactory | |||
435 | { | |||
436 | /// <summary> | |||
437 | /// Get data for a specific tag value. | |||
438 | /// </summary> | |||
439 | /// <param name="tag">The tag ID to find.</param> | |||
440 | /// <param name="data">The data to search.</param> | |||
441 | /// <param name="offset">The offset to begin extracting data from.</param> | |||
442 | /// <param name="count">The number of bytes to extract.</param> | |||
443 | /// <returns>The located <see cref="ITaggedData">value found</see>, or null if not found.</returns> | |||
444 | ITaggedData Create(short tag, byte[] data, int offset, int count); | |||
445 | } | |||
446 | | |||
447 | /// | |||
448 | /// <summary> | |||
449 | /// A class to handle the extra data field for Zip entries | |||
450 | /// </summary> | |||
451 | /// <remarks> | |||
452 | /// Extra data contains 0 or more values each prefixed by a header tag and length. | |||
453 | /// They contain zero or more bytes of actual data. | |||
454 | /// The data is held internally using a copy on write strategy. This is more efficient but | |||
455 | /// means that for extra data created by passing in data can have the values modified by the caller | |||
456 | /// in some circumstances. | |||
457 | /// </remarks> | |||
458 | sealed public class ZipExtraData : IDisposable | |||
459 | { | |||
460 | #region Constructors | |||
461 | /// <summary> | |||
462 | /// Initialise a default instance. | |||
463 | /// </summary> | |||
464 | public ZipExtraData() | |||
465 | { | |||
466 | Clear(); | |||
467 | } | |||
468 | | |||
469 | /// <summary> | |||
470 | /// Initialise with known extra data. | |||
471 | /// </summary> | |||
472 | /// <param name="data">The extra data.</param> | |||
473 | public ZipExtraData(byte[] data) | |||
474 | { | |||
475 | if (data == null) { | |||
476 | _data = new byte[0]; | |||
477 | } else { | |||
478 | _data = data; | |||
479 | } | |||
480 | } | |||
481 | #endregion | |||
482 | | |||
483 | /// <summary> | |||
484 | /// Get the raw extra data value | |||
485 | /// </summary> | |||
486 | /// <returns>Returns the raw byte[] extra data this instance represents.</returns> | |||
487 | public byte[] GetEntryData() | |||
488 | { | |||
489 | if (Length > ushort.MaxValue) { | |||
490 | throw new ZipException("Data exceeds maximum length"); | |||
491 | } | |||
492 | | |||
493 | return (byte[])_data.Clone(); | |||
494 | } | |||
495 | | |||
496 | /// <summary> | |||
497 | /// Clear the stored data. | |||
498 | /// </summary> | |||
499 | public void Clear() | |||
500 | { | |||
501 | if ((_data == null) || (_data.Length != 0)) { | |||
502 | _data = new byte[0]; | |||
503 | } | |||
504 | } | |||
505 | | |||
506 | /// <summary> | |||
507 | /// Gets the current extra data length. | |||
508 | /// </summary> | |||
509 | public int Length { | |||
510 | get { return _data.Length; } | |||
511 | } | |||
512 | | |||
513 | /// <summary> | |||
514 | /// Get a read-only <see cref="Stream"/> for the associated tag. | |||
515 | /// </summary> | |||
516 | /// <param name="tag">The tag to locate data for.</param> | |||
517 | /// <returns>Returns a <see cref="Stream"/> containing tag data or null if no tag was found.</returns> | |||
518 | public Stream GetStreamForTag(int tag) | |||
519 | { | |||
520 | Stream result = null; | |||
521 | if (Find(tag)) { | |||
522 | result = new MemoryStream(_data, _index, _readValueLength, false); | |||
523 | } | |||
524 | return result; | |||
525 | } | |||
526 | | |||
527 | /// <summary> | |||
528 | /// Get the <see cref="ITaggedData">tagged data</see> for a tag. | |||
529 | /// </summary> | |||
530 | /// <typeparam name="T">The tag to search for.</typeparam> | |||
531 | /// <returns>Returns a <see cref="ITaggedData">tagged value</see> or null if none found.</returns> | |||
532 | public T GetData<T>() | |||
533 | where T : class, ITaggedData, new() | |||
534 | { | |||
535 | T result = new T(); | |||
536 | if (Find(result.TagID)) | |||
537 | { | |||
538 | result.SetData(_data, _readValueStart, _readValueLength); | |||
539 | return result; | |||
540 | } | |||
541 | else return null; | |||
542 | } | |||
543 | | |||
544 | /// <summary> | |||
545 | /// Get the length of the last value found by <see cref="Find"/> | |||
546 | /// </summary> | |||
547 | /// <remarks>This is only valid if <see cref="Find"/> has previously returned true.</remarks> | |||
548 | public int ValueLength { | |||
549 | get { return _readValueLength; } | |||
550 | } | |||
551 | | |||
552 | /// <summary> | |||
553 | /// Get the index for the current read value. | |||
554 | /// </summary> | |||
555 | /// <remarks>This is only valid if <see cref="Find"/> has previously returned true. | |||
556 | /// Initially the result will be the index of the first byte of actual data. The value is updated after calls to | |||
557 | /// <see cref="ReadInt"/>, <see cref="ReadShort"/> and <see cref="ReadLong"/>. </remarks> | |||
558 | public int CurrentReadIndex { | |||
559 | get { return _index; } | |||
560 | } | |||
561 | | |||
562 | /// <summary> | |||
563 | /// Get the number of bytes remaining to be read for the current value; | |||
564 | /// </summary> | |||
565 | public int UnreadCount { | |||
566 | get { | |||
567 | if ((_readValueStart > _data.Length) || | |||
568 | (_readValueStart < 4)) { | |||
569 | throw new ZipException("Find must be called before calling a Read method"); | |||
570 | } | |||
571 | | |||
572 | return _readValueStart + _readValueLength - _index; | |||
573 | } | |||
574 | } | |||
575 | | |||
576 | /// <summary> | |||
577 | /// Find an extra data value | |||
578 | /// </summary> | |||
579 | /// <param name="headerID">The identifier for the value to find.</param> | |||
580 | /// <returns>Returns true if the value was found; false otherwise.</returns> | |||
581 | public bool Find(int headerID) | |||
582 | { | |||
583 | _readValueStart = _data.Length; | |||
584 | _readValueLength = 0; | |||
585 | _index = 0; | |||
586 | | |||
587 | int localLength = _readValueStart; | |||
588 | int localTag = headerID - 1; | |||
589 | | |||
590 | // Trailing bytes that cant make up an entry (as there arent enough | |||
591 | // bytes for a tag and length) are ignored! | |||
592 | while ((localTag != headerID) && (_index < _data.Length - 3)) { | |||
593 | localTag = ReadShortInternal(); | |||
594 | localLength = ReadShortInternal(); | |||
595 | if (localTag != headerID) { | |||
596 | _index += localLength; | |||
597 | } | |||
598 | } | |||
599 | | |||
600 | bool result = (localTag == headerID) && ((_index + localLength) <= _data.Length); | |||
601 | | |||
602 | if (result) { | |||
603 | _readValueStart = _index; | |||
604 | _readValueLength = localLength; | |||
605 | } | |||
606 | | |||
607 | return result; | |||
608 | } | |||
609 | | |||
610 | /// <summary> | |||
611 | /// Add a new entry to extra data. | |||
612 | /// </summary> | |||
613 | /// <param name="taggedData">The <see cref="ITaggedData"/> value to add.</param> | |||
614 | public void AddEntry(ITaggedData taggedData) | |||
615 | { | |||
616 | if (taggedData == null) { | |||
617 | throw new ArgumentNullException(nameof(taggedData)); | |||
618 | } | |||
619 | AddEntry(taggedData.TagID, taggedData.GetData()); | |||
620 | } | |||
621 | | |||
622 | /// <summary> | |||
623 | /// Add a new entry to extra data | |||
624 | /// </summary> | |||
625 | /// <param name="headerID">The ID for this entry.</param> | |||
626 | /// <param name="fieldData">The data to add.</param> | |||
627 | /// <remarks>If the ID already exists its contents are replaced.</remarks> | |||
628 | public void AddEntry(int headerID, byte[] fieldData) | |||
629 | { | |||
630 | if ((headerID > ushort.MaxValue) || (headerID < 0)) { | |||
631 | throw new ArgumentOutOfRangeException(nameof(headerID)); | |||
632 | } | |||
633 | | |||
634 | int addLength = (fieldData == null) ? 0 : fieldData.Length; | |||
635 | | |||
636 | if (addLength > ushort.MaxValue) { | |||
637 | throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length"); | |||
638 | } | |||
639 | | |||
640 | // Test for new length before adjusting data. | |||
641 | int newLength = _data.Length + addLength + 4; | |||
642 | | |||
643 | if (Find(headerID)) { | |||
644 | newLength -= (ValueLength + 4); | |||
645 | } | |||
646 | | |||
647 | if (newLength > ushort.MaxValue) { | |||
648 | throw new ZipException("Data exceeds maximum length"); | |||
649 | } | |||
650 | | |||
651 | Delete(headerID); | |||
652 | | |||
653 | byte[] newData = new byte[newLength]; | |||
654 | _data.CopyTo(newData, 0); | |||
655 | int index = _data.Length; | |||
656 | _data = newData; | |||
657 | SetShort(ref index, headerID); | |||
658 | SetShort(ref index, addLength); | |||
659 | if (fieldData != null) { | |||
660 | fieldData.CopyTo(newData, index); | |||
661 | } | |||
662 | } | |||
663 | | |||
664 | /// <summary> | |||
665 | /// Start adding a new entry. | |||
666 | /// </summary> | |||
667 | /// <remarks>Add data using <see cref="AddData(byte[])"/>, <see cref="AddLeShort"/>, <see cref="AddLeInt"/>, or <see | |||
668 | /// The new entry is completed and actually added by calling <see cref="AddNewEntry"/></remarks> | |||
669 | /// <seealso cref="AddEntry(ITaggedData)"/> | |||
670 | public void StartNewEntry() | |||
671 | { | |||
672 | _newEntry = new MemoryStream(); | |||
673 | } | |||
674 | | |||
675 | /// <summary> | |||
676 | /// Add entry data added since <see cref="StartNewEntry"/> using the ID passed. | |||
677 | /// </summary> | |||
678 | /// <param name="headerID">The identifier to use for this entry.</param> | |||
679 | public void AddNewEntry(int headerID) | |||
680 | { | |||
681 | byte[] newData = _newEntry.ToArray(); | |||
682 | _newEntry = null; | |||
683 | AddEntry(headerID, newData); | |||
684 | } | |||
685 | | |||
686 | /// <summary> | |||
687 | /// Add a byte of data to the pending new entry. | |||
688 | /// </summary> | |||
689 | /// <param name="data">The byte to add.</param> | |||
690 | /// <seealso cref="StartNewEntry"/> | |||
691 | public void AddData(byte data) | |||
692 | { | |||
693 | _newEntry.WriteByte(data); | |||
694 | } | |||
695 | | |||
696 | /// <summary> | |||
697 | /// Add data to a pending new entry. | |||
698 | /// </summary> | |||
699 | /// <param name="data">The data to add.</param> | |||
700 | /// <seealso cref="StartNewEntry"/> | |||
701 | public void AddData(byte[] data) | |||
702 | { | |||
703 | if (data == null) { | |||
704 | throw new ArgumentNullException(nameof(data)); | |||
705 | } | |||
706 | | |||
707 | _newEntry.Write(data, 0, data.Length); | |||
708 | } | |||
709 | | |||
710 | /// <summary> | |||
711 | /// Add a short value in little endian order to the pending new entry. | |||
712 | /// </summary> | |||
713 | /// <param name="toAdd">The data to add.</param> | |||
714 | /// <seealso cref="StartNewEntry"/> | |||
715 | public void AddLeShort(int toAdd) | |||
716 | { | |||
717 | unchecked { | |||
718 | _newEntry.WriteByte((byte)toAdd); | |||
719 | _newEntry.WriteByte((byte)(toAdd >> 8)); | |||
720 | } | |||
721 | } | |||
722 | | |||
723 | /// <summary> | |||
724 | /// Add an integer value in little endian order to the pending new entry. | |||
725 | /// </summary> | |||
726 | /// <param name="toAdd">The data to add.</param> | |||
727 | /// <seealso cref="StartNewEntry"/> | |||
728 | public void AddLeInt(int toAdd) | |||
729 | { | |||
730 | unchecked { | |||
731 | AddLeShort((short)toAdd); | |||
732 | AddLeShort((short)(toAdd >> 16)); | |||
733 | } | |||
734 | } | |||
735 | | |||
736 | /// <summary> | |||
737 | /// Add a long value in little endian order to the pending new entry. | |||
738 | /// </summary> | |||
739 | /// <param name="toAdd">The data to add.</param> | |||
740 | /// <seealso cref="StartNewEntry"/> | |||
741 | public void AddLeLong(long toAdd) | |||
742 | { | |||
743 | unchecked { | |||
744 | AddLeInt((int)(toAdd & 0xffffffff)); | |||
745 | AddLeInt((int)(toAdd >> 32)); | |||
746 | } | |||
747 | } | |||
748 | | |||
749 | /// <summary> | |||
750 | /// Delete an extra data field. | |||
751 | /// </summary> | |||
752 | /// <param name="headerID">The identifier of the field to delete.</param> | |||
753 | /// <returns>Returns true if the field was found and deleted.</returns> | |||
754 | public bool Delete(int headerID) | |||
755 | { | |||
756 | bool result = false; | |||
757 | | |||
758 | if (Find(headerID)) { | |||
759 | result = true; | |||
760 | int trueStart = _readValueStart - 4; | |||
761 | | |||
762 | byte[] newData = new byte[_data.Length - (ValueLength + 4)]; | |||
763 | Array.Copy(_data, 0, newData, 0, trueStart); | |||
764 | | |||
765 | int trueEnd = trueStart + ValueLength + 4; | |||
766 | Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd); | |||
767 | _data = newData; | |||
768 | } | |||
769 | return result; | |||
770 | } | |||
771 | | |||
772 | #region Reading Support | |||
773 | /// <summary> | |||
774 | /// Read a long in little endian form from the last <see cref="Find">found</see> data value | |||
775 | /// </summary> | |||
776 | /// <returns>Returns the long value read.</returns> | |||
777 | public long ReadLong() | |||
778 | { | |||
779 | ReadCheck(8); | |||
780 | return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32); | |||
781 | } | |||
782 | | |||
783 | /// <summary> | |||
784 | /// Read an integer in little endian form from the last <see cref="Find">found</see> data value. | |||
785 | /// </summary> | |||
786 | /// <returns>Returns the integer read.</returns> | |||
787 | public int ReadInt() | |||
788 | { | |||
789 | ReadCheck(4); | |||
790 | | |||
791 | int result = _data[_index] + (_data[_index + 1] << 8) + | |||
792 | (_data[_index + 2] << 16) + (_data[_index + 3] << 24); | |||
793 | _index += 4; | |||
794 | return result; | |||
795 | } | |||
796 | | |||
797 | /// <summary> | |||
798 | /// Read a short value in little endian form from the last <see cref="Find">found</see> data value. | |||
799 | /// </summary> | |||
800 | /// <returns>Returns the short value read.</returns> | |||
801 | public int ReadShort() | |||
802 | { | |||
803 | ReadCheck(2); | |||
804 | int result = _data[_index] + (_data[_index + 1] << 8); | |||
805 | _index += 2; | |||
806 | return result; | |||
807 | } | |||
808 | | |||
809 | /// <summary> | |||
810 | /// Read a byte from an extra data | |||
811 | /// </summary> | |||
812 | /// <returns>The byte value read or -1 if the end of data has been reached.</returns> | |||
813 | public int ReadByte() | |||
814 | { | |||
815 | int result = -1; | |||
816 | if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index)) { | |||
817 | result = _data[_index]; | |||
818 | _index += 1; | |||
819 | } | |||
820 | return result; | |||
821 | } | |||
822 | | |||
823 | /// <summary> | |||
824 | /// Skip data during reading. | |||
825 | /// </summary> | |||
826 | /// <param name="amount">The number of bytes to skip.</param> | |||
827 | public void Skip(int amount) | |||
828 | { | |||
829 | ReadCheck(amount); | |||
830 | _index += amount; | |||
831 | } | |||
832 | | |||
833 | void ReadCheck(int length) | |||
834 | { | |||
835 | if ((_readValueStart > _data.Length) || | |||
836 | (_readValueStart < 4)) { | |||
837 | throw new ZipException("Find must be called before calling a Read method"); | |||
838 | } | |||
839 | | |||
840 | if (_index > _readValueStart + _readValueLength - length) { | |||
841 | throw new ZipException("End of extra data"); | |||
842 | } | |||
843 | | |||
844 | if (_index + length < 4) { | |||
845 | throw new ZipException("Cannot read before start of tag"); | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | /// <summary> | |||
850 | /// Internal form of <see cref="ReadShort"/> that reads data at any location. | |||
851 | /// </summary> | |||
852 | /// <returns>Returns the short value read.</returns> | |||
853 | int ReadShortInternal() | |||
854 | { | |||
855 | if (_index > _data.Length - 2) { | |||
856 | throw new ZipException("End of extra data"); | |||
857 | } | |||
858 | | |||
859 | int result = _data[_index] + (_data[_index + 1] << 8); | |||
860 | _index += 2; | |||
861 | return result; | |||
862 | } | |||
863 | | |||
864 | void SetShort(ref int index, int source) | |||
865 | { | |||
866 | _data[index] = (byte)source; | |||
867 | _data[index + 1] = (byte)(source >> 8); | |||
868 | index += 2; | |||
869 | } | |||
870 | | |||
871 | #endregion | |||
872 | | |||
873 | #region IDisposable Members | |||
874 | | |||
875 | /// <summary> | |||
876 | /// Dispose of this instance. | |||
877 | /// </summary> | |||
878 | public void Dispose() | |||
879 | { | |||
880 | if (_newEntry != null) { | |||
881 | _newEntry.Close(); | |||
882 | } | |||
883 | } | |||
884 | | |||
885 | #endregion | |||
886 | | |||
887 | #region Instance Fields | |||
888 | int _index; | |||
889 | int _readValueStart; | |||
890 | int _readValueLength; | |||
891 | | |||
892 | MemoryStream _newEntry; | |||
893 | byte[] _data; | |||
894 | #endregion | |||
895 | } | |||
896 | } |
| Class: | ICSharpCode.SharpZipLib.Core.NameAndSizeFilter |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\PathFilter.cs |
| Covered lines: | 0 |
| Uncovered lines: | 23 |
| Coverable lines: | 23 |
| Total lines: | 280 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| IsMatch(...) | 3 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Core | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// PathFilter filters directories and files using a form of <see cref="System.Text.RegularExpressions.Regex">regular | |||
8 | /// by full path name. | |||
9 | /// See <see cref="NameFilter">NameFilter</see> for more detail on filtering. | |||
10 | /// </summary> | |||
11 | public class PathFilter : IScanFilter | |||
12 | { | |||
13 | #region Constructors | |||
14 | /// <summary> | |||
15 | /// Initialise a new instance of <see cref="PathFilter"></see>. | |||
16 | /// </summary> | |||
17 | /// <param name="filter">The <see cref="NameFilter">filter</see> expression to apply.</param> | |||
18 | public PathFilter(string filter) | |||
19 | { | |||
20 | nameFilter_ = new NameFilter(filter); | |||
21 | } | |||
22 | #endregion | |||
23 | | |||
24 | #region IScanFilter Members | |||
25 | /// <summary> | |||
26 | /// Test a name to see if it matches the filter. | |||
27 | /// </summary> | |||
28 | /// <param name="name">The name to test.</param> | |||
29 | /// <returns>True if the name matches, false otherwise.</returns> | |||
30 | /// <remarks><see cref="Path.GetFullPath(string)"/> is used to get the full path before matching.</remarks> | |||
31 | public virtual bool IsMatch(string name) | |||
32 | { | |||
33 | bool result = false; | |||
34 | | |||
35 | if (name != null) { | |||
36 | string cooked = (name.Length > 0) ? Path.GetFullPath(name) : ""; | |||
37 | result = nameFilter_.IsMatch(cooked); | |||
38 | } | |||
39 | return result; | |||
40 | } | |||
41 | | |||
42 | readonly | |||
43 | #endregion | |||
44 | | |||
45 | #region Instance Fields | |||
46 | NameFilter nameFilter_; | |||
47 | #endregion | |||
48 | } | |||
49 | | |||
50 | /// <summary> | |||
51 | /// ExtendedPathFilter filters based on name, file size, and the last write time of the file. | |||
52 | /// </summary> | |||
53 | /// <remarks>Provides an example of how to customise filtering.</remarks> | |||
54 | public class ExtendedPathFilter : PathFilter | |||
55 | { | |||
56 | #region Constructors | |||
57 | /// <summary> | |||
58 | /// Initialise a new instance of ExtendedPathFilter. | |||
59 | /// </summary> | |||
60 | /// <param name="filter">The filter to apply.</param> | |||
61 | /// <param name="minSize">The minimum file size to include.</param> | |||
62 | /// <param name="maxSize">The maximum file size to include.</param> | |||
63 | public ExtendedPathFilter(string filter, | |||
64 | long minSize, long maxSize) | |||
65 | : base(filter) | |||
66 | { | |||
67 | MinSize = minSize; | |||
68 | MaxSize = maxSize; | |||
69 | } | |||
70 | | |||
71 | /// <summary> | |||
72 | /// Initialise a new instance of ExtendedPathFilter. | |||
73 | /// </summary> | |||
74 | /// <param name="filter">The filter to apply.</param> | |||
75 | /// <param name="minDate">The minimum <see cref="DateTime"/> to include.</param> | |||
76 | /// <param name="maxDate">The maximum <see cref="DateTime"/> to include.</param> | |||
77 | public ExtendedPathFilter(string filter, | |||
78 | DateTime minDate, DateTime maxDate) | |||
79 | : base(filter) | |||
80 | { | |||
81 | MinDate = minDate; | |||
82 | MaxDate = maxDate; | |||
83 | } | |||
84 | | |||
85 | /// <summary> | |||
86 | /// Initialise a new instance of ExtendedPathFilter. | |||
87 | /// </summary> | |||
88 | /// <param name="filter">The filter to apply.</param> | |||
89 | /// <param name="minSize">The minimum file size to include.</param> | |||
90 | /// <param name="maxSize">The maximum file size to include.</param> | |||
91 | /// <param name="minDate">The minimum <see cref="DateTime"/> to include.</param> | |||
92 | /// <param name="maxDate">The maximum <see cref="DateTime"/> to include.</param> | |||
93 | public ExtendedPathFilter(string filter, | |||
94 | long minSize, long maxSize, | |||
95 | DateTime minDate, DateTime maxDate) | |||
96 | : base(filter) | |||
97 | { | |||
98 | MinSize = minSize; | |||
99 | MaxSize = maxSize; | |||
100 | MinDate = minDate; | |||
101 | MaxDate = maxDate; | |||
102 | } | |||
103 | #endregion | |||
104 | | |||
105 | #region IScanFilter Members | |||
106 | /// <summary> | |||
107 | /// Test a filename to see if it matches the filter. | |||
108 | /// </summary> | |||
109 | /// <param name="name">The filename to test.</param> | |||
110 | /// <returns>True if the filter matches, false otherwise.</returns> | |||
111 | /// <exception cref="System.IO.FileNotFoundException">The <see paramref="fileName"/> doesnt exist</exception> | |||
112 | public override bool IsMatch(string name) | |||
113 | { | |||
114 | bool result = base.IsMatch(name); | |||
115 | | |||
116 | if (result) { | |||
117 | var fileInfo = new FileInfo(name); | |||
118 | result = | |||
119 | (MinSize <= fileInfo.Length) && | |||
120 | (MaxSize >= fileInfo.Length) && | |||
121 | (MinDate <= fileInfo.LastWriteTime) && | |||
122 | (MaxDate >= fileInfo.LastWriteTime) | |||
123 | ; | |||
124 | } | |||
125 | return result; | |||
126 | } | |||
127 | #endregion | |||
128 | | |||
129 | #region Properties | |||
130 | /// <summary> | |||
131 | /// Get/set the minimum size/length for a file that will match this filter. | |||
132 | /// </summary> | |||
133 | /// <remarks>The default value is zero.</remarks> | |||
134 | /// <exception cref="ArgumentOutOfRangeException">value is less than zero; greater than <see cref="MaxSize"/></excep | |||
135 | public long MinSize { | |||
136 | get { return minSize_; } | |||
137 | set { | |||
138 | if ((value < 0) || (maxSize_ < value)) { | |||
139 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
140 | } | |||
141 | | |||
142 | minSize_ = value; | |||
143 | } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get/set the maximum size/length for a file that will match this filter. | |||
148 | /// </summary> | |||
149 | /// <remarks>The default value is <see cref="System.Int64.MaxValue"/></remarks> | |||
150 | /// <exception cref="ArgumentOutOfRangeException">value is less than zero or less than <see cref="MinSize"/></except | |||
151 | public long MaxSize { | |||
152 | get { return maxSize_; } | |||
153 | set { | |||
154 | if ((value < 0) || (minSize_ > value)) { | |||
155 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
156 | } | |||
157 | | |||
158 | maxSize_ = value; | |||
159 | } | |||
160 | } | |||
161 | | |||
162 | /// <summary> | |||
163 | /// Get/set the minimum <see cref="DateTime"/> value that will match for this filter. | |||
164 | /// </summary> | |||
165 | /// <remarks>Files with a LastWrite time less than this value are excluded by the filter.</remarks> | |||
166 | public DateTime MinDate { | |||
167 | get { | |||
168 | return minDate_; | |||
169 | } | |||
170 | | |||
171 | set { | |||
172 | if (value > maxDate_) { | |||
173 | throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MaxDate"); | |||
174 | } | |||
175 | | |||
176 | minDate_ = value; | |||
177 | } | |||
178 | } | |||
179 | | |||
180 | /// <summary> | |||
181 | /// Get/set the maximum <see cref="DateTime"/> value that will match for this filter. | |||
182 | /// </summary> | |||
183 | /// <remarks>Files with a LastWrite time greater than this value are excluded by the filter.</remarks> | |||
184 | public DateTime MaxDate { | |||
185 | get { | |||
186 | return maxDate_; | |||
187 | } | |||
188 | | |||
189 | set { | |||
190 | if (minDate_ > value) { | |||
191 | throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MinDate"); | |||
192 | } | |||
193 | | |||
194 | maxDate_ = value; | |||
195 | } | |||
196 | } | |||
197 | #endregion | |||
198 | | |||
199 | #region Instance Fields | |||
200 | long minSize_; | |||
201 | long maxSize_ = long.MaxValue; | |||
202 | DateTime minDate_ = DateTime.MinValue; | |||
203 | DateTime maxDate_ = DateTime.MaxValue; | |||
204 | #endregion | |||
205 | } | |||
206 | | |||
207 | /// <summary> | |||
208 | /// NameAndSizeFilter filters based on name and file size. | |||
209 | /// </summary> | |||
210 | /// <remarks>A sample showing how filters might be extended.</remarks> | |||
211 | [Obsolete("Use ExtendedPathFilter instead")] | |||
212 | public class NameAndSizeFilter : PathFilter | |||
213 | { | |||
214 | | |||
215 | /// <summary> | |||
216 | /// Initialise a new instance of NameAndSizeFilter. | |||
217 | /// </summary> | |||
218 | /// <param name="filter">The filter to apply.</param> | |||
219 | /// <param name="minSize">The minimum file size to include.</param> | |||
220 | /// <param name="maxSize">The maximum file size to include.</param> | |||
221 | public NameAndSizeFilter(string filter, long minSize, long maxSize) | |||
| 0 | 222 | : base(filter) | ||
223 | { | |||
| 0 | 224 | MinSize = minSize; | ||
| 0 | 225 | MaxSize = maxSize; | ||
| 0 | 226 | } | ||
227 | | |||
228 | /// <summary> | |||
229 | /// Test a filename to see if it matches the filter. | |||
230 | /// </summary> | |||
231 | /// <param name="name">The filename to test.</param> | |||
232 | /// <returns>True if the filter matches, false otherwise.</returns> | |||
233 | public override bool IsMatch(string name) | |||
234 | { | |||
| 0 | 235 | bool result = base.IsMatch(name); | ||
236 | | |||
| 0 | 237 | if (result) { | ||
| 0 | 238 | var fileInfo = new FileInfo(name); | ||
| 0 | 239 | long length = fileInfo.Length; | ||
| 0 | 240 | result = | ||
| 0 | 241 | (MinSize <= length) && | ||
| 0 | 242 | (MaxSize >= length); | ||
243 | } | |||
| 0 | 244 | return result; | ||
245 | } | |||
246 | | |||
247 | /// <summary> | |||
248 | /// Get/set the minimum size for a file that will match this filter. | |||
249 | /// </summary> | |||
250 | public long MinSize { | |||
| 0 | 251 | get { return minSize_; } | ||
252 | set { | |||
| 0 | 253 | if ((value < 0) || (maxSize_ < value)) { | ||
| 0 | 254 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
255 | } | |||
256 | | |||
| 0 | 257 | minSize_ = value; | ||
| 0 | 258 | } | ||
259 | } | |||
260 | | |||
261 | /// <summary> | |||
262 | /// Get/set the maximum size for a file that will match this filter. | |||
263 | /// </summary> | |||
264 | public long MaxSize { | |||
| 0 | 265 | get { return maxSize_; } | ||
266 | set { | |||
| 0 | 267 | if ((value < 0) || (minSize_ > value)) { | ||
| 0 | 268 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
269 | } | |||
270 | | |||
| 0 | 271 | maxSize_ = value; | ||
| 0 | 272 | } | ||
273 | } | |||
274 | | |||
275 | #region Instance Fields | |||
276 | long minSize_; | |||
| 0 | 277 | long maxSize_ = long.MaxValue; | ||
278 | #endregion | |||
279 | } | |||
280 | } |
| Class: | ICSharpCode.SharpZipLib.Core.NameFilter |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\NameFilter.cs |
| Covered lines: | 68 |
| Uncovered lines: | 18 |
| Coverable lines: | 86 |
| Total lines: | 235 |
| Line coverage: | 79% |
| Branch coverage: | 73.9% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| IsValidExpression(...) | 1 | 0 | 0 |
| IsValidFilterExpression(...) | 7 | 80 | 69.23 |
| SplitQuoted(...) | 8 | 96 | 93.33 |
| ToString() | 1 | 0 | 0 |
| IsIncluded(...) | 5 | 100 | 100 |
| IsExcluded(...) | 4 | 50 | 33.33 |
| IsMatch(...) | 2 | 100 | 100 |
| Compile() | 8 | 75 | 66.67 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.Text; | |||
4 | using System.Text.RegularExpressions; | |||
5 | | |||
6 | namespace ICSharpCode.SharpZipLib.Core | |||
7 | { | |||
8 | /// <summary> | |||
9 | /// NameFilter is a string matching class which allows for both positive and negative | |||
10 | /// matching. | |||
11 | /// A filter is a sequence of independant <see cref="Regex">regular expressions</see> separated by semi-colons ';'. | |||
12 | /// To include a semi-colon it may be quoted as in \;. Each expression can be prefixed by a plus '+' sign or | |||
13 | /// a minus '-' sign to denote the expression is intended to include or exclude names. | |||
14 | /// If neither a plus or minus sign is found include is the default. | |||
15 | /// A given name is tested for inclusion before checking exclusions. Only names matching an include spec | |||
16 | /// and not matching an exclude spec are deemed to match the filter. | |||
17 | /// An empty filter matches any name. | |||
18 | /// </summary> | |||
19 | /// <example>The following expression includes all name ending in '.dat' with the exception of 'dummy.dat' | |||
20 | /// "+\.dat$;-^dummy\.dat$" | |||
21 | /// </example> | |||
22 | public class NameFilter : IScanFilter | |||
23 | { | |||
24 | #region Constructors | |||
25 | /// <summary> | |||
26 | /// Construct an instance based on the filter expression passed | |||
27 | /// </summary> | |||
28 | /// <param name="filter">The filter expression.</param> | |||
| 11 | 29 | public NameFilter(string filter) | ||
30 | { | |||
| 11 | 31 | filter_ = filter; | ||
| 11 | 32 | inclusions_ = new ArrayList(); | ||
| 11 | 33 | exclusions_ = new ArrayList(); | ||
| 11 | 34 | Compile(); | ||
| 11 | 35 | } | ||
36 | #endregion | |||
37 | | |||
38 | /// <summary> | |||
39 | /// Test a string to see if it is a valid regular expression. | |||
40 | /// </summary> | |||
41 | /// <param name="expression">The expression to test.</param> | |||
42 | /// <returns>True if expression is a valid <see cref="System.Text.RegularExpressions.Regex"/> false otherwise.</retu | |||
43 | public static bool IsValidExpression(string expression) | |||
44 | { | |||
| 0 | 45 | bool result = true; | ||
46 | try { | |||
| 0 | 47 | var exp = new Regex(expression, RegexOptions.IgnoreCase | RegexOptions.Singleline); | ||
| 0 | 48 | } catch (ArgumentException) { | ||
| 0 | 49 | result = false; | ||
| 0 | 50 | } | ||
| 0 | 51 | return result; | ||
52 | } | |||
53 | | |||
54 | /// <summary> | |||
55 | /// Test an expression to see if it is valid as a filter. | |||
56 | /// </summary> | |||
57 | /// <param name="toTest">The filter expression to test.</param> | |||
58 | /// <returns>True if the expression is valid, false otherwise.</returns> | |||
59 | public static bool IsValidFilterExpression(string toTest) | |||
60 | { | |||
| 5 | 61 | bool result = true; | ||
62 | | |||
63 | try { | |||
| 5 | 64 | if (toTest != null) { | ||
| 4 | 65 | string[] items = SplitQuoted(toTest); | ||
| 10 | 66 | for (int i = 0; i < items.Length; ++i) { | ||
| 3 | 67 | if ((items[i] != null) && (items[i].Length > 0)) { | ||
68 | string toCompile; | |||
69 | | |||
| 3 | 70 | if (items[i][0] == '+') { | ||
| 0 | 71 | toCompile = items[i].Substring(1, items[i].Length - 1); | ||
| 3 | 72 | } else if (items[i][0] == '-') { | ||
| 0 | 73 | toCompile = items[i].Substring(1, items[i].Length - 1); | ||
| 0 | 74 | } else { | ||
| 3 | 75 | toCompile = items[i]; | ||
76 | } | |||
77 | | |||
| 3 | 78 | var testRegex = new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Singleline); | ||
79 | } | |||
80 | } | |||
81 | } | |||
| 5 | 82 | } catch (ArgumentException) { | ||
| 2 | 83 | result = false; | ||
| 2 | 84 | } | ||
85 | | |||
| 5 | 86 | return result; | ||
87 | } | |||
88 | | |||
89 | /// <summary> | |||
90 | /// Split a string into its component pieces | |||
91 | /// </summary> | |||
92 | /// <param name="original">The original string</param> | |||
93 | /// <returns>Returns an array of <see cref="T:System.String"/> values containing the individual filter elements.</re | |||
94 | public static string[] SplitQuoted(string original) | |||
95 | { | |||
| 13 | 96 | char escape = '\\'; | ||
| 13 | 97 | char[] separators = { ';' }; | ||
98 | | |||
| 13 | 99 | var result = new ArrayList(); | ||
100 | | |||
| 13 | 101 | if (!string.IsNullOrEmpty(original)) { | ||
| 11 | 102 | int endIndex = -1; | ||
| 11 | 103 | var b = new StringBuilder(); | ||
104 | | |||
| 78 | 105 | while (endIndex < original.Length) { | ||
| 67 | 106 | endIndex += 1; | ||
| 67 | 107 | if (endIndex >= original.Length) { | ||
| 11 | 108 | result.Add(b.ToString()); | ||
| 67 | 109 | } else if (original[endIndex] == escape) { | ||
| 11 | 110 | endIndex += 1; | ||
| 11 | 111 | if (endIndex >= original.Length) { | ||
| 0 | 112 | throw new ArgumentException("Missing terminating escape character", nameof(original)); | ||
113 | } | |||
114 | // include escape if this is not an escaped separator | |||
| 11 | 115 | if (Array.IndexOf(separators, original[endIndex]) < 0) | ||
| 6 | 116 | b.Append(escape); | ||
117 | | |||
| 11 | 118 | b.Append(original[endIndex]); | ||
| 11 | 119 | } else { | ||
| 45 | 120 | if (Array.IndexOf(separators, original[endIndex]) >= 0) { | ||
| 11 | 121 | result.Add(b.ToString()); | ||
| 11 | 122 | b.Length = 0; | ||
| 11 | 123 | } else { | ||
| 34 | 124 | b.Append(original[endIndex]); | ||
125 | } | |||
126 | } | |||
127 | } | |||
128 | } | |||
129 | | |||
| 13 | 130 | return (string[])result.ToArray(typeof(string)); | ||
131 | } | |||
132 | | |||
133 | /// <summary> | |||
134 | /// Convert this filter to its string equivalent. | |||
135 | /// </summary> | |||
136 | /// <returns>The string equivalent for this filter.</returns> | |||
137 | public override string ToString() | |||
138 | { | |||
| 0 | 139 | return filter_; | ||
140 | } | |||
141 | | |||
142 | /// <summary> | |||
143 | /// Test a value to see if it is included by the filter. | |||
144 | /// </summary> | |||
145 | /// <param name="name">The value to test.</param> | |||
146 | /// <returns>True if the value is included, false otherwise.</returns> | |||
147 | public bool IsIncluded(string name) | |||
148 | { | |||
| 14149 | 149 | bool result = false; | ||
| 14149 | 150 | if (inclusions_.Count == 0) { | ||
| 2 | 151 | result = true; | ||
| 2 | 152 | } else { | ||
| 56584 | 153 | foreach (Regex r in inclusions_) { | ||
| 14147 | 154 | if (r.IsMatch(name)) { | ||
| 4 | 155 | result = true; | ||
| 4 | 156 | break; | ||
157 | } | |||
158 | } | |||
159 | } | |||
| 14149 | 160 | return result; | ||
161 | } | |||
162 | | |||
163 | /// <summary> | |||
164 | /// Test a value to see if it is excluded by the filter. | |||
165 | /// </summary> | |||
166 | /// <param name="name">The value to test.</param> | |||
167 | /// <returns>True if the value is excluded, false otherwise.</returns> | |||
168 | public bool IsExcluded(string name) | |||
169 | { | |||
| 5 | 170 | bool result = false; | ||
| 10 | 171 | foreach (Regex r in exclusions_) { | ||
| 0 | 172 | if (r.IsMatch(name)) { | ||
| 0 | 173 | result = true; | ||
| 0 | 174 | break; | ||
175 | } | |||
176 | } | |||
| 5 | 177 | return result; | ||
178 | } | |||
179 | | |||
180 | #region IScanFilter Members | |||
181 | /// <summary> | |||
182 | /// Test a value to see if it matches the filter. | |||
183 | /// </summary> | |||
184 | /// <param name="name">The value to test.</param> | |||
185 | /// <returns>True if the value matches, false otherwise.</returns> | |||
186 | public bool IsMatch(string name) | |||
187 | { | |||
| 14148 | 188 | return (IsIncluded(name) && !IsExcluded(name)); | ||
189 | } | |||
190 | #endregion | |||
191 | | |||
192 | /// <summary> | |||
193 | /// Compile this filter. | |||
194 | /// </summary> | |||
195 | void Compile() | |||
196 | { | |||
197 | // TODO: Check to see if combining RE's makes it faster/smaller. | |||
198 | // simple scheme would be to have one RE for inclusion and one for exclusion. | |||
| 11 | 199 | if (filter_ == null) { | ||
| 6 | 200 | return; | ||
201 | } | |||
202 | | |||
| 5 | 203 | string[] items = SplitQuoted(filter_); | ||
| 20 | 204 | for (int i = 0; i < items.Length; ++i) { | ||
| 5 | 205 | if ((items[i] != null) && (items[i].Length > 0)) { | ||
| 5 | 206 | bool include = (items[i][0] != '-'); | ||
207 | string toCompile; | |||
208 | | |||
| 5 | 209 | if (items[i][0] == '+') { | ||
| 0 | 210 | toCompile = items[i].Substring(1, items[i].Length - 1); | ||
| 5 | 211 | } else if (items[i][0] == '-') { | ||
| 0 | 212 | toCompile = items[i].Substring(1, items[i].Length - 1); | ||
| 0 | 213 | } else { | ||
| 5 | 214 | toCompile = items[i]; | ||
215 | } | |||
216 | | |||
217 | // NOTE: Regular expressions can fail to compile here for a number of reasons that cause an exception | |||
218 | // these are left unhandled here as the caller is responsible for ensuring all is valid. | |||
219 | // several functions IsValidFilterExpression and IsValidExpression are provided for such checking | |||
| 5 | 220 | if (include) { | ||
| 5 | 221 | inclusions_.Add(new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleli | ||
| 5 | 222 | } else { | ||
| 0 | 223 | exclusions_.Add(new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleli | ||
224 | } | |||
225 | } | |||
226 | } | |||
| 5 | 227 | } | ||
228 | | |||
229 | #region Instance Fields | |||
230 | string filter_; | |||
231 | ArrayList inclusions_; | |||
232 | ArrayList exclusions_; | |||
233 | #endregion | |||
234 | } | |||
235 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.Streams.OutputWindow |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\Streams\OutputWindow.cs |
| Covered lines: | 46 |
| Uncovered lines: | 20 |
| Coverable lines: | 66 |
| Total lines: | 195 |
| Line coverage: | 69.6% |
| Branch coverage: | 53.3% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| Write(...) | 2 | 80 | 66.67 |
| SlowRepeat(...) | 2 | 0 | 0 |
| Repeat(...) | 6 | 78.57 | 72.73 |
| CopyStored(...) | 3 | 100 | 80 |
| CopyDict(...) | 4 | 0 | 0 |
| GetFreeSpace() | 1 | 100 | 100 |
| GetAvailable() | 1 | 100 | 100 |
| CopyOutput(...) | 4 | 93.75 | 85.71 |
| Reset() | 1 | 100 | 100 |
| .ctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// Contains the output from the Inflation process. | |||
7 | /// We need to have a window so that we can refer backwards into the output stream | |||
8 | /// to repeat stuff.<br/> | |||
9 | /// Author of the original java version : John Leuner | |||
10 | /// </summary> | |||
11 | public class OutputWindow | |||
12 | { | |||
13 | #region Constants | |||
14 | const int WindowSize = 1 << 15; | |||
15 | const int WindowMask = WindowSize - 1; | |||
16 | #endregion | |||
17 | | |||
18 | #region Instance Fields | |||
| 435 | 19 | byte[] window = new byte[WindowSize]; //The window is 2^15 bytes | ||
20 | int windowEnd; | |||
21 | int windowFilled; | |||
22 | #endregion | |||
23 | | |||
24 | /// <summary> | |||
25 | /// Write a byte to this output window | |||
26 | /// </summary> | |||
27 | /// <param name="value">value to write</param> | |||
28 | /// <exception cref="InvalidOperationException"> | |||
29 | /// if window is full | |||
30 | /// </exception> | |||
31 | public void Write(int value) | |||
32 | { | |||
| 9229 | 33 | if (windowFilled++ == WindowSize) { | ||
| 0 | 34 | throw new InvalidOperationException("Window full"); | ||
35 | } | |||
| 9229 | 36 | window[windowEnd++] = (byte)value; | ||
| 9229 | 37 | windowEnd &= WindowMask; | ||
| 9229 | 38 | } | ||
39 | | |||
40 | | |||
41 | private void SlowRepeat(int repStart, int length, int distance) | |||
42 | { | |||
| 0 | 43 | while (length-- > 0) { | ||
| 0 | 44 | window[windowEnd++] = window[repStart++]; | ||
| 0 | 45 | windowEnd &= WindowMask; | ||
| 0 | 46 | repStart &= WindowMask; | ||
47 | } | |||
| 0 | 48 | } | ||
49 | | |||
50 | /// <summary> | |||
51 | /// Append a byte pattern already in the window itself | |||
52 | /// </summary> | |||
53 | /// <param name="length">length of pattern to copy</param> | |||
54 | /// <param name="distance">distance from end of window pattern occurs</param> | |||
55 | /// <exception cref="InvalidOperationException"> | |||
56 | /// If the repeated data overflows the window | |||
57 | /// </exception> | |||
58 | public void Repeat(int length, int distance) | |||
59 | { | |||
| 97 | 60 | if ((windowFilled += length) > WindowSize) { | ||
| 0 | 61 | throw new InvalidOperationException("Window full"); | ||
62 | } | |||
63 | | |||
| 97 | 64 | int repStart = (windowEnd - distance) & WindowMask; | ||
| 97 | 65 | int border = WindowSize - length; | ||
| 97 | 66 | if ((repStart <= border) && (windowEnd < border)) { | ||
| 97 | 67 | if (length <= distance) { | ||
| 37 | 68 | System.Array.Copy(window, repStart, window, windowEnd, length); | ||
| 37 | 69 | windowEnd += length; | ||
| 37 | 70 | } else { | ||
71 | // We have to copy manually, since the repeat pattern overlaps. | |||
| 11461 | 72 | while (length-- > 0) { | ||
| 11401 | 73 | window[windowEnd++] = window[repStart++]; | ||
74 | } | |||
75 | } | |||
| 60 | 76 | } else { | ||
| 0 | 77 | SlowRepeat(repStart, length, distance); | ||
78 | } | |||
| 0 | 79 | } | ||
80 | | |||
81 | /// <summary> | |||
82 | /// Copy from input manipulator to internal window | |||
83 | /// </summary> | |||
84 | /// <param name="input">source of data</param> | |||
85 | /// <param name="length">length of data to copy</param> | |||
86 | /// <returns>the number of bytes copied</returns> | |||
87 | public int CopyStored(StreamManipulator input, int length) | |||
88 | { | |||
| 2280 | 89 | length = Math.Min(Math.Min(length, WindowSize - windowFilled), input.AvailableBytes); | ||
90 | int copied; | |||
91 | | |||
| 2280 | 92 | int tailLen = WindowSize - windowEnd; | ||
| 2280 | 93 | if (length > tailLen) { | ||
| 96 | 94 | copied = input.CopyBytes(window, windowEnd, tailLen); | ||
| 96 | 95 | if (copied == tailLen) { | ||
| 96 | 96 | copied += input.CopyBytes(window, 0, length - tailLen); | ||
97 | } | |||
| 96 | 98 | } else { | ||
| 2184 | 99 | copied = input.CopyBytes(window, windowEnd, length); | ||
100 | } | |||
101 | | |||
| 2280 | 102 | windowEnd = (windowEnd + copied) & WindowMask; | ||
| 2280 | 103 | windowFilled += copied; | ||
| 2280 | 104 | return copied; | ||
105 | } | |||
106 | | |||
107 | /// <summary> | |||
108 | /// Copy dictionary to window | |||
109 | /// </summary> | |||
110 | /// <param name="dictionary">source dictionary</param> | |||
111 | /// <param name="offset">offset of start in source dictionary</param> | |||
112 | /// <param name="length">length of dictionary</param> | |||
113 | /// <exception cref="InvalidOperationException"> | |||
114 | /// If window isnt empty | |||
115 | /// </exception> | |||
116 | public void CopyDict(byte[] dictionary, int offset, int length) | |||
117 | { | |||
| 0 | 118 | if (dictionary == null) { | ||
| 0 | 119 | throw new ArgumentNullException(nameof(dictionary)); | ||
120 | } | |||
121 | | |||
| 0 | 122 | if (windowFilled > 0) { | ||
| 0 | 123 | throw new InvalidOperationException(); | ||
124 | } | |||
125 | | |||
| 0 | 126 | if (length > WindowSize) { | ||
| 0 | 127 | offset += length - WindowSize; | ||
| 0 | 128 | length = WindowSize; | ||
129 | } | |||
| 0 | 130 | System.Array.Copy(dictionary, offset, window, 0, length); | ||
| 0 | 131 | windowEnd = length & WindowMask; | ||
| 0 | 132 | } | ||
133 | | |||
134 | /// <summary> | |||
135 | /// Get remaining unfilled space in window | |||
136 | /// </summary> | |||
137 | /// <returns>Number of bytes left in window</returns> | |||
138 | public int GetFreeSpace() | |||
139 | { | |||
| 371 | 140 | return WindowSize - windowFilled; | ||
141 | } | |||
142 | | |||
143 | /// <summary> | |||
144 | /// Get bytes available for output in window | |||
145 | /// </summary> | |||
146 | /// <returns>Number of bytes filled</returns> | |||
147 | public int GetAvailable() | |||
148 | { | |||
| 3783 | 149 | return windowFilled; | ||
150 | } | |||
151 | | |||
152 | /// <summary> | |||
153 | /// Copy contents of window to output | |||
154 | /// </summary> | |||
155 | /// <param name="output">buffer to copy to</param> | |||
156 | /// <param name="offset">offset to start at</param> | |||
157 | /// <param name="len">number of bytes to count</param> | |||
158 | /// <returns>The number of bytes copied</returns> | |||
159 | /// <exception cref="InvalidOperationException"> | |||
160 | /// If a window underflow occurs | |||
161 | /// </exception> | |||
162 | public int CopyOutput(byte[] output, int offset, int len) | |||
163 | { | |||
| 4523 | 164 | int copyEnd = windowEnd; | ||
| 4523 | 165 | if (len > windowFilled) { | ||
| 4401 | 166 | len = windowFilled; | ||
| 4401 | 167 | } else { | ||
| 122 | 168 | copyEnd = (windowEnd - windowFilled + len) & WindowMask; | ||
169 | } | |||
170 | | |||
| 4523 | 171 | int copied = len; | ||
| 4523 | 172 | int tailLen = len - copyEnd; | ||
173 | | |||
| 4523 | 174 | if (tailLen > 0) { | ||
| 102 | 175 | System.Array.Copy(window, WindowSize - tailLen, output, offset, tailLen); | ||
| 102 | 176 | offset += tailLen; | ||
| 102 | 177 | len = copyEnd; | ||
178 | } | |||
| 4523 | 179 | System.Array.Copy(window, copyEnd - len, output, offset, len); | ||
| 4523 | 180 | windowFilled -= copied; | ||
| 4523 | 181 | if (windowFilled < 0) { | ||
| 0 | 182 | throw new InvalidOperationException(); | ||
183 | } | |||
| 4523 | 184 | return copied; | ||
185 | } | |||
186 | | |||
187 | /// <summary> | |||
188 | /// Reset by clearing window so <see cref="GetAvailable">GetAvailable</see> returns 0 | |||
189 | /// </summary> | |||
190 | public void Reset() | |||
191 | { | |||
| 41 | 192 | windowFilled = windowEnd = 0; | ||
| 41 | 193 | } | ||
194 | } | |||
195 | } |
| Class: | ICSharpCode.SharpZipLib.Core.PathFilter |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\PathFilter.cs |
| Covered lines: | 8 |
| Uncovered lines: | 0 |
| Coverable lines: | 8 |
| Total lines: | 280 |
| Line coverage: | 100% |
| Branch coverage: | 50% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| IsMatch(...) | 4 | 100 | 60 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Core | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// PathFilter filters directories and files using a form of <see cref="System.Text.RegularExpressions.Regex">regular | |||
8 | /// by full path name. | |||
9 | /// See <see cref="NameFilter">NameFilter</see> for more detail on filtering. | |||
10 | /// </summary> | |||
11 | public class PathFilter : IScanFilter | |||
12 | { | |||
13 | #region Constructors | |||
14 | /// <summary> | |||
15 | /// Initialise a new instance of <see cref="PathFilter"></see>. | |||
16 | /// </summary> | |||
17 | /// <param name="filter">The <see cref="NameFilter">filter</see> expression to apply.</param> | |||
| 8 | 18 | public PathFilter(string filter) | ||
19 | { | |||
| 8 | 20 | nameFilter_ = new NameFilter(filter); | ||
| 8 | 21 | } | ||
22 | #endregion | |||
23 | | |||
24 | #region IScanFilter Members | |||
25 | /// <summary> | |||
26 | /// Test a name to see if it matches the filter. | |||
27 | /// </summary> | |||
28 | /// <param name="name">The name to test.</param> | |||
29 | /// <returns>True if the name matches, false otherwise.</returns> | |||
30 | /// <remarks><see cref="Path.GetFullPath(string)"/> is used to get the full path before matching.</remarks> | |||
31 | public virtual bool IsMatch(string name) | |||
32 | { | |||
| 14147 | 33 | bool result = false; | ||
34 | | |||
| 14147 | 35 | if (name != null) { | ||
| 14147 | 36 | string cooked = (name.Length > 0) ? Path.GetFullPath(name) : ""; | ||
| 14147 | 37 | result = nameFilter_.IsMatch(cooked); | ||
38 | } | |||
| 14147 | 39 | return result; | ||
40 | } | |||
41 | | |||
42 | readonly | |||
43 | #endregion | |||
44 | | |||
45 | #region Instance Fields | |||
46 | NameFilter nameFilter_; | |||
47 | #endregion | |||
48 | } | |||
49 | | |||
50 | /// <summary> | |||
51 | /// ExtendedPathFilter filters based on name, file size, and the last write time of the file. | |||
52 | /// </summary> | |||
53 | /// <remarks>Provides an example of how to customise filtering.</remarks> | |||
54 | public class ExtendedPathFilter : PathFilter | |||
55 | { | |||
56 | #region Constructors | |||
57 | /// <summary> | |||
58 | /// Initialise a new instance of ExtendedPathFilter. | |||
59 | /// </summary> | |||
60 | /// <param name="filter">The filter to apply.</param> | |||
61 | /// <param name="minSize">The minimum file size to include.</param> | |||
62 | /// <param name="maxSize">The maximum file size to include.</param> | |||
63 | public ExtendedPathFilter(string filter, | |||
64 | long minSize, long maxSize) | |||
65 | : base(filter) | |||
66 | { | |||
67 | MinSize = minSize; | |||
68 | MaxSize = maxSize; | |||
69 | } | |||
70 | | |||
71 | /// <summary> | |||
72 | /// Initialise a new instance of ExtendedPathFilter. | |||
73 | /// </summary> | |||
74 | /// <param name="filter">The filter to apply.</param> | |||
75 | /// <param name="minDate">The minimum <see cref="DateTime"/> to include.</param> | |||
76 | /// <param name="maxDate">The maximum <see cref="DateTime"/> to include.</param> | |||
77 | public ExtendedPathFilter(string filter, | |||
78 | DateTime minDate, DateTime maxDate) | |||
79 | : base(filter) | |||
80 | { | |||
81 | MinDate = minDate; | |||
82 | MaxDate = maxDate; | |||
83 | } | |||
84 | | |||
85 | /// <summary> | |||
86 | /// Initialise a new instance of ExtendedPathFilter. | |||
87 | /// </summary> | |||
88 | /// <param name="filter">The filter to apply.</param> | |||
89 | /// <param name="minSize">The minimum file size to include.</param> | |||
90 | /// <param name="maxSize">The maximum file size to include.</param> | |||
91 | /// <param name="minDate">The minimum <see cref="DateTime"/> to include.</param> | |||
92 | /// <param name="maxDate">The maximum <see cref="DateTime"/> to include.</param> | |||
93 | public ExtendedPathFilter(string filter, | |||
94 | long minSize, long maxSize, | |||
95 | DateTime minDate, DateTime maxDate) | |||
96 | : base(filter) | |||
97 | { | |||
98 | MinSize = minSize; | |||
99 | MaxSize = maxSize; | |||
100 | MinDate = minDate; | |||
101 | MaxDate = maxDate; | |||
102 | } | |||
103 | #endregion | |||
104 | | |||
105 | #region IScanFilter Members | |||
106 | /// <summary> | |||
107 | /// Test a filename to see if it matches the filter. | |||
108 | /// </summary> | |||
109 | /// <param name="name">The filename to test.</param> | |||
110 | /// <returns>True if the filter matches, false otherwise.</returns> | |||
111 | /// <exception cref="System.IO.FileNotFoundException">The <see paramref="fileName"/> doesnt exist</exception> | |||
112 | public override bool IsMatch(string name) | |||
113 | { | |||
114 | bool result = base.IsMatch(name); | |||
115 | | |||
116 | if (result) { | |||
117 | var fileInfo = new FileInfo(name); | |||
118 | result = | |||
119 | (MinSize <= fileInfo.Length) && | |||
120 | (MaxSize >= fileInfo.Length) && | |||
121 | (MinDate <= fileInfo.LastWriteTime) && | |||
122 | (MaxDate >= fileInfo.LastWriteTime) | |||
123 | ; | |||
124 | } | |||
125 | return result; | |||
126 | } | |||
127 | #endregion | |||
128 | | |||
129 | #region Properties | |||
130 | /// <summary> | |||
131 | /// Get/set the minimum size/length for a file that will match this filter. | |||
132 | /// </summary> | |||
133 | /// <remarks>The default value is zero.</remarks> | |||
134 | /// <exception cref="ArgumentOutOfRangeException">value is less than zero; greater than <see cref="MaxSize"/></excep | |||
135 | public long MinSize { | |||
136 | get { return minSize_; } | |||
137 | set { | |||
138 | if ((value < 0) || (maxSize_ < value)) { | |||
139 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
140 | } | |||
141 | | |||
142 | minSize_ = value; | |||
143 | } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get/set the maximum size/length for a file that will match this filter. | |||
148 | /// </summary> | |||
149 | /// <remarks>The default value is <see cref="System.Int64.MaxValue"/></remarks> | |||
150 | /// <exception cref="ArgumentOutOfRangeException">value is less than zero or less than <see cref="MinSize"/></except | |||
151 | public long MaxSize { | |||
152 | get { return maxSize_; } | |||
153 | set { | |||
154 | if ((value < 0) || (minSize_ > value)) { | |||
155 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
156 | } | |||
157 | | |||
158 | maxSize_ = value; | |||
159 | } | |||
160 | } | |||
161 | | |||
162 | /// <summary> | |||
163 | /// Get/set the minimum <see cref="DateTime"/> value that will match for this filter. | |||
164 | /// </summary> | |||
165 | /// <remarks>Files with a LastWrite time less than this value are excluded by the filter.</remarks> | |||
166 | public DateTime MinDate { | |||
167 | get { | |||
168 | return minDate_; | |||
169 | } | |||
170 | | |||
171 | set { | |||
172 | if (value > maxDate_) { | |||
173 | throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MaxDate"); | |||
174 | } | |||
175 | | |||
176 | minDate_ = value; | |||
177 | } | |||
178 | } | |||
179 | | |||
180 | /// <summary> | |||
181 | /// Get/set the maximum <see cref="DateTime"/> value that will match for this filter. | |||
182 | /// </summary> | |||
183 | /// <remarks>Files with a LastWrite time greater than this value are excluded by the filter.</remarks> | |||
184 | public DateTime MaxDate { | |||
185 | get { | |||
186 | return maxDate_; | |||
187 | } | |||
188 | | |||
189 | set { | |||
190 | if (minDate_ > value) { | |||
191 | throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MinDate"); | |||
192 | } | |||
193 | | |||
194 | maxDate_ = value; | |||
195 | } | |||
196 | } | |||
197 | #endregion | |||
198 | | |||
199 | #region Instance Fields | |||
200 | long minSize_; | |||
201 | long maxSize_ = long.MaxValue; | |||
202 | DateTime minDate_ = DateTime.MinValue; | |||
203 | DateTime maxDate_ = DateTime.MaxValue; | |||
204 | #endregion | |||
205 | } | |||
206 | | |||
207 | /// <summary> | |||
208 | /// NameAndSizeFilter filters based on name and file size. | |||
209 | /// </summary> | |||
210 | /// <remarks>A sample showing how filters might be extended.</remarks> | |||
211 | [Obsolete("Use ExtendedPathFilter instead")] | |||
212 | public class NameAndSizeFilter : PathFilter | |||
213 | { | |||
214 | | |||
215 | /// <summary> | |||
216 | /// Initialise a new instance of NameAndSizeFilter. | |||
217 | /// </summary> | |||
218 | /// <param name="filter">The filter to apply.</param> | |||
219 | /// <param name="minSize">The minimum file size to include.</param> | |||
220 | /// <param name="maxSize">The maximum file size to include.</param> | |||
221 | public NameAndSizeFilter(string filter, long minSize, long maxSize) | |||
222 | : base(filter) | |||
223 | { | |||
224 | MinSize = minSize; | |||
225 | MaxSize = maxSize; | |||
226 | } | |||
227 | | |||
228 | /// <summary> | |||
229 | /// Test a filename to see if it matches the filter. | |||
230 | /// </summary> | |||
231 | /// <param name="name">The filename to test.</param> | |||
232 | /// <returns>True if the filter matches, false otherwise.</returns> | |||
233 | public override bool IsMatch(string name) | |||
234 | { | |||
235 | bool result = base.IsMatch(name); | |||
236 | | |||
237 | if (result) { | |||
238 | var fileInfo = new FileInfo(name); | |||
239 | long length = fileInfo.Length; | |||
240 | result = | |||
241 | (MinSize <= length) && | |||
242 | (MaxSize >= length); | |||
243 | } | |||
244 | return result; | |||
245 | } | |||
246 | | |||
247 | /// <summary> | |||
248 | /// Get/set the minimum size for a file that will match this filter. | |||
249 | /// </summary> | |||
250 | public long MinSize { | |||
251 | get { return minSize_; } | |||
252 | set { | |||
253 | if ((value < 0) || (maxSize_ < value)) { | |||
254 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
255 | } | |||
256 | | |||
257 | minSize_ = value; | |||
258 | } | |||
259 | } | |||
260 | | |||
261 | /// <summary> | |||
262 | /// Get/set the maximum size for a file that will match this filter. | |||
263 | /// </summary> | |||
264 | public long MaxSize { | |||
265 | get { return maxSize_; } | |||
266 | set { | |||
267 | if ((value < 0) || (minSize_ > value)) { | |||
268 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
269 | } | |||
270 | | |||
271 | maxSize_ = value; | |||
272 | } | |||
273 | } | |||
274 | | |||
275 | #region Instance Fields | |||
276 | long minSize_; | |||
277 | long maxSize_ = long.MaxValue; | |||
278 | #endregion | |||
279 | } | |||
280 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.PendingBuffer |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\PendingBuffer.cs |
| Covered lines: | 40 |
| Uncovered lines: | 18 |
| Coverable lines: | 58 |
| Total lines: | 255 |
| Line coverage: | 68.9% |
| Branch coverage: | 90% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| Reset() | 1 | 100 | 100 |
| WriteByte(...) | 1 | 0 | 0 |
| WriteShort(...) | 1 | 100 | 100 |
| WriteInt(...) | 1 | 0 | 0 |
| WriteBlock(...) | 1 | 100 | 100 |
| AlignToByte() | 3 | 100 | 100 |
| WriteBits(...) | 2 | 100 | 100 |
| WriteShortMSB(...) | 1 | 100 | 100 |
| Flush(...) | 3 | 76.92 | 80 |
| ToByteArray() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Zip.Compression | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// This class is general purpose class for writing data to a buffer. | |||
7 | /// | |||
8 | /// It allows you to write bits as well as bytes | |||
9 | /// Based on DeflaterPending.java | |||
10 | /// | |||
11 | /// author of the original java version : Jochen Hoenicke | |||
12 | /// </summary> | |||
13 | public class PendingBuffer | |||
14 | { | |||
15 | readonly | |||
16 | #region Instance Fields | |||
17 | /// <summary> | |||
18 | /// Internal work buffer | |||
19 | /// </summary> | |||
20 | byte[] buffer_; | |||
21 | | |||
22 | int start; | |||
23 | int end; | |||
24 | | |||
25 | uint bits; | |||
26 | int bitCount; | |||
27 | #endregion | |||
28 | | |||
29 | #region Constructors | |||
30 | /// <summary> | |||
31 | /// construct instance using default buffer size of 4096 | |||
32 | /// </summary> | |||
| 0 | 33 | public PendingBuffer() : this(4096) | ||
34 | { | |||
| 0 | 35 | } | ||
36 | | |||
37 | /// <summary> | |||
38 | /// construct instance using specified buffer size | |||
39 | /// </summary> | |||
40 | /// <param name="bufferSize"> | |||
41 | /// size to use for internal buffer | |||
42 | /// </param> | |||
| 273 | 43 | public PendingBuffer(int bufferSize) | ||
44 | { | |||
| 273 | 45 | buffer_ = new byte[bufferSize]; | ||
| 273 | 46 | } | ||
47 | | |||
48 | #endregion | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Clear internal state/buffers | |||
52 | /// </summary> | |||
53 | public void Reset() | |||
54 | { | |||
| 392 | 55 | start = end = bitCount = 0; | ||
| 392 | 56 | } | ||
57 | | |||
58 | /// <summary> | |||
59 | /// Write a byte to buffer | |||
60 | /// </summary> | |||
61 | /// <param name="value"> | |||
62 | /// The value to write | |||
63 | /// </param> | |||
64 | public void WriteByte(int value) | |||
65 | { | |||
66 | #if DebugDeflation | |||
67 | if (DeflaterConstants.DEBUGGING && (start != 0) ) | |||
68 | { | |||
69 | throw new SharpZipBaseException("Debug check: start != 0"); | |||
70 | } | |||
71 | #endif | |||
| 0 | 72 | buffer_[end++] = unchecked((byte)value); | ||
| 0 | 73 | } | ||
74 | | |||
75 | /// <summary> | |||
76 | /// Write a short value to buffer LSB first | |||
77 | /// </summary> | |||
78 | /// <param name="value"> | |||
79 | /// The value to write. | |||
80 | /// </param> | |||
81 | public void WriteShort(int value) | |||
82 | { | |||
83 | #if DebugDeflation | |||
84 | if (DeflaterConstants.DEBUGGING && (start != 0) ) | |||
85 | { | |||
86 | throw new SharpZipBaseException("Debug check: start != 0"); | |||
87 | } | |||
88 | #endif | |||
| 586 | 89 | buffer_[end++] = unchecked((byte)value); | ||
| 586 | 90 | buffer_[end++] = unchecked((byte)(value >> 8)); | ||
| 586 | 91 | } | ||
92 | | |||
93 | /// <summary> | |||
94 | /// write an integer LSB first | |||
95 | /// </summary> | |||
96 | /// <param name="value">The value to write.</param> | |||
97 | public void WriteInt(int value) | |||
98 | { | |||
99 | #if DebugDeflation | |||
100 | if (DeflaterConstants.DEBUGGING && (start != 0) ) | |||
101 | { | |||
102 | throw new SharpZipBaseException("Debug check: start != 0"); | |||
103 | } | |||
104 | #endif | |||
| 0 | 105 | buffer_[end++] = unchecked((byte)value); | ||
| 0 | 106 | buffer_[end++] = unchecked((byte)(value >> 8)); | ||
| 0 | 107 | buffer_[end++] = unchecked((byte)(value >> 16)); | ||
| 0 | 108 | buffer_[end++] = unchecked((byte)(value >> 24)); | ||
| 0 | 109 | } | ||
110 | | |||
111 | /// <summary> | |||
112 | /// Write a block of data to buffer | |||
113 | /// </summary> | |||
114 | /// <param name="block">data to write</param> | |||
115 | /// <param name="offset">offset of first byte to write</param> | |||
116 | /// <param name="length">number of bytes to write</param> | |||
117 | public void WriteBlock(byte[] block, int offset, int length) | |||
118 | { | |||
119 | #if DebugDeflation | |||
120 | if (DeflaterConstants.DEBUGGING && (start != 0) ) | |||
121 | { | |||
122 | throw new SharpZipBaseException("Debug check: start != 0"); | |||
123 | } | |||
124 | #endif | |||
| 293 | 125 | System.Array.Copy(block, offset, buffer_, end, length); | ||
| 293 | 126 | end += length; | ||
| 293 | 127 | } | ||
128 | | |||
129 | /// <summary> | |||
130 | /// The number of bits written to the buffer | |||
131 | /// </summary> | |||
132 | public int BitCount { | |||
133 | get { | |||
| 0 | 134 | return bitCount; | ||
135 | } | |||
136 | } | |||
137 | | |||
138 | /// <summary> | |||
139 | /// Align internal buffer on a byte boundary | |||
140 | /// </summary> | |||
141 | public void AlignToByte() | |||
142 | { | |||
143 | #if DebugDeflation | |||
144 | if (DeflaterConstants.DEBUGGING && (start != 0) ) | |||
145 | { | |||
146 | throw new SharpZipBaseException("Debug check: start != 0"); | |||
147 | } | |||
148 | #endif | |||
| 598 | 149 | if (bitCount > 0) { | ||
| 531 | 150 | buffer_[end++] = unchecked((byte)bits); | ||
| 531 | 151 | if (bitCount > 8) { | ||
| 174 | 152 | buffer_[end++] = unchecked((byte)(bits >> 8)); | ||
153 | } | |||
154 | } | |||
| 598 | 155 | bits = 0; | ||
| 598 | 156 | bitCount = 0; | ||
| 598 | 157 | } | ||
158 | | |||
159 | /// <summary> | |||
160 | /// Write bits to internal buffer | |||
161 | /// </summary> | |||
162 | /// <param name="b">source of bits</param> | |||
163 | /// <param name="count">number of bits to write</param> | |||
164 | public void WriteBits(int b, int count) | |||
165 | { | |||
166 | #if DebugDeflation | |||
167 | if (DeflaterConstants.DEBUGGING && (start != 0) ) | |||
168 | { | |||
169 | throw new SharpZipBaseException("Debug check: start != 0"); | |||
170 | } | |||
171 | | |||
172 | // if (DeflaterConstants.DEBUGGING) { | |||
173 | // //Console.WriteLine("writeBits("+b+","+count+")"); | |||
174 | // } | |||
175 | #endif | |||
| 8954 | 176 | bits |= (uint)(b << bitCount); | ||
| 8954 | 177 | bitCount += count; | ||
| 8954 | 178 | if (bitCount >= 16) { | ||
| 4181 | 179 | buffer_[end++] = unchecked((byte)bits); | ||
| 4181 | 180 | buffer_[end++] = unchecked((byte)(bits >> 8)); | ||
| 4181 | 181 | bits >>= 16; | ||
| 4181 | 182 | bitCount -= 16; | ||
183 | } | |||
| 8954 | 184 | } | ||
185 | | |||
186 | /// <summary> | |||
187 | /// Write a short value to internal buffer most significant byte first | |||
188 | /// </summary> | |||
189 | /// <param name="s">value to write</param> | |||
190 | public void WriteShortMSB(int s) | |||
191 | { | |||
192 | #if DebugDeflation | |||
193 | if (DeflaterConstants.DEBUGGING && (start != 0) ) | |||
194 | { | |||
195 | throw new SharpZipBaseException("Debug check: start != 0"); | |||
196 | } | |||
197 | #endif | |||
| 42 | 198 | buffer_[end++] = unchecked((byte)(s >> 8)); | ||
| 42 | 199 | buffer_[end++] = unchecked((byte)s); | ||
| 42 | 200 | } | ||
201 | | |||
202 | /// <summary> | |||
203 | /// Indicates if buffer has been flushed | |||
204 | /// </summary> | |||
205 | public bool IsFlushed { | |||
206 | get { | |||
| 10175 | 207 | return end == 0; | ||
208 | } | |||
209 | } | |||
210 | | |||
211 | /// <summary> | |||
212 | /// Flushes the pending buffer into the given output array. If the | |||
213 | /// output array is to small, only a partial flush is done. | |||
214 | /// </summary> | |||
215 | /// <param name="output">The output array.</param> | |||
216 | /// <param name="offset">The offset into output array.</param> | |||
217 | /// <param name="length">The maximum number of bytes to store.</param> | |||
218 | /// <returns>The number of bytes flushed.</returns> | |||
219 | public int Flush(byte[] output, int offset, int length) | |||
220 | { | |||
| 13248 | 221 | if (bitCount >= 8) { | ||
| 0 | 222 | buffer_[end++] = unchecked((byte)bits); | ||
| 0 | 223 | bits >>= 8; | ||
| 0 | 224 | bitCount -= 8; | ||
225 | } | |||
226 | | |||
| 13248 | 227 | if (length > end - start) { | ||
| 5183 | 228 | length = end - start; | ||
| 5183 | 229 | System.Array.Copy(buffer_, start, output, offset, length); | ||
| 5183 | 230 | start = 0; | ||
| 5183 | 231 | end = 0; | ||
| 5183 | 232 | } else { | ||
| 8065 | 233 | System.Array.Copy(buffer_, start, output, offset, length); | ||
| 8065 | 234 | start += length; | ||
235 | } | |||
| 13248 | 236 | return length; | ||
237 | } | |||
238 | | |||
239 | /// <summary> | |||
240 | /// Convert internal buffer to byte array. | |||
241 | /// Buffer is empty on completion | |||
242 | /// </summary> | |||
243 | /// <returns> | |||
244 | /// The internal buffer contents converted to a byte array. | |||
245 | /// </returns> | |||
246 | public byte[] ToByteArray() | |||
247 | { | |||
| 0 | 248 | byte[] result = new byte[end - start]; | ||
| 0 | 249 | System.Array.Copy(buffer_, start, result, 0, result.Length); | ||
| 0 | 250 | start = 0; | ||
| 0 | 251 | end = 0; | ||
| 0 | 252 | return result; | ||
253 | } | |||
254 | } | |||
255 | } |
| Class: | ICSharpCode.SharpZipLib.Encryption.PkzipClassic |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Encryption\PkzipClassic.cs |
| Covered lines: | 26 |
| Uncovered lines: | 2 |
| Coverable lines: | 28 |
| Total lines: | 445 |
| Line coverage: | 92.8% |
| Branch coverage: | 66.6% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| GenerateKeys(...) | 4 | 92.31 | 71.43 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Security.Cryptography; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Encryption | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives. | |||
9 | /// While it has been superceded by more recent and more powerful algorithms, its still in use and | |||
10 | /// is viable for preventing casual snooping | |||
11 | /// </summary> | |||
12 | public abstract class PkzipClassic : SymmetricAlgorithm | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Generates new encryption keys based on given seed | |||
16 | /// </summary> | |||
17 | /// <param name="seed">The seed value to initialise keys with.</param> | |||
18 | /// <returns>A new key value.</returns> | |||
19 | static public byte[] GenerateKeys(byte[] seed) | |||
20 | { | |||
| 68 | 21 | if (seed == null) { | ||
| 0 | 22 | throw new ArgumentNullException(nameof(seed)); | ||
23 | } | |||
24 | | |||
| 68 | 25 | if (seed.Length == 0) { | ||
| 0 | 26 | throw new ArgumentException("Length is zero", nameof(seed)); | ||
27 | } | |||
28 | | |||
| 68 | 29 | uint[] newKeys = { | ||
| 68 | 30 | 0x12345678, | ||
| 68 | 31 | 0x23456789, | ||
| 68 | 32 | 0x34567890 | ||
| 68 | 33 | }; | ||
34 | | |||
| 1034 | 35 | for (int i = 0; i < seed.Length; ++i) { | ||
| 449 | 36 | newKeys[0] = Crc32.ComputeCrc32(newKeys[0], seed[i]); | ||
| 449 | 37 | newKeys[1] = newKeys[1] + (byte)newKeys[0]; | ||
| 449 | 38 | newKeys[1] = newKeys[1] * 134775813 + 1; | ||
| 449 | 39 | newKeys[2] = Crc32.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24)); | ||
40 | } | |||
41 | | |||
| 68 | 42 | byte[] result = new byte[12]; | ||
| 68 | 43 | result[0] = (byte)(newKeys[0] & 0xff); | ||
| 68 | 44 | result[1] = (byte)((newKeys[0] >> 8) & 0xff); | ||
| 68 | 45 | result[2] = (byte)((newKeys[0] >> 16) & 0xff); | ||
| 68 | 46 | result[3] = (byte)((newKeys[0] >> 24) & 0xff); | ||
| 68 | 47 | result[4] = (byte)(newKeys[1] & 0xff); | ||
| 68 | 48 | result[5] = (byte)((newKeys[1] >> 8) & 0xff); | ||
| 68 | 49 | result[6] = (byte)((newKeys[1] >> 16) & 0xff); | ||
| 68 | 50 | result[7] = (byte)((newKeys[1] >> 24) & 0xff); | ||
| 68 | 51 | result[8] = (byte)(newKeys[2] & 0xff); | ||
| 68 | 52 | result[9] = (byte)((newKeys[2] >> 8) & 0xff); | ||
| 68 | 53 | result[10] = (byte)((newKeys[2] >> 16) & 0xff); | ||
| 68 | 54 | result[11] = (byte)((newKeys[2] >> 24) & 0xff); | ||
| 68 | 55 | return result; | ||
56 | } | |||
57 | } | |||
58 | | |||
59 | /// <summary> | |||
60 | /// PkzipClassicCryptoBase provides the low level facilities for encryption | |||
61 | /// and decryption using the PkzipClassic algorithm. | |||
62 | /// </summary> | |||
63 | class PkzipClassicCryptoBase | |||
64 | { | |||
65 | /// <summary> | |||
66 | /// Transform a single byte | |||
67 | /// </summary> | |||
68 | /// <returns> | |||
69 | /// The transformed value | |||
70 | /// </returns> | |||
71 | protected byte TransformByte() | |||
72 | { | |||
73 | uint temp = ((keys[2] & 0xFFFF) | 2); | |||
74 | return (byte)((temp * (temp ^ 1)) >> 8); | |||
75 | } | |||
76 | | |||
77 | /// <summary> | |||
78 | /// Set the key schedule for encryption/decryption. | |||
79 | /// </summary> | |||
80 | /// <param name="keyData">The data use to set the keys from.</param> | |||
81 | protected void SetKeys(byte[] keyData) | |||
82 | { | |||
83 | if (keyData == null) { | |||
84 | throw new ArgumentNullException(nameof(keyData)); | |||
85 | } | |||
86 | | |||
87 | if (keyData.Length != 12) { | |||
88 | throw new InvalidOperationException("Key length is not valid"); | |||
89 | } | |||
90 | | |||
91 | keys = new uint[3]; | |||
92 | keys[0] = (uint)((keyData[3] << 24) | (keyData[2] << 16) | (keyData[1] << 8) | keyData[0]); | |||
93 | keys[1] = (uint)((keyData[7] << 24) | (keyData[6] << 16) | (keyData[5] << 8) | keyData[4]); | |||
94 | keys[2] = (uint)((keyData[11] << 24) | (keyData[10] << 16) | (keyData[9] << 8) | keyData[8]); | |||
95 | } | |||
96 | | |||
97 | /// <summary> | |||
98 | /// Update encryption keys | |||
99 | /// </summary> | |||
100 | protected void UpdateKeys(byte ch) | |||
101 | { | |||
102 | keys[0] = Crc32.ComputeCrc32(keys[0], ch); | |||
103 | keys[1] = keys[1] + (byte)keys[0]; | |||
104 | keys[1] = keys[1] * 134775813 + 1; | |||
105 | keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); | |||
106 | } | |||
107 | | |||
108 | /// <summary> | |||
109 | /// Reset the internal state. | |||
110 | /// </summary> | |||
111 | protected void Reset() | |||
112 | { | |||
113 | keys[0] = 0; | |||
114 | keys[1] = 0; | |||
115 | keys[2] = 0; | |||
116 | } | |||
117 | | |||
118 | #region Instance Fields | |||
119 | uint[] keys; | |||
120 | #endregion | |||
121 | } | |||
122 | | |||
123 | /// <summary> | |||
124 | /// PkzipClassic CryptoTransform for encryption. | |||
125 | /// </summary> | |||
126 | class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
127 | { | |||
128 | /// <summary> | |||
129 | /// Initialise a new instance of <see cref="PkzipClassicEncryptCryptoTransform"></see> | |||
130 | /// </summary> | |||
131 | /// <param name="keyBlock">The key block to use.</param> | |||
132 | internal PkzipClassicEncryptCryptoTransform(byte[] keyBlock) | |||
133 | { | |||
134 | SetKeys(keyBlock); | |||
135 | } | |||
136 | | |||
137 | #region ICryptoTransform Members | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Transforms the specified region of the specified byte array. | |||
141 | /// </summary> | |||
142 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
143 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
144 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
145 | /// <returns>The computed transform.</returns> | |||
146 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
147 | { | |||
148 | byte[] result = new byte[inputCount]; | |||
149 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | |||
150 | return result; | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Transforms the specified region of the input byte array and copies | |||
155 | /// the resulting transform to the specified region of the output byte array. | |||
156 | /// </summary> | |||
157 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
158 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
159 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
160 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
161 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
162 | /// <returns>The number of bytes written.</returns> | |||
163 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
164 | { | |||
165 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | |||
166 | byte oldbyte = inputBuffer[i]; | |||
167 | outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte()); | |||
168 | UpdateKeys(oldbyte); | |||
169 | } | |||
170 | return inputCount; | |||
171 | } | |||
172 | | |||
173 | /// <summary> | |||
174 | /// Gets a value indicating whether the current transform can be reused. | |||
175 | /// </summary> | |||
176 | public bool CanReuseTransform { | |||
177 | get { | |||
178 | return true; | |||
179 | } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Gets the size of the input data blocks in bytes. | |||
184 | /// </summary> | |||
185 | public int InputBlockSize { | |||
186 | get { | |||
187 | return 1; | |||
188 | } | |||
189 | } | |||
190 | | |||
191 | /// <summary> | |||
192 | /// Gets the size of the output data blocks in bytes. | |||
193 | /// </summary> | |||
194 | public int OutputBlockSize { | |||
195 | get { | |||
196 | return 1; | |||
197 | } | |||
198 | } | |||
199 | | |||
200 | /// <summary> | |||
201 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
202 | /// </summary> | |||
203 | public bool CanTransformMultipleBlocks { | |||
204 | get { | |||
205 | return true; | |||
206 | } | |||
207 | } | |||
208 | | |||
209 | #endregion | |||
210 | | |||
211 | #region IDisposable Members | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Cleanup internal state. | |||
215 | /// </summary> | |||
216 | public void Dispose() | |||
217 | { | |||
218 | Reset(); | |||
219 | } | |||
220 | | |||
221 | #endregion | |||
222 | } | |||
223 | | |||
224 | | |||
225 | /// <summary> | |||
226 | /// PkzipClassic CryptoTransform for decryption. | |||
227 | /// </summary> | |||
228 | class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
229 | { | |||
230 | /// <summary> | |||
231 | /// Initialise a new instance of <see cref="PkzipClassicDecryptCryptoTransform"></see>. | |||
232 | /// </summary> | |||
233 | /// <param name="keyBlock">The key block to decrypt with.</param> | |||
234 | internal PkzipClassicDecryptCryptoTransform(byte[] keyBlock) | |||
235 | { | |||
236 | SetKeys(keyBlock); | |||
237 | } | |||
238 | | |||
239 | #region ICryptoTransform Members | |||
240 | | |||
241 | /// <summary> | |||
242 | /// Transforms the specified region of the specified byte array. | |||
243 | /// </summary> | |||
244 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
245 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
246 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
247 | /// <returns>The computed transform.</returns> | |||
248 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
249 | { | |||
250 | byte[] result = new byte[inputCount]; | |||
251 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | |||
252 | return result; | |||
253 | } | |||
254 | | |||
255 | /// <summary> | |||
256 | /// Transforms the specified region of the input byte array and copies | |||
257 | /// the resulting transform to the specified region of the output byte array. | |||
258 | /// </summary> | |||
259 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
260 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
261 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
262 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
263 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
264 | /// <returns>The number of bytes written.</returns> | |||
265 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
266 | { | |||
267 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | |||
268 | var newByte = (byte)(inputBuffer[i] ^ TransformByte()); | |||
269 | outputBuffer[outputOffset++] = newByte; | |||
270 | UpdateKeys(newByte); | |||
271 | } | |||
272 | return inputCount; | |||
273 | } | |||
274 | | |||
275 | /// <summary> | |||
276 | /// Gets a value indicating whether the current transform can be reused. | |||
277 | /// </summary> | |||
278 | public bool CanReuseTransform { | |||
279 | get { | |||
280 | return true; | |||
281 | } | |||
282 | } | |||
283 | | |||
284 | /// <summary> | |||
285 | /// Gets the size of the input data blocks in bytes. | |||
286 | /// </summary> | |||
287 | public int InputBlockSize { | |||
288 | get { | |||
289 | return 1; | |||
290 | } | |||
291 | } | |||
292 | | |||
293 | /// <summary> | |||
294 | /// Gets the size of the output data blocks in bytes. | |||
295 | /// </summary> | |||
296 | public int OutputBlockSize { | |||
297 | get { | |||
298 | return 1; | |||
299 | } | |||
300 | } | |||
301 | | |||
302 | /// <summary> | |||
303 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
304 | /// </summary> | |||
305 | public bool CanTransformMultipleBlocks { | |||
306 | get { | |||
307 | return true; | |||
308 | } | |||
309 | } | |||
310 | | |||
311 | #endregion | |||
312 | | |||
313 | #region IDisposable Members | |||
314 | | |||
315 | /// <summary> | |||
316 | /// Cleanup internal state. | |||
317 | /// </summary> | |||
318 | public void Dispose() | |||
319 | { | |||
320 | Reset(); | |||
321 | } | |||
322 | | |||
323 | #endregion | |||
324 | } | |||
325 | | |||
326 | /// <summary> | |||
327 | /// Defines a wrapper object to access the Pkzip algorithm. | |||
328 | /// This class cannot be inherited. | |||
329 | /// </summary> | |||
330 | public sealed class PkzipClassicManaged : PkzipClassic | |||
331 | { | |||
332 | /// <summary> | |||
333 | /// Get / set the applicable block size in bits. | |||
334 | /// </summary> | |||
335 | /// <remarks>The only valid block size is 8.</remarks> | |||
336 | public override int BlockSize { | |||
337 | get { | |||
338 | return 8; | |||
339 | } | |||
340 | | |||
341 | set { | |||
342 | if (value != 8) { | |||
343 | throw new CryptographicException("Block size is invalid"); | |||
344 | } | |||
345 | } | |||
346 | } | |||
347 | | |||
348 | /// <summary> | |||
349 | /// Get an array of legal <see cref="KeySizes">key sizes.</see> | |||
350 | /// </summary> | |||
351 | public override KeySizes[] LegalKeySizes { | |||
352 | get { | |||
353 | KeySizes[] keySizes = new KeySizes[1]; | |||
354 | keySizes[0] = new KeySizes(12 * 8, 12 * 8, 0); | |||
355 | return keySizes; | |||
356 | } | |||
357 | } | |||
358 | | |||
359 | /// <summary> | |||
360 | /// Generate an initial vector. | |||
361 | /// </summary> | |||
362 | public override void GenerateIV() | |||
363 | { | |||
364 | // Do nothing. | |||
365 | } | |||
366 | | |||
367 | /// <summary> | |||
368 | /// Get an array of legal <see cref="KeySizes">block sizes</see>. | |||
369 | /// </summary> | |||
370 | public override KeySizes[] LegalBlockSizes { | |||
371 | get { | |||
372 | KeySizes[] keySizes = new KeySizes[1]; | |||
373 | keySizes[0] = new KeySizes(1 * 8, 1 * 8, 0); | |||
374 | return keySizes; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Get / set the key value applicable. | |||
380 | /// </summary> | |||
381 | public override byte[] Key { | |||
382 | get { | |||
383 | if (key_ == null) { | |||
384 | GenerateKey(); | |||
385 | } | |||
386 | | |||
387 | return (byte[])key_.Clone(); | |||
388 | } | |||
389 | | |||
390 | set { | |||
391 | if (value == null) { | |||
392 | throw new ArgumentNullException(nameof(value)); | |||
393 | } | |||
394 | | |||
395 | if (value.Length != 12) { | |||
396 | throw new CryptographicException("Key size is illegal"); | |||
397 | } | |||
398 | | |||
399 | key_ = (byte[])value.Clone(); | |||
400 | } | |||
401 | } | |||
402 | | |||
403 | /// <summary> | |||
404 | /// Generate a new random key. | |||
405 | /// </summary> | |||
406 | public override void GenerateKey() | |||
407 | { | |||
408 | key_ = new byte[12]; | |||
409 | var rnd = new Random(); | |||
410 | rnd.NextBytes(key_); | |||
411 | } | |||
412 | | |||
413 | /// <summary> | |||
414 | /// Create an encryptor. | |||
415 | /// </summary> | |||
416 | /// <param name="rgbKey">The key to use for this encryptor.</param> | |||
417 | /// <param name="rgbIV">Initialisation vector for the new encryptor.</param> | |||
418 | /// <returns>Returns a new PkzipClassic encryptor</returns> | |||
419 | public override ICryptoTransform CreateEncryptor( | |||
420 | byte[] rgbKey, | |||
421 | byte[] rgbIV) | |||
422 | { | |||
423 | key_ = rgbKey; | |||
424 | return new PkzipClassicEncryptCryptoTransform(Key); | |||
425 | } | |||
426 | | |||
427 | /// <summary> | |||
428 | /// Create a decryptor. | |||
429 | /// </summary> | |||
430 | /// <param name="rgbKey">Keys to use for this new decryptor.</param> | |||
431 | /// <param name="rgbIV">Initialisation vector for the new decryptor.</param> | |||
432 | /// <returns>Returns a new decryptor.</returns> | |||
433 | public override ICryptoTransform CreateDecryptor( | |||
434 | byte[] rgbKey, | |||
435 | byte[] rgbIV) | |||
436 | { | |||
437 | key_ = rgbKey; | |||
438 | return new PkzipClassicDecryptCryptoTransform(Key); | |||
439 | } | |||
440 | | |||
441 | #region Instance Fields | |||
442 | byte[] key_; | |||
443 | #endregion | |||
444 | } | |||
445 | } |
| Class: | ICSharpCode.SharpZipLib.Encryption.PkzipClassicCryptoBase |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Encryption\PkzipClassic.cs |
| Covered lines: | 18 |
| Uncovered lines: | 2 |
| Coverable lines: | 20 |
| Total lines: | 445 |
| Line coverage: | 90% |
| Branch coverage: | 50% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| TransformByte() | 1 | 100 | 100 |
| SetKeys(...) | 3 | 77.78 | 60 |
| UpdateKeys(...) | 1 | 100 | 100 |
| Reset() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Security.Cryptography; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Encryption | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives. | |||
9 | /// While it has been superceded by more recent and more powerful algorithms, its still in use and | |||
10 | /// is viable for preventing casual snooping | |||
11 | /// </summary> | |||
12 | public abstract class PkzipClassic : SymmetricAlgorithm | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Generates new encryption keys based on given seed | |||
16 | /// </summary> | |||
17 | /// <param name="seed">The seed value to initialise keys with.</param> | |||
18 | /// <returns>A new key value.</returns> | |||
19 | static public byte[] GenerateKeys(byte[] seed) | |||
20 | { | |||
21 | if (seed == null) { | |||
22 | throw new ArgumentNullException(nameof(seed)); | |||
23 | } | |||
24 | | |||
25 | if (seed.Length == 0) { | |||
26 | throw new ArgumentException("Length is zero", nameof(seed)); | |||
27 | } | |||
28 | | |||
29 | uint[] newKeys = { | |||
30 | 0x12345678, | |||
31 | 0x23456789, | |||
32 | 0x34567890 | |||
33 | }; | |||
34 | | |||
35 | for (int i = 0; i < seed.Length; ++i) { | |||
36 | newKeys[0] = Crc32.ComputeCrc32(newKeys[0], seed[i]); | |||
37 | newKeys[1] = newKeys[1] + (byte)newKeys[0]; | |||
38 | newKeys[1] = newKeys[1] * 134775813 + 1; | |||
39 | newKeys[2] = Crc32.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24)); | |||
40 | } | |||
41 | | |||
42 | byte[] result = new byte[12]; | |||
43 | result[0] = (byte)(newKeys[0] & 0xff); | |||
44 | result[1] = (byte)((newKeys[0] >> 8) & 0xff); | |||
45 | result[2] = (byte)((newKeys[0] >> 16) & 0xff); | |||
46 | result[3] = (byte)((newKeys[0] >> 24) & 0xff); | |||
47 | result[4] = (byte)(newKeys[1] & 0xff); | |||
48 | result[5] = (byte)((newKeys[1] >> 8) & 0xff); | |||
49 | result[6] = (byte)((newKeys[1] >> 16) & 0xff); | |||
50 | result[7] = (byte)((newKeys[1] >> 24) & 0xff); | |||
51 | result[8] = (byte)(newKeys[2] & 0xff); | |||
52 | result[9] = (byte)((newKeys[2] >> 8) & 0xff); | |||
53 | result[10] = (byte)((newKeys[2] >> 16) & 0xff); | |||
54 | result[11] = (byte)((newKeys[2] >> 24) & 0xff); | |||
55 | return result; | |||
56 | } | |||
57 | } | |||
58 | | |||
59 | /// <summary> | |||
60 | /// PkzipClassicCryptoBase provides the low level facilities for encryption | |||
61 | /// and decryption using the PkzipClassic algorithm. | |||
62 | /// </summary> | |||
63 | class PkzipClassicCryptoBase | |||
64 | { | |||
65 | /// <summary> | |||
66 | /// Transform a single byte | |||
67 | /// </summary> | |||
68 | /// <returns> | |||
69 | /// The transformed value | |||
70 | /// </returns> | |||
71 | protected byte TransformByte() | |||
72 | { | |||
| 2257872 | 73 | uint temp = ((keys[2] & 0xFFFF) | 2); | ||
| 2257872 | 74 | return (byte)((temp * (temp ^ 1)) >> 8); | ||
75 | } | |||
76 | | |||
77 | /// <summary> | |||
78 | /// Set the key schedule for encryption/decryption. | |||
79 | /// </summary> | |||
80 | /// <param name="keyData">The data use to set the keys from.</param> | |||
81 | protected void SetKeys(byte[] keyData) | |||
82 | { | |||
| 72 | 83 | if (keyData == null) { | ||
| 0 | 84 | throw new ArgumentNullException(nameof(keyData)); | ||
85 | } | |||
86 | | |||
| 72 | 87 | if (keyData.Length != 12) { | ||
| 0 | 88 | throw new InvalidOperationException("Key length is not valid"); | ||
89 | } | |||
90 | | |||
| 72 | 91 | keys = new uint[3]; | ||
| 72 | 92 | keys[0] = (uint)((keyData[3] << 24) | (keyData[2] << 16) | (keyData[1] << 8) | keyData[0]); | ||
| 72 | 93 | keys[1] = (uint)((keyData[7] << 24) | (keyData[6] << 16) | (keyData[5] << 8) | keyData[4]); | ||
| 72 | 94 | keys[2] = (uint)((keyData[11] << 24) | (keyData[10] << 16) | (keyData[9] << 8) | keyData[8]); | ||
| 72 | 95 | } | ||
96 | | |||
97 | /// <summary> | |||
98 | /// Update encryption keys | |||
99 | /// </summary> | |||
100 | protected void UpdateKeys(byte ch) | |||
101 | { | |||
| 2257872 | 102 | keys[0] = Crc32.ComputeCrc32(keys[0], ch); | ||
| 2257872 | 103 | keys[1] = keys[1] + (byte)keys[0]; | ||
| 2257872 | 104 | keys[1] = keys[1] * 134775813 + 1; | ||
| 2257872 | 105 | keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); | ||
| 2257872 | 106 | } | ||
107 | | |||
108 | /// <summary> | |||
109 | /// Reset the internal state. | |||
110 | /// </summary> | |||
111 | protected void Reset() | |||
112 | { | |||
| 32 | 113 | keys[0] = 0; | ||
| 32 | 114 | keys[1] = 0; | ||
| 32 | 115 | keys[2] = 0; | ||
| 32 | 116 | } | ||
117 | | |||
118 | #region Instance Fields | |||
119 | uint[] keys; | |||
120 | #endregion | |||
121 | } | |||
122 | | |||
123 | /// <summary> | |||
124 | /// PkzipClassic CryptoTransform for encryption. | |||
125 | /// </summary> | |||
126 | class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
127 | { | |||
128 | /// <summary> | |||
129 | /// Initialise a new instance of <see cref="PkzipClassicEncryptCryptoTransform"></see> | |||
130 | /// </summary> | |||
131 | /// <param name="keyBlock">The key block to use.</param> | |||
132 | internal PkzipClassicEncryptCryptoTransform(byte[] keyBlock) | |||
133 | { | |||
134 | SetKeys(keyBlock); | |||
135 | } | |||
136 | | |||
137 | #region ICryptoTransform Members | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Transforms the specified region of the specified byte array. | |||
141 | /// </summary> | |||
142 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
143 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
144 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
145 | /// <returns>The computed transform.</returns> | |||
146 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
147 | { | |||
148 | byte[] result = new byte[inputCount]; | |||
149 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | |||
150 | return result; | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Transforms the specified region of the input byte array and copies | |||
155 | /// the resulting transform to the specified region of the output byte array. | |||
156 | /// </summary> | |||
157 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
158 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
159 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
160 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
161 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
162 | /// <returns>The number of bytes written.</returns> | |||
163 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
164 | { | |||
165 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | |||
166 | byte oldbyte = inputBuffer[i]; | |||
167 | outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte()); | |||
168 | UpdateKeys(oldbyte); | |||
169 | } | |||
170 | return inputCount; | |||
171 | } | |||
172 | | |||
173 | /// <summary> | |||
174 | /// Gets a value indicating whether the current transform can be reused. | |||
175 | /// </summary> | |||
176 | public bool CanReuseTransform { | |||
177 | get { | |||
178 | return true; | |||
179 | } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Gets the size of the input data blocks in bytes. | |||
184 | /// </summary> | |||
185 | public int InputBlockSize { | |||
186 | get { | |||
187 | return 1; | |||
188 | } | |||
189 | } | |||
190 | | |||
191 | /// <summary> | |||
192 | /// Gets the size of the output data blocks in bytes. | |||
193 | /// </summary> | |||
194 | public int OutputBlockSize { | |||
195 | get { | |||
196 | return 1; | |||
197 | } | |||
198 | } | |||
199 | | |||
200 | /// <summary> | |||
201 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
202 | /// </summary> | |||
203 | public bool CanTransformMultipleBlocks { | |||
204 | get { | |||
205 | return true; | |||
206 | } | |||
207 | } | |||
208 | | |||
209 | #endregion | |||
210 | | |||
211 | #region IDisposable Members | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Cleanup internal state. | |||
215 | /// </summary> | |||
216 | public void Dispose() | |||
217 | { | |||
218 | Reset(); | |||
219 | } | |||
220 | | |||
221 | #endregion | |||
222 | } | |||
223 | | |||
224 | | |||
225 | /// <summary> | |||
226 | /// PkzipClassic CryptoTransform for decryption. | |||
227 | /// </summary> | |||
228 | class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
229 | { | |||
230 | /// <summary> | |||
231 | /// Initialise a new instance of <see cref="PkzipClassicDecryptCryptoTransform"></see>. | |||
232 | /// </summary> | |||
233 | /// <param name="keyBlock">The key block to decrypt with.</param> | |||
234 | internal PkzipClassicDecryptCryptoTransform(byte[] keyBlock) | |||
235 | { | |||
236 | SetKeys(keyBlock); | |||
237 | } | |||
238 | | |||
239 | #region ICryptoTransform Members | |||
240 | | |||
241 | /// <summary> | |||
242 | /// Transforms the specified region of the specified byte array. | |||
243 | /// </summary> | |||
244 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
245 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
246 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
247 | /// <returns>The computed transform.</returns> | |||
248 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
249 | { | |||
250 | byte[] result = new byte[inputCount]; | |||
251 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | |||
252 | return result; | |||
253 | } | |||
254 | | |||
255 | /// <summary> | |||
256 | /// Transforms the specified region of the input byte array and copies | |||
257 | /// the resulting transform to the specified region of the output byte array. | |||
258 | /// </summary> | |||
259 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
260 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
261 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
262 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
263 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
264 | /// <returns>The number of bytes written.</returns> | |||
265 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
266 | { | |||
267 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | |||
268 | var newByte = (byte)(inputBuffer[i] ^ TransformByte()); | |||
269 | outputBuffer[outputOffset++] = newByte; | |||
270 | UpdateKeys(newByte); | |||
271 | } | |||
272 | return inputCount; | |||
273 | } | |||
274 | | |||
275 | /// <summary> | |||
276 | /// Gets a value indicating whether the current transform can be reused. | |||
277 | /// </summary> | |||
278 | public bool CanReuseTransform { | |||
279 | get { | |||
280 | return true; | |||
281 | } | |||
282 | } | |||
283 | | |||
284 | /// <summary> | |||
285 | /// Gets the size of the input data blocks in bytes. | |||
286 | /// </summary> | |||
287 | public int InputBlockSize { | |||
288 | get { | |||
289 | return 1; | |||
290 | } | |||
291 | } | |||
292 | | |||
293 | /// <summary> | |||
294 | /// Gets the size of the output data blocks in bytes. | |||
295 | /// </summary> | |||
296 | public int OutputBlockSize { | |||
297 | get { | |||
298 | return 1; | |||
299 | } | |||
300 | } | |||
301 | | |||
302 | /// <summary> | |||
303 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
304 | /// </summary> | |||
305 | public bool CanTransformMultipleBlocks { | |||
306 | get { | |||
307 | return true; | |||
308 | } | |||
309 | } | |||
310 | | |||
311 | #endregion | |||
312 | | |||
313 | #region IDisposable Members | |||
314 | | |||
315 | /// <summary> | |||
316 | /// Cleanup internal state. | |||
317 | /// </summary> | |||
318 | public void Dispose() | |||
319 | { | |||
320 | Reset(); | |||
321 | } | |||
322 | | |||
323 | #endregion | |||
324 | } | |||
325 | | |||
326 | /// <summary> | |||
327 | /// Defines a wrapper object to access the Pkzip algorithm. | |||
328 | /// This class cannot be inherited. | |||
329 | /// </summary> | |||
330 | public sealed class PkzipClassicManaged : PkzipClassic | |||
331 | { | |||
332 | /// <summary> | |||
333 | /// Get / set the applicable block size in bits. | |||
334 | /// </summary> | |||
335 | /// <remarks>The only valid block size is 8.</remarks> | |||
336 | public override int BlockSize { | |||
337 | get { | |||
338 | return 8; | |||
339 | } | |||
340 | | |||
341 | set { | |||
342 | if (value != 8) { | |||
343 | throw new CryptographicException("Block size is invalid"); | |||
344 | } | |||
345 | } | |||
346 | } | |||
347 | | |||
348 | /// <summary> | |||
349 | /// Get an array of legal <see cref="KeySizes">key sizes.</see> | |||
350 | /// </summary> | |||
351 | public override KeySizes[] LegalKeySizes { | |||
352 | get { | |||
353 | KeySizes[] keySizes = new KeySizes[1]; | |||
354 | keySizes[0] = new KeySizes(12 * 8, 12 * 8, 0); | |||
355 | return keySizes; | |||
356 | } | |||
357 | } | |||
358 | | |||
359 | /// <summary> | |||
360 | /// Generate an initial vector. | |||
361 | /// </summary> | |||
362 | public override void GenerateIV() | |||
363 | { | |||
364 | // Do nothing. | |||
365 | } | |||
366 | | |||
367 | /// <summary> | |||
368 | /// Get an array of legal <see cref="KeySizes">block sizes</see>. | |||
369 | /// </summary> | |||
370 | public override KeySizes[] LegalBlockSizes { | |||
371 | get { | |||
372 | KeySizes[] keySizes = new KeySizes[1]; | |||
373 | keySizes[0] = new KeySizes(1 * 8, 1 * 8, 0); | |||
374 | return keySizes; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Get / set the key value applicable. | |||
380 | /// </summary> | |||
381 | public override byte[] Key { | |||
382 | get { | |||
383 | if (key_ == null) { | |||
384 | GenerateKey(); | |||
385 | } | |||
386 | | |||
387 | return (byte[])key_.Clone(); | |||
388 | } | |||
389 | | |||
390 | set { | |||
391 | if (value == null) { | |||
392 | throw new ArgumentNullException(nameof(value)); | |||
393 | } | |||
394 | | |||
395 | if (value.Length != 12) { | |||
396 | throw new CryptographicException("Key size is illegal"); | |||
397 | } | |||
398 | | |||
399 | key_ = (byte[])value.Clone(); | |||
400 | } | |||
401 | } | |||
402 | | |||
403 | /// <summary> | |||
404 | /// Generate a new random key. | |||
405 | /// </summary> | |||
406 | public override void GenerateKey() | |||
407 | { | |||
408 | key_ = new byte[12]; | |||
409 | var rnd = new Random(); | |||
410 | rnd.NextBytes(key_); | |||
411 | } | |||
412 | | |||
413 | /// <summary> | |||
414 | /// Create an encryptor. | |||
415 | /// </summary> | |||
416 | /// <param name="rgbKey">The key to use for this encryptor.</param> | |||
417 | /// <param name="rgbIV">Initialisation vector for the new encryptor.</param> | |||
418 | /// <returns>Returns a new PkzipClassic encryptor</returns> | |||
419 | public override ICryptoTransform CreateEncryptor( | |||
420 | byte[] rgbKey, | |||
421 | byte[] rgbIV) | |||
422 | { | |||
423 | key_ = rgbKey; | |||
424 | return new PkzipClassicEncryptCryptoTransform(Key); | |||
425 | } | |||
426 | | |||
427 | /// <summary> | |||
428 | /// Create a decryptor. | |||
429 | /// </summary> | |||
430 | /// <param name="rgbKey">Keys to use for this new decryptor.</param> | |||
431 | /// <param name="rgbIV">Initialisation vector for the new decryptor.</param> | |||
432 | /// <returns>Returns a new decryptor.</returns> | |||
433 | public override ICryptoTransform CreateDecryptor( | |||
434 | byte[] rgbKey, | |||
435 | byte[] rgbIV) | |||
436 | { | |||
437 | key_ = rgbKey; | |||
438 | return new PkzipClassicDecryptCryptoTransform(Key); | |||
439 | } | |||
440 | | |||
441 | #region Instance Fields | |||
442 | byte[] key_; | |||
443 | #endregion | |||
444 | } | |||
445 | } |
| Class: | ICSharpCode.SharpZipLib.Encryption.PkzipClassicDecryptCryptoTransform |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Encryption\PkzipClassic.cs |
| Covered lines: | 14 |
| Uncovered lines: | 3 |
| Coverable lines: | 17 |
| Total lines: | 445 |
| Line coverage: | 82.3% |
| Branch coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| TransformFinalBlock(...) | 1 | 100 | 100 |
| TransformBlock(...) | 2 | 100 | 100 |
| Dispose() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Security.Cryptography; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Encryption | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives. | |||
9 | /// While it has been superceded by more recent and more powerful algorithms, its still in use and | |||
10 | /// is viable for preventing casual snooping | |||
11 | /// </summary> | |||
12 | public abstract class PkzipClassic : SymmetricAlgorithm | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Generates new encryption keys based on given seed | |||
16 | /// </summary> | |||
17 | /// <param name="seed">The seed value to initialise keys with.</param> | |||
18 | /// <returns>A new key value.</returns> | |||
19 | static public byte[] GenerateKeys(byte[] seed) | |||
20 | { | |||
21 | if (seed == null) { | |||
22 | throw new ArgumentNullException(nameof(seed)); | |||
23 | } | |||
24 | | |||
25 | if (seed.Length == 0) { | |||
26 | throw new ArgumentException("Length is zero", nameof(seed)); | |||
27 | } | |||
28 | | |||
29 | uint[] newKeys = { | |||
30 | 0x12345678, | |||
31 | 0x23456789, | |||
32 | 0x34567890 | |||
33 | }; | |||
34 | | |||
35 | for (int i = 0; i < seed.Length; ++i) { | |||
36 | newKeys[0] = Crc32.ComputeCrc32(newKeys[0], seed[i]); | |||
37 | newKeys[1] = newKeys[1] + (byte)newKeys[0]; | |||
38 | newKeys[1] = newKeys[1] * 134775813 + 1; | |||
39 | newKeys[2] = Crc32.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24)); | |||
40 | } | |||
41 | | |||
42 | byte[] result = new byte[12]; | |||
43 | result[0] = (byte)(newKeys[0] & 0xff); | |||
44 | result[1] = (byte)((newKeys[0] >> 8) & 0xff); | |||
45 | result[2] = (byte)((newKeys[0] >> 16) & 0xff); | |||
46 | result[3] = (byte)((newKeys[0] >> 24) & 0xff); | |||
47 | result[4] = (byte)(newKeys[1] & 0xff); | |||
48 | result[5] = (byte)((newKeys[1] >> 8) & 0xff); | |||
49 | result[6] = (byte)((newKeys[1] >> 16) & 0xff); | |||
50 | result[7] = (byte)((newKeys[1] >> 24) & 0xff); | |||
51 | result[8] = (byte)(newKeys[2] & 0xff); | |||
52 | result[9] = (byte)((newKeys[2] >> 8) & 0xff); | |||
53 | result[10] = (byte)((newKeys[2] >> 16) & 0xff); | |||
54 | result[11] = (byte)((newKeys[2] >> 24) & 0xff); | |||
55 | return result; | |||
56 | } | |||
57 | } | |||
58 | | |||
59 | /// <summary> | |||
60 | /// PkzipClassicCryptoBase provides the low level facilities for encryption | |||
61 | /// and decryption using the PkzipClassic algorithm. | |||
62 | /// </summary> | |||
63 | class PkzipClassicCryptoBase | |||
64 | { | |||
65 | /// <summary> | |||
66 | /// Transform a single byte | |||
67 | /// </summary> | |||
68 | /// <returns> | |||
69 | /// The transformed value | |||
70 | /// </returns> | |||
71 | protected byte TransformByte() | |||
72 | { | |||
73 | uint temp = ((keys[2] & 0xFFFF) | 2); | |||
74 | return (byte)((temp * (temp ^ 1)) >> 8); | |||
75 | } | |||
76 | | |||
77 | /// <summary> | |||
78 | /// Set the key schedule for encryption/decryption. | |||
79 | /// </summary> | |||
80 | /// <param name="keyData">The data use to set the keys from.</param> | |||
81 | protected void SetKeys(byte[] keyData) | |||
82 | { | |||
83 | if (keyData == null) { | |||
84 | throw new ArgumentNullException(nameof(keyData)); | |||
85 | } | |||
86 | | |||
87 | if (keyData.Length != 12) { | |||
88 | throw new InvalidOperationException("Key length is not valid"); | |||
89 | } | |||
90 | | |||
91 | keys = new uint[3]; | |||
92 | keys[0] = (uint)((keyData[3] << 24) | (keyData[2] << 16) | (keyData[1] << 8) | keyData[0]); | |||
93 | keys[1] = (uint)((keyData[7] << 24) | (keyData[6] << 16) | (keyData[5] << 8) | keyData[4]); | |||
94 | keys[2] = (uint)((keyData[11] << 24) | (keyData[10] << 16) | (keyData[9] << 8) | keyData[8]); | |||
95 | } | |||
96 | | |||
97 | /// <summary> | |||
98 | /// Update encryption keys | |||
99 | /// </summary> | |||
100 | protected void UpdateKeys(byte ch) | |||
101 | { | |||
102 | keys[0] = Crc32.ComputeCrc32(keys[0], ch); | |||
103 | keys[1] = keys[1] + (byte)keys[0]; | |||
104 | keys[1] = keys[1] * 134775813 + 1; | |||
105 | keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); | |||
106 | } | |||
107 | | |||
108 | /// <summary> | |||
109 | /// Reset the internal state. | |||
110 | /// </summary> | |||
111 | protected void Reset() | |||
112 | { | |||
113 | keys[0] = 0; | |||
114 | keys[1] = 0; | |||
115 | keys[2] = 0; | |||
116 | } | |||
117 | | |||
118 | #region Instance Fields | |||
119 | uint[] keys; | |||
120 | #endregion | |||
121 | } | |||
122 | | |||
123 | /// <summary> | |||
124 | /// PkzipClassic CryptoTransform for encryption. | |||
125 | /// </summary> | |||
126 | class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
127 | { | |||
128 | /// <summary> | |||
129 | /// Initialise a new instance of <see cref="PkzipClassicEncryptCryptoTransform"></see> | |||
130 | /// </summary> | |||
131 | /// <param name="keyBlock">The key block to use.</param> | |||
132 | internal PkzipClassicEncryptCryptoTransform(byte[] keyBlock) | |||
133 | { | |||
134 | SetKeys(keyBlock); | |||
135 | } | |||
136 | | |||
137 | #region ICryptoTransform Members | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Transforms the specified region of the specified byte array. | |||
141 | /// </summary> | |||
142 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
143 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
144 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
145 | /// <returns>The computed transform.</returns> | |||
146 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
147 | { | |||
148 | byte[] result = new byte[inputCount]; | |||
149 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | |||
150 | return result; | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Transforms the specified region of the input byte array and copies | |||
155 | /// the resulting transform to the specified region of the output byte array. | |||
156 | /// </summary> | |||
157 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
158 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
159 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
160 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
161 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
162 | /// <returns>The number of bytes written.</returns> | |||
163 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
164 | { | |||
165 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | |||
166 | byte oldbyte = inputBuffer[i]; | |||
167 | outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte()); | |||
168 | UpdateKeys(oldbyte); | |||
169 | } | |||
170 | return inputCount; | |||
171 | } | |||
172 | | |||
173 | /// <summary> | |||
174 | /// Gets a value indicating whether the current transform can be reused. | |||
175 | /// </summary> | |||
176 | public bool CanReuseTransform { | |||
177 | get { | |||
178 | return true; | |||
179 | } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Gets the size of the input data blocks in bytes. | |||
184 | /// </summary> | |||
185 | public int InputBlockSize { | |||
186 | get { | |||
187 | return 1; | |||
188 | } | |||
189 | } | |||
190 | | |||
191 | /// <summary> | |||
192 | /// Gets the size of the output data blocks in bytes. | |||
193 | /// </summary> | |||
194 | public int OutputBlockSize { | |||
195 | get { | |||
196 | return 1; | |||
197 | } | |||
198 | } | |||
199 | | |||
200 | /// <summary> | |||
201 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
202 | /// </summary> | |||
203 | public bool CanTransformMultipleBlocks { | |||
204 | get { | |||
205 | return true; | |||
206 | } | |||
207 | } | |||
208 | | |||
209 | #endregion | |||
210 | | |||
211 | #region IDisposable Members | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Cleanup internal state. | |||
215 | /// </summary> | |||
216 | public void Dispose() | |||
217 | { | |||
218 | Reset(); | |||
219 | } | |||
220 | | |||
221 | #endregion | |||
222 | } | |||
223 | | |||
224 | | |||
225 | /// <summary> | |||
226 | /// PkzipClassic CryptoTransform for decryption. | |||
227 | /// </summary> | |||
228 | class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
229 | { | |||
230 | /// <summary> | |||
231 | /// Initialise a new instance of <see cref="PkzipClassicDecryptCryptoTransform"></see>. | |||
232 | /// </summary> | |||
233 | /// <param name="keyBlock">The key block to decrypt with.</param> | |||
| 34 | 234 | internal PkzipClassicDecryptCryptoTransform(byte[] keyBlock) | ||
235 | { | |||
| 34 | 236 | SetKeys(keyBlock); | ||
| 34 | 237 | } | ||
238 | | |||
239 | #region ICryptoTransform Members | |||
240 | | |||
241 | /// <summary> | |||
242 | /// Transforms the specified region of the specified byte array. | |||
243 | /// </summary> | |||
244 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
245 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
246 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
247 | /// <returns>The computed transform.</returns> | |||
248 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
249 | { | |||
| 10 | 250 | byte[] result = new byte[inputCount]; | ||
| 10 | 251 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | ||
| 10 | 252 | return result; | ||
253 | } | |||
254 | | |||
255 | /// <summary> | |||
256 | /// Transforms the specified region of the input byte array and copies | |||
257 | /// the resulting transform to the specified region of the output byte array. | |||
258 | /// </summary> | |||
259 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
260 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
261 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
262 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
263 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
264 | /// <returns>The number of bytes written.</returns> | |||
265 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
266 | { | |||
| 2262036 | 267 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | ||
| 1130694 | 268 | var newByte = (byte)(inputBuffer[i] ^ TransformByte()); | ||
| 1130694 | 269 | outputBuffer[outputOffset++] = newByte; | ||
| 1130694 | 270 | UpdateKeys(newByte); | ||
271 | } | |||
| 324 | 272 | return inputCount; | ||
273 | } | |||
274 | | |||
275 | /// <summary> | |||
276 | /// Gets a value indicating whether the current transform can be reused. | |||
277 | /// </summary> | |||
278 | public bool CanReuseTransform { | |||
279 | get { | |||
| 0 | 280 | return true; | ||
281 | } | |||
282 | } | |||
283 | | |||
284 | /// <summary> | |||
285 | /// Gets the size of the input data blocks in bytes. | |||
286 | /// </summary> | |||
287 | public int InputBlockSize { | |||
288 | get { | |||
| 10 | 289 | return 1; | ||
290 | } | |||
291 | } | |||
292 | | |||
293 | /// <summary> | |||
294 | /// Gets the size of the output data blocks in bytes. | |||
295 | /// </summary> | |||
296 | public int OutputBlockSize { | |||
297 | get { | |||
| 10 | 298 | return 1; | ||
299 | } | |||
300 | } | |||
301 | | |||
302 | /// <summary> | |||
303 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
304 | /// </summary> | |||
305 | public bool CanTransformMultipleBlocks { | |||
306 | get { | |||
| 26 | 307 | return true; | ||
308 | } | |||
309 | } | |||
310 | | |||
311 | #endregion | |||
312 | | |||
313 | #region IDisposable Members | |||
314 | | |||
315 | /// <summary> | |||
316 | /// Cleanup internal state. | |||
317 | /// </summary> | |||
318 | public void Dispose() | |||
319 | { | |||
| 0 | 320 | Reset(); | ||
| 0 | 321 | } | ||
322 | | |||
323 | #endregion | |||
324 | } | |||
325 | | |||
326 | /// <summary> | |||
327 | /// Defines a wrapper object to access the Pkzip algorithm. | |||
328 | /// This class cannot be inherited. | |||
329 | /// </summary> | |||
330 | public sealed class PkzipClassicManaged : PkzipClassic | |||
331 | { | |||
332 | /// <summary> | |||
333 | /// Get / set the applicable block size in bits. | |||
334 | /// </summary> | |||
335 | /// <remarks>The only valid block size is 8.</remarks> | |||
336 | public override int BlockSize { | |||
337 | get { | |||
338 | return 8; | |||
339 | } | |||
340 | | |||
341 | set { | |||
342 | if (value != 8) { | |||
343 | throw new CryptographicException("Block size is invalid"); | |||
344 | } | |||
345 | } | |||
346 | } | |||
347 | | |||
348 | /// <summary> | |||
349 | /// Get an array of legal <see cref="KeySizes">key sizes.</see> | |||
350 | /// </summary> | |||
351 | public override KeySizes[] LegalKeySizes { | |||
352 | get { | |||
353 | KeySizes[] keySizes = new KeySizes[1]; | |||
354 | keySizes[0] = new KeySizes(12 * 8, 12 * 8, 0); | |||
355 | return keySizes; | |||
356 | } | |||
357 | } | |||
358 | | |||
359 | /// <summary> | |||
360 | /// Generate an initial vector. | |||
361 | /// </summary> | |||
362 | public override void GenerateIV() | |||
363 | { | |||
364 | // Do nothing. | |||
365 | } | |||
366 | | |||
367 | /// <summary> | |||
368 | /// Get an array of legal <see cref="KeySizes">block sizes</see>. | |||
369 | /// </summary> | |||
370 | public override KeySizes[] LegalBlockSizes { | |||
371 | get { | |||
372 | KeySizes[] keySizes = new KeySizes[1]; | |||
373 | keySizes[0] = new KeySizes(1 * 8, 1 * 8, 0); | |||
374 | return keySizes; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Get / set the key value applicable. | |||
380 | /// </summary> | |||
381 | public override byte[] Key { | |||
382 | get { | |||
383 | if (key_ == null) { | |||
384 | GenerateKey(); | |||
385 | } | |||
386 | | |||
387 | return (byte[])key_.Clone(); | |||
388 | } | |||
389 | | |||
390 | set { | |||
391 | if (value == null) { | |||
392 | throw new ArgumentNullException(nameof(value)); | |||
393 | } | |||
394 | | |||
395 | if (value.Length != 12) { | |||
396 | throw new CryptographicException("Key size is illegal"); | |||
397 | } | |||
398 | | |||
399 | key_ = (byte[])value.Clone(); | |||
400 | } | |||
401 | } | |||
402 | | |||
403 | /// <summary> | |||
404 | /// Generate a new random key. | |||
405 | /// </summary> | |||
406 | public override void GenerateKey() | |||
407 | { | |||
408 | key_ = new byte[12]; | |||
409 | var rnd = new Random(); | |||
410 | rnd.NextBytes(key_); | |||
411 | } | |||
412 | | |||
413 | /// <summary> | |||
414 | /// Create an encryptor. | |||
415 | /// </summary> | |||
416 | /// <param name="rgbKey">The key to use for this encryptor.</param> | |||
417 | /// <param name="rgbIV">Initialisation vector for the new encryptor.</param> | |||
418 | /// <returns>Returns a new PkzipClassic encryptor</returns> | |||
419 | public override ICryptoTransform CreateEncryptor( | |||
420 | byte[] rgbKey, | |||
421 | byte[] rgbIV) | |||
422 | { | |||
423 | key_ = rgbKey; | |||
424 | return new PkzipClassicEncryptCryptoTransform(Key); | |||
425 | } | |||
426 | | |||
427 | /// <summary> | |||
428 | /// Create a decryptor. | |||
429 | /// </summary> | |||
430 | /// <param name="rgbKey">Keys to use for this new decryptor.</param> | |||
431 | /// <param name="rgbIV">Initialisation vector for the new decryptor.</param> | |||
432 | /// <returns>Returns a new decryptor.</returns> | |||
433 | public override ICryptoTransform CreateDecryptor( | |||
434 | byte[] rgbKey, | |||
435 | byte[] rgbIV) | |||
436 | { | |||
437 | key_ = rgbKey; | |||
438 | return new PkzipClassicDecryptCryptoTransform(Key); | |||
439 | } | |||
440 | | |||
441 | #region Instance Fields | |||
442 | byte[] key_; | |||
443 | #endregion | |||
444 | } | |||
445 | } |
| Class: | ICSharpCode.SharpZipLib.Encryption.PkzipClassicEncryptCryptoTransform |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Encryption\PkzipClassic.cs |
| Covered lines: | 13 |
| Uncovered lines: | 4 |
| Coverable lines: | 17 |
| Total lines: | 445 |
| Line coverage: | 76.4% |
| Branch coverage: | 100% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| TransformFinalBlock(...) | 1 | 0 | 0 |
| TransformBlock(...) | 2 | 100 | 100 |
| Dispose() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Security.Cryptography; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Encryption | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives. | |||
9 | /// While it has been superceded by more recent and more powerful algorithms, its still in use and | |||
10 | /// is viable for preventing casual snooping | |||
11 | /// </summary> | |||
12 | public abstract class PkzipClassic : SymmetricAlgorithm | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Generates new encryption keys based on given seed | |||
16 | /// </summary> | |||
17 | /// <param name="seed">The seed value to initialise keys with.</param> | |||
18 | /// <returns>A new key value.</returns> | |||
19 | static public byte[] GenerateKeys(byte[] seed) | |||
20 | { | |||
21 | if (seed == null) { | |||
22 | throw new ArgumentNullException(nameof(seed)); | |||
23 | } | |||
24 | | |||
25 | if (seed.Length == 0) { | |||
26 | throw new ArgumentException("Length is zero", nameof(seed)); | |||
27 | } | |||
28 | | |||
29 | uint[] newKeys = { | |||
30 | 0x12345678, | |||
31 | 0x23456789, | |||
32 | 0x34567890 | |||
33 | }; | |||
34 | | |||
35 | for (int i = 0; i < seed.Length; ++i) { | |||
36 | newKeys[0] = Crc32.ComputeCrc32(newKeys[0], seed[i]); | |||
37 | newKeys[1] = newKeys[1] + (byte)newKeys[0]; | |||
38 | newKeys[1] = newKeys[1] * 134775813 + 1; | |||
39 | newKeys[2] = Crc32.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24)); | |||
40 | } | |||
41 | | |||
42 | byte[] result = new byte[12]; | |||
43 | result[0] = (byte)(newKeys[0] & 0xff); | |||
44 | result[1] = (byte)((newKeys[0] >> 8) & 0xff); | |||
45 | result[2] = (byte)((newKeys[0] >> 16) & 0xff); | |||
46 | result[3] = (byte)((newKeys[0] >> 24) & 0xff); | |||
47 | result[4] = (byte)(newKeys[1] & 0xff); | |||
48 | result[5] = (byte)((newKeys[1] >> 8) & 0xff); | |||
49 | result[6] = (byte)((newKeys[1] >> 16) & 0xff); | |||
50 | result[7] = (byte)((newKeys[1] >> 24) & 0xff); | |||
51 | result[8] = (byte)(newKeys[2] & 0xff); | |||
52 | result[9] = (byte)((newKeys[2] >> 8) & 0xff); | |||
53 | result[10] = (byte)((newKeys[2] >> 16) & 0xff); | |||
54 | result[11] = (byte)((newKeys[2] >> 24) & 0xff); | |||
55 | return result; | |||
56 | } | |||
57 | } | |||
58 | | |||
59 | /// <summary> | |||
60 | /// PkzipClassicCryptoBase provides the low level facilities for encryption | |||
61 | /// and decryption using the PkzipClassic algorithm. | |||
62 | /// </summary> | |||
63 | class PkzipClassicCryptoBase | |||
64 | { | |||
65 | /// <summary> | |||
66 | /// Transform a single byte | |||
67 | /// </summary> | |||
68 | /// <returns> | |||
69 | /// The transformed value | |||
70 | /// </returns> | |||
71 | protected byte TransformByte() | |||
72 | { | |||
73 | uint temp = ((keys[2] & 0xFFFF) | 2); | |||
74 | return (byte)((temp * (temp ^ 1)) >> 8); | |||
75 | } | |||
76 | | |||
77 | /// <summary> | |||
78 | /// Set the key schedule for encryption/decryption. | |||
79 | /// </summary> | |||
80 | /// <param name="keyData">The data use to set the keys from.</param> | |||
81 | protected void SetKeys(byte[] keyData) | |||
82 | { | |||
83 | if (keyData == null) { | |||
84 | throw new ArgumentNullException(nameof(keyData)); | |||
85 | } | |||
86 | | |||
87 | if (keyData.Length != 12) { | |||
88 | throw new InvalidOperationException("Key length is not valid"); | |||
89 | } | |||
90 | | |||
91 | keys = new uint[3]; | |||
92 | keys[0] = (uint)((keyData[3] << 24) | (keyData[2] << 16) | (keyData[1] << 8) | keyData[0]); | |||
93 | keys[1] = (uint)((keyData[7] << 24) | (keyData[6] << 16) | (keyData[5] << 8) | keyData[4]); | |||
94 | keys[2] = (uint)((keyData[11] << 24) | (keyData[10] << 16) | (keyData[9] << 8) | keyData[8]); | |||
95 | } | |||
96 | | |||
97 | /// <summary> | |||
98 | /// Update encryption keys | |||
99 | /// </summary> | |||
100 | protected void UpdateKeys(byte ch) | |||
101 | { | |||
102 | keys[0] = Crc32.ComputeCrc32(keys[0], ch); | |||
103 | keys[1] = keys[1] + (byte)keys[0]; | |||
104 | keys[1] = keys[1] * 134775813 + 1; | |||
105 | keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); | |||
106 | } | |||
107 | | |||
108 | /// <summary> | |||
109 | /// Reset the internal state. | |||
110 | /// </summary> | |||
111 | protected void Reset() | |||
112 | { | |||
113 | keys[0] = 0; | |||
114 | keys[1] = 0; | |||
115 | keys[2] = 0; | |||
116 | } | |||
117 | | |||
118 | #region Instance Fields | |||
119 | uint[] keys; | |||
120 | #endregion | |||
121 | } | |||
122 | | |||
123 | /// <summary> | |||
124 | /// PkzipClassic CryptoTransform for encryption. | |||
125 | /// </summary> | |||
126 | class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
127 | { | |||
128 | /// <summary> | |||
129 | /// Initialise a new instance of <see cref="PkzipClassicEncryptCryptoTransform"></see> | |||
130 | /// </summary> | |||
131 | /// <param name="keyBlock">The key block to use.</param> | |||
| 38 | 132 | internal PkzipClassicEncryptCryptoTransform(byte[] keyBlock) | ||
133 | { | |||
| 38 | 134 | SetKeys(keyBlock); | ||
| 38 | 135 | } | ||
136 | | |||
137 | #region ICryptoTransform Members | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Transforms the specified region of the specified byte array. | |||
141 | /// </summary> | |||
142 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
143 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
144 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
145 | /// <returns>The computed transform.</returns> | |||
146 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
147 | { | |||
| 0 | 148 | byte[] result = new byte[inputCount]; | ||
| 0 | 149 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | ||
| 0 | 150 | return result; | ||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Transforms the specified region of the input byte array and copies | |||
155 | /// the resulting transform to the specified region of the output byte array. | |||
156 | /// </summary> | |||
157 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
158 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
159 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
160 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
161 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
162 | /// <returns>The number of bytes written.</returns> | |||
163 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
164 | { | |||
| 2258986 | 165 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | ||
| 1127178 | 166 | byte oldbyte = inputBuffer[i]; | ||
| 1127178 | 167 | outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte()); | ||
| 1127178 | 168 | UpdateKeys(oldbyte); | ||
169 | } | |||
| 2315 | 170 | return inputCount; | ||
171 | } | |||
172 | | |||
173 | /// <summary> | |||
174 | /// Gets a value indicating whether the current transform can be reused. | |||
175 | /// </summary> | |||
176 | public bool CanReuseTransform { | |||
177 | get { | |||
| 0 | 178 | return true; | ||
179 | } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Gets the size of the input data blocks in bytes. | |||
184 | /// </summary> | |||
185 | public int InputBlockSize { | |||
186 | get { | |||
| 5 | 187 | return 1; | ||
188 | } | |||
189 | } | |||
190 | | |||
191 | /// <summary> | |||
192 | /// Gets the size of the output data blocks in bytes. | |||
193 | /// </summary> | |||
194 | public int OutputBlockSize { | |||
195 | get { | |||
| 5 | 196 | return 1; | ||
197 | } | |||
198 | } | |||
199 | | |||
200 | /// <summary> | |||
201 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
202 | /// </summary> | |||
203 | public bool CanTransformMultipleBlocks { | |||
204 | get { | |||
| 10 | 205 | return true; | ||
206 | } | |||
207 | } | |||
208 | | |||
209 | #endregion | |||
210 | | |||
211 | #region IDisposable Members | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Cleanup internal state. | |||
215 | /// </summary> | |||
216 | public void Dispose() | |||
217 | { | |||
| 32 | 218 | Reset(); | ||
| 32 | 219 | } | ||
220 | | |||
221 | #endregion | |||
222 | } | |||
223 | | |||
224 | | |||
225 | /// <summary> | |||
226 | /// PkzipClassic CryptoTransform for decryption. | |||
227 | /// </summary> | |||
228 | class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
229 | { | |||
230 | /// <summary> | |||
231 | /// Initialise a new instance of <see cref="PkzipClassicDecryptCryptoTransform"></see>. | |||
232 | /// </summary> | |||
233 | /// <param name="keyBlock">The key block to decrypt with.</param> | |||
234 | internal PkzipClassicDecryptCryptoTransform(byte[] keyBlock) | |||
235 | { | |||
236 | SetKeys(keyBlock); | |||
237 | } | |||
238 | | |||
239 | #region ICryptoTransform Members | |||
240 | | |||
241 | /// <summary> | |||
242 | /// Transforms the specified region of the specified byte array. | |||
243 | /// </summary> | |||
244 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
245 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
246 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
247 | /// <returns>The computed transform.</returns> | |||
248 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
249 | { | |||
250 | byte[] result = new byte[inputCount]; | |||
251 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | |||
252 | return result; | |||
253 | } | |||
254 | | |||
255 | /// <summary> | |||
256 | /// Transforms the specified region of the input byte array and copies | |||
257 | /// the resulting transform to the specified region of the output byte array. | |||
258 | /// </summary> | |||
259 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
260 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
261 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
262 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
263 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
264 | /// <returns>The number of bytes written.</returns> | |||
265 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
266 | { | |||
267 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | |||
268 | var newByte = (byte)(inputBuffer[i] ^ TransformByte()); | |||
269 | outputBuffer[outputOffset++] = newByte; | |||
270 | UpdateKeys(newByte); | |||
271 | } | |||
272 | return inputCount; | |||
273 | } | |||
274 | | |||
275 | /// <summary> | |||
276 | /// Gets a value indicating whether the current transform can be reused. | |||
277 | /// </summary> | |||
278 | public bool CanReuseTransform { | |||
279 | get { | |||
280 | return true; | |||
281 | } | |||
282 | } | |||
283 | | |||
284 | /// <summary> | |||
285 | /// Gets the size of the input data blocks in bytes. | |||
286 | /// </summary> | |||
287 | public int InputBlockSize { | |||
288 | get { | |||
289 | return 1; | |||
290 | } | |||
291 | } | |||
292 | | |||
293 | /// <summary> | |||
294 | /// Gets the size of the output data blocks in bytes. | |||
295 | /// </summary> | |||
296 | public int OutputBlockSize { | |||
297 | get { | |||
298 | return 1; | |||
299 | } | |||
300 | } | |||
301 | | |||
302 | /// <summary> | |||
303 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
304 | /// </summary> | |||
305 | public bool CanTransformMultipleBlocks { | |||
306 | get { | |||
307 | return true; | |||
308 | } | |||
309 | } | |||
310 | | |||
311 | #endregion | |||
312 | | |||
313 | #region IDisposable Members | |||
314 | | |||
315 | /// <summary> | |||
316 | /// Cleanup internal state. | |||
317 | /// </summary> | |||
318 | public void Dispose() | |||
319 | { | |||
320 | Reset(); | |||
321 | } | |||
322 | | |||
323 | #endregion | |||
324 | } | |||
325 | | |||
326 | /// <summary> | |||
327 | /// Defines a wrapper object to access the Pkzip algorithm. | |||
328 | /// This class cannot be inherited. | |||
329 | /// </summary> | |||
330 | public sealed class PkzipClassicManaged : PkzipClassic | |||
331 | { | |||
332 | /// <summary> | |||
333 | /// Get / set the applicable block size in bits. | |||
334 | /// </summary> | |||
335 | /// <remarks>The only valid block size is 8.</remarks> | |||
336 | public override int BlockSize { | |||
337 | get { | |||
338 | return 8; | |||
339 | } | |||
340 | | |||
341 | set { | |||
342 | if (value != 8) { | |||
343 | throw new CryptographicException("Block size is invalid"); | |||
344 | } | |||
345 | } | |||
346 | } | |||
347 | | |||
348 | /// <summary> | |||
349 | /// Get an array of legal <see cref="KeySizes">key sizes.</see> | |||
350 | /// </summary> | |||
351 | public override KeySizes[] LegalKeySizes { | |||
352 | get { | |||
353 | KeySizes[] keySizes = new KeySizes[1]; | |||
354 | keySizes[0] = new KeySizes(12 * 8, 12 * 8, 0); | |||
355 | return keySizes; | |||
356 | } | |||
357 | } | |||
358 | | |||
359 | /// <summary> | |||
360 | /// Generate an initial vector. | |||
361 | /// </summary> | |||
362 | public override void GenerateIV() | |||
363 | { | |||
364 | // Do nothing. | |||
365 | } | |||
366 | | |||
367 | /// <summary> | |||
368 | /// Get an array of legal <see cref="KeySizes">block sizes</see>. | |||
369 | /// </summary> | |||
370 | public override KeySizes[] LegalBlockSizes { | |||
371 | get { | |||
372 | KeySizes[] keySizes = new KeySizes[1]; | |||
373 | keySizes[0] = new KeySizes(1 * 8, 1 * 8, 0); | |||
374 | return keySizes; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Get / set the key value applicable. | |||
380 | /// </summary> | |||
381 | public override byte[] Key { | |||
382 | get { | |||
383 | if (key_ == null) { | |||
384 | GenerateKey(); | |||
385 | } | |||
386 | | |||
387 | return (byte[])key_.Clone(); | |||
388 | } | |||
389 | | |||
390 | set { | |||
391 | if (value == null) { | |||
392 | throw new ArgumentNullException(nameof(value)); | |||
393 | } | |||
394 | | |||
395 | if (value.Length != 12) { | |||
396 | throw new CryptographicException("Key size is illegal"); | |||
397 | } | |||
398 | | |||
399 | key_ = (byte[])value.Clone(); | |||
400 | } | |||
401 | } | |||
402 | | |||
403 | /// <summary> | |||
404 | /// Generate a new random key. | |||
405 | /// </summary> | |||
406 | public override void GenerateKey() | |||
407 | { | |||
408 | key_ = new byte[12]; | |||
409 | var rnd = new Random(); | |||
410 | rnd.NextBytes(key_); | |||
411 | } | |||
412 | | |||
413 | /// <summary> | |||
414 | /// Create an encryptor. | |||
415 | /// </summary> | |||
416 | /// <param name="rgbKey">The key to use for this encryptor.</param> | |||
417 | /// <param name="rgbIV">Initialisation vector for the new encryptor.</param> | |||
418 | /// <returns>Returns a new PkzipClassic encryptor</returns> | |||
419 | public override ICryptoTransform CreateEncryptor( | |||
420 | byte[] rgbKey, | |||
421 | byte[] rgbIV) | |||
422 | { | |||
423 | key_ = rgbKey; | |||
424 | return new PkzipClassicEncryptCryptoTransform(Key); | |||
425 | } | |||
426 | | |||
427 | /// <summary> | |||
428 | /// Create a decryptor. | |||
429 | /// </summary> | |||
430 | /// <param name="rgbKey">Keys to use for this new decryptor.</param> | |||
431 | /// <param name="rgbIV">Initialisation vector for the new decryptor.</param> | |||
432 | /// <returns>Returns a new decryptor.</returns> | |||
433 | public override ICryptoTransform CreateDecryptor( | |||
434 | byte[] rgbKey, | |||
435 | byte[] rgbIV) | |||
436 | { | |||
437 | key_ = rgbKey; | |||
438 | return new PkzipClassicDecryptCryptoTransform(Key); | |||
439 | } | |||
440 | | |||
441 | #region Instance Fields | |||
442 | byte[] key_; | |||
443 | #endregion | |||
444 | } | |||
445 | } |
| Class: | ICSharpCode.SharpZipLib.Encryption.PkzipClassicManaged |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Encryption\PkzipClassic.cs |
| Covered lines: | 6 |
| Uncovered lines: | 22 |
| Coverable lines: | 28 |
| Total lines: | 445 |
| Line coverage: | 21.4% |
| Branch coverage: | 12.5% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| GenerateIV() | 1 | 0 | 0 |
| GenerateKey() | 1 | 0 | 0 |
| CreateEncryptor(...) | 1 | 100 | 100 |
| CreateDecryptor(...) | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Security.Cryptography; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Encryption | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives. | |||
9 | /// While it has been superceded by more recent and more powerful algorithms, its still in use and | |||
10 | /// is viable for preventing casual snooping | |||
11 | /// </summary> | |||
12 | public abstract class PkzipClassic : SymmetricAlgorithm | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Generates new encryption keys based on given seed | |||
16 | /// </summary> | |||
17 | /// <param name="seed">The seed value to initialise keys with.</param> | |||
18 | /// <returns>A new key value.</returns> | |||
19 | static public byte[] GenerateKeys(byte[] seed) | |||
20 | { | |||
21 | if (seed == null) { | |||
22 | throw new ArgumentNullException(nameof(seed)); | |||
23 | } | |||
24 | | |||
25 | if (seed.Length == 0) { | |||
26 | throw new ArgumentException("Length is zero", nameof(seed)); | |||
27 | } | |||
28 | | |||
29 | uint[] newKeys = { | |||
30 | 0x12345678, | |||
31 | 0x23456789, | |||
32 | 0x34567890 | |||
33 | }; | |||
34 | | |||
35 | for (int i = 0; i < seed.Length; ++i) { | |||
36 | newKeys[0] = Crc32.ComputeCrc32(newKeys[0], seed[i]); | |||
37 | newKeys[1] = newKeys[1] + (byte)newKeys[0]; | |||
38 | newKeys[1] = newKeys[1] * 134775813 + 1; | |||
39 | newKeys[2] = Crc32.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24)); | |||
40 | } | |||
41 | | |||
42 | byte[] result = new byte[12]; | |||
43 | result[0] = (byte)(newKeys[0] & 0xff); | |||
44 | result[1] = (byte)((newKeys[0] >> 8) & 0xff); | |||
45 | result[2] = (byte)((newKeys[0] >> 16) & 0xff); | |||
46 | result[3] = (byte)((newKeys[0] >> 24) & 0xff); | |||
47 | result[4] = (byte)(newKeys[1] & 0xff); | |||
48 | result[5] = (byte)((newKeys[1] >> 8) & 0xff); | |||
49 | result[6] = (byte)((newKeys[1] >> 16) & 0xff); | |||
50 | result[7] = (byte)((newKeys[1] >> 24) & 0xff); | |||
51 | result[8] = (byte)(newKeys[2] & 0xff); | |||
52 | result[9] = (byte)((newKeys[2] >> 8) & 0xff); | |||
53 | result[10] = (byte)((newKeys[2] >> 16) & 0xff); | |||
54 | result[11] = (byte)((newKeys[2] >> 24) & 0xff); | |||
55 | return result; | |||
56 | } | |||
57 | } | |||
58 | | |||
59 | /// <summary> | |||
60 | /// PkzipClassicCryptoBase provides the low level facilities for encryption | |||
61 | /// and decryption using the PkzipClassic algorithm. | |||
62 | /// </summary> | |||
63 | class PkzipClassicCryptoBase | |||
64 | { | |||
65 | /// <summary> | |||
66 | /// Transform a single byte | |||
67 | /// </summary> | |||
68 | /// <returns> | |||
69 | /// The transformed value | |||
70 | /// </returns> | |||
71 | protected byte TransformByte() | |||
72 | { | |||
73 | uint temp = ((keys[2] & 0xFFFF) | 2); | |||
74 | return (byte)((temp * (temp ^ 1)) >> 8); | |||
75 | } | |||
76 | | |||
77 | /// <summary> | |||
78 | /// Set the key schedule for encryption/decryption. | |||
79 | /// </summary> | |||
80 | /// <param name="keyData">The data use to set the keys from.</param> | |||
81 | protected void SetKeys(byte[] keyData) | |||
82 | { | |||
83 | if (keyData == null) { | |||
84 | throw new ArgumentNullException(nameof(keyData)); | |||
85 | } | |||
86 | | |||
87 | if (keyData.Length != 12) { | |||
88 | throw new InvalidOperationException("Key length is not valid"); | |||
89 | } | |||
90 | | |||
91 | keys = new uint[3]; | |||
92 | keys[0] = (uint)((keyData[3] << 24) | (keyData[2] << 16) | (keyData[1] << 8) | keyData[0]); | |||
93 | keys[1] = (uint)((keyData[7] << 24) | (keyData[6] << 16) | (keyData[5] << 8) | keyData[4]); | |||
94 | keys[2] = (uint)((keyData[11] << 24) | (keyData[10] << 16) | (keyData[9] << 8) | keyData[8]); | |||
95 | } | |||
96 | | |||
97 | /// <summary> | |||
98 | /// Update encryption keys | |||
99 | /// </summary> | |||
100 | protected void UpdateKeys(byte ch) | |||
101 | { | |||
102 | keys[0] = Crc32.ComputeCrc32(keys[0], ch); | |||
103 | keys[1] = keys[1] + (byte)keys[0]; | |||
104 | keys[1] = keys[1] * 134775813 + 1; | |||
105 | keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); | |||
106 | } | |||
107 | | |||
108 | /// <summary> | |||
109 | /// Reset the internal state. | |||
110 | /// </summary> | |||
111 | protected void Reset() | |||
112 | { | |||
113 | keys[0] = 0; | |||
114 | keys[1] = 0; | |||
115 | keys[2] = 0; | |||
116 | } | |||
117 | | |||
118 | #region Instance Fields | |||
119 | uint[] keys; | |||
120 | #endregion | |||
121 | } | |||
122 | | |||
123 | /// <summary> | |||
124 | /// PkzipClassic CryptoTransform for encryption. | |||
125 | /// </summary> | |||
126 | class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
127 | { | |||
128 | /// <summary> | |||
129 | /// Initialise a new instance of <see cref="PkzipClassicEncryptCryptoTransform"></see> | |||
130 | /// </summary> | |||
131 | /// <param name="keyBlock">The key block to use.</param> | |||
132 | internal PkzipClassicEncryptCryptoTransform(byte[] keyBlock) | |||
133 | { | |||
134 | SetKeys(keyBlock); | |||
135 | } | |||
136 | | |||
137 | #region ICryptoTransform Members | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Transforms the specified region of the specified byte array. | |||
141 | /// </summary> | |||
142 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
143 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
144 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
145 | /// <returns>The computed transform.</returns> | |||
146 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
147 | { | |||
148 | byte[] result = new byte[inputCount]; | |||
149 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | |||
150 | return result; | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Transforms the specified region of the input byte array and copies | |||
155 | /// the resulting transform to the specified region of the output byte array. | |||
156 | /// </summary> | |||
157 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
158 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
159 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
160 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
161 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
162 | /// <returns>The number of bytes written.</returns> | |||
163 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
164 | { | |||
165 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | |||
166 | byte oldbyte = inputBuffer[i]; | |||
167 | outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte()); | |||
168 | UpdateKeys(oldbyte); | |||
169 | } | |||
170 | return inputCount; | |||
171 | } | |||
172 | | |||
173 | /// <summary> | |||
174 | /// Gets a value indicating whether the current transform can be reused. | |||
175 | /// </summary> | |||
176 | public bool CanReuseTransform { | |||
177 | get { | |||
178 | return true; | |||
179 | } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Gets the size of the input data blocks in bytes. | |||
184 | /// </summary> | |||
185 | public int InputBlockSize { | |||
186 | get { | |||
187 | return 1; | |||
188 | } | |||
189 | } | |||
190 | | |||
191 | /// <summary> | |||
192 | /// Gets the size of the output data blocks in bytes. | |||
193 | /// </summary> | |||
194 | public int OutputBlockSize { | |||
195 | get { | |||
196 | return 1; | |||
197 | } | |||
198 | } | |||
199 | | |||
200 | /// <summary> | |||
201 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
202 | /// </summary> | |||
203 | public bool CanTransformMultipleBlocks { | |||
204 | get { | |||
205 | return true; | |||
206 | } | |||
207 | } | |||
208 | | |||
209 | #endregion | |||
210 | | |||
211 | #region IDisposable Members | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Cleanup internal state. | |||
215 | /// </summary> | |||
216 | public void Dispose() | |||
217 | { | |||
218 | Reset(); | |||
219 | } | |||
220 | | |||
221 | #endregion | |||
222 | } | |||
223 | | |||
224 | | |||
225 | /// <summary> | |||
226 | /// PkzipClassic CryptoTransform for decryption. | |||
227 | /// </summary> | |||
228 | class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform | |||
229 | { | |||
230 | /// <summary> | |||
231 | /// Initialise a new instance of <see cref="PkzipClassicDecryptCryptoTransform"></see>. | |||
232 | /// </summary> | |||
233 | /// <param name="keyBlock">The key block to decrypt with.</param> | |||
234 | internal PkzipClassicDecryptCryptoTransform(byte[] keyBlock) | |||
235 | { | |||
236 | SetKeys(keyBlock); | |||
237 | } | |||
238 | | |||
239 | #region ICryptoTransform Members | |||
240 | | |||
241 | /// <summary> | |||
242 | /// Transforms the specified region of the specified byte array. | |||
243 | /// </summary> | |||
244 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
245 | /// <param name="inputOffset">The offset into the byte array from which to begin using data.</param> | |||
246 | /// <param name="inputCount">The number of bytes in the byte array to use as data.</param> | |||
247 | /// <returns>The computed transform.</returns> | |||
248 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
249 | { | |||
250 | byte[] result = new byte[inputCount]; | |||
251 | TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); | |||
252 | return result; | |||
253 | } | |||
254 | | |||
255 | /// <summary> | |||
256 | /// Transforms the specified region of the input byte array and copies | |||
257 | /// the resulting transform to the specified region of the output byte array. | |||
258 | /// </summary> | |||
259 | /// <param name="inputBuffer">The input for which to compute the transform.</param> | |||
260 | /// <param name="inputOffset">The offset into the input byte array from which to begin using data.</param> | |||
261 | /// <param name="inputCount">The number of bytes in the input byte array to use as data.</param> | |||
262 | /// <param name="outputBuffer">The output to which to write the transform.</param> | |||
263 | /// <param name="outputOffset">The offset into the output byte array from which to begin writing data.</param> | |||
264 | /// <returns>The number of bytes written.</returns> | |||
265 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
266 | { | |||
267 | for (int i = inputOffset; i < inputOffset + inputCount; ++i) { | |||
268 | var newByte = (byte)(inputBuffer[i] ^ TransformByte()); | |||
269 | outputBuffer[outputOffset++] = newByte; | |||
270 | UpdateKeys(newByte); | |||
271 | } | |||
272 | return inputCount; | |||
273 | } | |||
274 | | |||
275 | /// <summary> | |||
276 | /// Gets a value indicating whether the current transform can be reused. | |||
277 | /// </summary> | |||
278 | public bool CanReuseTransform { | |||
279 | get { | |||
280 | return true; | |||
281 | } | |||
282 | } | |||
283 | | |||
284 | /// <summary> | |||
285 | /// Gets the size of the input data blocks in bytes. | |||
286 | /// </summary> | |||
287 | public int InputBlockSize { | |||
288 | get { | |||
289 | return 1; | |||
290 | } | |||
291 | } | |||
292 | | |||
293 | /// <summary> | |||
294 | /// Gets the size of the output data blocks in bytes. | |||
295 | /// </summary> | |||
296 | public int OutputBlockSize { | |||
297 | get { | |||
298 | return 1; | |||
299 | } | |||
300 | } | |||
301 | | |||
302 | /// <summary> | |||
303 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
304 | /// </summary> | |||
305 | public bool CanTransformMultipleBlocks { | |||
306 | get { | |||
307 | return true; | |||
308 | } | |||
309 | } | |||
310 | | |||
311 | #endregion | |||
312 | | |||
313 | #region IDisposable Members | |||
314 | | |||
315 | /// <summary> | |||
316 | /// Cleanup internal state. | |||
317 | /// </summary> | |||
318 | public void Dispose() | |||
319 | { | |||
320 | Reset(); | |||
321 | } | |||
322 | | |||
323 | #endregion | |||
324 | } | |||
325 | | |||
326 | /// <summary> | |||
327 | /// Defines a wrapper object to access the Pkzip algorithm. | |||
328 | /// This class cannot be inherited. | |||
329 | /// </summary> | |||
330 | public sealed class PkzipClassicManaged : PkzipClassic | |||
331 | { | |||
332 | /// <summary> | |||
333 | /// Get / set the applicable block size in bits. | |||
334 | /// </summary> | |||
335 | /// <remarks>The only valid block size is 8.</remarks> | |||
336 | public override int BlockSize { | |||
337 | get { | |||
| 0 | 338 | return 8; | ||
339 | } | |||
340 | | |||
341 | set { | |||
| 0 | 342 | if (value != 8) { | ||
| 0 | 343 | throw new CryptographicException("Block size is invalid"); | ||
344 | } | |||
| 0 | 345 | } | ||
346 | } | |||
347 | | |||
348 | /// <summary> | |||
349 | /// Get an array of legal <see cref="KeySizes">key sizes.</see> | |||
350 | /// </summary> | |||
351 | public override KeySizes[] LegalKeySizes { | |||
352 | get { | |||
| 0 | 353 | KeySizes[] keySizes = new KeySizes[1]; | ||
| 0 | 354 | keySizes[0] = new KeySizes(12 * 8, 12 * 8, 0); | ||
| 0 | 355 | return keySizes; | ||
356 | } | |||
357 | } | |||
358 | | |||
359 | /// <summary> | |||
360 | /// Generate an initial vector. | |||
361 | /// </summary> | |||
362 | public override void GenerateIV() | |||
363 | { | |||
364 | // Do nothing. | |||
| 0 | 365 | } | ||
366 | | |||
367 | /// <summary> | |||
368 | /// Get an array of legal <see cref="KeySizes">block sizes</see>. | |||
369 | /// </summary> | |||
370 | public override KeySizes[] LegalBlockSizes { | |||
371 | get { | |||
| 0 | 372 | KeySizes[] keySizes = new KeySizes[1]; | ||
| 0 | 373 | keySizes[0] = new KeySizes(1 * 8, 1 * 8, 0); | ||
| 0 | 374 | return keySizes; | ||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Get / set the key value applicable. | |||
380 | /// </summary> | |||
381 | public override byte[] Key { | |||
382 | get { | |||
| 72 | 383 | if (key_ == null) { | ||
| 0 | 384 | GenerateKey(); | ||
385 | } | |||
386 | | |||
| 72 | 387 | return (byte[])key_.Clone(); | ||
388 | } | |||
389 | | |||
390 | set { | |||
| 0 | 391 | if (value == null) { | ||
| 0 | 392 | throw new ArgumentNullException(nameof(value)); | ||
393 | } | |||
394 | | |||
| 0 | 395 | if (value.Length != 12) { | ||
| 0 | 396 | throw new CryptographicException("Key size is illegal"); | ||
397 | } | |||
398 | | |||
| 0 | 399 | key_ = (byte[])value.Clone(); | ||
| 0 | 400 | } | ||
401 | } | |||
402 | | |||
403 | /// <summary> | |||
404 | /// Generate a new random key. | |||
405 | /// </summary> | |||
406 | public override void GenerateKey() | |||
407 | { | |||
| 0 | 408 | key_ = new byte[12]; | ||
| 0 | 409 | var rnd = new Random(); | ||
| 0 | 410 | rnd.NextBytes(key_); | ||
| 0 | 411 | } | ||
412 | | |||
413 | /// <summary> | |||
414 | /// Create an encryptor. | |||
415 | /// </summary> | |||
416 | /// <param name="rgbKey">The key to use for this encryptor.</param> | |||
417 | /// <param name="rgbIV">Initialisation vector for the new encryptor.</param> | |||
418 | /// <returns>Returns a new PkzipClassic encryptor</returns> | |||
419 | public override ICryptoTransform CreateEncryptor( | |||
420 | byte[] rgbKey, | |||
421 | byte[] rgbIV) | |||
422 | { | |||
| 38 | 423 | key_ = rgbKey; | ||
| 38 | 424 | return new PkzipClassicEncryptCryptoTransform(Key); | ||
425 | } | |||
426 | | |||
427 | /// <summary> | |||
428 | /// Create a decryptor. | |||
429 | /// </summary> | |||
430 | /// <param name="rgbKey">Keys to use for this new decryptor.</param> | |||
431 | /// <param name="rgbIV">Initialisation vector for the new decryptor.</param> | |||
432 | /// <returns>Returns a new decryptor.</returns> | |||
433 | public override ICryptoTransform CreateDecryptor( | |||
434 | byte[] rgbKey, | |||
435 | byte[] rgbIV) | |||
436 | { | |||
| 34 | 437 | key_ = rgbKey; | ||
| 34 | 438 | return new PkzipClassicDecryptCryptoTransform(Key); | ||
439 | } | |||
440 | | |||
441 | #region Instance Fields | |||
442 | byte[] key_; | |||
443 | #endregion | |||
444 | } | |||
445 | } |
| Class: | ICSharpCode.SharpZipLib.Core.ProcessFileHandler |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | |
| Covered lines: | 0 |
| Uncovered lines: | 0 |
| Coverable lines: | 0 |
| Total lines: | 0 |
| Line coverage: |
No files found. This usually happens if a file isn't covered by a test or the class does not contain any sequence points (e.g. a class that only contains auto properties).
+| Class: | ICSharpCode.SharpZipLib.Core.ProgressEventArgs |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\FileSystemScanner.cs |
| Covered lines: | 0 |
| Uncovered lines: | 16 |
| Coverable lines: | 16 |
| Total lines: | 475 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Core | |||
4 | { | |||
5 | #region EventArgs | |||
6 | /// <summary> | |||
7 | /// Event arguments for scanning. | |||
8 | /// </summary> | |||
9 | public class ScanEventArgs : EventArgs | |||
10 | { | |||
11 | #region Constructors | |||
12 | /// <summary> | |||
13 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
14 | /// </summary> | |||
15 | /// <param name="name">The file or directory name.</param> | |||
16 | public ScanEventArgs(string name) | |||
17 | { | |||
18 | name_ = name; | |||
19 | } | |||
20 | #endregion | |||
21 | | |||
22 | /// <summary> | |||
23 | /// The file or directory name for this event. | |||
24 | /// </summary> | |||
25 | public string Name { | |||
26 | get { return name_; } | |||
27 | } | |||
28 | | |||
29 | /// <summary> | |||
30 | /// Get set a value indicating if scanning should continue or not. | |||
31 | /// </summary> | |||
32 | public bool ContinueRunning { | |||
33 | get { return continueRunning_; } | |||
34 | set { continueRunning_ = value; } | |||
35 | } | |||
36 | | |||
37 | #region Instance Fields | |||
38 | string name_; | |||
39 | bool continueRunning_ = true; | |||
40 | #endregion | |||
41 | } | |||
42 | | |||
43 | /// <summary> | |||
44 | /// Event arguments during processing of a single file or directory. | |||
45 | /// </summary> | |||
46 | public class ProgressEventArgs : EventArgs | |||
47 | { | |||
48 | #region Constructors | |||
49 | /// <summary> | |||
50 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
51 | /// </summary> | |||
52 | /// <param name="name">The file or directory name if known.</param> | |||
53 | /// <param name="processed">The number of bytes processed so far</param> | |||
54 | /// <param name="target">The total number of bytes to process, 0 if not known</param> | |||
| 0 | 55 | public ProgressEventArgs(string name, long processed, long target) | ||
56 | { | |||
| 0 | 57 | name_ = name; | ||
| 0 | 58 | processed_ = processed; | ||
| 0 | 59 | target_ = target; | ||
| 0 | 60 | } | ||
61 | #endregion | |||
62 | | |||
63 | /// <summary> | |||
64 | /// The name for this event if known. | |||
65 | /// </summary> | |||
66 | public string Name { | |||
| 0 | 67 | get { return name_; } | ||
68 | } | |||
69 | | |||
70 | /// <summary> | |||
71 | /// Get set a value indicating wether scanning should continue or not. | |||
72 | /// </summary> | |||
73 | public bool ContinueRunning { | |||
| 0 | 74 | get { return continueRunning_; } | ||
| 0 | 75 | set { continueRunning_ = value; } | ||
76 | } | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Get a percentage representing how much of the <see cref="Target"></see> has been processed | |||
80 | /// </summary> | |||
81 | /// <value>0.0 to 100.0 percent; 0 if target is not known.</value> | |||
82 | public float PercentComplete { | |||
83 | get { | |||
84 | float result; | |||
| 0 | 85 | if (target_ <= 0) { | ||
| 0 | 86 | result = 0; | ||
| 0 | 87 | } else { | ||
| 0 | 88 | result = ((float)processed_ / (float)target_) * 100.0f; | ||
89 | } | |||
| 0 | 90 | return result; | ||
91 | } | |||
92 | } | |||
93 | | |||
94 | /// <summary> | |||
95 | /// The number of bytes processed so far | |||
96 | /// </summary> | |||
97 | public long Processed { | |||
| 0 | 98 | get { return processed_; } | ||
99 | } | |||
100 | | |||
101 | /// <summary> | |||
102 | /// The number of bytes to process. | |||
103 | /// </summary> | |||
104 | /// <remarks>Target may be 0 or negative if the value isnt known.</remarks> | |||
105 | public long Target { | |||
| 0 | 106 | get { return target_; } | ||
107 | } | |||
108 | | |||
109 | #region Instance Fields | |||
110 | string name_; | |||
111 | long processed_; | |||
112 | long target_; | |||
| 0 | 113 | bool continueRunning_ = true; | ||
114 | #endregion | |||
115 | } | |||
116 | | |||
117 | /// <summary> | |||
118 | /// Event arguments for directories. | |||
119 | /// </summary> | |||
120 | public class DirectoryEventArgs : ScanEventArgs | |||
121 | { | |||
122 | #region Constructors | |||
123 | /// <summary> | |||
124 | /// Initialize an instance of <see cref="DirectoryEventArgs"></see>. | |||
125 | /// </summary> | |||
126 | /// <param name="name">The name for this directory.</param> | |||
127 | /// <param name="hasMatchingFiles">Flag value indicating if any matching files are contained in this directory.</par | |||
128 | public DirectoryEventArgs(string name, bool hasMatchingFiles) | |||
129 | : base(name) | |||
130 | { | |||
131 | hasMatchingFiles_ = hasMatchingFiles; | |||
132 | } | |||
133 | #endregion | |||
134 | | |||
135 | /// <summary> | |||
136 | /// Get a value indicating if the directory contains any matching files or not. | |||
137 | /// </summary> | |||
138 | public bool HasMatchingFiles { | |||
139 | get { return hasMatchingFiles_; } | |||
140 | } | |||
141 | | |||
142 | readonly | |||
143 | | |||
144 | #region Instance Fields | |||
145 | bool hasMatchingFiles_; | |||
146 | #endregion | |||
147 | } | |||
148 | | |||
149 | /// <summary> | |||
150 | /// Arguments passed when scan failures are detected. | |||
151 | /// </summary> | |||
152 | public class ScanFailureEventArgs : EventArgs | |||
153 | { | |||
154 | #region Constructors | |||
155 | /// <summary> | |||
156 | /// Initialise a new instance of <see cref="ScanFailureEventArgs"></see> | |||
157 | /// </summary> | |||
158 | /// <param name="name">The name to apply.</param> | |||
159 | /// <param name="e">The exception to use.</param> | |||
160 | public ScanFailureEventArgs(string name, Exception e) | |||
161 | { | |||
162 | name_ = name; | |||
163 | exception_ = e; | |||
164 | continueRunning_ = true; | |||
165 | } | |||
166 | #endregion | |||
167 | | |||
168 | /// <summary> | |||
169 | /// The applicable name. | |||
170 | /// </summary> | |||
171 | public string Name { | |||
172 | get { return name_; } | |||
173 | } | |||
174 | | |||
175 | /// <summary> | |||
176 | /// The applicable exception. | |||
177 | /// </summary> | |||
178 | public Exception Exception { | |||
179 | get { return exception_; } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Get / set a value indicating wether scanning should continue. | |||
184 | /// </summary> | |||
185 | public bool ContinueRunning { | |||
186 | get { return continueRunning_; } | |||
187 | set { continueRunning_ = value; } | |||
188 | } | |||
189 | | |||
190 | #region Instance Fields | |||
191 | string name_; | |||
192 | Exception exception_; | |||
193 | bool continueRunning_; | |||
194 | #endregion | |||
195 | } | |||
196 | | |||
197 | #endregion | |||
198 | | |||
199 | #region Delegates | |||
200 | /// <summary> | |||
201 | /// Delegate invoked before starting to process a file. | |||
202 | /// </summary> | |||
203 | /// <param name="sender">The source of the event</param> | |||
204 | /// <param name="e">The event arguments.</param> | |||
205 | public delegate void ProcessFileHandler(object sender, ScanEventArgs e); | |||
206 | | |||
207 | /// <summary> | |||
208 | /// Delegate invoked during processing of a file or directory | |||
209 | /// </summary> | |||
210 | /// <param name="sender">The source of the event</param> | |||
211 | /// <param name="e">The event arguments.</param> | |||
212 | public delegate void ProgressHandler(object sender, ProgressEventArgs e); | |||
213 | | |||
214 | /// <summary> | |||
215 | /// Delegate invoked when a file has been completely processed. | |||
216 | /// </summary> | |||
217 | /// <param name="sender">The source of the event</param> | |||
218 | /// <param name="e">The event arguments.</param> | |||
219 | public delegate void CompletedFileHandler(object sender, ScanEventArgs e); | |||
220 | | |||
221 | /// <summary> | |||
222 | /// Delegate invoked when a directory failure is detected. | |||
223 | /// </summary> | |||
224 | /// <param name="sender">The source of the event</param> | |||
225 | /// <param name="e">The event arguments.</param> | |||
226 | public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e); | |||
227 | | |||
228 | /// <summary> | |||
229 | /// Delegate invoked when a file failure is detected. | |||
230 | /// </summary> | |||
231 | /// <param name="sender">The source of the event</param> | |||
232 | /// <param name="e">The event arguments.</param> | |||
233 | public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e); | |||
234 | #endregion | |||
235 | | |||
236 | /// <summary> | |||
237 | /// FileSystemScanner provides facilities scanning of files and directories. | |||
238 | /// </summary> | |||
239 | public class FileSystemScanner | |||
240 | { | |||
241 | #region Constructors | |||
242 | /// <summary> | |||
243 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
244 | /// </summary> | |||
245 | /// <param name="filter">The <see cref="PathFilter">file filter</see> to apply when scanning.</param> | |||
246 | public FileSystemScanner(string filter) | |||
247 | { | |||
248 | fileFilter_ = new PathFilter(filter); | |||
249 | } | |||
250 | | |||
251 | /// <summary> | |||
252 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
253 | /// </summary> | |||
254 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
255 | /// <param name="directoryFilter">The <see cref="PathFilter"> directory filter</see> to apply.</param> | |||
256 | public FileSystemScanner(string fileFilter, string directoryFilter) | |||
257 | { | |||
258 | fileFilter_ = new PathFilter(fileFilter); | |||
259 | directoryFilter_ = new PathFilter(directoryFilter); | |||
260 | } | |||
261 | | |||
262 | /// <summary> | |||
263 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
264 | /// </summary> | |||
265 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
266 | public FileSystemScanner(IScanFilter fileFilter) | |||
267 | { | |||
268 | fileFilter_ = fileFilter; | |||
269 | } | |||
270 | | |||
271 | /// <summary> | |||
272 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
273 | /// </summary> | |||
274 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
275 | /// <param name="directoryFilter">The directory <see cref="IScanFilter">filter</see> to apply.</param> | |||
276 | public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter) | |||
277 | { | |||
278 | fileFilter_ = fileFilter; | |||
279 | directoryFilter_ = directoryFilter; | |||
280 | } | |||
281 | #endregion | |||
282 | | |||
283 | #region Delegates | |||
284 | /// <summary> | |||
285 | /// Delegate to invoke when a directory is processed. | |||
286 | /// </summary> | |||
287 | public event EventHandler<DirectoryEventArgs> ProcessDirectory; | |||
288 | | |||
289 | /// <summary> | |||
290 | /// Delegate to invoke when a file is processed. | |||
291 | /// </summary> | |||
292 | public ProcessFileHandler ProcessFile; | |||
293 | | |||
294 | /// <summary> | |||
295 | /// Delegate to invoke when processing for a file has finished. | |||
296 | /// </summary> | |||
297 | public CompletedFileHandler CompletedFile; | |||
298 | | |||
299 | /// <summary> | |||
300 | /// Delegate to invoke when a directory failure is detected. | |||
301 | /// </summary> | |||
302 | public DirectoryFailureHandler DirectoryFailure; | |||
303 | | |||
304 | /// <summary> | |||
305 | /// Delegate to invoke when a file failure is detected. | |||
306 | /// </summary> | |||
307 | public FileFailureHandler FileFailure; | |||
308 | #endregion | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Raise the DirectoryFailure event. | |||
312 | /// </summary> | |||
313 | /// <param name="directory">The directory name.</param> | |||
314 | /// <param name="e">The exception detected.</param> | |||
315 | bool OnDirectoryFailure(string directory, Exception e) | |||
316 | { | |||
317 | DirectoryFailureHandler handler = DirectoryFailure; | |||
318 | bool result = (handler != null); | |||
319 | if (result) { | |||
320 | var args = new ScanFailureEventArgs(directory, e); | |||
321 | handler(this, args); | |||
322 | alive_ = args.ContinueRunning; | |||
323 | } | |||
324 | return result; | |||
325 | } | |||
326 | | |||
327 | /// <summary> | |||
328 | /// Raise the FileFailure event. | |||
329 | /// </summary> | |||
330 | /// <param name="file">The file name.</param> | |||
331 | /// <param name="e">The exception detected.</param> | |||
332 | bool OnFileFailure(string file, Exception e) | |||
333 | { | |||
334 | FileFailureHandler handler = FileFailure; | |||
335 | | |||
336 | bool result = (handler != null); | |||
337 | | |||
338 | if (result) { | |||
339 | var args = new ScanFailureEventArgs(file, e); | |||
340 | FileFailure(this, args); | |||
341 | alive_ = args.ContinueRunning; | |||
342 | } | |||
343 | return result; | |||
344 | } | |||
345 | | |||
346 | /// <summary> | |||
347 | /// Raise the ProcessFile event. | |||
348 | /// </summary> | |||
349 | /// <param name="file">The file name.</param> | |||
350 | void OnProcessFile(string file) | |||
351 | { | |||
352 | ProcessFileHandler handler = ProcessFile; | |||
353 | | |||
354 | if (handler != null) { | |||
355 | var args = new ScanEventArgs(file); | |||
356 | handler(this, args); | |||
357 | alive_ = args.ContinueRunning; | |||
358 | } | |||
359 | } | |||
360 | | |||
361 | /// <summary> | |||
362 | /// Raise the complete file event | |||
363 | /// </summary> | |||
364 | /// <param name="file">The file name</param> | |||
365 | void OnCompleteFile(string file) | |||
366 | { | |||
367 | CompletedFileHandler handler = CompletedFile; | |||
368 | | |||
369 | if (handler != null) { | |||
370 | var args = new ScanEventArgs(file); | |||
371 | handler(this, args); | |||
372 | alive_ = args.ContinueRunning; | |||
373 | } | |||
374 | } | |||
375 | | |||
376 | /// <summary> | |||
377 | /// Raise the ProcessDirectory event. | |||
378 | /// </summary> | |||
379 | /// <param name="directory">The directory name.</param> | |||
380 | /// <param name="hasMatchingFiles">Flag indicating if the directory has matching files.</param> | |||
381 | void OnProcessDirectory(string directory, bool hasMatchingFiles) | |||
382 | { | |||
383 | EventHandler<DirectoryEventArgs> handler = ProcessDirectory; | |||
384 | | |||
385 | if (handler != null) { | |||
386 | var args = new DirectoryEventArgs(directory, hasMatchingFiles); | |||
387 | handler(this, args); | |||
388 | alive_ = args.ContinueRunning; | |||
389 | } | |||
390 | } | |||
391 | | |||
392 | /// <summary> | |||
393 | /// Scan a directory. | |||
394 | /// </summary> | |||
395 | /// <param name="directory">The base directory to scan.</param> | |||
396 | /// <param name="recurse">True to recurse subdirectories, false to scan a single directory.</param> | |||
397 | public void Scan(string directory, bool recurse) | |||
398 | { | |||
399 | alive_ = true; | |||
400 | ScanDir(directory, recurse); | |||
401 | } | |||
402 | | |||
403 | void ScanDir(string directory, bool recurse) | |||
404 | { | |||
405 | | |||
406 | try { | |||
407 | string[] names = System.IO.Directory.GetFiles(directory); | |||
408 | bool hasMatch = false; | |||
409 | for (int fileIndex = 0; fileIndex < names.Length; ++fileIndex) { | |||
410 | if (!fileFilter_.IsMatch(names[fileIndex])) { | |||
411 | names[fileIndex] = null; | |||
412 | } else { | |||
413 | hasMatch = true; | |||
414 | } | |||
415 | } | |||
416 | | |||
417 | OnProcessDirectory(directory, hasMatch); | |||
418 | | |||
419 | if (alive_ && hasMatch) { | |||
420 | foreach (string fileName in names) { | |||
421 | try { | |||
422 | if (fileName != null) { | |||
423 | OnProcessFile(fileName); | |||
424 | if (!alive_) { | |||
425 | break; | |||
426 | } | |||
427 | } | |||
428 | } catch (Exception e) { | |||
429 | if (!OnFileFailure(fileName, e)) { | |||
430 | throw; | |||
431 | } | |||
432 | } | |||
433 | } | |||
434 | } | |||
435 | } catch (Exception e) { | |||
436 | if (!OnDirectoryFailure(directory, e)) { | |||
437 | throw; | |||
438 | } | |||
439 | } | |||
440 | | |||
441 | if (alive_ && recurse) { | |||
442 | try { | |||
443 | string[] names = System.IO.Directory.GetDirectories(directory); | |||
444 | foreach (string fulldir in names) { | |||
445 | if ((directoryFilter_ == null) || (directoryFilter_.IsMatch(fulldir))) { | |||
446 | ScanDir(fulldir, true); | |||
447 | if (!alive_) { | |||
448 | break; | |||
449 | } | |||
450 | } | |||
451 | } | |||
452 | } catch (Exception e) { | |||
453 | if (!OnDirectoryFailure(directory, e)) { | |||
454 | throw; | |||
455 | } | |||
456 | } | |||
457 | } | |||
458 | } | |||
459 | | |||
460 | #region Instance Fields | |||
461 | /// <summary> | |||
462 | /// The file filter currently in use. | |||
463 | /// </summary> | |||
464 | IScanFilter fileFilter_; | |||
465 | /// <summary> | |||
466 | /// The directory filter currently in use. | |||
467 | /// </summary> | |||
468 | IScanFilter directoryFilter_; | |||
469 | /// <summary> | |||
470 | /// Flag indicating if scanning should continue running. | |||
471 | /// </summary> | |||
472 | bool alive_; | |||
473 | #endregion | |||
474 | } | |||
475 | } |
| Class: | ICSharpCode.SharpZipLib.Core.ProgressHandler |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | |
| Covered lines: | 0 |
| Uncovered lines: | 0 |
| Coverable lines: | 0 |
| Total lines: | 0 |
| Line coverage: |
No files found. This usually happens if a file isn't covered by a test or the class does not contain any sequence points (e.g. a class that only contains auto properties).
+| Class: | ICSharpCode.SharpZipLib.Tar.ProgressMessageHandler |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | |
| Covered lines: | 0 |
| Uncovered lines: | 0 |
| Coverable lines: | 0 |
| Total lines: | 0 |
| Line coverage: |
No files found. This usually happens if a file isn't covered by a test or the class does not contain any sequence points (e.g. a class that only contains auto properties).
+| Class: | ICSharpCode.SharpZipLib.Zip.RawTaggedData |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipExtraData.cs |
| Covered lines: | 0 |
| Uncovered lines: | 13 |
| Coverable lines: | 13 |
| Total lines: | 896 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| SetData(...) | 2 | 0 | 0 |
| GetData() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip | |||
5 | { | |||
6 | // TODO: Sort out wether tagged data is useful and what a good implementation might look like. | |||
7 | // Its just a sketch of an idea at the moment. | |||
8 | | |||
9 | /// <summary> | |||
10 | /// ExtraData tagged value interface. | |||
11 | /// </summary> | |||
12 | public interface ITaggedData | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Get the ID for this tagged data value. | |||
16 | /// </summary> | |||
17 | short TagID { get; } | |||
18 | | |||
19 | /// <summary> | |||
20 | /// Set the contents of this instance from the data passed. | |||
21 | /// </summary> | |||
22 | /// <param name="data">The data to extract contents from.</param> | |||
23 | /// <param name="offset">The offset to begin extracting data from.</param> | |||
24 | /// <param name="count">The number of bytes to extract.</param> | |||
25 | void SetData(byte[] data, int offset, int count); | |||
26 | | |||
27 | /// <summary> | |||
28 | /// Get the data representing this instance. | |||
29 | /// </summary> | |||
30 | /// <returns>Returns the data for this instance.</returns> | |||
31 | byte[] GetData(); | |||
32 | } | |||
33 | | |||
34 | /// <summary> | |||
35 | /// A raw binary tagged value | |||
36 | /// </summary> | |||
37 | public class RawTaggedData : ITaggedData | |||
38 | { | |||
39 | /// <summary> | |||
40 | /// Initialise a new instance. | |||
41 | /// </summary> | |||
42 | /// <param name="tag">The tag ID.</param> | |||
| 0 | 43 | public RawTaggedData(short tag) | ||
44 | { | |||
| 0 | 45 | _tag = tag; | ||
| 0 | 46 | } | ||
47 | | |||
48 | #region ITaggedData Members | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Get the ID for this tagged data value. | |||
52 | /// </summary> | |||
53 | public short TagID { | |||
| 0 | 54 | get { return _tag; } | ||
| 0 | 55 | set { _tag = value; } | ||
56 | } | |||
57 | | |||
58 | /// <summary> | |||
59 | /// Set the data from the raw values provided. | |||
60 | /// </summary> | |||
61 | /// <param name="data">The raw data to extract values from.</param> | |||
62 | /// <param name="offset">The index to start extracting values from.</param> | |||
63 | /// <param name="count">The number of bytes available.</param> | |||
64 | public void SetData(byte[] data, int offset, int count) | |||
65 | { | |||
| 0 | 66 | if (data == null) { | ||
| 0 | 67 | throw new ArgumentNullException(nameof(data)); | ||
68 | } | |||
69 | | |||
| 0 | 70 | _data = new byte[count]; | ||
| 0 | 71 | Array.Copy(data, offset, _data, 0, count); | ||
| 0 | 72 | } | ||
73 | | |||
74 | /// <summary> | |||
75 | /// Get the binary data representing this instance. | |||
76 | /// </summary> | |||
77 | /// <returns>The raw binary data representing this instance.</returns> | |||
78 | public byte[] GetData() | |||
79 | { | |||
| 0 | 80 | return _data; | ||
81 | } | |||
82 | | |||
83 | #endregion | |||
84 | | |||
85 | /// <summary> | |||
86 | /// Get /set the binary data representing this instance. | |||
87 | /// </summary> | |||
88 | /// <returns>The raw binary data representing this instance.</returns> | |||
89 | public byte[] Data { | |||
| 0 | 90 | get { return _data; } | ||
| 0 | 91 | set { _data = value; } | ||
92 | } | |||
93 | | |||
94 | #region Instance Fields | |||
95 | /// <summary> | |||
96 | /// The tag ID for this instance. | |||
97 | /// </summary> | |||
98 | short _tag; | |||
99 | | |||
100 | byte[] _data; | |||
101 | #endregion | |||
102 | } | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Class representing extended unix date time values. | |||
106 | /// </summary> | |||
107 | public class ExtendedUnixData : ITaggedData | |||
108 | { | |||
109 | /// <summary> | |||
110 | /// Flags indicate which values are included in this instance. | |||
111 | /// </summary> | |||
112 | [Flags] | |||
113 | public enum Flags : byte | |||
114 | { | |||
115 | /// <summary> | |||
116 | /// The modification time is included | |||
117 | /// </summary> | |||
118 | ModificationTime = 0x01, | |||
119 | | |||
120 | /// <summary> | |||
121 | /// The access time is included | |||
122 | /// </summary> | |||
123 | AccessTime = 0x02, | |||
124 | | |||
125 | /// <summary> | |||
126 | /// The create time is included. | |||
127 | /// </summary> | |||
128 | CreateTime = 0x04, | |||
129 | } | |||
130 | | |||
131 | #region ITaggedData Members | |||
132 | | |||
133 | /// <summary> | |||
134 | /// Get the ID | |||
135 | /// </summary> | |||
136 | public short TagID { | |||
137 | get { return 0x5455; } | |||
138 | } | |||
139 | | |||
140 | /// <summary> | |||
141 | /// Set the data from the raw values provided. | |||
142 | /// </summary> | |||
143 | /// <param name="data">The raw data to extract values from.</param> | |||
144 | /// <param name="index">The index to start extracting values from.</param> | |||
145 | /// <param name="count">The number of bytes available.</param> | |||
146 | public void SetData(byte[] data, int index, int count) | |||
147 | { | |||
148 | using (MemoryStream ms = new MemoryStream(data, index, count, false)) | |||
149 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
150 | // bit 0 if set, modification time is present | |||
151 | // bit 1 if set, access time is present | |||
152 | // bit 2 if set, creation time is present | |||
153 | | |||
154 | _flags = (Flags)helperStream.ReadByte(); | |||
155 | if (((_flags & Flags.ModificationTime) != 0)) | |||
156 | { | |||
157 | int iTime = helperStream.ReadLEInt(); | |||
158 | | |||
159 | _modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
160 | new TimeSpan(0, 0, 0, iTime, 0); | |||
161 | | |||
162 | // Central-header version is truncated after modification time | |||
163 | if (count <= 5) return; | |||
164 | } | |||
165 | | |||
166 | if ((_flags & Flags.AccessTime) != 0) { | |||
167 | int iTime = helperStream.ReadLEInt(); | |||
168 | | |||
169 | _lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
170 | new TimeSpan(0, 0, 0, iTime, 0); | |||
171 | } | |||
172 | | |||
173 | if ((_flags & Flags.CreateTime) != 0) { | |||
174 | int iTime = helperStream.ReadLEInt(); | |||
175 | | |||
176 | _createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
177 | new TimeSpan(0, 0, 0, iTime, 0); | |||
178 | } | |||
179 | } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Get the binary data representing this instance. | |||
184 | /// </summary> | |||
185 | /// <returns>The raw binary data representing this instance.</returns> | |||
186 | public byte[] GetData() | |||
187 | { | |||
188 | using (MemoryStream ms = new MemoryStream()) | |||
189 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
190 | helperStream.IsStreamOwner = false; | |||
191 | helperStream.WriteByte((byte)_flags); // Flags | |||
192 | if ((_flags & Flags.ModificationTime) != 0) { | |||
193 | TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
194 | var seconds = (int)span.TotalSeconds; | |||
195 | helperStream.WriteLEInt(seconds); | |||
196 | } | |||
197 | if ((_flags & Flags.AccessTime) != 0) { | |||
198 | TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
199 | var seconds = (int)span.TotalSeconds; | |||
200 | helperStream.WriteLEInt(seconds); | |||
201 | } | |||
202 | if ((_flags & Flags.CreateTime) != 0) { | |||
203 | TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
204 | var seconds = (int)span.TotalSeconds; | |||
205 | helperStream.WriteLEInt(seconds); | |||
206 | } | |||
207 | return ms.ToArray(); | |||
208 | } | |||
209 | } | |||
210 | | |||
211 | #endregion | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Test a <see cref="DateTime"> value to see if is valid and can be represented here.</see> | |||
215 | /// </summary> | |||
216 | /// <param name="value">The <see cref="DateTime">value</see> to test.</param> | |||
217 | /// <returns>Returns true if the value is valid and can be represented; false if not.</returns> | |||
218 | /// <remarks>The standard Unix time is a signed integer data type, directly encoding the Unix time number, | |||
219 | /// which is the number of seconds since 1970-01-01. | |||
220 | /// Being 32 bits means the values here cover a range of about 136 years. | |||
221 | /// The minimum representable time is 1901-12-13 20:45:52, | |||
222 | /// and the maximum representable time is 2038-01-19 03:14:07. | |||
223 | /// </remarks> | |||
224 | public static bool IsValidValue(DateTime value) | |||
225 | { | |||
226 | return ((value >= new DateTime(1901, 12, 13, 20, 45, 52)) || | |||
227 | (value <= new DateTime(2038, 1, 19, 03, 14, 07))); | |||
228 | } | |||
229 | | |||
230 | /// <summary> | |||
231 | /// Get /set the Modification Time | |||
232 | /// </summary> | |||
233 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
234 | /// <seealso cref="IsValidValue"></seealso> | |||
235 | public DateTime ModificationTime { | |||
236 | get { return _modificationTime; } | |||
237 | set { | |||
238 | if (!IsValidValue(value)) { | |||
239 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
240 | } | |||
241 | | |||
242 | _flags |= Flags.ModificationTime; | |||
243 | _modificationTime = value; | |||
244 | } | |||
245 | } | |||
246 | | |||
247 | /// <summary> | |||
248 | /// Get / set the Access Time | |||
249 | /// </summary> | |||
250 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
251 | /// <seealso cref="IsValidValue"></seealso> | |||
252 | public DateTime AccessTime { | |||
253 | get { return _lastAccessTime; } | |||
254 | set { | |||
255 | if (!IsValidValue(value)) { | |||
256 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
257 | } | |||
258 | | |||
259 | _flags |= Flags.AccessTime; | |||
260 | _lastAccessTime = value; | |||
261 | } | |||
262 | } | |||
263 | | |||
264 | /// <summary> | |||
265 | /// Get / Set the Create Time | |||
266 | /// </summary> | |||
267 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
268 | /// <seealso cref="IsValidValue"></seealso> | |||
269 | public DateTime CreateTime { | |||
270 | get { return _createTime; } | |||
271 | set { | |||
272 | if (!IsValidValue(value)) { | |||
273 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
274 | } | |||
275 | | |||
276 | _flags |= Flags.CreateTime; | |||
277 | _createTime = value; | |||
278 | } | |||
279 | } | |||
280 | | |||
281 | /// <summary> | |||
282 | /// Get/set the <see cref="Flags">values</see> to include. | |||
283 | /// </summary> | |||
284 | public Flags Include | |||
285 | { | |||
286 | get { return _flags; } | |||
287 | set { _flags = value; } | |||
288 | } | |||
289 | | |||
290 | #region Instance Fields | |||
291 | Flags _flags; | |||
292 | DateTime _modificationTime = new DateTime(1970, 1, 1); | |||
293 | DateTime _lastAccessTime = new DateTime(1970, 1, 1); | |||
294 | DateTime _createTime = new DateTime(1970, 1, 1); | |||
295 | #endregion | |||
296 | } | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Class handling NT date time values. | |||
300 | /// </summary> | |||
301 | public class NTTaggedData : ITaggedData | |||
302 | { | |||
303 | /// <summary> | |||
304 | /// Get the ID for this tagged data value. | |||
305 | /// </summary> | |||
306 | public short TagID { | |||
307 | get { return 10; } | |||
308 | } | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Set the data from the raw values provided. | |||
312 | /// </summary> | |||
313 | /// <param name="data">The raw data to extract values from.</param> | |||
314 | /// <param name="index">The index to start extracting values from.</param> | |||
315 | /// <param name="count">The number of bytes available.</param> | |||
316 | public void SetData(byte[] data, int index, int count) | |||
317 | { | |||
318 | using (MemoryStream ms = new MemoryStream(data, index, count, false)) | |||
319 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
320 | helperStream.ReadLEInt(); // Reserved | |||
321 | while (helperStream.Position < helperStream.Length) { | |||
322 | int ntfsTag = helperStream.ReadLEShort(); | |||
323 | int ntfsLength = helperStream.ReadLEShort(); | |||
324 | if (ntfsTag == 1) { | |||
325 | if (ntfsLength >= 24) { | |||
326 | long lastModificationTicks = helperStream.ReadLELong(); | |||
327 | _lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks); | |||
328 | | |||
329 | long lastAccessTicks = helperStream.ReadLELong(); | |||
330 | _lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks); | |||
331 | | |||
332 | long createTimeTicks = helperStream.ReadLELong(); | |||
333 | _createTime = DateTime.FromFileTimeUtc(createTimeTicks); | |||
334 | } | |||
335 | break; | |||
336 | } else { | |||
337 | // An unknown NTFS tag so simply skip it. | |||
338 | helperStream.Seek(ntfsLength, SeekOrigin.Current); | |||
339 | } | |||
340 | } | |||
341 | } | |||
342 | } | |||
343 | | |||
344 | /// <summary> | |||
345 | /// Get the binary data representing this instance. | |||
346 | /// </summary> | |||
347 | /// <returns>The raw binary data representing this instance.</returns> | |||
348 | public byte[] GetData() | |||
349 | { | |||
350 | using (MemoryStream ms = new MemoryStream()) | |||
351 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
352 | helperStream.IsStreamOwner = false; | |||
353 | helperStream.WriteLEInt(0); // Reserved | |||
354 | helperStream.WriteLEShort(1); // Tag | |||
355 | helperStream.WriteLEShort(24); // Length = 3 x 8. | |||
356 | helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc()); | |||
357 | helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc()); | |||
358 | helperStream.WriteLELong(_createTime.ToFileTimeUtc()); | |||
359 | return ms.ToArray(); | |||
360 | } | |||
361 | } | |||
362 | | |||
363 | /// <summary> | |||
364 | /// Test a <see cref="DateTime"> valuie to see if is valid and can be represented here.</see> | |||
365 | /// </summary> | |||
366 | /// <param name="value">The <see cref="DateTime">value</see> to test.</param> | |||
367 | /// <returns>Returns true if the value is valid and can be represented; false if not.</returns> | |||
368 | /// <remarks> | |||
369 | /// NTFS filetimes are 64-bit unsigned integers, stored in Intel | |||
370 | /// (least significant byte first) byte order. They determine the | |||
371 | /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch", | |||
372 | /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit | |||
373 | /// </remarks> | |||
374 | public static bool IsValidValue(DateTime value) | |||
375 | { | |||
376 | bool result = true; | |||
377 | try { | |||
378 | value.ToFileTimeUtc(); | |||
379 | } catch { | |||
380 | result = false; | |||
381 | } | |||
382 | return result; | |||
383 | } | |||
384 | | |||
385 | /// <summary> | |||
386 | /// Get/set the <see cref="DateTime">last modification time</see>. | |||
387 | /// </summary> | |||
388 | public DateTime LastModificationTime { | |||
389 | get { return _lastModificationTime; } | |||
390 | set { | |||
391 | if (!IsValidValue(value)) { | |||
392 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
393 | } | |||
394 | _lastModificationTime = value; | |||
395 | } | |||
396 | } | |||
397 | | |||
398 | /// <summary> | |||
399 | /// Get /set the <see cref="DateTime">create time</see> | |||
400 | /// </summary> | |||
401 | public DateTime CreateTime { | |||
402 | get { return _createTime; } | |||
403 | set { | |||
404 | if (!IsValidValue(value)) { | |||
405 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
406 | } | |||
407 | _createTime = value; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Get /set the <see cref="DateTime">last access time</see>. | |||
413 | /// </summary> | |||
414 | public DateTime LastAccessTime { | |||
415 | get { return _lastAccessTime; } | |||
416 | set { | |||
417 | if (!IsValidValue(value)) { | |||
418 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
419 | } | |||
420 | _lastAccessTime = value; | |||
421 | } | |||
422 | } | |||
423 | | |||
424 | #region Instance Fields | |||
425 | DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0); | |||
426 | DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0); | |||
427 | DateTime _createTime = DateTime.FromFileTimeUtc(0); | |||
428 | #endregion | |||
429 | } | |||
430 | | |||
431 | /// <summary> | |||
432 | /// A factory that creates <see cref="ITaggedData">tagged data</see> instances. | |||
433 | /// </summary> | |||
434 | interface ITaggedDataFactory | |||
435 | { | |||
436 | /// <summary> | |||
437 | /// Get data for a specific tag value. | |||
438 | /// </summary> | |||
439 | /// <param name="tag">The tag ID to find.</param> | |||
440 | /// <param name="data">The data to search.</param> | |||
441 | /// <param name="offset">The offset to begin extracting data from.</param> | |||
442 | /// <param name="count">The number of bytes to extract.</param> | |||
443 | /// <returns>The located <see cref="ITaggedData">value found</see>, or null if not found.</returns> | |||
444 | ITaggedData Create(short tag, byte[] data, int offset, int count); | |||
445 | } | |||
446 | | |||
447 | /// | |||
448 | /// <summary> | |||
449 | /// A class to handle the extra data field for Zip entries | |||
450 | /// </summary> | |||
451 | /// <remarks> | |||
452 | /// Extra data contains 0 or more values each prefixed by a header tag and length. | |||
453 | /// They contain zero or more bytes of actual data. | |||
454 | /// The data is held internally using a copy on write strategy. This is more efficient but | |||
455 | /// means that for extra data created by passing in data can have the values modified by the caller | |||
456 | /// in some circumstances. | |||
457 | /// </remarks> | |||
458 | sealed public class ZipExtraData : IDisposable | |||
459 | { | |||
460 | #region Constructors | |||
461 | /// <summary> | |||
462 | /// Initialise a default instance. | |||
463 | /// </summary> | |||
464 | public ZipExtraData() | |||
465 | { | |||
466 | Clear(); | |||
467 | } | |||
468 | | |||
469 | /// <summary> | |||
470 | /// Initialise with known extra data. | |||
471 | /// </summary> | |||
472 | /// <param name="data">The extra data.</param> | |||
473 | public ZipExtraData(byte[] data) | |||
474 | { | |||
475 | if (data == null) { | |||
476 | _data = new byte[0]; | |||
477 | } else { | |||
478 | _data = data; | |||
479 | } | |||
480 | } | |||
481 | #endregion | |||
482 | | |||
483 | /// <summary> | |||
484 | /// Get the raw extra data value | |||
485 | /// </summary> | |||
486 | /// <returns>Returns the raw byte[] extra data this instance represents.</returns> | |||
487 | public byte[] GetEntryData() | |||
488 | { | |||
489 | if (Length > ushort.MaxValue) { | |||
490 | throw new ZipException("Data exceeds maximum length"); | |||
491 | } | |||
492 | | |||
493 | return (byte[])_data.Clone(); | |||
494 | } | |||
495 | | |||
496 | /// <summary> | |||
497 | /// Clear the stored data. | |||
498 | /// </summary> | |||
499 | public void Clear() | |||
500 | { | |||
501 | if ((_data == null) || (_data.Length != 0)) { | |||
502 | _data = new byte[0]; | |||
503 | } | |||
504 | } | |||
505 | | |||
506 | /// <summary> | |||
507 | /// Gets the current extra data length. | |||
508 | /// </summary> | |||
509 | public int Length { | |||
510 | get { return _data.Length; } | |||
511 | } | |||
512 | | |||
513 | /// <summary> | |||
514 | /// Get a read-only <see cref="Stream"/> for the associated tag. | |||
515 | /// </summary> | |||
516 | /// <param name="tag">The tag to locate data for.</param> | |||
517 | /// <returns>Returns a <see cref="Stream"/> containing tag data or null if no tag was found.</returns> | |||
518 | public Stream GetStreamForTag(int tag) | |||
519 | { | |||
520 | Stream result = null; | |||
521 | if (Find(tag)) { | |||
522 | result = new MemoryStream(_data, _index, _readValueLength, false); | |||
523 | } | |||
524 | return result; | |||
525 | } | |||
526 | | |||
527 | /// <summary> | |||
528 | /// Get the <see cref="ITaggedData">tagged data</see> for a tag. | |||
529 | /// </summary> | |||
530 | /// <typeparam name="T">The tag to search for.</typeparam> | |||
531 | /// <returns>Returns a <see cref="ITaggedData">tagged value</see> or null if none found.</returns> | |||
532 | public T GetData<T>() | |||
533 | where T : class, ITaggedData, new() | |||
534 | { | |||
535 | T result = new T(); | |||
536 | if (Find(result.TagID)) | |||
537 | { | |||
538 | result.SetData(_data, _readValueStart, _readValueLength); | |||
539 | return result; | |||
540 | } | |||
541 | else return null; | |||
542 | } | |||
543 | | |||
544 | /// <summary> | |||
545 | /// Get the length of the last value found by <see cref="Find"/> | |||
546 | /// </summary> | |||
547 | /// <remarks>This is only valid if <see cref="Find"/> has previously returned true.</remarks> | |||
548 | public int ValueLength { | |||
549 | get { return _readValueLength; } | |||
550 | } | |||
551 | | |||
552 | /// <summary> | |||
553 | /// Get the index for the current read value. | |||
554 | /// </summary> | |||
555 | /// <remarks>This is only valid if <see cref="Find"/> has previously returned true. | |||
556 | /// Initially the result will be the index of the first byte of actual data. The value is updated after calls to | |||
557 | /// <see cref="ReadInt"/>, <see cref="ReadShort"/> and <see cref="ReadLong"/>. </remarks> | |||
558 | public int CurrentReadIndex { | |||
559 | get { return _index; } | |||
560 | } | |||
561 | | |||
562 | /// <summary> | |||
563 | /// Get the number of bytes remaining to be read for the current value; | |||
564 | /// </summary> | |||
565 | public int UnreadCount { | |||
566 | get { | |||
567 | if ((_readValueStart > _data.Length) || | |||
568 | (_readValueStart < 4)) { | |||
569 | throw new ZipException("Find must be called before calling a Read method"); | |||
570 | } | |||
571 | | |||
572 | return _readValueStart + _readValueLength - _index; | |||
573 | } | |||
574 | } | |||
575 | | |||
576 | /// <summary> | |||
577 | /// Find an extra data value | |||
578 | /// </summary> | |||
579 | /// <param name="headerID">The identifier for the value to find.</param> | |||
580 | /// <returns>Returns true if the value was found; false otherwise.</returns> | |||
581 | public bool Find(int headerID) | |||
582 | { | |||
583 | _readValueStart = _data.Length; | |||
584 | _readValueLength = 0; | |||
585 | _index = 0; | |||
586 | | |||
587 | int localLength = _readValueStart; | |||
588 | int localTag = headerID - 1; | |||
589 | | |||
590 | // Trailing bytes that cant make up an entry (as there arent enough | |||
591 | // bytes for a tag and length) are ignored! | |||
592 | while ((localTag != headerID) && (_index < _data.Length - 3)) { | |||
593 | localTag = ReadShortInternal(); | |||
594 | localLength = ReadShortInternal(); | |||
595 | if (localTag != headerID) { | |||
596 | _index += localLength; | |||
597 | } | |||
598 | } | |||
599 | | |||
600 | bool result = (localTag == headerID) && ((_index + localLength) <= _data.Length); | |||
601 | | |||
602 | if (result) { | |||
603 | _readValueStart = _index; | |||
604 | _readValueLength = localLength; | |||
605 | } | |||
606 | | |||
607 | return result; | |||
608 | } | |||
609 | | |||
610 | /// <summary> | |||
611 | /// Add a new entry to extra data. | |||
612 | /// </summary> | |||
613 | /// <param name="taggedData">The <see cref="ITaggedData"/> value to add.</param> | |||
614 | public void AddEntry(ITaggedData taggedData) | |||
615 | { | |||
616 | if (taggedData == null) { | |||
617 | throw new ArgumentNullException(nameof(taggedData)); | |||
618 | } | |||
619 | AddEntry(taggedData.TagID, taggedData.GetData()); | |||
620 | } | |||
621 | | |||
622 | /// <summary> | |||
623 | /// Add a new entry to extra data | |||
624 | /// </summary> | |||
625 | /// <param name="headerID">The ID for this entry.</param> | |||
626 | /// <param name="fieldData">The data to add.</param> | |||
627 | /// <remarks>If the ID already exists its contents are replaced.</remarks> | |||
628 | public void AddEntry(int headerID, byte[] fieldData) | |||
629 | { | |||
630 | if ((headerID > ushort.MaxValue) || (headerID < 0)) { | |||
631 | throw new ArgumentOutOfRangeException(nameof(headerID)); | |||
632 | } | |||
633 | | |||
634 | int addLength = (fieldData == null) ? 0 : fieldData.Length; | |||
635 | | |||
636 | if (addLength > ushort.MaxValue) { | |||
637 | throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length"); | |||
638 | } | |||
639 | | |||
640 | // Test for new length before adjusting data. | |||
641 | int newLength = _data.Length + addLength + 4; | |||
642 | | |||
643 | if (Find(headerID)) { | |||
644 | newLength -= (ValueLength + 4); | |||
645 | } | |||
646 | | |||
647 | if (newLength > ushort.MaxValue) { | |||
648 | throw new ZipException("Data exceeds maximum length"); | |||
649 | } | |||
650 | | |||
651 | Delete(headerID); | |||
652 | | |||
653 | byte[] newData = new byte[newLength]; | |||
654 | _data.CopyTo(newData, 0); | |||
655 | int index = _data.Length; | |||
656 | _data = newData; | |||
657 | SetShort(ref index, headerID); | |||
658 | SetShort(ref index, addLength); | |||
659 | if (fieldData != null) { | |||
660 | fieldData.CopyTo(newData, index); | |||
661 | } | |||
662 | } | |||
663 | | |||
664 | /// <summary> | |||
665 | /// Start adding a new entry. | |||
666 | /// </summary> | |||
667 | /// <remarks>Add data using <see cref="AddData(byte[])"/>, <see cref="AddLeShort"/>, <see cref="AddLeInt"/>, or <see | |||
668 | /// The new entry is completed and actually added by calling <see cref="AddNewEntry"/></remarks> | |||
669 | /// <seealso cref="AddEntry(ITaggedData)"/> | |||
670 | public void StartNewEntry() | |||
671 | { | |||
672 | _newEntry = new MemoryStream(); | |||
673 | } | |||
674 | | |||
675 | /// <summary> | |||
676 | /// Add entry data added since <see cref="StartNewEntry"/> using the ID passed. | |||
677 | /// </summary> | |||
678 | /// <param name="headerID">The identifier to use for this entry.</param> | |||
679 | public void AddNewEntry(int headerID) | |||
680 | { | |||
681 | byte[] newData = _newEntry.ToArray(); | |||
682 | _newEntry = null; | |||
683 | AddEntry(headerID, newData); | |||
684 | } | |||
685 | | |||
686 | /// <summary> | |||
687 | /// Add a byte of data to the pending new entry. | |||
688 | /// </summary> | |||
689 | /// <param name="data">The byte to add.</param> | |||
690 | /// <seealso cref="StartNewEntry"/> | |||
691 | public void AddData(byte data) | |||
692 | { | |||
693 | _newEntry.WriteByte(data); | |||
694 | } | |||
695 | | |||
696 | /// <summary> | |||
697 | /// Add data to a pending new entry. | |||
698 | /// </summary> | |||
699 | /// <param name="data">The data to add.</param> | |||
700 | /// <seealso cref="StartNewEntry"/> | |||
701 | public void AddData(byte[] data) | |||
702 | { | |||
703 | if (data == null) { | |||
704 | throw new ArgumentNullException(nameof(data)); | |||
705 | } | |||
706 | | |||
707 | _newEntry.Write(data, 0, data.Length); | |||
708 | } | |||
709 | | |||
710 | /// <summary> | |||
711 | /// Add a short value in little endian order to the pending new entry. | |||
712 | /// </summary> | |||
713 | /// <param name="toAdd">The data to add.</param> | |||
714 | /// <seealso cref="StartNewEntry"/> | |||
715 | public void AddLeShort(int toAdd) | |||
716 | { | |||
717 | unchecked { | |||
718 | _newEntry.WriteByte((byte)toAdd); | |||
719 | _newEntry.WriteByte((byte)(toAdd >> 8)); | |||
720 | } | |||
721 | } | |||
722 | | |||
723 | /// <summary> | |||
724 | /// Add an integer value in little endian order to the pending new entry. | |||
725 | /// </summary> | |||
726 | /// <param name="toAdd">The data to add.</param> | |||
727 | /// <seealso cref="StartNewEntry"/> | |||
728 | public void AddLeInt(int toAdd) | |||
729 | { | |||
730 | unchecked { | |||
731 | AddLeShort((short)toAdd); | |||
732 | AddLeShort((short)(toAdd >> 16)); | |||
733 | } | |||
734 | } | |||
735 | | |||
736 | /// <summary> | |||
737 | /// Add a long value in little endian order to the pending new entry. | |||
738 | /// </summary> | |||
739 | /// <param name="toAdd">The data to add.</param> | |||
740 | /// <seealso cref="StartNewEntry"/> | |||
741 | public void AddLeLong(long toAdd) | |||
742 | { | |||
743 | unchecked { | |||
744 | AddLeInt((int)(toAdd & 0xffffffff)); | |||
745 | AddLeInt((int)(toAdd >> 32)); | |||
746 | } | |||
747 | } | |||
748 | | |||
749 | /// <summary> | |||
750 | /// Delete an extra data field. | |||
751 | /// </summary> | |||
752 | /// <param name="headerID">The identifier of the field to delete.</param> | |||
753 | /// <returns>Returns true if the field was found and deleted.</returns> | |||
754 | public bool Delete(int headerID) | |||
755 | { | |||
756 | bool result = false; | |||
757 | | |||
758 | if (Find(headerID)) { | |||
759 | result = true; | |||
760 | int trueStart = _readValueStart - 4; | |||
761 | | |||
762 | byte[] newData = new byte[_data.Length - (ValueLength + 4)]; | |||
763 | Array.Copy(_data, 0, newData, 0, trueStart); | |||
764 | | |||
765 | int trueEnd = trueStart + ValueLength + 4; | |||
766 | Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd); | |||
767 | _data = newData; | |||
768 | } | |||
769 | return result; | |||
770 | } | |||
771 | | |||
772 | #region Reading Support | |||
773 | /// <summary> | |||
774 | /// Read a long in little endian form from the last <see cref="Find">found</see> data value | |||
775 | /// </summary> | |||
776 | /// <returns>Returns the long value read.</returns> | |||
777 | public long ReadLong() | |||
778 | { | |||
779 | ReadCheck(8); | |||
780 | return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32); | |||
781 | } | |||
782 | | |||
783 | /// <summary> | |||
784 | /// Read an integer in little endian form from the last <see cref="Find">found</see> data value. | |||
785 | /// </summary> | |||
786 | /// <returns>Returns the integer read.</returns> | |||
787 | public int ReadInt() | |||
788 | { | |||
789 | ReadCheck(4); | |||
790 | | |||
791 | int result = _data[_index] + (_data[_index + 1] << 8) + | |||
792 | (_data[_index + 2] << 16) + (_data[_index + 3] << 24); | |||
793 | _index += 4; | |||
794 | return result; | |||
795 | } | |||
796 | | |||
797 | /// <summary> | |||
798 | /// Read a short value in little endian form from the last <see cref="Find">found</see> data value. | |||
799 | /// </summary> | |||
800 | /// <returns>Returns the short value read.</returns> | |||
801 | public int ReadShort() | |||
802 | { | |||
803 | ReadCheck(2); | |||
804 | int result = _data[_index] + (_data[_index + 1] << 8); | |||
805 | _index += 2; | |||
806 | return result; | |||
807 | } | |||
808 | | |||
809 | /// <summary> | |||
810 | /// Read a byte from an extra data | |||
811 | /// </summary> | |||
812 | /// <returns>The byte value read or -1 if the end of data has been reached.</returns> | |||
813 | public int ReadByte() | |||
814 | { | |||
815 | int result = -1; | |||
816 | if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index)) { | |||
817 | result = _data[_index]; | |||
818 | _index += 1; | |||
819 | } | |||
820 | return result; | |||
821 | } | |||
822 | | |||
823 | /// <summary> | |||
824 | /// Skip data during reading. | |||
825 | /// </summary> | |||
826 | /// <param name="amount">The number of bytes to skip.</param> | |||
827 | public void Skip(int amount) | |||
828 | { | |||
829 | ReadCheck(amount); | |||
830 | _index += amount; | |||
831 | } | |||
832 | | |||
833 | void ReadCheck(int length) | |||
834 | { | |||
835 | if ((_readValueStart > _data.Length) || | |||
836 | (_readValueStart < 4)) { | |||
837 | throw new ZipException("Find must be called before calling a Read method"); | |||
838 | } | |||
839 | | |||
840 | if (_index > _readValueStart + _readValueLength - length) { | |||
841 | throw new ZipException("End of extra data"); | |||
842 | } | |||
843 | | |||
844 | if (_index + length < 4) { | |||
845 | throw new ZipException("Cannot read before start of tag"); | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | /// <summary> | |||
850 | /// Internal form of <see cref="ReadShort"/> that reads data at any location. | |||
851 | /// </summary> | |||
852 | /// <returns>Returns the short value read.</returns> | |||
853 | int ReadShortInternal() | |||
854 | { | |||
855 | if (_index > _data.Length - 2) { | |||
856 | throw new ZipException("End of extra data"); | |||
857 | } | |||
858 | | |||
859 | int result = _data[_index] + (_data[_index + 1] << 8); | |||
860 | _index += 2; | |||
861 | return result; | |||
862 | } | |||
863 | | |||
864 | void SetShort(ref int index, int source) | |||
865 | { | |||
866 | _data[index] = (byte)source; | |||
867 | _data[index + 1] = (byte)(source >> 8); | |||
868 | index += 2; | |||
869 | } | |||
870 | | |||
871 | #endregion | |||
872 | | |||
873 | #region IDisposable Members | |||
874 | | |||
875 | /// <summary> | |||
876 | /// Dispose of this instance. | |||
877 | /// </summary> | |||
878 | public void Dispose() | |||
879 | { | |||
880 | if (_newEntry != null) { | |||
881 | _newEntry.Close(); | |||
882 | } | |||
883 | } | |||
884 | | |||
885 | #endregion | |||
886 | | |||
887 | #region Instance Fields | |||
888 | int _index; | |||
889 | int _readValueStart; | |||
890 | int _readValueLength; | |||
891 | | |||
892 | MemoryStream _newEntry; | |||
893 | byte[] _data; | |||
894 | #endregion | |||
895 | } | |||
896 | } |
| Class: | ICSharpCode.SharpZipLib.Core.ScanEventArgs |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\FileSystemScanner.cs |
| Covered lines: | 6 |
| Uncovered lines: | 1 |
| Coverable lines: | 7 |
| Total lines: | 475 |
| Line coverage: | 85.7% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Core | |||
4 | { | |||
5 | #region EventArgs | |||
6 | /// <summary> | |||
7 | /// Event arguments for scanning. | |||
8 | /// </summary> | |||
9 | public class ScanEventArgs : EventArgs | |||
10 | { | |||
11 | #region Constructors | |||
12 | /// <summary> | |||
13 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
14 | /// </summary> | |||
15 | /// <param name="name">The file or directory name.</param> | |||
| 4 | 16 | public ScanEventArgs(string name) | ||
17 | { | |||
| 4 | 18 | name_ = name; | ||
| 4 | 19 | } | ||
20 | #endregion | |||
21 | | |||
22 | /// <summary> | |||
23 | /// The file or directory name for this event. | |||
24 | /// </summary> | |||
25 | public string Name { | |||
| 12 | 26 | get { return name_; } | ||
27 | } | |||
28 | | |||
29 | /// <summary> | |||
30 | /// Get set a value indicating if scanning should continue or not. | |||
31 | /// </summary> | |||
32 | public bool ContinueRunning { | |||
| 8 | 33 | get { return continueRunning_; } | ||
| 0 | 34 | set { continueRunning_ = value; } | ||
35 | } | |||
36 | | |||
37 | #region Instance Fields | |||
38 | string name_; | |||
| 4 | 39 | bool continueRunning_ = true; | ||
40 | #endregion | |||
41 | } | |||
42 | | |||
43 | /// <summary> | |||
44 | /// Event arguments during processing of a single file or directory. | |||
45 | /// </summary> | |||
46 | public class ProgressEventArgs : EventArgs | |||
47 | { | |||
48 | #region Constructors | |||
49 | /// <summary> | |||
50 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
51 | /// </summary> | |||
52 | /// <param name="name">The file or directory name if known.</param> | |||
53 | /// <param name="processed">The number of bytes processed so far</param> | |||
54 | /// <param name="target">The total number of bytes to process, 0 if not known</param> | |||
55 | public ProgressEventArgs(string name, long processed, long target) | |||
56 | { | |||
57 | name_ = name; | |||
58 | processed_ = processed; | |||
59 | target_ = target; | |||
60 | } | |||
61 | #endregion | |||
62 | | |||
63 | /// <summary> | |||
64 | /// The name for this event if known. | |||
65 | /// </summary> | |||
66 | public string Name { | |||
67 | get { return name_; } | |||
68 | } | |||
69 | | |||
70 | /// <summary> | |||
71 | /// Get set a value indicating wether scanning should continue or not. | |||
72 | /// </summary> | |||
73 | public bool ContinueRunning { | |||
74 | get { return continueRunning_; } | |||
75 | set { continueRunning_ = value; } | |||
76 | } | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Get a percentage representing how much of the <see cref="Target"></see> has been processed | |||
80 | /// </summary> | |||
81 | /// <value>0.0 to 100.0 percent; 0 if target is not known.</value> | |||
82 | public float PercentComplete { | |||
83 | get { | |||
84 | float result; | |||
85 | if (target_ <= 0) { | |||
86 | result = 0; | |||
87 | } else { | |||
88 | result = ((float)processed_ / (float)target_) * 100.0f; | |||
89 | } | |||
90 | return result; | |||
91 | } | |||
92 | } | |||
93 | | |||
94 | /// <summary> | |||
95 | /// The number of bytes processed so far | |||
96 | /// </summary> | |||
97 | public long Processed { | |||
98 | get { return processed_; } | |||
99 | } | |||
100 | | |||
101 | /// <summary> | |||
102 | /// The number of bytes to process. | |||
103 | /// </summary> | |||
104 | /// <remarks>Target may be 0 or negative if the value isnt known.</remarks> | |||
105 | public long Target { | |||
106 | get { return target_; } | |||
107 | } | |||
108 | | |||
109 | #region Instance Fields | |||
110 | string name_; | |||
111 | long processed_; | |||
112 | long target_; | |||
113 | bool continueRunning_ = true; | |||
114 | #endregion | |||
115 | } | |||
116 | | |||
117 | /// <summary> | |||
118 | /// Event arguments for directories. | |||
119 | /// </summary> | |||
120 | public class DirectoryEventArgs : ScanEventArgs | |||
121 | { | |||
122 | #region Constructors | |||
123 | /// <summary> | |||
124 | /// Initialize an instance of <see cref="DirectoryEventArgs"></see>. | |||
125 | /// </summary> | |||
126 | /// <param name="name">The name for this directory.</param> | |||
127 | /// <param name="hasMatchingFiles">Flag value indicating if any matching files are contained in this directory.</par | |||
128 | public DirectoryEventArgs(string name, bool hasMatchingFiles) | |||
129 | : base(name) | |||
130 | { | |||
131 | hasMatchingFiles_ = hasMatchingFiles; | |||
132 | } | |||
133 | #endregion | |||
134 | | |||
135 | /// <summary> | |||
136 | /// Get a value indicating if the directory contains any matching files or not. | |||
137 | /// </summary> | |||
138 | public bool HasMatchingFiles { | |||
139 | get { return hasMatchingFiles_; } | |||
140 | } | |||
141 | | |||
142 | readonly | |||
143 | | |||
144 | #region Instance Fields | |||
145 | bool hasMatchingFiles_; | |||
146 | #endregion | |||
147 | } | |||
148 | | |||
149 | /// <summary> | |||
150 | /// Arguments passed when scan failures are detected. | |||
151 | /// </summary> | |||
152 | public class ScanFailureEventArgs : EventArgs | |||
153 | { | |||
154 | #region Constructors | |||
155 | /// <summary> | |||
156 | /// Initialise a new instance of <see cref="ScanFailureEventArgs"></see> | |||
157 | /// </summary> | |||
158 | /// <param name="name">The name to apply.</param> | |||
159 | /// <param name="e">The exception to use.</param> | |||
160 | public ScanFailureEventArgs(string name, Exception e) | |||
161 | { | |||
162 | name_ = name; | |||
163 | exception_ = e; | |||
164 | continueRunning_ = true; | |||
165 | } | |||
166 | #endregion | |||
167 | | |||
168 | /// <summary> | |||
169 | /// The applicable name. | |||
170 | /// </summary> | |||
171 | public string Name { | |||
172 | get { return name_; } | |||
173 | } | |||
174 | | |||
175 | /// <summary> | |||
176 | /// The applicable exception. | |||
177 | /// </summary> | |||
178 | public Exception Exception { | |||
179 | get { return exception_; } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Get / set a value indicating wether scanning should continue. | |||
184 | /// </summary> | |||
185 | public bool ContinueRunning { | |||
186 | get { return continueRunning_; } | |||
187 | set { continueRunning_ = value; } | |||
188 | } | |||
189 | | |||
190 | #region Instance Fields | |||
191 | string name_; | |||
192 | Exception exception_; | |||
193 | bool continueRunning_; | |||
194 | #endregion | |||
195 | } | |||
196 | | |||
197 | #endregion | |||
198 | | |||
199 | #region Delegates | |||
200 | /// <summary> | |||
201 | /// Delegate invoked before starting to process a file. | |||
202 | /// </summary> | |||
203 | /// <param name="sender">The source of the event</param> | |||
204 | /// <param name="e">The event arguments.</param> | |||
205 | public delegate void ProcessFileHandler(object sender, ScanEventArgs e); | |||
206 | | |||
207 | /// <summary> | |||
208 | /// Delegate invoked during processing of a file or directory | |||
209 | /// </summary> | |||
210 | /// <param name="sender">The source of the event</param> | |||
211 | /// <param name="e">The event arguments.</param> | |||
212 | public delegate void ProgressHandler(object sender, ProgressEventArgs e); | |||
213 | | |||
214 | /// <summary> | |||
215 | /// Delegate invoked when a file has been completely processed. | |||
216 | /// </summary> | |||
217 | /// <param name="sender">The source of the event</param> | |||
218 | /// <param name="e">The event arguments.</param> | |||
219 | public delegate void CompletedFileHandler(object sender, ScanEventArgs e); | |||
220 | | |||
221 | /// <summary> | |||
222 | /// Delegate invoked when a directory failure is detected. | |||
223 | /// </summary> | |||
224 | /// <param name="sender">The source of the event</param> | |||
225 | /// <param name="e">The event arguments.</param> | |||
226 | public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e); | |||
227 | | |||
228 | /// <summary> | |||
229 | /// Delegate invoked when a file failure is detected. | |||
230 | /// </summary> | |||
231 | /// <param name="sender">The source of the event</param> | |||
232 | /// <param name="e">The event arguments.</param> | |||
233 | public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e); | |||
234 | #endregion | |||
235 | | |||
236 | /// <summary> | |||
237 | /// FileSystemScanner provides facilities scanning of files and directories. | |||
238 | /// </summary> | |||
239 | public class FileSystemScanner | |||
240 | { | |||
241 | #region Constructors | |||
242 | /// <summary> | |||
243 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
244 | /// </summary> | |||
245 | /// <param name="filter">The <see cref="PathFilter">file filter</see> to apply when scanning.</param> | |||
246 | public FileSystemScanner(string filter) | |||
247 | { | |||
248 | fileFilter_ = new PathFilter(filter); | |||
249 | } | |||
250 | | |||
251 | /// <summary> | |||
252 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
253 | /// </summary> | |||
254 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
255 | /// <param name="directoryFilter">The <see cref="PathFilter"> directory filter</see> to apply.</param> | |||
256 | public FileSystemScanner(string fileFilter, string directoryFilter) | |||
257 | { | |||
258 | fileFilter_ = new PathFilter(fileFilter); | |||
259 | directoryFilter_ = new PathFilter(directoryFilter); | |||
260 | } | |||
261 | | |||
262 | /// <summary> | |||
263 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
264 | /// </summary> | |||
265 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
266 | public FileSystemScanner(IScanFilter fileFilter) | |||
267 | { | |||
268 | fileFilter_ = fileFilter; | |||
269 | } | |||
270 | | |||
271 | /// <summary> | |||
272 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
273 | /// </summary> | |||
274 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
275 | /// <param name="directoryFilter">The directory <see cref="IScanFilter">filter</see> to apply.</param> | |||
276 | public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter) | |||
277 | { | |||
278 | fileFilter_ = fileFilter; | |||
279 | directoryFilter_ = directoryFilter; | |||
280 | } | |||
281 | #endregion | |||
282 | | |||
283 | #region Delegates | |||
284 | /// <summary> | |||
285 | /// Delegate to invoke when a directory is processed. | |||
286 | /// </summary> | |||
287 | public event EventHandler<DirectoryEventArgs> ProcessDirectory; | |||
288 | | |||
289 | /// <summary> | |||
290 | /// Delegate to invoke when a file is processed. | |||
291 | /// </summary> | |||
292 | public ProcessFileHandler ProcessFile; | |||
293 | | |||
294 | /// <summary> | |||
295 | /// Delegate to invoke when processing for a file has finished. | |||
296 | /// </summary> | |||
297 | public CompletedFileHandler CompletedFile; | |||
298 | | |||
299 | /// <summary> | |||
300 | /// Delegate to invoke when a directory failure is detected. | |||
301 | /// </summary> | |||
302 | public DirectoryFailureHandler DirectoryFailure; | |||
303 | | |||
304 | /// <summary> | |||
305 | /// Delegate to invoke when a file failure is detected. | |||
306 | /// </summary> | |||
307 | public FileFailureHandler FileFailure; | |||
308 | #endregion | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Raise the DirectoryFailure event. | |||
312 | /// </summary> | |||
313 | /// <param name="directory">The directory name.</param> | |||
314 | /// <param name="e">The exception detected.</param> | |||
315 | bool OnDirectoryFailure(string directory, Exception e) | |||
316 | { | |||
317 | DirectoryFailureHandler handler = DirectoryFailure; | |||
318 | bool result = (handler != null); | |||
319 | if (result) { | |||
320 | var args = new ScanFailureEventArgs(directory, e); | |||
321 | handler(this, args); | |||
322 | alive_ = args.ContinueRunning; | |||
323 | } | |||
324 | return result; | |||
325 | } | |||
326 | | |||
327 | /// <summary> | |||
328 | /// Raise the FileFailure event. | |||
329 | /// </summary> | |||
330 | /// <param name="file">The file name.</param> | |||
331 | /// <param name="e">The exception detected.</param> | |||
332 | bool OnFileFailure(string file, Exception e) | |||
333 | { | |||
334 | FileFailureHandler handler = FileFailure; | |||
335 | | |||
336 | bool result = (handler != null); | |||
337 | | |||
338 | if (result) { | |||
339 | var args = new ScanFailureEventArgs(file, e); | |||
340 | FileFailure(this, args); | |||
341 | alive_ = args.ContinueRunning; | |||
342 | } | |||
343 | return result; | |||
344 | } | |||
345 | | |||
346 | /// <summary> | |||
347 | /// Raise the ProcessFile event. | |||
348 | /// </summary> | |||
349 | /// <param name="file">The file name.</param> | |||
350 | void OnProcessFile(string file) | |||
351 | { | |||
352 | ProcessFileHandler handler = ProcessFile; | |||
353 | | |||
354 | if (handler != null) { | |||
355 | var args = new ScanEventArgs(file); | |||
356 | handler(this, args); | |||
357 | alive_ = args.ContinueRunning; | |||
358 | } | |||
359 | } | |||
360 | | |||
361 | /// <summary> | |||
362 | /// Raise the complete file event | |||
363 | /// </summary> | |||
364 | /// <param name="file">The file name</param> | |||
365 | void OnCompleteFile(string file) | |||
366 | { | |||
367 | CompletedFileHandler handler = CompletedFile; | |||
368 | | |||
369 | if (handler != null) { | |||
370 | var args = new ScanEventArgs(file); | |||
371 | handler(this, args); | |||
372 | alive_ = args.ContinueRunning; | |||
373 | } | |||
374 | } | |||
375 | | |||
376 | /// <summary> | |||
377 | /// Raise the ProcessDirectory event. | |||
378 | /// </summary> | |||
379 | /// <param name="directory">The directory name.</param> | |||
380 | /// <param name="hasMatchingFiles">Flag indicating if the directory has matching files.</param> | |||
381 | void OnProcessDirectory(string directory, bool hasMatchingFiles) | |||
382 | { | |||
383 | EventHandler<DirectoryEventArgs> handler = ProcessDirectory; | |||
384 | | |||
385 | if (handler != null) { | |||
386 | var args = new DirectoryEventArgs(directory, hasMatchingFiles); | |||
387 | handler(this, args); | |||
388 | alive_ = args.ContinueRunning; | |||
389 | } | |||
390 | } | |||
391 | | |||
392 | /// <summary> | |||
393 | /// Scan a directory. | |||
394 | /// </summary> | |||
395 | /// <param name="directory">The base directory to scan.</param> | |||
396 | /// <param name="recurse">True to recurse subdirectories, false to scan a single directory.</param> | |||
397 | public void Scan(string directory, bool recurse) | |||
398 | { | |||
399 | alive_ = true; | |||
400 | ScanDir(directory, recurse); | |||
401 | } | |||
402 | | |||
403 | void ScanDir(string directory, bool recurse) | |||
404 | { | |||
405 | | |||
406 | try { | |||
407 | string[] names = System.IO.Directory.GetFiles(directory); | |||
408 | bool hasMatch = false; | |||
409 | for (int fileIndex = 0; fileIndex < names.Length; ++fileIndex) { | |||
410 | if (!fileFilter_.IsMatch(names[fileIndex])) { | |||
411 | names[fileIndex] = null; | |||
412 | } else { | |||
413 | hasMatch = true; | |||
414 | } | |||
415 | } | |||
416 | | |||
417 | OnProcessDirectory(directory, hasMatch); | |||
418 | | |||
419 | if (alive_ && hasMatch) { | |||
420 | foreach (string fileName in names) { | |||
421 | try { | |||
422 | if (fileName != null) { | |||
423 | OnProcessFile(fileName); | |||
424 | if (!alive_) { | |||
425 | break; | |||
426 | } | |||
427 | } | |||
428 | } catch (Exception e) { | |||
429 | if (!OnFileFailure(fileName, e)) { | |||
430 | throw; | |||
431 | } | |||
432 | } | |||
433 | } | |||
434 | } | |||
435 | } catch (Exception e) { | |||
436 | if (!OnDirectoryFailure(directory, e)) { | |||
437 | throw; | |||
438 | } | |||
439 | } | |||
440 | | |||
441 | if (alive_ && recurse) { | |||
442 | try { | |||
443 | string[] names = System.IO.Directory.GetDirectories(directory); | |||
444 | foreach (string fulldir in names) { | |||
445 | if ((directoryFilter_ == null) || (directoryFilter_.IsMatch(fulldir))) { | |||
446 | ScanDir(fulldir, true); | |||
447 | if (!alive_) { | |||
448 | break; | |||
449 | } | |||
450 | } | |||
451 | } | |||
452 | } catch (Exception e) { | |||
453 | if (!OnDirectoryFailure(directory, e)) { | |||
454 | throw; | |||
455 | } | |||
456 | } | |||
457 | } | |||
458 | } | |||
459 | | |||
460 | #region Instance Fields | |||
461 | /// <summary> | |||
462 | /// The file filter currently in use. | |||
463 | /// </summary> | |||
464 | IScanFilter fileFilter_; | |||
465 | /// <summary> | |||
466 | /// The directory filter currently in use. | |||
467 | /// </summary> | |||
468 | IScanFilter directoryFilter_; | |||
469 | /// <summary> | |||
470 | /// Flag indicating if scanning should continue running. | |||
471 | /// </summary> | |||
472 | bool alive_; | |||
473 | #endregion | |||
474 | } | |||
475 | } |
| Class: | ICSharpCode.SharpZipLib.Core.ScanFailureEventArgs |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\FileSystemScanner.cs |
| Covered lines: | 0 |
| Uncovered lines: | 9 |
| Coverable lines: | 9 |
| Total lines: | 475 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Core | |||
4 | { | |||
5 | #region EventArgs | |||
6 | /// <summary> | |||
7 | /// Event arguments for scanning. | |||
8 | /// </summary> | |||
9 | public class ScanEventArgs : EventArgs | |||
10 | { | |||
11 | #region Constructors | |||
12 | /// <summary> | |||
13 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
14 | /// </summary> | |||
15 | /// <param name="name">The file or directory name.</param> | |||
16 | public ScanEventArgs(string name) | |||
17 | { | |||
18 | name_ = name; | |||
19 | } | |||
20 | #endregion | |||
21 | | |||
22 | /// <summary> | |||
23 | /// The file or directory name for this event. | |||
24 | /// </summary> | |||
25 | public string Name { | |||
26 | get { return name_; } | |||
27 | } | |||
28 | | |||
29 | /// <summary> | |||
30 | /// Get set a value indicating if scanning should continue or not. | |||
31 | /// </summary> | |||
32 | public bool ContinueRunning { | |||
33 | get { return continueRunning_; } | |||
34 | set { continueRunning_ = value; } | |||
35 | } | |||
36 | | |||
37 | #region Instance Fields | |||
38 | string name_; | |||
39 | bool continueRunning_ = true; | |||
40 | #endregion | |||
41 | } | |||
42 | | |||
43 | /// <summary> | |||
44 | /// Event arguments during processing of a single file or directory. | |||
45 | /// </summary> | |||
46 | public class ProgressEventArgs : EventArgs | |||
47 | { | |||
48 | #region Constructors | |||
49 | /// <summary> | |||
50 | /// Initialise a new instance of <see cref="ScanEventArgs"/> | |||
51 | /// </summary> | |||
52 | /// <param name="name">The file or directory name if known.</param> | |||
53 | /// <param name="processed">The number of bytes processed so far</param> | |||
54 | /// <param name="target">The total number of bytes to process, 0 if not known</param> | |||
55 | public ProgressEventArgs(string name, long processed, long target) | |||
56 | { | |||
57 | name_ = name; | |||
58 | processed_ = processed; | |||
59 | target_ = target; | |||
60 | } | |||
61 | #endregion | |||
62 | | |||
63 | /// <summary> | |||
64 | /// The name for this event if known. | |||
65 | /// </summary> | |||
66 | public string Name { | |||
67 | get { return name_; } | |||
68 | } | |||
69 | | |||
70 | /// <summary> | |||
71 | /// Get set a value indicating wether scanning should continue or not. | |||
72 | /// </summary> | |||
73 | public bool ContinueRunning { | |||
74 | get { return continueRunning_; } | |||
75 | set { continueRunning_ = value; } | |||
76 | } | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Get a percentage representing how much of the <see cref="Target"></see> has been processed | |||
80 | /// </summary> | |||
81 | /// <value>0.0 to 100.0 percent; 0 if target is not known.</value> | |||
82 | public float PercentComplete { | |||
83 | get { | |||
84 | float result; | |||
85 | if (target_ <= 0) { | |||
86 | result = 0; | |||
87 | } else { | |||
88 | result = ((float)processed_ / (float)target_) * 100.0f; | |||
89 | } | |||
90 | return result; | |||
91 | } | |||
92 | } | |||
93 | | |||
94 | /// <summary> | |||
95 | /// The number of bytes processed so far | |||
96 | /// </summary> | |||
97 | public long Processed { | |||
98 | get { return processed_; } | |||
99 | } | |||
100 | | |||
101 | /// <summary> | |||
102 | /// The number of bytes to process. | |||
103 | /// </summary> | |||
104 | /// <remarks>Target may be 0 or negative if the value isnt known.</remarks> | |||
105 | public long Target { | |||
106 | get { return target_; } | |||
107 | } | |||
108 | | |||
109 | #region Instance Fields | |||
110 | string name_; | |||
111 | long processed_; | |||
112 | long target_; | |||
113 | bool continueRunning_ = true; | |||
114 | #endregion | |||
115 | } | |||
116 | | |||
117 | /// <summary> | |||
118 | /// Event arguments for directories. | |||
119 | /// </summary> | |||
120 | public class DirectoryEventArgs : ScanEventArgs | |||
121 | { | |||
122 | #region Constructors | |||
123 | /// <summary> | |||
124 | /// Initialize an instance of <see cref="DirectoryEventArgs"></see>. | |||
125 | /// </summary> | |||
126 | /// <param name="name">The name for this directory.</param> | |||
127 | /// <param name="hasMatchingFiles">Flag value indicating if any matching files are contained in this directory.</par | |||
128 | public DirectoryEventArgs(string name, bool hasMatchingFiles) | |||
129 | : base(name) | |||
130 | { | |||
131 | hasMatchingFiles_ = hasMatchingFiles; | |||
132 | } | |||
133 | #endregion | |||
134 | | |||
135 | /// <summary> | |||
136 | /// Get a value indicating if the directory contains any matching files or not. | |||
137 | /// </summary> | |||
138 | public bool HasMatchingFiles { | |||
139 | get { return hasMatchingFiles_; } | |||
140 | } | |||
141 | | |||
142 | readonly | |||
143 | | |||
144 | #region Instance Fields | |||
145 | bool hasMatchingFiles_; | |||
146 | #endregion | |||
147 | } | |||
148 | | |||
149 | /// <summary> | |||
150 | /// Arguments passed when scan failures are detected. | |||
151 | /// </summary> | |||
152 | public class ScanFailureEventArgs : EventArgs | |||
153 | { | |||
154 | #region Constructors | |||
155 | /// <summary> | |||
156 | /// Initialise a new instance of <see cref="ScanFailureEventArgs"></see> | |||
157 | /// </summary> | |||
158 | /// <param name="name">The name to apply.</param> | |||
159 | /// <param name="e">The exception to use.</param> | |||
| 0 | 160 | public ScanFailureEventArgs(string name, Exception e) | ||
161 | { | |||
| 0 | 162 | name_ = name; | ||
| 0 | 163 | exception_ = e; | ||
| 0 | 164 | continueRunning_ = true; | ||
| 0 | 165 | } | ||
166 | #endregion | |||
167 | | |||
168 | /// <summary> | |||
169 | /// The applicable name. | |||
170 | /// </summary> | |||
171 | public string Name { | |||
| 0 | 172 | get { return name_; } | ||
173 | } | |||
174 | | |||
175 | /// <summary> | |||
176 | /// The applicable exception. | |||
177 | /// </summary> | |||
178 | public Exception Exception { | |||
| 0 | 179 | get { return exception_; } | ||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Get / set a value indicating wether scanning should continue. | |||
184 | /// </summary> | |||
185 | public bool ContinueRunning { | |||
| 0 | 186 | get { return continueRunning_; } | ||
| 0 | 187 | set { continueRunning_ = value; } | ||
188 | } | |||
189 | | |||
190 | #region Instance Fields | |||
191 | string name_; | |||
192 | Exception exception_; | |||
193 | bool continueRunning_; | |||
194 | #endregion | |||
195 | } | |||
196 | | |||
197 | #endregion | |||
198 | | |||
199 | #region Delegates | |||
200 | /// <summary> | |||
201 | /// Delegate invoked before starting to process a file. | |||
202 | /// </summary> | |||
203 | /// <param name="sender">The source of the event</param> | |||
204 | /// <param name="e">The event arguments.</param> | |||
205 | public delegate void ProcessFileHandler(object sender, ScanEventArgs e); | |||
206 | | |||
207 | /// <summary> | |||
208 | /// Delegate invoked during processing of a file or directory | |||
209 | /// </summary> | |||
210 | /// <param name="sender">The source of the event</param> | |||
211 | /// <param name="e">The event arguments.</param> | |||
212 | public delegate void ProgressHandler(object sender, ProgressEventArgs e); | |||
213 | | |||
214 | /// <summary> | |||
215 | /// Delegate invoked when a file has been completely processed. | |||
216 | /// </summary> | |||
217 | /// <param name="sender">The source of the event</param> | |||
218 | /// <param name="e">The event arguments.</param> | |||
219 | public delegate void CompletedFileHandler(object sender, ScanEventArgs e); | |||
220 | | |||
221 | /// <summary> | |||
222 | /// Delegate invoked when a directory failure is detected. | |||
223 | /// </summary> | |||
224 | /// <param name="sender">The source of the event</param> | |||
225 | /// <param name="e">The event arguments.</param> | |||
226 | public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e); | |||
227 | | |||
228 | /// <summary> | |||
229 | /// Delegate invoked when a file failure is detected. | |||
230 | /// </summary> | |||
231 | /// <param name="sender">The source of the event</param> | |||
232 | /// <param name="e">The event arguments.</param> | |||
233 | public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e); | |||
234 | #endregion | |||
235 | | |||
236 | /// <summary> | |||
237 | /// FileSystemScanner provides facilities scanning of files and directories. | |||
238 | /// </summary> | |||
239 | public class FileSystemScanner | |||
240 | { | |||
241 | #region Constructors | |||
242 | /// <summary> | |||
243 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
244 | /// </summary> | |||
245 | /// <param name="filter">The <see cref="PathFilter">file filter</see> to apply when scanning.</param> | |||
246 | public FileSystemScanner(string filter) | |||
247 | { | |||
248 | fileFilter_ = new PathFilter(filter); | |||
249 | } | |||
250 | | |||
251 | /// <summary> | |||
252 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
253 | /// </summary> | |||
254 | /// <param name="fileFilter">The <see cref="PathFilter">file filter</see> to apply.</param> | |||
255 | /// <param name="directoryFilter">The <see cref="PathFilter"> directory filter</see> to apply.</param> | |||
256 | public FileSystemScanner(string fileFilter, string directoryFilter) | |||
257 | { | |||
258 | fileFilter_ = new PathFilter(fileFilter); | |||
259 | directoryFilter_ = new PathFilter(directoryFilter); | |||
260 | } | |||
261 | | |||
262 | /// <summary> | |||
263 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
264 | /// </summary> | |||
265 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
266 | public FileSystemScanner(IScanFilter fileFilter) | |||
267 | { | |||
268 | fileFilter_ = fileFilter; | |||
269 | } | |||
270 | | |||
271 | /// <summary> | |||
272 | /// Initialise a new instance of <see cref="FileSystemScanner"></see> | |||
273 | /// </summary> | |||
274 | /// <param name="fileFilter">The file <see cref="IScanFilter">filter</see> to apply.</param> | |||
275 | /// <param name="directoryFilter">The directory <see cref="IScanFilter">filter</see> to apply.</param> | |||
276 | public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter) | |||
277 | { | |||
278 | fileFilter_ = fileFilter; | |||
279 | directoryFilter_ = directoryFilter; | |||
280 | } | |||
281 | #endregion | |||
282 | | |||
283 | #region Delegates | |||
284 | /// <summary> | |||
285 | /// Delegate to invoke when a directory is processed. | |||
286 | /// </summary> | |||
287 | public event EventHandler<DirectoryEventArgs> ProcessDirectory; | |||
288 | | |||
289 | /// <summary> | |||
290 | /// Delegate to invoke when a file is processed. | |||
291 | /// </summary> | |||
292 | public ProcessFileHandler ProcessFile; | |||
293 | | |||
294 | /// <summary> | |||
295 | /// Delegate to invoke when processing for a file has finished. | |||
296 | /// </summary> | |||
297 | public CompletedFileHandler CompletedFile; | |||
298 | | |||
299 | /// <summary> | |||
300 | /// Delegate to invoke when a directory failure is detected. | |||
301 | /// </summary> | |||
302 | public DirectoryFailureHandler DirectoryFailure; | |||
303 | | |||
304 | /// <summary> | |||
305 | /// Delegate to invoke when a file failure is detected. | |||
306 | /// </summary> | |||
307 | public FileFailureHandler FileFailure; | |||
308 | #endregion | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Raise the DirectoryFailure event. | |||
312 | /// </summary> | |||
313 | /// <param name="directory">The directory name.</param> | |||
314 | /// <param name="e">The exception detected.</param> | |||
315 | bool OnDirectoryFailure(string directory, Exception e) | |||
316 | { | |||
317 | DirectoryFailureHandler handler = DirectoryFailure; | |||
318 | bool result = (handler != null); | |||
319 | if (result) { | |||
320 | var args = new ScanFailureEventArgs(directory, e); | |||
321 | handler(this, args); | |||
322 | alive_ = args.ContinueRunning; | |||
323 | } | |||
324 | return result; | |||
325 | } | |||
326 | | |||
327 | /// <summary> | |||
328 | /// Raise the FileFailure event. | |||
329 | /// </summary> | |||
330 | /// <param name="file">The file name.</param> | |||
331 | /// <param name="e">The exception detected.</param> | |||
332 | bool OnFileFailure(string file, Exception e) | |||
333 | { | |||
334 | FileFailureHandler handler = FileFailure; | |||
335 | | |||
336 | bool result = (handler != null); | |||
337 | | |||
338 | if (result) { | |||
339 | var args = new ScanFailureEventArgs(file, e); | |||
340 | FileFailure(this, args); | |||
341 | alive_ = args.ContinueRunning; | |||
342 | } | |||
343 | return result; | |||
344 | } | |||
345 | | |||
346 | /// <summary> | |||
347 | /// Raise the ProcessFile event. | |||
348 | /// </summary> | |||
349 | /// <param name="file">The file name.</param> | |||
350 | void OnProcessFile(string file) | |||
351 | { | |||
352 | ProcessFileHandler handler = ProcessFile; | |||
353 | | |||
354 | if (handler != null) { | |||
355 | var args = new ScanEventArgs(file); | |||
356 | handler(this, args); | |||
357 | alive_ = args.ContinueRunning; | |||
358 | } | |||
359 | } | |||
360 | | |||
361 | /// <summary> | |||
362 | /// Raise the complete file event | |||
363 | /// </summary> | |||
364 | /// <param name="file">The file name</param> | |||
365 | void OnCompleteFile(string file) | |||
366 | { | |||
367 | CompletedFileHandler handler = CompletedFile; | |||
368 | | |||
369 | if (handler != null) { | |||
370 | var args = new ScanEventArgs(file); | |||
371 | handler(this, args); | |||
372 | alive_ = args.ContinueRunning; | |||
373 | } | |||
374 | } | |||
375 | | |||
376 | /// <summary> | |||
377 | /// Raise the ProcessDirectory event. | |||
378 | /// </summary> | |||
379 | /// <param name="directory">The directory name.</param> | |||
380 | /// <param name="hasMatchingFiles">Flag indicating if the directory has matching files.</param> | |||
381 | void OnProcessDirectory(string directory, bool hasMatchingFiles) | |||
382 | { | |||
383 | EventHandler<DirectoryEventArgs> handler = ProcessDirectory; | |||
384 | | |||
385 | if (handler != null) { | |||
386 | var args = new DirectoryEventArgs(directory, hasMatchingFiles); | |||
387 | handler(this, args); | |||
388 | alive_ = args.ContinueRunning; | |||
389 | } | |||
390 | } | |||
391 | | |||
392 | /// <summary> | |||
393 | /// Scan a directory. | |||
394 | /// </summary> | |||
395 | /// <param name="directory">The base directory to scan.</param> | |||
396 | /// <param name="recurse">True to recurse subdirectories, false to scan a single directory.</param> | |||
397 | public void Scan(string directory, bool recurse) | |||
398 | { | |||
399 | alive_ = true; | |||
400 | ScanDir(directory, recurse); | |||
401 | } | |||
402 | | |||
403 | void ScanDir(string directory, bool recurse) | |||
404 | { | |||
405 | | |||
406 | try { | |||
407 | string[] names = System.IO.Directory.GetFiles(directory); | |||
408 | bool hasMatch = false; | |||
409 | for (int fileIndex = 0; fileIndex < names.Length; ++fileIndex) { | |||
410 | if (!fileFilter_.IsMatch(names[fileIndex])) { | |||
411 | names[fileIndex] = null; | |||
412 | } else { | |||
413 | hasMatch = true; | |||
414 | } | |||
415 | } | |||
416 | | |||
417 | OnProcessDirectory(directory, hasMatch); | |||
418 | | |||
419 | if (alive_ && hasMatch) { | |||
420 | foreach (string fileName in names) { | |||
421 | try { | |||
422 | if (fileName != null) { | |||
423 | OnProcessFile(fileName); | |||
424 | if (!alive_) { | |||
425 | break; | |||
426 | } | |||
427 | } | |||
428 | } catch (Exception e) { | |||
429 | if (!OnFileFailure(fileName, e)) { | |||
430 | throw; | |||
431 | } | |||
432 | } | |||
433 | } | |||
434 | } | |||
435 | } catch (Exception e) { | |||
436 | if (!OnDirectoryFailure(directory, e)) { | |||
437 | throw; | |||
438 | } | |||
439 | } | |||
440 | | |||
441 | if (alive_ && recurse) { | |||
442 | try { | |||
443 | string[] names = System.IO.Directory.GetDirectories(directory); | |||
444 | foreach (string fulldir in names) { | |||
445 | if ((directoryFilter_ == null) || (directoryFilter_.IsMatch(fulldir))) { | |||
446 | ScanDir(fulldir, true); | |||
447 | if (!alive_) { | |||
448 | break; | |||
449 | } | |||
450 | } | |||
451 | } | |||
452 | } catch (Exception e) { | |||
453 | if (!OnDirectoryFailure(directory, e)) { | |||
454 | throw; | |||
455 | } | |||
456 | } | |||
457 | } | |||
458 | } | |||
459 | | |||
460 | #region Instance Fields | |||
461 | /// <summary> | |||
462 | /// The file filter currently in use. | |||
463 | /// </summary> | |||
464 | IScanFilter fileFilter_; | |||
465 | /// <summary> | |||
466 | /// The directory filter currently in use. | |||
467 | /// </summary> | |||
468 | IScanFilter directoryFilter_; | |||
469 | /// <summary> | |||
470 | /// Flag indicating if scanning should continue running. | |||
471 | /// </summary> | |||
472 | bool alive_; | |||
473 | #endregion | |||
474 | } | |||
475 | } |
| Class: | ICSharpCode.SharpZipLib.SharpZipBaseException |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\SharpZipBaseException.cs |
| Covered lines: | 2 |
| Uncovered lines: | 6 |
| Coverable lines: | 8 |
| Total lines: | 52 |
| Line coverage: | 25% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Runtime.Serialization; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// SharpZipBaseException is the base exception class for SharpZipLib. | |||
8 | /// All library exceptions are derived from this. | |||
9 | /// </summary> | |||
10 | /// <remarks>NOTE: Not all exceptions thrown will be derived from this class. | |||
11 | /// A variety of other exceptions are possible for example <see cref="ArgumentNullException"></see></remarks> | |||
12 | [Serializable] | |||
13 | public class SharpZipBaseException : Exception | |||
14 | { | |||
15 | /// <summary> | |||
16 | /// Deserialization constructor | |||
17 | /// </summary> | |||
18 | /// <param name="info"><see cref="System.Runtime.Serialization.SerializationInfo"/> for this constructor</param> | |||
19 | /// <param name="context"><see cref="StreamingContext"/> for this constructor</param> | |||
20 | protected SharpZipBaseException(SerializationInfo info, StreamingContext context) | |||
| 0 | 21 | : base(info, context) | ||
22 | { | |||
| 0 | 23 | } | ||
24 | | |||
25 | /// <summary> | |||
26 | /// Initializes a new instance of the SharpZipBaseException class. | |||
27 | /// </summary> | |||
| 0 | 28 | public SharpZipBaseException() | ||
29 | { | |||
| 0 | 30 | } | ||
31 | | |||
32 | /// <summary> | |||
33 | /// Initializes a new instance of the SharpZipBaseException class with a specified error message. | |||
34 | /// </summary> | |||
35 | /// <param name="message">A message describing the exception.</param> | |||
36 | public SharpZipBaseException(string message) | |||
| 15 | 37 | : base(message) | ||
38 | { | |||
| 15 | 39 | } | ||
40 | | |||
41 | /// <summary> | |||
42 | /// Initializes a new instance of the SharpZipBaseException class with a specified | |||
43 | /// error message and a reference to the inner exception that is the cause of this exception. | |||
44 | /// </summary> | |||
45 | /// <param name="message">A message describing the exception.</param> | |||
46 | /// <param name="innerException">The inner exception</param> | |||
47 | public SharpZipBaseException(string message, Exception innerException) | |||
| 0 | 48 | : base(message, innerException) | ||
49 | { | |||
| 0 | 50 | } | ||
51 | } | |||
52 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.StaticDiskDataSource |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs |
| Covered lines: | 0 |
| Uncovered lines: | 4 |
| Coverable lines: | 4 |
| Total lines: | 4263 |
| Line coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| GetSource() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.IO; | |||
4 | using System.Text; | |||
5 | using System.Globalization; | |||
6 | using System.Security.Cryptography; | |||
7 | using ICSharpCode.SharpZipLib.Encryption; | |||
8 | using ICSharpCode.SharpZipLib.Core; | |||
9 | using ICSharpCode.SharpZipLib.Checksum; | |||
10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
11 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
12 | | |||
13 | namespace ICSharpCode.SharpZipLib.Zip | |||
14 | { | |||
15 | #region Keys Required Event Args | |||
16 | /// <summary> | |||
17 | /// Arguments used with KeysRequiredEvent | |||
18 | /// </summary> | |||
19 | public class KeysRequiredEventArgs : EventArgs | |||
20 | { | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
24 | /// </summary> | |||
25 | /// <param name="name">The name of the file for which keys are required.</param> | |||
26 | public KeysRequiredEventArgs(string name) | |||
27 | { | |||
28 | fileName = name; | |||
29 | } | |||
30 | | |||
31 | /// <summary> | |||
32 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
33 | /// </summary> | |||
34 | /// <param name="name">The name of the file for which keys are required.</param> | |||
35 | /// <param name="keyValue">The current key value.</param> | |||
36 | public KeysRequiredEventArgs(string name, byte[] keyValue) | |||
37 | { | |||
38 | fileName = name; | |||
39 | key = keyValue; | |||
40 | } | |||
41 | | |||
42 | #endregion | |||
43 | #region Properties | |||
44 | /// <summary> | |||
45 | /// Gets the name of the file for which keys are required. | |||
46 | /// </summary> | |||
47 | public string FileName { | |||
48 | get { return fileName; } | |||
49 | } | |||
50 | | |||
51 | /// <summary> | |||
52 | /// Gets or sets the key value | |||
53 | /// </summary> | |||
54 | public byte[] Key { | |||
55 | get { return key; } | |||
56 | set { key = value; } | |||
57 | } | |||
58 | #endregion | |||
59 | | |||
60 | #region Instance Fields | |||
61 | string fileName; | |||
62 | byte[] key; | |||
63 | #endregion | |||
64 | } | |||
65 | #endregion | |||
66 | | |||
67 | #region Test Definitions | |||
68 | /// <summary> | |||
69 | /// The strategy to apply to testing. | |||
70 | /// </summary> | |||
71 | public enum TestStrategy | |||
72 | { | |||
73 | /// <summary> | |||
74 | /// Find the first error only. | |||
75 | /// </summary> | |||
76 | FindFirstError, | |||
77 | /// <summary> | |||
78 | /// Find all possible errors. | |||
79 | /// </summary> | |||
80 | FindAllErrors, | |||
81 | } | |||
82 | | |||
83 | /// <summary> | |||
84 | /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. | |||
85 | /// </summary> | |||
86 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
87 | public enum TestOperation | |||
88 | { | |||
89 | /// <summary> | |||
90 | /// Setting up testing. | |||
91 | /// </summary> | |||
92 | Initialising, | |||
93 | | |||
94 | /// <summary> | |||
95 | /// Testing an individual entries header | |||
96 | /// </summary> | |||
97 | EntryHeader, | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Testing an individual entries data | |||
101 | /// </summary> | |||
102 | EntryData, | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Testing an individual entry has completed. | |||
106 | /// </summary> | |||
107 | EntryComplete, | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Running miscellaneous tests | |||
111 | /// </summary> | |||
112 | MiscellaneousTests, | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Testing is complete | |||
116 | /// </summary> | |||
117 | Complete, | |||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. | |||
122 | /// </summary> | |||
123 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
124 | public class TestStatus | |||
125 | { | |||
126 | #region Constructors | |||
127 | /// <summary> | |||
128 | /// Initialise a new instance of <see cref="TestStatus"/> | |||
129 | /// </summary> | |||
130 | /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> | |||
131 | public TestStatus(ZipFile file) | |||
132 | { | |||
133 | file_ = file; | |||
134 | } | |||
135 | #endregion | |||
136 | | |||
137 | #region Properties | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Get the current <see cref="TestOperation"/> in progress. | |||
141 | /// </summary> | |||
142 | public TestOperation Operation { | |||
143 | get { return operation_; } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get the <see cref="ZipFile"/> this status is applicable to. | |||
148 | /// </summary> | |||
149 | public ZipFile File { | |||
150 | get { return file_; } | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Get the current/last entry tested. | |||
155 | /// </summary> | |||
156 | public ZipEntry Entry { | |||
157 | get { return entry_; } | |||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Get the number of errors detected so far. | |||
162 | /// </summary> | |||
163 | public int ErrorCount { | |||
164 | get { return errorCount_; } | |||
165 | } | |||
166 | | |||
167 | /// <summary> | |||
168 | /// Get the number of bytes tested so far for the current entry. | |||
169 | /// </summary> | |||
170 | public long BytesTested { | |||
171 | get { return bytesTested_; } | |||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Get a value indicating wether the last entry test was valid. | |||
176 | /// </summary> | |||
177 | public bool EntryValid { | |||
178 | get { return entryValid_; } | |||
179 | } | |||
180 | #endregion | |||
181 | | |||
182 | #region Internal API | |||
183 | internal void AddError() | |||
184 | { | |||
185 | errorCount_++; | |||
186 | entryValid_ = false; | |||
187 | } | |||
188 | | |||
189 | internal void SetOperation(TestOperation operation) | |||
190 | { | |||
191 | operation_ = operation; | |||
192 | } | |||
193 | | |||
194 | internal void SetEntry(ZipEntry entry) | |||
195 | { | |||
196 | entry_ = entry; | |||
197 | entryValid_ = true; | |||
198 | bytesTested_ = 0; | |||
199 | } | |||
200 | | |||
201 | internal void SetBytesTested(long value) | |||
202 | { | |||
203 | bytesTested_ = value; | |||
204 | } | |||
205 | #endregion | |||
206 | | |||
207 | #region Instance Fields | |||
208 | ZipFile file_; | |||
209 | ZipEntry entry_; | |||
210 | bool entryValid_; | |||
211 | int errorCount_; | |||
212 | long bytesTested_; | |||
213 | TestOperation operation_; | |||
214 | #endregion | |||
215 | } | |||
216 | | |||
217 | /// <summary> | |||
218 | /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if | |||
219 | /// </summary> | |||
220 | /// <remarks>If the message is non-null an error has occured. If the message is null | |||
221 | /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> | |||
222 | public delegate void ZipTestResultHandler(TestStatus status, string message); | |||
223 | #endregion | |||
224 | | |||
225 | #region Update Definitions | |||
226 | /// <summary> | |||
227 | /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. | |||
228 | /// </summary> | |||
229 | public enum FileUpdateMode | |||
230 | { | |||
231 | /// <summary> | |||
232 | /// Perform all updates on temporary files ensuring that the original file is saved. | |||
233 | /// </summary> | |||
234 | Safe, | |||
235 | /// <summary> | |||
236 | /// Update the archive directly, which is faster but less safe. | |||
237 | /// </summary> | |||
238 | Direct, | |||
239 | } | |||
240 | #endregion | |||
241 | | |||
242 | #region ZipFile Class | |||
243 | /// <summary> | |||
244 | /// This class represents a Zip archive. You can ask for the contained | |||
245 | /// entries, or get an input stream for a file entry. The entry is | |||
246 | /// automatically decompressed. | |||
247 | /// | |||
248 | /// You can also update the archive adding or deleting entries. | |||
249 | /// | |||
250 | /// This class is thread safe for input: You can open input streams for arbitrary | |||
251 | /// entries in different threads. | |||
252 | /// <br/> | |||
253 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
254 | /// </summary> | |||
255 | /// <example> | |||
256 | /// <code> | |||
257 | /// using System; | |||
258 | /// using System.Text; | |||
259 | /// using System.Collections; | |||
260 | /// using System.IO; | |||
261 | /// | |||
262 | /// using ICSharpCode.SharpZipLib.Zip; | |||
263 | /// | |||
264 | /// class MainClass | |||
265 | /// { | |||
266 | /// static public void Main(string[] args) | |||
267 | /// { | |||
268 | /// using (ZipFile zFile = new ZipFile(args[0])) { | |||
269 | /// Console.WriteLine("Listing of : " + zFile.Name); | |||
270 | /// Console.WriteLine(""); | |||
271 | /// Console.WriteLine("Raw Size Size Date Time Name"); | |||
272 | /// Console.WriteLine("-------- -------- -------- ------ ---------"); | |||
273 | /// foreach (ZipEntry e in zFile) { | |||
274 | /// if ( e.IsFile ) { | |||
275 | /// DateTime d = e.DateTime; | |||
276 | /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, | |||
277 | /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), | |||
278 | /// e.Name); | |||
279 | /// } | |||
280 | /// } | |||
281 | /// } | |||
282 | /// } | |||
283 | /// } | |||
284 | /// </code> | |||
285 | /// </example> | |||
286 | public class ZipFile : IEnumerable, IDisposable | |||
287 | { | |||
288 | #region KeyHandling | |||
289 | | |||
290 | /// <summary> | |||
291 | /// Delegate for handling keys/password setting during compresion/decompression. | |||
292 | /// </summary> | |||
293 | public delegate void KeysRequiredEventHandler( | |||
294 | object sender, | |||
295 | KeysRequiredEventArgs e | |||
296 | ); | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Event handler for handling encryption keys. | |||
300 | /// </summary> | |||
301 | public KeysRequiredEventHandler KeysRequired; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Handles getting of encryption keys when required. | |||
305 | /// </summary> | |||
306 | /// <param name="fileName">The file for which encryption keys are required.</param> | |||
307 | void OnKeysRequired(string fileName) | |||
308 | { | |||
309 | if (KeysRequired != null) { | |||
310 | var krea = new KeysRequiredEventArgs(fileName, key); | |||
311 | KeysRequired(this, krea); | |||
312 | key = krea.Key; | |||
313 | } | |||
314 | } | |||
315 | | |||
316 | /// <summary> | |||
317 | /// Get/set the encryption key value. | |||
318 | /// </summary> | |||
319 | byte[] Key { | |||
320 | get { return key; } | |||
321 | set { key = value; } | |||
322 | } | |||
323 | | |||
324 | /// <summary> | |||
325 | /// Password to be used for encrypting/decrypting files. | |||
326 | /// </summary> | |||
327 | /// <remarks>Set to null if no password is required.</remarks> | |||
328 | public string Password { | |||
329 | set { | |||
330 | if (string.IsNullOrEmpty(value)) { | |||
331 | key = null; | |||
332 | } else { | |||
333 | rawPassword_ = value; | |||
334 | key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); | |||
335 | } | |||
336 | } | |||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Get a value indicating wether encryption keys are currently available. | |||
341 | /// </summary> | |||
342 | bool HaveKeys { | |||
343 | get { return key != null; } | |||
344 | } | |||
345 | #endregion | |||
346 | | |||
347 | #region Constructors | |||
348 | /// <summary> | |||
349 | /// Opens a Zip file with the given name for reading. | |||
350 | /// </summary> | |||
351 | /// <param name="name">The name of the file to open.</param> | |||
352 | /// <exception cref="ArgumentNullException">The argument supplied is null.</exception> | |||
353 | /// <exception cref="IOException"> | |||
354 | /// An i/o error occurs | |||
355 | /// </exception> | |||
356 | /// <exception cref="ZipException"> | |||
357 | /// The file doesn't contain a valid zip archive. | |||
358 | /// </exception> | |||
359 | public ZipFile(string name) | |||
360 | { | |||
361 | if (name == null) { | |||
362 | throw new ArgumentNullException(nameof(name)); | |||
363 | } | |||
364 | | |||
365 | name_ = name; | |||
366 | | |||
367 | baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
368 | isStreamOwner = true; | |||
369 | | |||
370 | try { | |||
371 | ReadEntries(); | |||
372 | } catch { | |||
373 | DisposeInternal(true); | |||
374 | throw; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Opens a Zip file reading the given <see cref="FileStream"/>. | |||
380 | /// </summary> | |||
381 | /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> | |||
382 | /// <exception cref="ArgumentNullException">The supplied argument is null.</exception> | |||
383 | /// <exception cref="IOException"> | |||
384 | /// An i/o error occurs. | |||
385 | /// </exception> | |||
386 | /// <exception cref="ZipException"> | |||
387 | /// The file doesn't contain a valid zip archive. | |||
388 | /// </exception> | |||
389 | public ZipFile(FileStream file) | |||
390 | { | |||
391 | if (file == null) { | |||
392 | throw new ArgumentNullException(nameof(file)); | |||
393 | } | |||
394 | | |||
395 | if (!file.CanSeek) { | |||
396 | throw new ArgumentException("Stream is not seekable", nameof(file)); | |||
397 | } | |||
398 | | |||
399 | baseStream_ = file; | |||
400 | name_ = file.Name; | |||
401 | isStreamOwner = true; | |||
402 | | |||
403 | try { | |||
404 | ReadEntries(); | |||
405 | } catch { | |||
406 | DisposeInternal(true); | |||
407 | throw; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Opens a Zip file reading the given <see cref="Stream"/>. | |||
413 | /// </summary> | |||
414 | /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> | |||
415 | /// <exception cref="IOException"> | |||
416 | /// An i/o error occurs | |||
417 | /// </exception> | |||
418 | /// <exception cref="ZipException"> | |||
419 | /// The stream doesn't contain a valid zip archive.<br/> | |||
420 | /// </exception> | |||
421 | /// <exception cref="ArgumentException"> | |||
422 | /// The <see cref="Stream">stream</see> doesnt support seeking. | |||
423 | /// </exception> | |||
424 | /// <exception cref="ArgumentNullException"> | |||
425 | /// The <see cref="Stream">stream</see> argument is null. | |||
426 | /// </exception> | |||
427 | public ZipFile(Stream stream) | |||
428 | { | |||
429 | if (stream == null) { | |||
430 | throw new ArgumentNullException(nameof(stream)); | |||
431 | } | |||
432 | | |||
433 | if (!stream.CanSeek) { | |||
434 | throw new ArgumentException("Stream is not seekable", nameof(stream)); | |||
435 | } | |||
436 | | |||
437 | baseStream_ = stream; | |||
438 | isStreamOwner = true; | |||
439 | | |||
440 | if (baseStream_.Length > 0) { | |||
441 | try { | |||
442 | ReadEntries(); | |||
443 | } catch { | |||
444 | DisposeInternal(true); | |||
445 | throw; | |||
446 | } | |||
447 | } else { | |||
448 | entries_ = new ZipEntry[0]; | |||
449 | isNewArchive_ = true; | |||
450 | } | |||
451 | } | |||
452 | | |||
453 | /// <summary> | |||
454 | /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. | |||
455 | /// </summary> | |||
456 | internal ZipFile() | |||
457 | { | |||
458 | entries_ = new ZipEntry[0]; | |||
459 | isNewArchive_ = true; | |||
460 | } | |||
461 | | |||
462 | #endregion | |||
463 | | |||
464 | #region Destructors and Closing | |||
465 | /// <summary> | |||
466 | /// Finalize this instance. | |||
467 | /// </summary> | |||
468 | ~ZipFile() | |||
469 | { | |||
470 | Dispose(false); | |||
471 | } | |||
472 | | |||
473 | /// <summary> | |||
474 | /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying | |||
475 | /// Once closed, no further instance methods should be called. | |||
476 | /// </summary> | |||
477 | /// <exception cref="System.IO.IOException"> | |||
478 | /// An i/o error occurs. | |||
479 | /// </exception> | |||
480 | public void Close() | |||
481 | { | |||
482 | DisposeInternal(true); | |||
483 | GC.SuppressFinalize(this); | |||
484 | } | |||
485 | | |||
486 | #endregion | |||
487 | | |||
488 | #region Creators | |||
489 | /// <summary> | |||
490 | /// Create a new <see cref="ZipFile"/> whose data will be stored in a file. | |||
491 | /// </summary> | |||
492 | /// <param name="fileName">The name of the archive to create.</param> | |||
493 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
494 | /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception> | |||
495 | public static ZipFile Create(string fileName) | |||
496 | { | |||
497 | if (fileName == null) { | |||
498 | throw new ArgumentNullException(nameof(fileName)); | |||
499 | } | |||
500 | | |||
501 | FileStream fs = File.Create(fileName); | |||
502 | | |||
503 | var result = new ZipFile(); | |||
504 | result.name_ = fileName; | |||
505 | result.baseStream_ = fs; | |||
506 | result.isStreamOwner = true; | |||
507 | return result; | |||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. | |||
512 | /// </summary> | |||
513 | /// <param name="outStream">The stream providing data storage.</param> | |||
514 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
515 | /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception> | |||
516 | /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception> | |||
517 | public static ZipFile Create(Stream outStream) | |||
518 | { | |||
519 | if (outStream == null) { | |||
520 | throw new ArgumentNullException(nameof(outStream)); | |||
521 | } | |||
522 | | |||
523 | if (!outStream.CanWrite) { | |||
524 | throw new ArgumentException("Stream is not writeable", nameof(outStream)); | |||
525 | } | |||
526 | | |||
527 | if (!outStream.CanSeek) { | |||
528 | throw new ArgumentException("Stream is not seekable", nameof(outStream)); | |||
529 | } | |||
530 | | |||
531 | var result = new ZipFile(); | |||
532 | result.baseStream_ = outStream; | |||
533 | return result; | |||
534 | } | |||
535 | | |||
536 | #endregion | |||
537 | | |||
538 | #region Properties | |||
539 | /// <summary> | |||
540 | /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. | |||
541 | /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. | |||
542 | /// </summary> | |||
543 | /// <remarks> | |||
544 | /// The default value is true in all cases. | |||
545 | /// </remarks> | |||
546 | public bool IsStreamOwner { | |||
547 | get { return isStreamOwner; } | |||
548 | set { isStreamOwner = value; } | |||
549 | } | |||
550 | | |||
551 | /// <summary> | |||
552 | /// Get a value indicating wether | |||
553 | /// this archive is embedded in another file or not. | |||
554 | /// </summary> | |||
555 | public bool IsEmbeddedArchive { | |||
556 | // Not strictly correct in all circumstances currently | |||
557 | get { return offsetOfFirstEntry > 0; } | |||
558 | } | |||
559 | | |||
560 | /// <summary> | |||
561 | /// Get a value indicating that this archive is a new one. | |||
562 | /// </summary> | |||
563 | public bool IsNewArchive { | |||
564 | get { return isNewArchive_; } | |||
565 | } | |||
566 | | |||
567 | /// <summary> | |||
568 | /// Gets the comment for the zip file. | |||
569 | /// </summary> | |||
570 | public string ZipFileComment { | |||
571 | get { return comment_; } | |||
572 | } | |||
573 | | |||
574 | /// <summary> | |||
575 | /// Gets the name of this zip file. | |||
576 | /// </summary> | |||
577 | public string Name { | |||
578 | get { return name_; } | |||
579 | } | |||
580 | | |||
581 | /// <summary> | |||
582 | /// Gets the number of entries in this zip file. | |||
583 | /// </summary> | |||
584 | /// <exception cref="InvalidOperationException"> | |||
585 | /// The Zip file has been closed. | |||
586 | /// </exception> | |||
587 | [Obsolete("Use the Count property instead")] | |||
588 | public int Size { | |||
589 | get { | |||
590 | return entries_.Length; | |||
591 | } | |||
592 | } | |||
593 | | |||
594 | /// <summary> | |||
595 | /// Get the number of entries contained in this <see cref="ZipFile"/>. | |||
596 | /// </summary> | |||
597 | public long Count { | |||
598 | get { | |||
599 | return entries_.Length; | |||
600 | } | |||
601 | } | |||
602 | | |||
603 | /// <summary> | |||
604 | /// Indexer property for ZipEntries | |||
605 | /// </summary> | |||
606 | [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] | |||
607 | public ZipEntry this[int index] { | |||
608 | get { | |||
609 | return (ZipEntry)entries_[index].Clone(); | |||
610 | } | |||
611 | } | |||
612 | | |||
613 | #endregion | |||
614 | | |||
615 | #region Input Handling | |||
616 | /// <summary> | |||
617 | /// Gets an enumerator for the Zip entries in this Zip file. | |||
618 | /// </summary> | |||
619 | /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> | |||
620 | /// <exception cref="ObjectDisposedException"> | |||
621 | /// The Zip file has been closed. | |||
622 | /// </exception> | |||
623 | public IEnumerator GetEnumerator() | |||
624 | { | |||
625 | if (isDisposed_) { | |||
626 | throw new ObjectDisposedException("ZipFile"); | |||
627 | } | |||
628 | | |||
629 | return new ZipEntryEnumerator(entries_); | |||
630 | } | |||
631 | | |||
632 | /// <summary> | |||
633 | /// Return the index of the entry with a matching name | |||
634 | /// </summary> | |||
635 | /// <param name="name">Entry name to find</param> | |||
636 | /// <param name="ignoreCase">If true the comparison is case insensitive</param> | |||
637 | /// <returns>The index position of the matching entry or -1 if not found</returns> | |||
638 | /// <exception cref="ObjectDisposedException"> | |||
639 | /// The Zip file has been closed. | |||
640 | /// </exception> | |||
641 | public int FindEntry(string name, bool ignoreCase) | |||
642 | { | |||
643 | if (isDisposed_) { | |||
644 | throw new ObjectDisposedException("ZipFile"); | |||
645 | } | |||
646 | | |||
647 | // TODO: This will be slow as the next ice age for huge archives! | |||
648 | for (int i = 0; i < entries_.Length; i++) { | |||
649 | if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) { | |||
650 | return i; | |||
651 | } | |||
652 | } | |||
653 | return -1; | |||
654 | } | |||
655 | | |||
656 | /// <summary> | |||
657 | /// Searches for a zip entry in this archive with the given name. | |||
658 | /// String comparisons are case insensitive | |||
659 | /// </summary> | |||
660 | /// <param name="name"> | |||
661 | /// The name to find. May contain directory components separated by slashes ('/'). | |||
662 | /// </param> | |||
663 | /// <returns> | |||
664 | /// A clone of the zip entry, or null if no entry with that name exists. | |||
665 | /// </returns> | |||
666 | /// <exception cref="ObjectDisposedException"> | |||
667 | /// The Zip file has been closed. | |||
668 | /// </exception> | |||
669 | public ZipEntry GetEntry(string name) | |||
670 | { | |||
671 | if (isDisposed_) { | |||
672 | throw new ObjectDisposedException("ZipFile"); | |||
673 | } | |||
674 | | |||
675 | int index = FindEntry(name, true); | |||
676 | return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; | |||
677 | } | |||
678 | | |||
679 | /// <summary> | |||
680 | /// Gets an input stream for reading the given zip entry data in an uncompressed form. | |||
681 | /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). | |||
682 | /// </summary> | |||
683 | /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> | |||
684 | /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> | |||
685 | /// <exception cref="ObjectDisposedException"> | |||
686 | /// The ZipFile has already been closed | |||
687 | /// </exception> | |||
688 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
689 | /// The compression method for the entry is unknown | |||
690 | /// </exception> | |||
691 | /// <exception cref="IndexOutOfRangeException"> | |||
692 | /// The entry is not found in the ZipFile | |||
693 | /// </exception> | |||
694 | public Stream GetInputStream(ZipEntry entry) | |||
695 | { | |||
696 | if (entry == null) { | |||
697 | throw new ArgumentNullException(nameof(entry)); | |||
698 | } | |||
699 | | |||
700 | if (isDisposed_) { | |||
701 | throw new ObjectDisposedException("ZipFile"); | |||
702 | } | |||
703 | | |||
704 | long index = entry.ZipFileIndex; | |||
705 | if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { | |||
706 | index = FindEntry(entry.Name, true); | |||
707 | if (index < 0) { | |||
708 | throw new ZipException("Entry cannot be found"); | |||
709 | } | |||
710 | } | |||
711 | return GetInputStream(index); | |||
712 | } | |||
713 | | |||
714 | /// <summary> | |||
715 | /// Creates an input stream reading a zip entry | |||
716 | /// </summary> | |||
717 | /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> | |||
718 | /// <returns> | |||
719 | /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> | |||
720 | /// </returns> | |||
721 | /// <exception cref="ObjectDisposedException"> | |||
722 | /// The ZipFile has already been closed | |||
723 | /// </exception> | |||
724 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
725 | /// The compression method for the entry is unknown | |||
726 | /// </exception> | |||
727 | /// <exception cref="IndexOutOfRangeException"> | |||
728 | /// The entry is not found in the ZipFile | |||
729 | /// </exception> | |||
730 | public Stream GetInputStream(long entryIndex) | |||
731 | { | |||
732 | if (isDisposed_) { | |||
733 | throw new ObjectDisposedException("ZipFile"); | |||
734 | } | |||
735 | | |||
736 | long start = LocateEntry(entries_[entryIndex]); | |||
737 | CompressionMethod method = entries_[entryIndex].CompressionMethod; | |||
738 | Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); | |||
739 | | |||
740 | if (entries_[entryIndex].IsCrypted == true) { | |||
741 | result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); | |||
742 | if (result == null) { | |||
743 | throw new ZipException("Unable to decrypt this entry"); | |||
744 | } | |||
745 | } | |||
746 | | |||
747 | switch (method) { | |||
748 | case CompressionMethod.Stored: | |||
749 | // read as is. | |||
750 | break; | |||
751 | | |||
752 | case CompressionMethod.Deflated: | |||
753 | // No need to worry about ownership and closing as underlying stream close does nothing. | |||
754 | result = new InflaterInputStream(result, new Inflater(true)); | |||
755 | break; | |||
756 | | |||
757 | default: | |||
758 | throw new ZipException("Unsupported compression method " + method); | |||
759 | } | |||
760 | | |||
761 | return result; | |||
762 | } | |||
763 | | |||
764 | #endregion | |||
765 | | |||
766 | #region Archive Testing | |||
767 | /// <summary> | |||
768 | /// Test an archive for integrity/validity | |||
769 | /// </summary> | |||
770 | /// <param name="testData">Perform low level data Crc check</param> | |||
771 | /// <returns>true if all tests pass, false otherwise</returns> | |||
772 | /// <remarks>Testing will terminate on the first error found.</remarks> | |||
773 | public bool TestArchive(bool testData) | |||
774 | { | |||
775 | return TestArchive(testData, TestStrategy.FindFirstError, null); | |||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Test an archive for integrity/validity | |||
780 | /// </summary> | |||
781 | /// <param name="testData">Perform low level data Crc check</param> | |||
782 | /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> | |||
783 | /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> | |||
784 | /// <returns>true if all tests pass, false otherwise</returns> | |||
785 | /// <exception cref="ObjectDisposedException">The object has already been closed.</exception> | |||
786 | public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) | |||
787 | { | |||
788 | if (isDisposed_) { | |||
789 | throw new ObjectDisposedException("ZipFile"); | |||
790 | } | |||
791 | | |||
792 | var status = new TestStatus(this); | |||
793 | | |||
794 | if (resultHandler != null) { | |||
795 | resultHandler(status, null); | |||
796 | } | |||
797 | | |||
798 | HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; | |||
799 | | |||
800 | bool testing = true; | |||
801 | | |||
802 | try { | |||
803 | int entryIndex = 0; | |||
804 | | |||
805 | while (testing && (entryIndex < Count)) { | |||
806 | if (resultHandler != null) { | |||
807 | status.SetEntry(this[entryIndex]); | |||
808 | status.SetOperation(TestOperation.EntryHeader); | |||
809 | resultHandler(status, null); | |||
810 | } | |||
811 | | |||
812 | try { | |||
813 | TestLocalHeader(this[entryIndex], test); | |||
814 | } catch (ZipException ex) { | |||
815 | status.AddError(); | |||
816 | | |||
817 | if (resultHandler != null) { | |||
818 | resultHandler(status, | |||
819 | string.Format("Exception during test - '{0}'", ex.Message)); | |||
820 | } | |||
821 | | |||
822 | testing &= strategy != TestStrategy.FindFirstError; | |||
823 | } | |||
824 | | |||
825 | if (testing && testData && this[entryIndex].IsFile) { | |||
826 | if (resultHandler != null) { | |||
827 | status.SetOperation(TestOperation.EntryData); | |||
828 | resultHandler(status, null); | |||
829 | } | |||
830 | | |||
831 | var crc = new Crc32(); | |||
832 | | |||
833 | using (Stream entryStream = this.GetInputStream(this[entryIndex])) { | |||
834 | | |||
835 | byte[] buffer = new byte[4096]; | |||
836 | long totalBytes = 0; | |||
837 | int bytesRead; | |||
838 | while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { | |||
839 | crc.Update(buffer, 0, bytesRead); | |||
840 | | |||
841 | if (resultHandler != null) { | |||
842 | totalBytes += bytesRead; | |||
843 | status.SetBytesTested(totalBytes); | |||
844 | resultHandler(status, null); | |||
845 | } | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | if (this[entryIndex].Crc != crc.Value) { | |||
850 | status.AddError(); | |||
851 | | |||
852 | if (resultHandler != null) { | |||
853 | resultHandler(status, "CRC mismatch"); | |||
854 | } | |||
855 | | |||
856 | testing &= strategy != TestStrategy.FindFirstError; | |||
857 | } | |||
858 | | |||
859 | if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
860 | var helper = new ZipHelperStream(baseStream_); | |||
861 | var data = new DescriptorData(); | |||
862 | helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); | |||
863 | if (this[entryIndex].Crc != data.Crc) { | |||
864 | status.AddError(); | |||
865 | } | |||
866 | | |||
867 | if (this[entryIndex].CompressedSize != data.CompressedSize) { | |||
868 | status.AddError(); | |||
869 | } | |||
870 | | |||
871 | if (this[entryIndex].Size != data.Size) { | |||
872 | status.AddError(); | |||
873 | } | |||
874 | } | |||
875 | } | |||
876 | | |||
877 | if (resultHandler != null) { | |||
878 | status.SetOperation(TestOperation.EntryComplete); | |||
879 | resultHandler(status, null); | |||
880 | } | |||
881 | | |||
882 | entryIndex += 1; | |||
883 | } | |||
884 | | |||
885 | if (resultHandler != null) { | |||
886 | status.SetOperation(TestOperation.MiscellaneousTests); | |||
887 | resultHandler(status, null); | |||
888 | } | |||
889 | | |||
890 | // TODO: the 'Corrina Johns' test where local headers are missing from | |||
891 | // the central directory. They are therefore invisible to many archivers. | |||
892 | } catch (Exception ex) { | |||
893 | status.AddError(); | |||
894 | | |||
895 | if (resultHandler != null) { | |||
896 | resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); | |||
897 | } | |||
898 | } | |||
899 | | |||
900 | if (resultHandler != null) { | |||
901 | status.SetOperation(TestOperation.Complete); | |||
902 | status.SetEntry(null); | |||
903 | resultHandler(status, null); | |||
904 | } | |||
905 | | |||
906 | return (status.ErrorCount == 0); | |||
907 | } | |||
908 | | |||
909 | [Flags] | |||
910 | enum HeaderTest | |||
911 | { | |||
912 | Extract = 0x01, // Check that this header represents an entry whose data can be extracted | |||
913 | Header = 0x02, // Check that this header contents are valid | |||
914 | } | |||
915 | | |||
916 | /// <summary> | |||
917 | /// Test a local header against that provided from the central directory | |||
918 | /// </summary> | |||
919 | /// <param name="entry"> | |||
920 | /// The entry to test against | |||
921 | /// </param> | |||
922 | /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> | |||
923 | /// <returns>The offset of the entries data in the file</returns> | |||
924 | long TestLocalHeader(ZipEntry entry, HeaderTest tests) | |||
925 | { | |||
926 | lock (baseStream_) { | |||
927 | bool testHeader = (tests & HeaderTest.Header) != 0; | |||
928 | bool testData = (tests & HeaderTest.Extract) != 0; | |||
929 | | |||
930 | baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); | |||
931 | if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { | |||
932 | throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset) | |||
933 | } | |||
934 | | |||
935 | var extractVersion = (short)(ReadLEUshort() & 0x00ff); | |||
936 | var localFlags = (short)ReadLEUshort(); | |||
937 | var compressionMethod = (short)ReadLEUshort(); | |||
938 | var fileTime = (short)ReadLEUshort(); | |||
939 | var fileDate = (short)ReadLEUshort(); | |||
940 | uint crcValue = ReadLEUint(); | |||
941 | long compressedSize = ReadLEUint(); | |||
942 | long size = ReadLEUint(); | |||
943 | int storedNameLength = ReadLEUshort(); | |||
944 | int extraDataLength = ReadLEUshort(); | |||
945 | | |||
946 | byte[] nameData = new byte[storedNameLength]; | |||
947 | StreamUtils.ReadFully(baseStream_, nameData); | |||
948 | | |||
949 | byte[] extraData = new byte[extraDataLength]; | |||
950 | StreamUtils.ReadFully(baseStream_, extraData); | |||
951 | | |||
952 | var localExtraData = new ZipExtraData(extraData); | |||
953 | | |||
954 | // Extra data / zip64 checks | |||
955 | if (localExtraData.Find(1)) { | |||
956 | // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 | |||
957 | // and size or compressedSize = MaxValue, due to rogue creators. | |||
958 | | |||
959 | size = localExtraData.ReadLong(); | |||
960 | compressedSize = localExtraData.ReadLong(); | |||
961 | | |||
962 | if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
963 | // These may be valid if patched later | |||
964 | if ((size != -1) && (size != entry.Size)) { | |||
965 | throw new ZipException("Size invalid for descriptor"); | |||
966 | } | |||
967 | | |||
968 | if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) { | |||
969 | throw new ZipException("Compressed size invalid for descriptor"); | |||
970 | } | |||
971 | } | |||
972 | } else { | |||
973 | // No zip64 extra data but entry requires it. | |||
974 | if ((extractVersion >= ZipConstants.VersionZip64) && | |||
975 | (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) { | |||
976 | throw new ZipException("Required Zip64 extended information missing"); | |||
977 | } | |||
978 | } | |||
979 | | |||
980 | if (testData) { | |||
981 | if (entry.IsFile) { | |||
982 | if (!entry.IsCompressionMethodSupported()) { | |||
983 | throw new ZipException("Compression method not supported"); | |||
984 | } | |||
985 | | |||
986 | if ((extractVersion > ZipConstants.VersionMadeBy) | |||
987 | || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { | |||
988 | throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract | |||
989 | } | |||
990 | | |||
991 | if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance | |||
992 | throw new ZipException("The library does not support the zip version required to extract this entry"); | |||
993 | } | |||
994 | } | |||
995 | } | |||
996 | | |||
997 | if (testHeader) { | |||
998 | if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. | |||
999 | (extractVersion != 10) && | |||
1000 | (extractVersion != 11) && | |||
1001 | (extractVersion != 20) && | |||
1002 | (extractVersion != 21) && | |||
1003 | (extractVersion != 25) && | |||
1004 | (extractVersion != 27) && | |||
1005 | (extractVersion != 45) && | |||
1006 | (extractVersion != 46) && | |||
1007 | (extractVersion != 50) && | |||
1008 | (extractVersion != 51) && | |||
1009 | (extractVersion != 52) && | |||
1010 | (extractVersion != 61) && | |||
1011 | (extractVersion != 62) && | |||
1012 | (extractVersion != 63) | |||
1013 | ) { | |||
1014 | throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersi | |||
1015 | } | |||
1016 | | |||
1017 | // Local entry flags dont have reserved bit set on. | |||
1018 | if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R | |||
1019 | throw new ZipException("Reserved bit flags cannot be set."); | |||
1020 | } | |||
1021 | | |||
1022 | // Encryption requires extract version >= 20 | |||
1023 | if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { | |||
1024 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0}) | |||
1025 | } | |||
1026 | | |||
1027 | // Strong encryption requires encryption flag to be set and extract version >= 50. | |||
1028 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1029 | if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) { | |||
1030 | throw new ZipException("Strong encryption flag set but encryption flag is not set"); | |||
1031 | } | |||
1032 | | |||
1033 | if (extractVersion < 50) { | |||
1034 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0 | |||
1035 | } | |||
1036 | } | |||
1037 | | |||
1038 | // Patched entries require extract version >= 27 | |||
1039 | if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { | |||
1040 | throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); | |||
1041 | } | |||
1042 | | |||
1043 | // Central header flags match local entry flags. | |||
1044 | if (localFlags != entry.Flags) { | |||
1045 | throw new ZipException("Central header/local header flags mismatch"); | |||
1046 | } | |||
1047 | | |||
1048 | // Central header compression method matches local entry | |||
1049 | if (entry.CompressionMethod != (CompressionMethod)compressionMethod) { | |||
1050 | throw new ZipException("Central header/local header compression method mismatch"); | |||
1051 | } | |||
1052 | | |||
1053 | if (entry.Version != extractVersion) { | |||
1054 | throw new ZipException("Extract version mismatch"); | |||
1055 | } | |||
1056 | | |||
1057 | // Strong encryption and extract version match | |||
1058 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1059 | if (extractVersion < 62) { | |||
1060 | throw new ZipException("Strong encryption flag set but version not high enough"); | |||
1061 | } | |||
1062 | } | |||
1063 | | |||
1064 | if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) { | |||
1065 | if ((fileTime != 0) || (fileDate != 0)) { | |||
1066 | throw new ZipException("Header masked set but date/time values non-zero"); | |||
1067 | } | |||
1068 | } | |||
1069 | | |||
1070 | if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) { | |||
1071 | if (crcValue != (uint)entry.Crc) { | |||
1072 | throw new ZipException("Central header/local header crc mismatch"); | |||
1073 | } | |||
1074 | } | |||
1075 | | |||
1076 | // Crc valid for empty entry. | |||
1077 | // This will also apply to streamed entries where size isnt known and the header cant be patched | |||
1078 | if ((size == 0) && (compressedSize == 0)) { | |||
1079 | if (crcValue != 0) { | |||
1080 | throw new ZipException("Invalid CRC for empty entry"); | |||
1081 | } | |||
1082 | } | |||
1083 | | |||
1084 | // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS str | |||
1085 | // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably | |||
1086 | if (entry.Name.Length > storedNameLength) { | |||
1087 | throw new ZipException("File name length mismatch"); | |||
1088 | } | |||
1089 | | |||
1090 | // Name data has already been read convert it and compare. | |||
1091 | string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); | |||
1092 | | |||
1093 | // Central directory and local entry name match | |||
1094 | if (localName != entry.Name) { | |||
1095 | throw new ZipException("Central header and local header file name mismatch"); | |||
1096 | } | |||
1097 | | |||
1098 | // Directories have zero actual size but can have compressed size | |||
1099 | if (entry.IsDirectory) { | |||
1100 | if (size > 0) { | |||
1101 | throw new ZipException("Directory cannot have size"); | |||
1102 | } | |||
1103 | | |||
1104 | // There may be other cases where the compressed size can be greater than this? | |||
1105 | // If so until details are known we will be strict. | |||
1106 | if (entry.IsCrypted) { | |||
1107 | if (compressedSize > ZipConstants.CryptoHeaderSize + 2) { | |||
1108 | throw new ZipException("Directory compressed size invalid"); | |||
1109 | } | |||
1110 | } else if (compressedSize > 2) { | |||
1111 | // When not compressed the directory size can validly be 2 bytes | |||
1112 | // if the true size wasnt known when data was originally being written. | |||
1113 | // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes | |||
1114 | throw new ZipException("Directory compressed size invalid"); | |||
1115 | } | |||
1116 | } | |||
1117 | | |||
1118 | if (!ZipNameTransform.IsValidName(localName, true)) { | |||
1119 | throw new ZipException("Name is invalid"); | |||
1120 | } | |||
1121 | } | |||
1122 | | |||
1123 | // Tests that apply to both data and header. | |||
1124 | | |||
1125 | // Size can be verified only if it is known in the local header. | |||
1126 | // it will always be known in the central header. | |||
1127 | if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || | |||
1128 | ((size > 0 || compressedSize > 0) && entry.Size > 0)) { | |||
1129 | | |||
1130 | if ((size != 0) | |||
1131 | && (size != entry.Size)) { | |||
1132 | throw new ZipException( | |||
1133 | string.Format("Size mismatch between central header({0}) and local header({1})", | |||
1134 | entry.Size, size)); | |||
1135 | } | |||
1136 | | |||
1137 | if ((compressedSize != 0) | |||
1138 | && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) { | |||
1139 | throw new ZipException( | |||
1140 | string.Format("Compressed size mismatch between central header({0}) and local header({1})", | |||
1141 | entry.CompressedSize, compressedSize)); | |||
1142 | } | |||
1143 | } | |||
1144 | | |||
1145 | int extraLength = storedNameLength + extraDataLength; | |||
1146 | return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; | |||
1147 | } | |||
1148 | } | |||
1149 | | |||
1150 | #endregion | |||
1151 | | |||
1152 | #region Updating | |||
1153 | | |||
1154 | const int DefaultBufferSize = 4096; | |||
1155 | | |||
1156 | /// <summary> | |||
1157 | /// The kind of update to apply. | |||
1158 | /// </summary> | |||
1159 | enum UpdateCommand | |||
1160 | { | |||
1161 | Copy, // Copy original file contents. | |||
1162 | Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. | |||
1163 | Add, // Add a new file to the archive. | |||
1164 | } | |||
1165 | | |||
1166 | #region Properties | |||
1167 | /// <summary> | |||
1168 | /// Get / set the <see cref="INameTransform"/> to apply to names when updating. | |||
1169 | /// </summary> | |||
1170 | public INameTransform NameTransform { | |||
1171 | get { | |||
1172 | return updateEntryFactory_.NameTransform; | |||
1173 | } | |||
1174 | | |||
1175 | set { | |||
1176 | updateEntryFactory_.NameTransform = value; | |||
1177 | } | |||
1178 | } | |||
1179 | | |||
1180 | /// <summary> | |||
1181 | /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values | |||
1182 | /// during updates. | |||
1183 | /// </summary> | |||
1184 | public IEntryFactory EntryFactory { | |||
1185 | get { | |||
1186 | return updateEntryFactory_; | |||
1187 | } | |||
1188 | | |||
1189 | set { | |||
1190 | if (value == null) { | |||
1191 | updateEntryFactory_ = new ZipEntryFactory(); | |||
1192 | } else { | |||
1193 | updateEntryFactory_ = value; | |||
1194 | } | |||
1195 | } | |||
1196 | } | |||
1197 | | |||
1198 | /// <summary> | |||
1199 | /// Get /set the buffer size to be used when updating this zip file. | |||
1200 | /// </summary> | |||
1201 | public int BufferSize { | |||
1202 | get { return bufferSize_; } | |||
1203 | set { | |||
1204 | if (value < 1024) { | |||
1205 | throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); | |||
1206 | } | |||
1207 | | |||
1208 | if (bufferSize_ != value) { | |||
1209 | bufferSize_ = value; | |||
1210 | copyBuffer_ = null; | |||
1211 | } | |||
1212 | } | |||
1213 | } | |||
1214 | | |||
1215 | /// <summary> | |||
1216 | /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. | |||
1217 | /// </summary> | |||
1218 | public bool IsUpdating { | |||
1219 | get { return updates_ != null; } | |||
1220 | } | |||
1221 | | |||
1222 | /// <summary> | |||
1223 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
1224 | /// </summary> | |||
1225 | public UseZip64 UseZip64 { | |||
1226 | get { return useZip64_; } | |||
1227 | set { useZip64_ = value; } | |||
1228 | } | |||
1229 | | |||
1230 | #endregion | |||
1231 | | |||
1232 | #region Immediate updating | |||
1233 | // TBD: Direct form of updating | |||
1234 | // | |||
1235 | // public void Update(IEntryMatcher deleteMatcher) | |||
1236 | // { | |||
1237 | // } | |||
1238 | // | |||
1239 | // public void Update(IScanner addScanner) | |||
1240 | // { | |||
1241 | // } | |||
1242 | #endregion | |||
1243 | | |||
1244 | #region Deferred Updating | |||
1245 | /// <summary> | |||
1246 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1247 | /// </summary> | |||
1248 | /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</p | |||
1249 | /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param | |||
1250 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1251 | /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception> | |||
1252 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1253 | public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) | |||
1254 | { | |||
1255 | if (archiveStorage == null) { | |||
1256 | throw new ArgumentNullException(nameof(archiveStorage)); | |||
1257 | } | |||
1258 | | |||
1259 | if (dataSource == null) { | |||
1260 | throw new ArgumentNullException(nameof(dataSource)); | |||
1261 | } | |||
1262 | | |||
1263 | if (isDisposed_) { | |||
1264 | throw new ObjectDisposedException("ZipFile"); | |||
1265 | } | |||
1266 | | |||
1267 | if (IsEmbeddedArchive) { | |||
1268 | throw new ZipException("Cannot update embedded/SFX archives"); | |||
1269 | } | |||
1270 | | |||
1271 | archiveStorage_ = archiveStorage; | |||
1272 | updateDataSource_ = dataSource; | |||
1273 | | |||
1274 | // NOTE: the baseStream_ may not currently support writing or seeking. | |||
1275 | | |||
1276 | updateIndex_ = new Hashtable(); | |||
1277 | | |||
1278 | updates_ = new ArrayList(entries_.Length); | |||
1279 | foreach (ZipEntry entry in entries_) { | |||
1280 | int index = updates_.Add(new ZipUpdate(entry)); | |||
1281 | updateIndex_.Add(entry.Name, index); | |||
1282 | } | |||
1283 | | |||
1284 | // We must sort by offset before using offset's calculated sizes | |||
1285 | updates_.Sort(new UpdateComparer()); | |||
1286 | | |||
1287 | int idx = 0; | |||
1288 | foreach (ZipUpdate update in updates_) { | |||
1289 | //If last entry, there is no next entry offset to use | |||
1290 | if (idx == updates_.Count - 1) | |||
1291 | break; | |||
1292 | | |||
1293 | update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; | |||
1294 | idx++; | |||
1295 | } | |||
1296 | updateCount_ = updates_.Count; | |||
1297 | | |||
1298 | contentsEdited_ = false; | |||
1299 | commentEdited_ = false; | |||
1300 | newComment_ = null; | |||
1301 | } | |||
1302 | | |||
1303 | /// <summary> | |||
1304 | /// Begin updating to this <see cref="ZipFile"/> archive. | |||
1305 | /// </summary> | |||
1306 | /// <param name="archiveStorage">The storage to use during the update.</param> | |||
1307 | public void BeginUpdate(IArchiveStorage archiveStorage) | |||
1308 | { | |||
1309 | BeginUpdate(archiveStorage, new DynamicDiskDataSource()); | |||
1310 | } | |||
1311 | | |||
1312 | /// <summary> | |||
1313 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1314 | /// </summary> | |||
1315 | /// <seealso cref="BeginUpdate(IArchiveStorage)"/> | |||
1316 | /// <seealso cref="CommitUpdate"></seealso> | |||
1317 | /// <seealso cref="AbortUpdate"></seealso> | |||
1318 | public void BeginUpdate() | |||
1319 | { | |||
1320 | if (Name == null) { | |||
1321 | BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); | |||
1322 | } else { | |||
1323 | BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); | |||
1324 | } | |||
1325 | } | |||
1326 | | |||
1327 | /// <summary> | |||
1328 | /// Commit current updates, updating this archive. | |||
1329 | /// </summary> | |||
1330 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1331 | /// <seealso cref="AbortUpdate"></seealso> | |||
1332 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1333 | public void CommitUpdate() | |||
1334 | { | |||
1335 | if (isDisposed_) { | |||
1336 | throw new ObjectDisposedException("ZipFile"); | |||
1337 | } | |||
1338 | | |||
1339 | CheckUpdating(); | |||
1340 | | |||
1341 | try { | |||
1342 | updateIndex_.Clear(); | |||
1343 | updateIndex_ = null; | |||
1344 | | |||
1345 | if (contentsEdited_) { | |||
1346 | RunUpdates(); | |||
1347 | } else if (commentEdited_) { | |||
1348 | UpdateCommentOnly(); | |||
1349 | } else { | |||
1350 | // Create an empty archive if none existed originally. | |||
1351 | if (entries_.Length == 0) { | |||
1352 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
1353 | using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) { | |||
1354 | zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); | |||
1355 | } | |||
1356 | } | |||
1357 | } | |||
1358 | | |||
1359 | } finally { | |||
1360 | PostUpdateCleanup(); | |||
1361 | } | |||
1362 | } | |||
1363 | | |||
1364 | /// <summary> | |||
1365 | /// Abort updating leaving the archive unchanged. | |||
1366 | /// </summary> | |||
1367 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1368 | /// <seealso cref="CommitUpdate"></seealso> | |||
1369 | public void AbortUpdate() | |||
1370 | { | |||
1371 | PostUpdateCleanup(); | |||
1372 | } | |||
1373 | | |||
1374 | /// <summary> | |||
1375 | /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. | |||
1376 | /// </summary> | |||
1377 | /// <param name="comment">The comment to record.</param> | |||
1378 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1379 | public void SetComment(string comment) | |||
1380 | { | |||
1381 | if (isDisposed_) { | |||
1382 | throw new ObjectDisposedException("ZipFile"); | |||
1383 | } | |||
1384 | | |||
1385 | CheckUpdating(); | |||
1386 | | |||
1387 | newComment_ = new ZipString(comment); | |||
1388 | | |||
1389 | if (newComment_.RawLength > 0xffff) { | |||
1390 | newComment_ = null; | |||
1391 | throw new ZipException("Comment length exceeds maximum - 65535"); | |||
1392 | } | |||
1393 | | |||
1394 | // We dont take account of the original and current comment appearing to be the same | |||
1395 | // as encoding may be different. | |||
1396 | commentEdited_ = true; | |||
1397 | } | |||
1398 | | |||
1399 | #endregion | |||
1400 | | |||
1401 | #region Adding Entries | |||
1402 | | |||
1403 | void AddUpdate(ZipUpdate update) | |||
1404 | { | |||
1405 | contentsEdited_ = true; | |||
1406 | | |||
1407 | int index = FindExistingUpdate(update.Entry.Name); | |||
1408 | | |||
1409 | if (index >= 0) { | |||
1410 | if (updates_[index] == null) { | |||
1411 | updateCount_ += 1; | |||
1412 | } | |||
1413 | | |||
1414 | // Direct replacement is faster than delete and add. | |||
1415 | updates_[index] = update; | |||
1416 | } else { | |||
1417 | index = updates_.Add(update); | |||
1418 | updateCount_ += 1; | |||
1419 | updateIndex_.Add(update.Entry.Name, index); | |||
1420 | } | |||
1421 | } | |||
1422 | | |||
1423 | /// <summary> | |||
1424 | /// Add a new entry to the archive. | |||
1425 | /// </summary> | |||
1426 | /// <param name="fileName">The name of the file to add.</param> | |||
1427 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1428 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> | |||
1429 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1430 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1431 | /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception> | |||
1432 | public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) | |||
1433 | { | |||
1434 | if (fileName == null) { | |||
1435 | throw new ArgumentNullException(nameof(fileName)); | |||
1436 | } | |||
1437 | | |||
1438 | if (isDisposed_) { | |||
1439 | throw new ObjectDisposedException("ZipFile"); | |||
1440 | } | |||
1441 | | |||
1442 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1443 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1444 | } | |||
1445 | | |||
1446 | CheckUpdating(); | |||
1447 | contentsEdited_ = true; | |||
1448 | | |||
1449 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1450 | entry.IsUnicodeText = useUnicodeText; | |||
1451 | entry.CompressionMethod = compressionMethod; | |||
1452 | | |||
1453 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1454 | } | |||
1455 | | |||
1456 | /// <summary> | |||
1457 | /// Add a new entry to the archive. | |||
1458 | /// </summary> | |||
1459 | /// <param name="fileName">The name of the file to add.</param> | |||
1460 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1461 | /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception> | |||
1462 | /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception> | |||
1463 | public void Add(string fileName, CompressionMethod compressionMethod) | |||
1464 | { | |||
1465 | if (fileName == null) { | |||
1466 | throw new ArgumentNullException(nameof(fileName)); | |||
1467 | } | |||
1468 | | |||
1469 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1470 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1471 | } | |||
1472 | | |||
1473 | CheckUpdating(); | |||
1474 | contentsEdited_ = true; | |||
1475 | | |||
1476 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1477 | entry.CompressionMethod = compressionMethod; | |||
1478 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1479 | } | |||
1480 | | |||
1481 | /// <summary> | |||
1482 | /// Add a file to the archive. | |||
1483 | /// </summary> | |||
1484 | /// <param name="fileName">The name of the file to add.</param> | |||
1485 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1486 | public void Add(string fileName) | |||
1487 | { | |||
1488 | if (fileName == null) { | |||
1489 | throw new ArgumentNullException(nameof(fileName)); | |||
1490 | } | |||
1491 | | |||
1492 | CheckUpdating(); | |||
1493 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); | |||
1494 | } | |||
1495 | | |||
1496 | /// <summary> | |||
1497 | /// Add a file to the archive. | |||
1498 | /// </summary> | |||
1499 | /// <param name="fileName">The name of the file to add.</param> | |||
1500 | /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param> | |||
1501 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1502 | public void Add(string fileName, string entryName) | |||
1503 | { | |||
1504 | if (fileName == null) { | |||
1505 | throw new ArgumentNullException(nameof(fileName)); | |||
1506 | } | |||
1507 | | |||
1508 | if (entryName == null) { | |||
1509 | throw new ArgumentNullException(nameof(entryName)); | |||
1510 | } | |||
1511 | | |||
1512 | CheckUpdating(); | |||
1513 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); | |||
1514 | } | |||
1515 | | |||
1516 | | |||
1517 | /// <summary> | |||
1518 | /// Add a file entry with data. | |||
1519 | /// </summary> | |||
1520 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1521 | /// <param name="entryName">The name to give to the entry.</param> | |||
1522 | public void Add(IStaticDataSource dataSource, string entryName) | |||
1523 | { | |||
1524 | if (dataSource == null) { | |||
1525 | throw new ArgumentNullException(nameof(dataSource)); | |||
1526 | } | |||
1527 | | |||
1528 | if (entryName == null) { | |||
1529 | throw new ArgumentNullException(nameof(entryName)); | |||
1530 | } | |||
1531 | | |||
1532 | CheckUpdating(); | |||
1533 | AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); | |||
1534 | } | |||
1535 | | |||
1536 | /// <summary> | |||
1537 | /// Add a file entry with data. | |||
1538 | /// </summary> | |||
1539 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1540 | /// <param name="entryName">The name to give to the entry.</param> | |||
1541 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1542 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
1543 | { | |||
1544 | if (dataSource == null) { | |||
1545 | throw new ArgumentNullException(nameof(dataSource)); | |||
1546 | } | |||
1547 | | |||
1548 | if (entryName == null) { | |||
1549 | throw new ArgumentNullException(nameof(entryName)); | |||
1550 | } | |||
1551 | | |||
1552 | CheckUpdating(); | |||
1553 | | |||
1554 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1555 | entry.CompressionMethod = compressionMethod; | |||
1556 | | |||
1557 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1558 | } | |||
1559 | | |||
1560 | /// <summary> | |||
1561 | /// Add a file entry with data. | |||
1562 | /// </summary> | |||
1563 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1564 | /// <param name="entryName">The name to give to the entry.</param> | |||
1565 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1566 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> | |||
1567 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicode | |||
1568 | { | |||
1569 | if (dataSource == null) { | |||
1570 | throw new ArgumentNullException(nameof(dataSource)); | |||
1571 | } | |||
1572 | | |||
1573 | if (entryName == null) { | |||
1574 | throw new ArgumentNullException(nameof(entryName)); | |||
1575 | } | |||
1576 | | |||
1577 | CheckUpdating(); | |||
1578 | | |||
1579 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1580 | entry.IsUnicodeText = useUnicodeText; | |||
1581 | entry.CompressionMethod = compressionMethod; | |||
1582 | | |||
1583 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1584 | } | |||
1585 | | |||
1586 | /// <summary> | |||
1587 | /// Add a <see cref="ZipEntry"/> that contains no data. | |||
1588 | /// </summary> | |||
1589 | /// <param name="entry">The entry to add.</param> | |||
1590 | /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> | |||
1591 | public void Add(ZipEntry entry) | |||
1592 | { | |||
1593 | if (entry == null) { | |||
1594 | throw new ArgumentNullException(nameof(entry)); | |||
1595 | } | |||
1596 | | |||
1597 | CheckUpdating(); | |||
1598 | | |||
1599 | if ((entry.Size != 0) || (entry.CompressedSize != 0)) { | |||
1600 | throw new ZipException("Entry cannot have any data"); | |||
1601 | } | |||
1602 | | |||
1603 | AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); | |||
1604 | } | |||
1605 | | |||
1606 | /// <summary> | |||
1607 | /// Add a directory entry to the archive. | |||
1608 | /// </summary> | |||
1609 | /// <param name="directoryName">The directory to add.</param> | |||
1610 | public void AddDirectory(string directoryName) | |||
1611 | { | |||
1612 | if (directoryName == null) { | |||
1613 | throw new ArgumentNullException(nameof(directoryName)); | |||
1614 | } | |||
1615 | | |||
1616 | CheckUpdating(); | |||
1617 | | |||
1618 | ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); | |||
1619 | AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); | |||
1620 | } | |||
1621 | | |||
1622 | #endregion | |||
1623 | | |||
1624 | #region Modifying Entries | |||
1625 | /* Modify not yet ready for public consumption. | |||
1626 | Direct modification of an entry should not overwrite original data before its read. | |||
1627 | Safe mode is trivial in this sense. | |||
1628 | public void Modify(ZipEntry original, ZipEntry updated) | |||
1629 | { | |||
1630 | if ( original == null ) { | |||
1631 | throw new ArgumentNullException("original"); | |||
1632 | } | |||
1633 | | |||
1634 | if ( updated == null ) { | |||
1635 | throw new ArgumentNullException("updated"); | |||
1636 | } | |||
1637 | | |||
1638 | CheckUpdating(); | |||
1639 | contentsEdited_ = true; | |||
1640 | updates_.Add(new ZipUpdate(original, updated)); | |||
1641 | } | |||
1642 | */ | |||
1643 | #endregion | |||
1644 | | |||
1645 | #region Deleting Entries | |||
1646 | /// <summary> | |||
1647 | /// Delete an entry by name | |||
1648 | /// </summary> | |||
1649 | /// <param name="fileName">The filename to delete</param> | |||
1650 | /// <returns>True if the entry was found and deleted; false otherwise.</returns> | |||
1651 | public bool Delete(string fileName) | |||
1652 | { | |||
1653 | if (fileName == null) { | |||
1654 | throw new ArgumentNullException(nameof(fileName)); | |||
1655 | } | |||
1656 | | |||
1657 | CheckUpdating(); | |||
1658 | | |||
1659 | bool result = false; | |||
1660 | int index = FindExistingUpdate(fileName); | |||
1661 | if ((index >= 0) && (updates_[index] != null)) { | |||
1662 | result = true; | |||
1663 | contentsEdited_ = true; | |||
1664 | updates_[index] = null; | |||
1665 | updateCount_ -= 1; | |||
1666 | } else { | |||
1667 | throw new ZipException("Cannot find entry to delete"); | |||
1668 | } | |||
1669 | return result; | |||
1670 | } | |||
1671 | | |||
1672 | /// <summary> | |||
1673 | /// Delete a <see cref="ZipEntry"/> from the archive. | |||
1674 | /// </summary> | |||
1675 | /// <param name="entry">The entry to delete.</param> | |||
1676 | public void Delete(ZipEntry entry) | |||
1677 | { | |||
1678 | if (entry == null) { | |||
1679 | throw new ArgumentNullException(nameof(entry)); | |||
1680 | } | |||
1681 | | |||
1682 | CheckUpdating(); | |||
1683 | | |||
1684 | int index = FindExistingUpdate(entry); | |||
1685 | if (index >= 0) { | |||
1686 | contentsEdited_ = true; | |||
1687 | updates_[index] = null; | |||
1688 | updateCount_ -= 1; | |||
1689 | } else { | |||
1690 | throw new ZipException("Cannot find entry to delete"); | |||
1691 | } | |||
1692 | } | |||
1693 | | |||
1694 | #endregion | |||
1695 | | |||
1696 | #region Update Support | |||
1697 | | |||
1698 | #region Writing Values/Headers | |||
1699 | void WriteLEShort(int value) | |||
1700 | { | |||
1701 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1702 | baseStream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
1703 | } | |||
1704 | | |||
1705 | /// <summary> | |||
1706 | /// Write an unsigned short in little endian byte order. | |||
1707 | /// </summary> | |||
1708 | void WriteLEUshort(ushort value) | |||
1709 | { | |||
1710 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1711 | baseStream_.WriteByte((byte)(value >> 8)); | |||
1712 | } | |||
1713 | | |||
1714 | /// <summary> | |||
1715 | /// Write an int in little endian byte order. | |||
1716 | /// </summary> | |||
1717 | void WriteLEInt(int value) | |||
1718 | { | |||
1719 | WriteLEShort(value & 0xffff); | |||
1720 | WriteLEShort(value >> 16); | |||
1721 | } | |||
1722 | | |||
1723 | /// <summary> | |||
1724 | /// Write an unsigned int in little endian byte order. | |||
1725 | /// </summary> | |||
1726 | void WriteLEUint(uint value) | |||
1727 | { | |||
1728 | WriteLEUshort((ushort)(value & 0xffff)); | |||
1729 | WriteLEUshort((ushort)(value >> 16)); | |||
1730 | } | |||
1731 | | |||
1732 | /// <summary> | |||
1733 | /// Write a long in little endian byte order. | |||
1734 | /// </summary> | |||
1735 | void WriteLeLong(long value) | |||
1736 | { | |||
1737 | WriteLEInt((int)(value & 0xffffffff)); | |||
1738 | WriteLEInt((int)(value >> 32)); | |||
1739 | } | |||
1740 | | |||
1741 | void WriteLEUlong(ulong value) | |||
1742 | { | |||
1743 | WriteLEUint((uint)(value & 0xffffffff)); | |||
1744 | WriteLEUint((uint)(value >> 32)); | |||
1745 | } | |||
1746 | | |||
1747 | void WriteLocalEntryHeader(ZipUpdate update) | |||
1748 | { | |||
1749 | ZipEntry entry = update.OutEntry; | |||
1750 | | |||
1751 | // TODO: Local offset will require adjusting for multi-disk zip files. | |||
1752 | entry.Offset = baseStream_.Position; | |||
1753 | | |||
1754 | // TODO: Need to clear any entry flags that dont make sense or throw an exception here. | |||
1755 | if (update.Command != UpdateCommand.Copy) { | |||
1756 | if (entry.CompressionMethod == CompressionMethod.Deflated) { | |||
1757 | if (entry.Size == 0) { | |||
1758 | // No need to compress - no data. | |||
1759 | entry.CompressedSize = entry.Size; | |||
1760 | entry.Crc = 0; | |||
1761 | entry.CompressionMethod = CompressionMethod.Stored; | |||
1762 | } | |||
1763 | } else if (entry.CompressionMethod == CompressionMethod.Stored) { | |||
1764 | entry.Flags &= ~(int)GeneralBitFlags.Descriptor; | |||
1765 | } | |||
1766 | | |||
1767 | if (HaveKeys) { | |||
1768 | entry.IsCrypted = true; | |||
1769 | if (entry.Crc < 0) { | |||
1770 | entry.Flags |= (int)GeneralBitFlags.Descriptor; | |||
1771 | } | |||
1772 | } else { | |||
1773 | entry.IsCrypted = false; | |||
1774 | } | |||
1775 | | |||
1776 | switch (useZip64_) { | |||
1777 | case UseZip64.Dynamic: | |||
1778 | if (entry.Size < 0) { | |||
1779 | entry.ForceZip64(); | |||
1780 | } | |||
1781 | break; | |||
1782 | | |||
1783 | case UseZip64.On: | |||
1784 | entry.ForceZip64(); | |||
1785 | break; | |||
1786 | | |||
1787 | case UseZip64.Off: | |||
1788 | // Do nothing. The entry itself may be using Zip64 independantly. | |||
1789 | break; | |||
1790 | } | |||
1791 | } | |||
1792 | | |||
1793 | // Write the local file header | |||
1794 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
1795 | | |||
1796 | WriteLEShort(entry.Version); | |||
1797 | WriteLEShort(entry.Flags); | |||
1798 | | |||
1799 | WriteLEShort((byte)entry.CompressionMethod); | |||
1800 | WriteLEInt((int)entry.DosTime); | |||
1801 | | |||
1802 | if (!entry.HasCrc) { | |||
1803 | // Note patch address for updating CRC later. | |||
1804 | update.CrcPatchOffset = baseStream_.Position; | |||
1805 | WriteLEInt((int)0); | |||
1806 | } else { | |||
1807 | WriteLEInt(unchecked((int)entry.Crc)); | |||
1808 | } | |||
1809 | | |||
1810 | if (entry.LocalHeaderRequiresZip64) { | |||
1811 | WriteLEInt(-1); | |||
1812 | WriteLEInt(-1); | |||
1813 | } else { | |||
1814 | if ((entry.CompressedSize < 0) || (entry.Size < 0)) { | |||
1815 | update.SizePatchOffset = baseStream_.Position; | |||
1816 | } | |||
1817 | | |||
1818 | WriteLEInt((int)entry.CompressedSize); | |||
1819 | WriteLEInt((int)entry.Size); | |||
1820 | } | |||
1821 | | |||
1822 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1823 | | |||
1824 | if (name.Length > 0xFFFF) { | |||
1825 | throw new ZipException("Entry name too long."); | |||
1826 | } | |||
1827 | | |||
1828 | var ed = new ZipExtraData(entry.ExtraData); | |||
1829 | | |||
1830 | if (entry.LocalHeaderRequiresZip64) { | |||
1831 | ed.StartNewEntry(); | |||
1832 | | |||
1833 | // Local entry header always includes size and compressed size. | |||
1834 | // NOTE the order of these fields is reversed when compared to the normal headers! | |||
1835 | ed.AddLeLong(entry.Size); | |||
1836 | ed.AddLeLong(entry.CompressedSize); | |||
1837 | ed.AddNewEntry(1); | |||
1838 | } else { | |||
1839 | ed.Delete(1); | |||
1840 | } | |||
1841 | | |||
1842 | entry.ExtraData = ed.GetEntryData(); | |||
1843 | | |||
1844 | WriteLEShort(name.Length); | |||
1845 | WriteLEShort(entry.ExtraData.Length); | |||
1846 | | |||
1847 | if (name.Length > 0) { | |||
1848 | baseStream_.Write(name, 0, name.Length); | |||
1849 | } | |||
1850 | | |||
1851 | if (entry.LocalHeaderRequiresZip64) { | |||
1852 | if (!ed.Find(1)) { | |||
1853 | throw new ZipException("Internal error cannot find extra data"); | |||
1854 | } | |||
1855 | | |||
1856 | update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; | |||
1857 | } | |||
1858 | | |||
1859 | if (entry.ExtraData.Length > 0) { | |||
1860 | baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); | |||
1861 | } | |||
1862 | } | |||
1863 | | |||
1864 | int WriteCentralDirectoryHeader(ZipEntry entry) | |||
1865 | { | |||
1866 | if (entry.CompressedSize < 0) { | |||
1867 | throw new ZipException("Attempt to write central directory entry with unknown csize"); | |||
1868 | } | |||
1869 | | |||
1870 | if (entry.Size < 0) { | |||
1871 | throw new ZipException("Attempt to write central directory entry with unknown size"); | |||
1872 | } | |||
1873 | | |||
1874 | if (entry.Crc < 0) { | |||
1875 | throw new ZipException("Attempt to write central directory entry with unknown crc"); | |||
1876 | } | |||
1877 | | |||
1878 | // Write the central file header | |||
1879 | WriteLEInt(ZipConstants.CentralHeaderSignature); | |||
1880 | | |||
1881 | // Version made by | |||
1882 | WriteLEShort(ZipConstants.VersionMadeBy); | |||
1883 | | |||
1884 | // Version required to extract | |||
1885 | WriteLEShort(entry.Version); | |||
1886 | | |||
1887 | WriteLEShort(entry.Flags); | |||
1888 | | |||
1889 | unchecked { | |||
1890 | WriteLEShort((byte)entry.CompressionMethod); | |||
1891 | WriteLEInt((int)entry.DosTime); | |||
1892 | WriteLEInt((int)entry.Crc); | |||
1893 | } | |||
1894 | | |||
1895 | if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { | |||
1896 | WriteLEInt(-1); | |||
1897 | } else { | |||
1898 | WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); | |||
1899 | } | |||
1900 | | |||
1901 | if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { | |||
1902 | WriteLEInt(-1); | |||
1903 | } else { | |||
1904 | WriteLEInt((int)entry.Size); | |||
1905 | } | |||
1906 | | |||
1907 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1908 | | |||
1909 | if (name.Length > 0xFFFF) { | |||
1910 | throw new ZipException("Entry name is too long."); | |||
1911 | } | |||
1912 | | |||
1913 | WriteLEShort(name.Length); | |||
1914 | | |||
1915 | // Central header extra data is different to local header version so regenerate. | |||
1916 | var ed = new ZipExtraData(entry.ExtraData); | |||
1917 | | |||
1918 | if (entry.CentralHeaderRequiresZip64) { | |||
1919 | ed.StartNewEntry(); | |||
1920 | | |||
1921 | if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1922 | ed.AddLeLong(entry.Size); | |||
1923 | } | |||
1924 | | |||
1925 | if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1926 | ed.AddLeLong(entry.CompressedSize); | |||
1927 | } | |||
1928 | | |||
1929 | if (entry.Offset >= 0xffffffff) { | |||
1930 | ed.AddLeLong(entry.Offset); | |||
1931 | } | |||
1932 | | |||
1933 | // Number of disk on which this file starts isnt supported and is never written here. | |||
1934 | ed.AddNewEntry(1); | |||
1935 | } else { | |||
1936 | // Should have already be done when local header was added. | |||
1937 | ed.Delete(1); | |||
1938 | } | |||
1939 | | |||
1940 | byte[] centralExtraData = ed.GetEntryData(); | |||
1941 | | |||
1942 | WriteLEShort(centralExtraData.Length); | |||
1943 | WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); | |||
1944 | | |||
1945 | WriteLEShort(0); // disk number | |||
1946 | WriteLEShort(0); // internal file attributes | |||
1947 | | |||
1948 | // External file attributes... | |||
1949 | if (entry.ExternalFileAttributes != -1) { | |||
1950 | WriteLEInt(entry.ExternalFileAttributes); | |||
1951 | } else { | |||
1952 | if (entry.IsDirectory) { | |||
1953 | WriteLEUint(16); | |||
1954 | } else { | |||
1955 | WriteLEUint(0); | |||
1956 | } | |||
1957 | } | |||
1958 | | |||
1959 | if (entry.Offset >= 0xffffffff) { | |||
1960 | WriteLEUint(0xffffffff); | |||
1961 | } else { | |||
1962 | WriteLEUint((uint)(int)entry.Offset); | |||
1963 | } | |||
1964 | | |||
1965 | if (name.Length > 0) { | |||
1966 | baseStream_.Write(name, 0, name.Length); | |||
1967 | } | |||
1968 | | |||
1969 | if (centralExtraData.Length > 0) { | |||
1970 | baseStream_.Write(centralExtraData, 0, centralExtraData.Length); | |||
1971 | } | |||
1972 | | |||
1973 | byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; | |||
1974 | | |||
1975 | if (rawComment.Length > 0) { | |||
1976 | baseStream_.Write(rawComment, 0, rawComment.Length); | |||
1977 | } | |||
1978 | | |||
1979 | return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; | |||
1980 | } | |||
1981 | #endregion | |||
1982 | | |||
1983 | void PostUpdateCleanup() | |||
1984 | { | |||
1985 | updateDataSource_ = null; | |||
1986 | updates_ = null; | |||
1987 | updateIndex_ = null; | |||
1988 | | |||
1989 | if (archiveStorage_ != null) { | |||
1990 | archiveStorage_.Dispose(); | |||
1991 | archiveStorage_ = null; | |||
1992 | } | |||
1993 | } | |||
1994 | | |||
1995 | string GetTransformedFileName(string name) | |||
1996 | { | |||
1997 | INameTransform transform = NameTransform; | |||
1998 | return (transform != null) ? | |||
1999 | transform.TransformFile(name) : | |||
2000 | name; | |||
2001 | } | |||
2002 | | |||
2003 | string GetTransformedDirectoryName(string name) | |||
2004 | { | |||
2005 | INameTransform transform = NameTransform; | |||
2006 | return (transform != null) ? | |||
2007 | transform.TransformDirectory(name) : | |||
2008 | name; | |||
2009 | } | |||
2010 | | |||
2011 | /// <summary> | |||
2012 | /// Get a raw memory buffer. | |||
2013 | /// </summary> | |||
2014 | /// <returns>Returns a raw memory buffer.</returns> | |||
2015 | byte[] GetBuffer() | |||
2016 | { | |||
2017 | if (copyBuffer_ == null) { | |||
2018 | copyBuffer_ = new byte[bufferSize_]; | |||
2019 | } | |||
2020 | return copyBuffer_; | |||
2021 | } | |||
2022 | | |||
2023 | void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) | |||
2024 | { | |||
2025 | int bytesToCopy = GetDescriptorSize(update); | |||
2026 | | |||
2027 | if (bytesToCopy > 0) { | |||
2028 | byte[] buffer = GetBuffer(); | |||
2029 | | |||
2030 | while (bytesToCopy > 0) { | |||
2031 | int readSize = Math.Min(buffer.Length, bytesToCopy); | |||
2032 | | |||
2033 | int bytesRead = source.Read(buffer, 0, readSize); | |||
2034 | if (bytesRead > 0) { | |||
2035 | dest.Write(buffer, 0, bytesRead); | |||
2036 | bytesToCopy -= bytesRead; | |||
2037 | } else { | |||
2038 | throw new ZipException("Unxpected end of stream"); | |||
2039 | } | |||
2040 | } | |||
2041 | } | |||
2042 | } | |||
2043 | | |||
2044 | void CopyBytes(ZipUpdate update, Stream destination, Stream source, | |||
2045 | long bytesToCopy, bool updateCrc) | |||
2046 | { | |||
2047 | if (destination == source) { | |||
2048 | throw new InvalidOperationException("Destination and source are the same"); | |||
2049 | } | |||
2050 | | |||
2051 | // NOTE: Compressed size is updated elsewhere. | |||
2052 | var crc = new Crc32(); | |||
2053 | byte[] buffer = GetBuffer(); | |||
2054 | | |||
2055 | long targetBytes = bytesToCopy; | |||
2056 | long totalBytesRead = 0; | |||
2057 | | |||
2058 | int bytesRead; | |||
2059 | do { | |||
2060 | int readSize = buffer.Length; | |||
2061 | | |||
2062 | if (bytesToCopy < readSize) { | |||
2063 | readSize = (int)bytesToCopy; | |||
2064 | } | |||
2065 | | |||
2066 | bytesRead = source.Read(buffer, 0, readSize); | |||
2067 | if (bytesRead > 0) { | |||
2068 | if (updateCrc) { | |||
2069 | crc.Update(buffer, 0, bytesRead); | |||
2070 | } | |||
2071 | destination.Write(buffer, 0, bytesRead); | |||
2072 | bytesToCopy -= bytesRead; | |||
2073 | totalBytesRead += bytesRead; | |||
2074 | } | |||
2075 | } | |||
2076 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2077 | | |||
2078 | if (totalBytesRead != targetBytes) { | |||
2079 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2080 | } | |||
2081 | | |||
2082 | if (updateCrc) { | |||
2083 | update.OutEntry.Crc = crc.Value; | |||
2084 | } | |||
2085 | } | |||
2086 | | |||
2087 | /// <summary> | |||
2088 | /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. | |||
2089 | /// </summary> | |||
2090 | /// <param name="update">The update to get the size for.</param> | |||
2091 | /// <returns>The descriptor size, zero if there isnt one.</returns> | |||
2092 | int GetDescriptorSize(ZipUpdate update) | |||
2093 | { | |||
2094 | int result = 0; | |||
2095 | if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
2096 | result = ZipConstants.DataDescriptorSize - 4; | |||
2097 | if (update.Entry.LocalHeaderRequiresZip64) { | |||
2098 | result = ZipConstants.Zip64DataDescriptorSize - 4; | |||
2099 | } | |||
2100 | } | |||
2101 | return result; | |||
2102 | } | |||
2103 | | |||
2104 | void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) | |||
2105 | { | |||
2106 | int bytesToCopy = GetDescriptorSize(update); | |||
2107 | | |||
2108 | while (bytesToCopy > 0) { | |||
2109 | var readSize = (int)bytesToCopy; | |||
2110 | byte[] buffer = GetBuffer(); | |||
2111 | | |||
2112 | stream.Position = sourcePosition; | |||
2113 | int bytesRead = stream.Read(buffer, 0, readSize); | |||
2114 | if (bytesRead > 0) { | |||
2115 | stream.Position = destinationPosition; | |||
2116 | stream.Write(buffer, 0, bytesRead); | |||
2117 | bytesToCopy -= bytesRead; | |||
2118 | destinationPosition += bytesRead; | |||
2119 | sourcePosition += bytesRead; | |||
2120 | } else { | |||
2121 | throw new ZipException("Unxpected end of stream"); | |||
2122 | } | |||
2123 | } | |||
2124 | } | |||
2125 | | |||
2126 | void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou | |||
2127 | { | |||
2128 | long bytesToCopy = update.Entry.CompressedSize; | |||
2129 | | |||
2130 | // NOTE: Compressed size is updated elsewhere. | |||
2131 | var crc = new Crc32(); | |||
2132 | byte[] buffer = GetBuffer(); | |||
2133 | | |||
2134 | long targetBytes = bytesToCopy; | |||
2135 | long totalBytesRead = 0; | |||
2136 | | |||
2137 | int bytesRead; | |||
2138 | do { | |||
2139 | int readSize = buffer.Length; | |||
2140 | | |||
2141 | if (bytesToCopy < readSize) { | |||
2142 | readSize = (int)bytesToCopy; | |||
2143 | } | |||
2144 | | |||
2145 | stream.Position = sourcePosition; | |||
2146 | bytesRead = stream.Read(buffer, 0, readSize); | |||
2147 | if (bytesRead > 0) { | |||
2148 | if (updateCrc) { | |||
2149 | crc.Update(buffer, 0, bytesRead); | |||
2150 | } | |||
2151 | stream.Position = destinationPosition; | |||
2152 | stream.Write(buffer, 0, bytesRead); | |||
2153 | | |||
2154 | destinationPosition += bytesRead; | |||
2155 | sourcePosition += bytesRead; | |||
2156 | bytesToCopy -= bytesRead; | |||
2157 | totalBytesRead += bytesRead; | |||
2158 | } | |||
2159 | } | |||
2160 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2161 | | |||
2162 | if (totalBytesRead != targetBytes) { | |||
2163 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2164 | } | |||
2165 | | |||
2166 | if (updateCrc) { | |||
2167 | update.OutEntry.Crc = crc.Value; | |||
2168 | } | |||
2169 | } | |||
2170 | | |||
2171 | int FindExistingUpdate(ZipEntry entry) | |||
2172 | { | |||
2173 | int result = -1; | |||
2174 | string convertedName = GetTransformedFileName(entry.Name); | |||
2175 | | |||
2176 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2177 | result = (int)updateIndex_[convertedName]; | |||
2178 | } | |||
2179 | /* | |||
2180 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2181 | // for CF? | |||
2182 | for (int index = 0; index < updates_.Count; ++index) | |||
2183 | { | |||
2184 | ZipUpdate zu = ( ZipUpdate )updates_[index]; | |||
2185 | if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && | |||
2186 | (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { | |||
2187 | result = index; | |||
2188 | break; | |||
2189 | } | |||
2190 | } | |||
2191 | */ | |||
2192 | return result; | |||
2193 | } | |||
2194 | | |||
2195 | int FindExistingUpdate(string fileName) | |||
2196 | { | |||
2197 | int result = -1; | |||
2198 | | |||
2199 | string convertedName = GetTransformedFileName(fileName); | |||
2200 | | |||
2201 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2202 | result = (int)updateIndex_[convertedName]; | |||
2203 | } | |||
2204 | | |||
2205 | /* | |||
2206 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2207 | // for CF? | |||
2208 | for ( int index = 0; index < updates_.Count; ++index ) { | |||
2209 | if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, | |||
2210 | true, CultureInfo.InvariantCulture) == 0 ) { | |||
2211 | result = index; | |||
2212 | break; | |||
2213 | } | |||
2214 | } | |||
2215 | */ | |||
2216 | | |||
2217 | return result; | |||
2218 | } | |||
2219 | | |||
2220 | /// <summary> | |||
2221 | /// Get an output stream for the specified <see cref="ZipEntry"/> | |||
2222 | /// </summary> | |||
2223 | /// <param name="entry">The entry to get an output stream for.</param> | |||
2224 | /// <returns>The output stream obtained for the entry.</returns> | |||
2225 | Stream GetOutputStream(ZipEntry entry) | |||
2226 | { | |||
2227 | Stream result = baseStream_; | |||
2228 | | |||
2229 | if (entry.IsCrypted == true) { | |||
2230 | result = CreateAndInitEncryptionStream(result, entry); | |||
2231 | } | |||
2232 | | |||
2233 | switch (entry.CompressionMethod) { | |||
2234 | case CompressionMethod.Stored: | |||
2235 | result = new UncompressedStream(result); | |||
2236 | break; | |||
2237 | | |||
2238 | case CompressionMethod.Deflated: | |||
2239 | var dos = new DeflaterOutputStream(result, new Deflater(9, true)); | |||
2240 | dos.IsStreamOwner = false; | |||
2241 | result = dos; | |||
2242 | break; | |||
2243 | | |||
2244 | default: | |||
2245 | throw new ZipException("Unknown compression method " + entry.CompressionMethod); | |||
2246 | } | |||
2247 | return result; | |||
2248 | } | |||
2249 | | |||
2250 | void AddEntry(ZipFile workFile, ZipUpdate update) | |||
2251 | { | |||
2252 | Stream source = null; | |||
2253 | | |||
2254 | if (update.Entry.IsFile) { | |||
2255 | source = update.GetSource(); | |||
2256 | | |||
2257 | if (source == null) { | |||
2258 | source = updateDataSource_.GetSource(update.Entry, update.Filename); | |||
2259 | } | |||
2260 | } | |||
2261 | | |||
2262 | if (source != null) { | |||
2263 | using (source) { | |||
2264 | long sourceStreamLength = source.Length; | |||
2265 | if (update.OutEntry.Size < 0) { | |||
2266 | update.OutEntry.Size = sourceStreamLength; | |||
2267 | } else { | |||
2268 | // Check for errant entries. | |||
2269 | if (update.OutEntry.Size != sourceStreamLength) { | |||
2270 | throw new ZipException("Entry size/stream size mismatch"); | |||
2271 | } | |||
2272 | } | |||
2273 | | |||
2274 | workFile.WriteLocalEntryHeader(update); | |||
2275 | | |||
2276 | long dataStart = workFile.baseStream_.Position; | |||
2277 | | |||
2278 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2279 | CopyBytes(update, output, source, sourceStreamLength, true); | |||
2280 | } | |||
2281 | | |||
2282 | long dataEnd = workFile.baseStream_.Position; | |||
2283 | update.OutEntry.CompressedSize = dataEnd - dataStart; | |||
2284 | | |||
2285 | if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) { | |||
2286 | var helper = new ZipHelperStream(workFile.baseStream_); | |||
2287 | helper.WriteDataDescriptor(update.OutEntry); | |||
2288 | } | |||
2289 | } | |||
2290 | } else { | |||
2291 | workFile.WriteLocalEntryHeader(update); | |||
2292 | update.OutEntry.CompressedSize = 0; | |||
2293 | } | |||
2294 | | |||
2295 | } | |||
2296 | | |||
2297 | void ModifyEntry(ZipFile workFile, ZipUpdate update) | |||
2298 | { | |||
2299 | workFile.WriteLocalEntryHeader(update); | |||
2300 | long dataStart = workFile.baseStream_.Position; | |||
2301 | | |||
2302 | // TODO: This is slow if the changes don't effect the data!! | |||
2303 | if (update.Entry.IsFile && (update.Filename != null)) { | |||
2304 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2305 | using (Stream source = this.GetInputStream(update.Entry)) { | |||
2306 | CopyBytes(update, output, source, source.Length, true); | |||
2307 | } | |||
2308 | } | |||
2309 | } | |||
2310 | | |||
2311 | long dataEnd = workFile.baseStream_.Position; | |||
2312 | update.Entry.CompressedSize = dataEnd - dataStart; | |||
2313 | } | |||
2314 | | |||
2315 | void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) | |||
2316 | { | |||
2317 | bool skipOver = false || update.Entry.Offset == destinationPosition; | |||
2318 | | |||
2319 | if (!skipOver) { | |||
2320 | baseStream_.Position = destinationPosition; | |||
2321 | workFile.WriteLocalEntryHeader(update); | |||
2322 | destinationPosition = baseStream_.Position; | |||
2323 | } | |||
2324 | | |||
2325 | long sourcePosition = 0; | |||
2326 | | |||
2327 | const int NameLengthOffset = 26; | |||
2328 | | |||
2329 | // TODO: Add base for SFX friendly handling | |||
2330 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2331 | | |||
2332 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2333 | | |||
2334 | // Clumsy way of handling retrieving the original name and extra data length for now. | |||
2335 | // TODO: Stop re-reading name and data length in CopyEntryDirect. | |||
2336 | uint nameLength = ReadLEUshort(); | |||
2337 | uint extraLength = ReadLEUshort(); | |||
2338 | | |||
2339 | sourcePosition = baseStream_.Position + nameLength + extraLength; | |||
2340 | | |||
2341 | if (skipOver) { | |||
2342 | if (update.OffsetBasedSize != -1) | |||
2343 | destinationPosition += update.OffsetBasedSize; | |||
2344 | else | |||
2345 | // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) ar | |||
2346 | // WinZip produces a warning on these entries: | |||
2347 | // "caution: value of lrec.csize (compressed size) changed from ..." | |||
2348 | destinationPosition += | |||
2349 | (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size | |||
2350 | update.Entry.CompressedSize + GetDescriptorSize(update); | |||
2351 | } else { | |||
2352 | if (update.Entry.CompressedSize > 0) { | |||
2353 | CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); | |||
2354 | } | |||
2355 | CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); | |||
2356 | } | |||
2357 | } | |||
2358 | | |||
2359 | void CopyEntry(ZipFile workFile, ZipUpdate update) | |||
2360 | { | |||
2361 | workFile.WriteLocalEntryHeader(update); | |||
2362 | | |||
2363 | if (update.Entry.CompressedSize > 0) { | |||
2364 | const int NameLengthOffset = 26; | |||
2365 | | |||
2366 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2367 | | |||
2368 | // TODO: This wont work for SFX files! | |||
2369 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2370 | | |||
2371 | uint nameLength = ReadLEUshort(); | |||
2372 | uint extraLength = ReadLEUshort(); | |||
2373 | | |||
2374 | baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); | |||
2375 | | |||
2376 | CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); | |||
2377 | } | |||
2378 | CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); | |||
2379 | } | |||
2380 | | |||
2381 | void Reopen(Stream source) | |||
2382 | { | |||
2383 | if (source == null) { | |||
2384 | throw new ZipException("Failed to reopen archive - no source"); | |||
2385 | } | |||
2386 | | |||
2387 | isNewArchive_ = false; | |||
2388 | baseStream_ = source; | |||
2389 | ReadEntries(); | |||
2390 | } | |||
2391 | | |||
2392 | void Reopen() | |||
2393 | { | |||
2394 | if (Name == null) { | |||
2395 | throw new InvalidOperationException("Name is not known cannot Reopen"); | |||
2396 | } | |||
2397 | | |||
2398 | Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); | |||
2399 | } | |||
2400 | | |||
2401 | void UpdateCommentOnly() | |||
2402 | { | |||
2403 | long baseLength = baseStream_.Length; | |||
2404 | | |||
2405 | ZipHelperStream updateFile = null; | |||
2406 | | |||
2407 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2408 | Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); | |||
2409 | updateFile = new ZipHelperStream(copyStream); | |||
2410 | updateFile.IsStreamOwner = true; | |||
2411 | | |||
2412 | baseStream_.Close(); | |||
2413 | baseStream_ = null; | |||
2414 | } else { | |||
2415 | if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2416 | // TODO: archiveStorage wasnt originally intended for this use. | |||
2417 | // Need to revisit this to tidy up handling as archive storage currently doesnt | |||
2418 | // handle the original stream well. | |||
2419 | // The problem is when using an existing zip archive with an in memory archive storage. | |||
2420 | // The open stream wont support writing but the memory storage should open the same file not an in memory one. | |||
2421 | | |||
2422 | // Need to tidy up the archive storage interface and contract basically. | |||
2423 | baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); | |||
2424 | updateFile = new ZipHelperStream(baseStream_); | |||
2425 | } else { | |||
2426 | baseStream_.Close(); | |||
2427 | baseStream_ = null; | |||
2428 | updateFile = new ZipHelperStream(Name); | |||
2429 | } | |||
2430 | } | |||
2431 | | |||
2432 | using (updateFile) { | |||
2433 | long locatedCentralDirOffset = | |||
2434 | updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2435 | baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2436 | if (locatedCentralDirOffset < 0) { | |||
2437 | throw new ZipException("Cannot find central directory"); | |||
2438 | } | |||
2439 | | |||
2440 | const int CentralHeaderCommentSizeOffset = 16; | |||
2441 | updateFile.Position += CentralHeaderCommentSizeOffset; | |||
2442 | | |||
2443 | byte[] rawComment = newComment_.RawComment; | |||
2444 | | |||
2445 | updateFile.WriteLEShort(rawComment.Length); | |||
2446 | updateFile.Write(rawComment, 0, rawComment.Length); | |||
2447 | updateFile.SetLength(updateFile.Position); | |||
2448 | } | |||
2449 | | |||
2450 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2451 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2452 | } else { | |||
2453 | ReadEntries(); | |||
2454 | } | |||
2455 | } | |||
2456 | | |||
2457 | /// <summary> | |||
2458 | /// Class used to sort updates. | |||
2459 | /// </summary> | |||
2460 | class UpdateComparer : IComparer | |||
2461 | { | |||
2462 | /// <summary> | |||
2463 | /// Compares two objects and returns a value indicating whether one is | |||
2464 | /// less than, equal to or greater than the other. | |||
2465 | /// </summary> | |||
2466 | /// <param name="x">First object to compare</param> | |||
2467 | /// <param name="y">Second object to compare.</param> | |||
2468 | /// <returns>Compare result.</returns> | |||
2469 | public int Compare( | |||
2470 | object x, | |||
2471 | object y) | |||
2472 | { | |||
2473 | var zx = x as ZipUpdate; | |||
2474 | var zy = y as ZipUpdate; | |||
2475 | | |||
2476 | int result; | |||
2477 | | |||
2478 | if (zx == null) { | |||
2479 | if (zy == null) { | |||
2480 | result = 0; | |||
2481 | } else { | |||
2482 | result = -1; | |||
2483 | } | |||
2484 | } else if (zy == null) { | |||
2485 | result = 1; | |||
2486 | } else { | |||
2487 | int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2488 | int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2489 | | |||
2490 | result = xCmdValue - yCmdValue; | |||
2491 | if (result == 0) { | |||
2492 | long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; | |||
2493 | if (offsetDiff < 0) { | |||
2494 | result = -1; | |||
2495 | } else if (offsetDiff == 0) { | |||
2496 | result = 0; | |||
2497 | } else { | |||
2498 | result = 1; | |||
2499 | } | |||
2500 | } | |||
2501 | } | |||
2502 | return result; | |||
2503 | } | |||
2504 | } | |||
2505 | | |||
2506 | void RunUpdates() | |||
2507 | { | |||
2508 | long sizeEntries = 0; | |||
2509 | long endOfStream = 0; | |||
2510 | bool directUpdate = false; | |||
2511 | long destinationPosition = 0; // NOT SFX friendly | |||
2512 | | |||
2513 | ZipFile workFile; | |||
2514 | | |||
2515 | if (IsNewArchive) { | |||
2516 | workFile = this; | |||
2517 | workFile.baseStream_.Position = 0; | |||
2518 | directUpdate = true; | |||
2519 | } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2520 | workFile = this; | |||
2521 | workFile.baseStream_.Position = 0; | |||
2522 | directUpdate = true; | |||
2523 | | |||
2524 | // Sort the updates by offset within copies/modifies, then adds. | |||
2525 | // This ensures that data required by copies will not be overwritten. | |||
2526 | updates_.Sort(new UpdateComparer()); | |||
2527 | } else { | |||
2528 | workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); | |||
2529 | workFile.UseZip64 = UseZip64; | |||
2530 | | |||
2531 | if (key != null) { | |||
2532 | workFile.key = (byte[])key.Clone(); | |||
2533 | } | |||
2534 | } | |||
2535 | | |||
2536 | try { | |||
2537 | foreach (ZipUpdate update in updates_) { | |||
2538 | if (update != null) { | |||
2539 | switch (update.Command) { | |||
2540 | case UpdateCommand.Copy: | |||
2541 | if (directUpdate) { | |||
2542 | CopyEntryDirect(workFile, update, ref destinationPosition); | |||
2543 | } else { | |||
2544 | CopyEntry(workFile, update); | |||
2545 | } | |||
2546 | break; | |||
2547 | | |||
2548 | case UpdateCommand.Modify: | |||
2549 | // TODO: Direct modifying of an entry will take some legwork. | |||
2550 | ModifyEntry(workFile, update); | |||
2551 | break; | |||
2552 | | |||
2553 | case UpdateCommand.Add: | |||
2554 | if (!IsNewArchive && directUpdate) { | |||
2555 | workFile.baseStream_.Position = destinationPosition; | |||
2556 | } | |||
2557 | | |||
2558 | AddEntry(workFile, update); | |||
2559 | | |||
2560 | if (directUpdate) { | |||
2561 | destinationPosition = workFile.baseStream_.Position; | |||
2562 | } | |||
2563 | break; | |||
2564 | } | |||
2565 | } | |||
2566 | } | |||
2567 | | |||
2568 | if (!IsNewArchive && directUpdate) { | |||
2569 | workFile.baseStream_.Position = destinationPosition; | |||
2570 | } | |||
2571 | | |||
2572 | long centralDirOffset = workFile.baseStream_.Position; | |||
2573 | | |||
2574 | foreach (ZipUpdate update in updates_) { | |||
2575 | if (update != null) { | |||
2576 | sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); | |||
2577 | } | |||
2578 | } | |||
2579 | | |||
2580 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
2581 | using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) { | |||
2582 | zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); | |||
2583 | } | |||
2584 | | |||
2585 | endOfStream = workFile.baseStream_.Position; | |||
2586 | | |||
2587 | // And now patch entries... | |||
2588 | foreach (ZipUpdate update in updates_) { | |||
2589 | if (update != null) { | |||
2590 | // If the size of the entry is zero leave the crc as 0 as well. | |||
2591 | // The calculated crc will be all bits on... | |||
2592 | if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { | |||
2593 | workFile.baseStream_.Position = update.CrcPatchOffset; | |||
2594 | workFile.WriteLEInt((int)update.OutEntry.Crc); | |||
2595 | } | |||
2596 | | |||
2597 | if (update.SizePatchOffset > 0) { | |||
2598 | workFile.baseStream_.Position = update.SizePatchOffset; | |||
2599 | if (update.OutEntry.LocalHeaderRequiresZip64) { | |||
2600 | workFile.WriteLeLong(update.OutEntry.Size); | |||
2601 | workFile.WriteLeLong(update.OutEntry.CompressedSize); | |||
2602 | } else { | |||
2603 | workFile.WriteLEInt((int)update.OutEntry.CompressedSize); | |||
2604 | workFile.WriteLEInt((int)update.OutEntry.Size); | |||
2605 | } | |||
2606 | } | |||
2607 | } | |||
2608 | } | |||
2609 | } catch { | |||
2610 | workFile.Close(); | |||
2611 | if (!directUpdate && (workFile.Name != null)) { | |||
2612 | File.Delete(workFile.Name); | |||
2613 | } | |||
2614 | throw; | |||
2615 | } | |||
2616 | | |||
2617 | if (directUpdate) { | |||
2618 | workFile.baseStream_.SetLength(endOfStream); | |||
2619 | workFile.baseStream_.Flush(); | |||
2620 | isNewArchive_ = false; | |||
2621 | ReadEntries(); | |||
2622 | } else { | |||
2623 | baseStream_.Close(); | |||
2624 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2625 | } | |||
2626 | } | |||
2627 | | |||
2628 | void CheckUpdating() | |||
2629 | { | |||
2630 | if (updates_ == null) { | |||
2631 | throw new InvalidOperationException("BeginUpdate has not been called"); | |||
2632 | } | |||
2633 | } | |||
2634 | | |||
2635 | #endregion | |||
2636 | | |||
2637 | #region ZipUpdate class | |||
2638 | /// <summary> | |||
2639 | /// Represents a pending update to a Zip file. | |||
2640 | /// </summary> | |||
2641 | class ZipUpdate | |||
2642 | { | |||
2643 | #region Constructors | |||
2644 | public ZipUpdate(string fileName, ZipEntry entry) | |||
2645 | { | |||
2646 | command_ = UpdateCommand.Add; | |||
2647 | entry_ = entry; | |||
2648 | filename_ = fileName; | |||
2649 | } | |||
2650 | | |||
2651 | [Obsolete] | |||
2652 | public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) | |||
2653 | { | |||
2654 | command_ = UpdateCommand.Add; | |||
2655 | entry_ = new ZipEntry(entryName); | |||
2656 | entry_.CompressionMethod = compressionMethod; | |||
2657 | filename_ = fileName; | |||
2658 | } | |||
2659 | | |||
2660 | [Obsolete] | |||
2661 | public ZipUpdate(string fileName, string entryName) | |||
2662 | : this(fileName, entryName, CompressionMethod.Deflated) | |||
2663 | { | |||
2664 | // Do nothing. | |||
2665 | } | |||
2666 | | |||
2667 | [Obsolete] | |||
2668 | public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
2669 | { | |||
2670 | command_ = UpdateCommand.Add; | |||
2671 | entry_ = new ZipEntry(entryName); | |||
2672 | entry_.CompressionMethod = compressionMethod; | |||
2673 | dataSource_ = dataSource; | |||
2674 | } | |||
2675 | | |||
2676 | public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) | |||
2677 | { | |||
2678 | command_ = UpdateCommand.Add; | |||
2679 | entry_ = entry; | |||
2680 | dataSource_ = dataSource; | |||
2681 | } | |||
2682 | | |||
2683 | public ZipUpdate(ZipEntry original, ZipEntry updated) | |||
2684 | { | |||
2685 | throw new ZipException("Modify not currently supported"); | |||
2686 | /* | |||
2687 | command_ = UpdateCommand.Modify; | |||
2688 | entry_ = ( ZipEntry )original.Clone(); | |||
2689 | outEntry_ = ( ZipEntry )updated.Clone(); | |||
2690 | */ | |||
2691 | } | |||
2692 | | |||
2693 | public ZipUpdate(UpdateCommand command, ZipEntry entry) | |||
2694 | { | |||
2695 | command_ = command; | |||
2696 | entry_ = (ZipEntry)entry.Clone(); | |||
2697 | } | |||
2698 | | |||
2699 | | |||
2700 | /// <summary> | |||
2701 | /// Copy an existing entry. | |||
2702 | /// </summary> | |||
2703 | /// <param name="entry">The existing entry to copy.</param> | |||
2704 | public ZipUpdate(ZipEntry entry) | |||
2705 | : this(UpdateCommand.Copy, entry) | |||
2706 | { | |||
2707 | // Do nothing. | |||
2708 | } | |||
2709 | #endregion | |||
2710 | | |||
2711 | /// <summary> | |||
2712 | /// Get the <see cref="ZipEntry"/> for this update. | |||
2713 | /// </summary> | |||
2714 | /// <remarks>This is the source or original entry.</remarks> | |||
2715 | public ZipEntry Entry { | |||
2716 | get { return entry_; } | |||
2717 | } | |||
2718 | | |||
2719 | /// <summary> | |||
2720 | /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. | |||
2721 | /// </summary> | |||
2722 | public ZipEntry OutEntry { | |||
2723 | get { | |||
2724 | if (outEntry_ == null) { | |||
2725 | outEntry_ = (ZipEntry)entry_.Clone(); | |||
2726 | } | |||
2727 | | |||
2728 | return outEntry_; | |||
2729 | } | |||
2730 | } | |||
2731 | | |||
2732 | /// <summary> | |||
2733 | /// Get the command for this update. | |||
2734 | /// </summary> | |||
2735 | public UpdateCommand Command { | |||
2736 | get { return command_; } | |||
2737 | } | |||
2738 | | |||
2739 | /// <summary> | |||
2740 | /// Get the filename if any for this update. Null if none exists. | |||
2741 | /// </summary> | |||
2742 | public string Filename { | |||
2743 | get { return filename_; } | |||
2744 | } | |||
2745 | | |||
2746 | /// <summary> | |||
2747 | /// Get/set the location of the size patch for this update. | |||
2748 | /// </summary> | |||
2749 | public long SizePatchOffset { | |||
2750 | get { return sizePatchOffset_; } | |||
2751 | set { sizePatchOffset_ = value; } | |||
2752 | } | |||
2753 | | |||
2754 | /// <summary> | |||
2755 | /// Get /set the location of the crc patch for this update. | |||
2756 | /// </summary> | |||
2757 | public long CrcPatchOffset { | |||
2758 | get { return crcPatchOffset_; } | |||
2759 | set { crcPatchOffset_ = value; } | |||
2760 | } | |||
2761 | | |||
2762 | /// <summary> | |||
2763 | /// Get/set the size calculated by offset. | |||
2764 | /// Specifically, the difference between this and next entry's starting offset. | |||
2765 | /// </summary> | |||
2766 | public long OffsetBasedSize { | |||
2767 | get { return _offsetBasedSize; } | |||
2768 | set { _offsetBasedSize = value; } | |||
2769 | } | |||
2770 | | |||
2771 | public Stream GetSource() | |||
2772 | { | |||
2773 | Stream result = null; | |||
2774 | if (dataSource_ != null) { | |||
2775 | result = dataSource_.GetSource(); | |||
2776 | } | |||
2777 | | |||
2778 | return result; | |||
2779 | } | |||
2780 | | |||
2781 | #region Instance Fields | |||
2782 | ZipEntry entry_; | |||
2783 | ZipEntry outEntry_; | |||
2784 | UpdateCommand command_; | |||
2785 | IStaticDataSource dataSource_; | |||
2786 | string filename_; | |||
2787 | long sizePatchOffset_ = -1; | |||
2788 | long crcPatchOffset_ = -1; | |||
2789 | long _offsetBasedSize = -1; | |||
2790 | #endregion | |||
2791 | } | |||
2792 | | |||
2793 | #endregion | |||
2794 | #endregion | |||
2795 | | |||
2796 | #region Disposing | |||
2797 | | |||
2798 | #region IDisposable Members | |||
2799 | void IDisposable.Dispose() | |||
2800 | { | |||
2801 | Close(); | |||
2802 | } | |||
2803 | #endregion | |||
2804 | | |||
2805 | void DisposeInternal(bool disposing) | |||
2806 | { | |||
2807 | if (!isDisposed_) { | |||
2808 | isDisposed_ = true; | |||
2809 | entries_ = new ZipEntry[0]; | |||
2810 | | |||
2811 | if (IsStreamOwner && (baseStream_ != null)) { | |||
2812 | lock (baseStream_) { | |||
2813 | baseStream_.Close(); | |||
2814 | } | |||
2815 | } | |||
2816 | | |||
2817 | PostUpdateCleanup(); | |||
2818 | } | |||
2819 | } | |||
2820 | | |||
2821 | /// <summary> | |||
2822 | /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. | |||
2823 | /// </summary> | |||
2824 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
2825 | /// false to release only unmanaged resources.</param> | |||
2826 | protected virtual void Dispose(bool disposing) | |||
2827 | { | |||
2828 | DisposeInternal(disposing); | |||
2829 | } | |||
2830 | | |||
2831 | #endregion | |||
2832 | | |||
2833 | #region Internal routines | |||
2834 | #region Reading | |||
2835 | /// <summary> | |||
2836 | /// Read an unsigned short in little endian byte order. | |||
2837 | /// </summary> | |||
2838 | /// <returns>Returns the value read.</returns> | |||
2839 | /// <exception cref="EndOfStreamException"> | |||
2840 | /// The stream ends prematurely | |||
2841 | /// </exception> | |||
2842 | ushort ReadLEUshort() | |||
2843 | { | |||
2844 | int data1 = baseStream_.ReadByte(); | |||
2845 | | |||
2846 | if (data1 < 0) { | |||
2847 | throw new EndOfStreamException("End of stream"); | |||
2848 | } | |||
2849 | | |||
2850 | int data2 = baseStream_.ReadByte(); | |||
2851 | | |||
2852 | if (data2 < 0) { | |||
2853 | throw new EndOfStreamException("End of stream"); | |||
2854 | } | |||
2855 | | |||
2856 | | |||
2857 | return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); | |||
2858 | } | |||
2859 | | |||
2860 | /// <summary> | |||
2861 | /// Read a uint in little endian byte order. | |||
2862 | /// </summary> | |||
2863 | /// <returns>Returns the value read.</returns> | |||
2864 | /// <exception cref="IOException"> | |||
2865 | /// An i/o error occurs. | |||
2866 | /// </exception> | |||
2867 | /// <exception cref="System.IO.EndOfStreamException"> | |||
2868 | /// The file ends prematurely | |||
2869 | /// </exception> | |||
2870 | uint ReadLEUint() | |||
2871 | { | |||
2872 | return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); | |||
2873 | } | |||
2874 | | |||
2875 | ulong ReadLEUlong() | |||
2876 | { | |||
2877 | return ReadLEUint() | ((ulong)ReadLEUint() << 32); | |||
2878 | } | |||
2879 | | |||
2880 | #endregion | |||
2881 | // NOTE this returns the offset of the first byte after the signature. | |||
2882 | long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
2883 | { | |||
2884 | using (ZipHelperStream les = new ZipHelperStream(baseStream_)) { | |||
2885 | return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); | |||
2886 | } | |||
2887 | } | |||
2888 | | |||
2889 | /// <summary> | |||
2890 | /// Search for and read the central directory of a zip file filling the entries array. | |||
2891 | /// </summary> | |||
2892 | /// <exception cref="System.IO.IOException"> | |||
2893 | /// An i/o error occurs. | |||
2894 | /// </exception> | |||
2895 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
2896 | /// The central directory is malformed or cannot be found | |||
2897 | /// </exception> | |||
2898 | void ReadEntries() | |||
2899 | { | |||
2900 | // Search for the End Of Central Directory. When a zip comment is | |||
2901 | // present the directory will start earlier | |||
2902 | // | |||
2903 | // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. | |||
2904 | // This should be compatible with both SFX and ZIP files but has only been tested for Zip files | |||
2905 | // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then | |||
2906 | // this could be invalid. | |||
2907 | // Could also speed this up by reading memory in larger blocks. | |||
2908 | | |||
2909 | if (baseStream_.CanSeek == false) { | |||
2910 | throw new ZipException("ZipFile stream must be seekable"); | |||
2911 | } | |||
2912 | | |||
2913 | long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2914 | baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2915 | | |||
2916 | if (locatedEndOfCentralDir < 0) { | |||
2917 | throw new ZipException("Cannot find central directory"); | |||
2918 | } | |||
2919 | | |||
2920 | // Read end of central directory record | |||
2921 | ushort thisDiskNumber = ReadLEUshort(); | |||
2922 | ushort startCentralDirDisk = ReadLEUshort(); | |||
2923 | ulong entriesForThisDisk = ReadLEUshort(); | |||
2924 | ulong entriesForWholeCentralDir = ReadLEUshort(); | |||
2925 | ulong centralDirSize = ReadLEUint(); | |||
2926 | long offsetOfCentralDir = ReadLEUint(); | |||
2927 | uint commentSize = ReadLEUshort(); | |||
2928 | | |||
2929 | if (commentSize > 0) { | |||
2930 | byte[] comment = new byte[commentSize]; | |||
2931 | | |||
2932 | StreamUtils.ReadFully(baseStream_, comment); | |||
2933 | comment_ = ZipConstants.ConvertToString(comment); | |||
2934 | } else { | |||
2935 | comment_ = string.Empty; | |||
2936 | } | |||
2937 | | |||
2938 | bool isZip64 = false; | |||
2939 | | |||
2940 | // Check if zip64 header information is required. | |||
2941 | if ((thisDiskNumber == 0xffff) || | |||
2942 | (startCentralDirDisk == 0xffff) || | |||
2943 | (entriesForThisDisk == 0xffff) || | |||
2944 | (entriesForWholeCentralDir == 0xffff) || | |||
2945 | (centralDirSize == 0xffffffff) || | |||
2946 | (offsetOfCentralDir == 0xffffffff)) { | |||
2947 | isZip64 = true; | |||
2948 | | |||
2949 | long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, | |||
2950 | if (offset < 0) { | |||
2951 | throw new ZipException("Cannot find Zip64 locator"); | |||
2952 | } | |||
2953 | | |||
2954 | // number of the disk with the start of the zip64 end of central directory 4 bytes | |||
2955 | // relative offset of the zip64 end of central directory record 8 bytes | |||
2956 | // total number of disks 4 bytes | |||
2957 | ReadLEUint(); // startDisk64 is not currently used | |||
2958 | ulong offset64 = ReadLEUlong(); | |||
2959 | uint totalDisks = ReadLEUint(); | |||
2960 | | |||
2961 | baseStream_.Position = (long)offset64; | |||
2962 | long sig64 = ReadLEUint(); | |||
2963 | | |||
2964 | if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { | |||
2965 | throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); | |||
2966 | } | |||
2967 | | |||
2968 | // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. | |||
2969 | ulong recordSize = ReadLEUlong(); | |||
2970 | int versionMadeBy = ReadLEUshort(); | |||
2971 | int versionToExtract = ReadLEUshort(); | |||
2972 | uint thisDisk = ReadLEUint(); | |||
2973 | uint centralDirDisk = ReadLEUint(); | |||
2974 | entriesForThisDisk = ReadLEUlong(); | |||
2975 | entriesForWholeCentralDir = ReadLEUlong(); | |||
2976 | centralDirSize = ReadLEUlong(); | |||
2977 | offsetOfCentralDir = (long)ReadLEUlong(); | |||
2978 | | |||
2979 | // NOTE: zip64 extensible data sector (variable size) is ignored. | |||
2980 | } | |||
2981 | | |||
2982 | entries_ = new ZipEntry[entriesForThisDisk]; | |||
2983 | | |||
2984 | // SFX/embedded support, find the offset of the first entry vis the start of the stream | |||
2985 | // This applies to Zip files that are appended to the end of an SFX stub. | |||
2986 | // Or are appended as a resource to an executable. | |||
2987 | // Zip files created by some archivers have the offsets altered to reflect the true offsets | |||
2988 | // and so dont require any adjustment here... | |||
2989 | // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? | |||
2990 | if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) { | |||
2991 | offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); | |||
2992 | if (offsetOfFirstEntry <= 0) { | |||
2993 | throw new ZipException("Invalid embedded zip archive"); | |||
2994 | } | |||
2995 | } | |||
2996 | | |||
2997 | baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); | |||
2998 | | |||
2999 | for (ulong i = 0; i < entriesForThisDisk; i++) { | |||
3000 | if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { | |||
3001 | throw new ZipException("Wrong Central Directory signature"); | |||
3002 | } | |||
3003 | | |||
3004 | int versionMadeBy = ReadLEUshort(); | |||
3005 | int versionToExtract = ReadLEUshort(); | |||
3006 | int bitFlags = ReadLEUshort(); | |||
3007 | int method = ReadLEUshort(); | |||
3008 | uint dostime = ReadLEUint(); | |||
3009 | uint crc = ReadLEUint(); | |||
3010 | var csize = (long)ReadLEUint(); | |||
3011 | var size = (long)ReadLEUint(); | |||
3012 | int nameLen = ReadLEUshort(); | |||
3013 | int extraLen = ReadLEUshort(); | |||
3014 | int commentLen = ReadLEUshort(); | |||
3015 | | |||
3016 | int diskStartNo = ReadLEUshort(); // Not currently used | |||
3017 | int internalAttributes = ReadLEUshort(); // Not currently used | |||
3018 | | |||
3019 | uint externalAttributes = ReadLEUint(); | |||
3020 | long offset = ReadLEUint(); | |||
3021 | | |||
3022 | byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; | |||
3023 | | |||
3024 | StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); | |||
3025 | string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); | |||
3026 | | |||
3027 | var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); | |||
3028 | entry.Crc = crc & 0xffffffffL; | |||
3029 | entry.Size = size & 0xffffffffL; | |||
3030 | entry.CompressedSize = csize & 0xffffffffL; | |||
3031 | entry.Flags = bitFlags; | |||
3032 | entry.DosTime = (uint)dostime; | |||
3033 | entry.ZipFileIndex = (long)i; | |||
3034 | entry.Offset = offset; | |||
3035 | entry.ExternalFileAttributes = (int)externalAttributes; | |||
3036 | | |||
3037 | if ((bitFlags & 8) == 0) { | |||
3038 | entry.CryptoCheckValue = (byte)(crc >> 24); | |||
3039 | } else { | |||
3040 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | |||
3041 | } | |||
3042 | | |||
3043 | if (extraLen > 0) { | |||
3044 | byte[] extra = new byte[extraLen]; | |||
3045 | StreamUtils.ReadFully(baseStream_, extra); | |||
3046 | entry.ExtraData = extra; | |||
3047 | } | |||
3048 | | |||
3049 | entry.ProcessExtraData(false); | |||
3050 | | |||
3051 | if (commentLen > 0) { | |||
3052 | StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); | |||
3053 | entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); | |||
3054 | } | |||
3055 | | |||
3056 | entries_[i] = entry; | |||
3057 | } | |||
3058 | } | |||
3059 | | |||
3060 | /// <summary> | |||
3061 | /// Locate the data for a given entry. | |||
3062 | /// </summary> | |||
3063 | /// <returns> | |||
3064 | /// The start offset of the data. | |||
3065 | /// </returns> | |||
3066 | /// <exception cref="System.IO.EndOfStreamException"> | |||
3067 | /// The stream ends prematurely | |||
3068 | /// </exception> | |||
3069 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
3070 | /// The local header signature is invalid, the entry and central header file name lengths are different | |||
3071 | /// or the local and entry compression methods dont match | |||
3072 | /// </exception> | |||
3073 | long LocateEntry(ZipEntry entry) | |||
3074 | { | |||
3075 | return TestLocalHeader(entry, HeaderTest.Extract); | |||
3076 | } | |||
3077 | | |||
3078 | Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) | |||
3079 | { | |||
3080 | CryptoStream result = null; | |||
3081 | | |||
3082 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3083 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3084 | var classicManaged = new PkzipClassicManaged(); | |||
3085 | | |||
3086 | OnKeysRequired(entry.Name); | |||
3087 | if (HaveKeys == false) { | |||
3088 | throw new ZipException("No password available for encrypted stream"); | |||
3089 | } | |||
3090 | | |||
3091 | result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); | |||
3092 | CheckClassicPassword(result, entry); | |||
3093 | } else { | |||
3094 | if (entry.Version == ZipConstants.VERSION_AES) { | |||
3095 | // | |||
3096 | OnKeysRequired(entry.Name); | |||
3097 | if (HaveKeys == false) { | |||
3098 | throw new ZipException("No password available for AES encrypted stream"); | |||
3099 | } | |||
3100 | int saltLen = entry.AESSaltLen; | |||
3101 | byte[] saltBytes = new byte[saltLen]; | |||
3102 | int saltIn = baseStream.Read(saltBytes, 0, saltLen); | |||
3103 | if (saltIn != saltLen) | |||
3104 | throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); | |||
3105 | // | |||
3106 | byte[] pwdVerifyRead = new byte[2]; | |||
3107 | baseStream.Read(pwdVerifyRead, 0, 2); | |||
3108 | int blockSize = entry.AESKeySize / 8; // bits to bytes | |||
3109 | | |||
3110 | var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); | |||
3111 | byte[] pwdVerifyCalc = decryptor.PwdVerifier; | |||
3112 | if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) | |||
3113 | throw new ZipException("Invalid password for AES"); | |||
3114 | result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); | |||
3115 | } else { | |||
3116 | throw new ZipException("Decryption method not supported"); | |||
3117 | } | |||
3118 | } | |||
3119 | | |||
3120 | return result; | |||
3121 | } | |||
3122 | | |||
3123 | Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) | |||
3124 | { | |||
3125 | CryptoStream result = null; | |||
3126 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3127 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3128 | var classicManaged = new PkzipClassicManaged(); | |||
3129 | | |||
3130 | OnKeysRequired(entry.Name); | |||
3131 | if (HaveKeys == false) { | |||
3132 | throw new ZipException("No password available for encrypted stream"); | |||
3133 | } | |||
3134 | | |||
3135 | // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream | |||
3136 | // which doesnt do this. | |||
3137 | result = new CryptoStream(new UncompressedStream(baseStream), | |||
3138 | classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); | |||
3139 | | |||
3140 | if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { | |||
3141 | WriteEncryptionHeader(result, entry.DosTime << 16); | |||
3142 | } else { | |||
3143 | WriteEncryptionHeader(result, entry.Crc); | |||
3144 | } | |||
3145 | } | |||
3146 | return result; | |||
3147 | } | |||
3148 | | |||
3149 | static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) | |||
3150 | { | |||
3151 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3152 | StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); | |||
3153 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | |||
3154 | throw new ZipException("Invalid password"); | |||
3155 | } | |||
3156 | } | |||
3157 | | |||
3158 | static void WriteEncryptionHeader(Stream stream, long crcValue) | |||
3159 | { | |||
3160 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3161 | var rnd = new Random(); | |||
3162 | rnd.NextBytes(cryptBuffer); | |||
3163 | cryptBuffer[11] = (byte)(crcValue >> 24); | |||
3164 | stream.Write(cryptBuffer, 0, cryptBuffer.Length); | |||
3165 | } | |||
3166 | | |||
3167 | #endregion | |||
3168 | | |||
3169 | #region Instance Fields | |||
3170 | bool isDisposed_; | |||
3171 | string name_; | |||
3172 | string comment_; | |||
3173 | string rawPassword_; | |||
3174 | Stream baseStream_; | |||
3175 | bool isStreamOwner; | |||
3176 | long offsetOfFirstEntry; | |||
3177 | ZipEntry[] entries_; | |||
3178 | byte[] key; | |||
3179 | bool isNewArchive_; | |||
3180 | | |||
3181 | // Default is dynamic which is not backwards compatible and can cause problems | |||
3182 | // with XP's built in compression which cant read Zip64 archives. | |||
3183 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
3184 | // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. | |||
3185 | UseZip64 useZip64_ = UseZip64.Dynamic; | |||
3186 | | |||
3187 | #region Zip Update Instance Fields | |||
3188 | ArrayList updates_; | |||
3189 | long updateCount_; // Count is managed manually as updates_ can contain nulls! | |||
3190 | Hashtable updateIndex_; | |||
3191 | IArchiveStorage archiveStorage_; | |||
3192 | IDynamicDataSource updateDataSource_; | |||
3193 | bool contentsEdited_; | |||
3194 | int bufferSize_ = DefaultBufferSize; | |||
3195 | byte[] copyBuffer_; | |||
3196 | ZipString newComment_; | |||
3197 | bool commentEdited_; | |||
3198 | IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); | |||
3199 | #endregion | |||
3200 | #endregion | |||
3201 | | |||
3202 | #region Support Classes | |||
3203 | /// <summary> | |||
3204 | /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. | |||
3205 | /// </summary> | |||
3206 | class ZipString | |||
3207 | { | |||
3208 | #region Constructors | |||
3209 | /// <summary> | |||
3210 | /// Initialise a <see cref="ZipString"/> with a string. | |||
3211 | /// </summary> | |||
3212 | /// <param name="comment">The textual string form.</param> | |||
3213 | public ZipString(string comment) | |||
3214 | { | |||
3215 | comment_ = comment; | |||
3216 | isSourceString_ = true; | |||
3217 | } | |||
3218 | | |||
3219 | /// <summary> | |||
3220 | /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. | |||
3221 | /// </summary> | |||
3222 | /// <param name="rawString"></param> | |||
3223 | public ZipString(byte[] rawString) | |||
3224 | { | |||
3225 | rawComment_ = rawString; | |||
3226 | } | |||
3227 | #endregion | |||
3228 | | |||
3229 | /// <summary> | |||
3230 | /// Get a value indicating the original source of data for this instance. | |||
3231 | /// True if the source was a string; false if the source was binary data. | |||
3232 | /// </summary> | |||
3233 | public bool IsSourceString { | |||
3234 | get { return isSourceString_; } | |||
3235 | } | |||
3236 | | |||
3237 | /// <summary> | |||
3238 | /// Get the length of the comment when represented as raw bytes. | |||
3239 | /// </summary> | |||
3240 | public int RawLength { | |||
3241 | get { | |||
3242 | MakeBytesAvailable(); | |||
3243 | return rawComment_.Length; | |||
3244 | } | |||
3245 | } | |||
3246 | | |||
3247 | /// <summary> | |||
3248 | /// Get the comment in its 'raw' form as plain bytes. | |||
3249 | /// </summary> | |||
3250 | public byte[] RawComment { | |||
3251 | get { | |||
3252 | MakeBytesAvailable(); | |||
3253 | return (byte[])rawComment_.Clone(); | |||
3254 | } | |||
3255 | } | |||
3256 | | |||
3257 | /// <summary> | |||
3258 | /// Reset the comment to its initial state. | |||
3259 | /// </summary> | |||
3260 | public void Reset() | |||
3261 | { | |||
3262 | if (isSourceString_) { | |||
3263 | rawComment_ = null; | |||
3264 | } else { | |||
3265 | comment_ = null; | |||
3266 | } | |||
3267 | } | |||
3268 | | |||
3269 | void MakeTextAvailable() | |||
3270 | { | |||
3271 | if (comment_ == null) { | |||
3272 | comment_ = ZipConstants.ConvertToString(rawComment_); | |||
3273 | } | |||
3274 | } | |||
3275 | | |||
3276 | void MakeBytesAvailable() | |||
3277 | { | |||
3278 | if (rawComment_ == null) { | |||
3279 | rawComment_ = ZipConstants.ConvertToArray(comment_); | |||
3280 | } | |||
3281 | } | |||
3282 | | |||
3283 | /// <summary> | |||
3284 | /// Implicit conversion of comment to a string. | |||
3285 | /// </summary> | |||
3286 | /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> | |||
3287 | /// <returns>The textual equivalent for the input value.</returns> | |||
3288 | static public implicit operator string(ZipString zipString) | |||
3289 | { | |||
3290 | zipString.MakeTextAvailable(); | |||
3291 | return zipString.comment_; | |||
3292 | } | |||
3293 | | |||
3294 | #region Instance Fields | |||
3295 | string comment_; | |||
3296 | byte[] rawComment_; | |||
3297 | bool isSourceString_; | |||
3298 | #endregion | |||
3299 | } | |||
3300 | | |||
3301 | /// <summary> | |||
3302 | /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> | |||
3303 | /// </summary> | |||
3304 | class ZipEntryEnumerator : IEnumerator | |||
3305 | { | |||
3306 | #region Constructors | |||
3307 | public ZipEntryEnumerator(ZipEntry[] entries) | |||
3308 | { | |||
3309 | array = entries; | |||
3310 | } | |||
3311 | | |||
3312 | #endregion | |||
3313 | #region IEnumerator Members | |||
3314 | public object Current { | |||
3315 | get { | |||
3316 | return array[index]; | |||
3317 | } | |||
3318 | } | |||
3319 | | |||
3320 | public void Reset() | |||
3321 | { | |||
3322 | index = -1; | |||
3323 | } | |||
3324 | | |||
3325 | public bool MoveNext() | |||
3326 | { | |||
3327 | return (++index < array.Length); | |||
3328 | } | |||
3329 | #endregion | |||
3330 | #region Instance Fields | |||
3331 | ZipEntry[] array; | |||
3332 | int index = -1; | |||
3333 | #endregion | |||
3334 | } | |||
3335 | | |||
3336 | /// <summary> | |||
3337 | /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data | |||
3338 | /// to and flush, but cannot read, seek or do anything else to. | |||
3339 | /// </summary> | |||
3340 | class UncompressedStream : Stream | |||
3341 | { | |||
3342 | #region Constructors | |||
3343 | public UncompressedStream(Stream baseStream) | |||
3344 | { | |||
3345 | baseStream_ = baseStream; | |||
3346 | } | |||
3347 | | |||
3348 | #endregion | |||
3349 | | |||
3350 | /// <summary> | |||
3351 | /// Close this stream instance. | |||
3352 | /// </summary> | |||
3353 | public override void Close() | |||
3354 | { | |||
3355 | // Do nothing | |||
3356 | } | |||
3357 | | |||
3358 | /// <summary> | |||
3359 | /// Gets a value indicating whether the current stream supports reading. | |||
3360 | /// </summary> | |||
3361 | public override bool CanRead { | |||
3362 | get { | |||
3363 | return false; | |||
3364 | } | |||
3365 | } | |||
3366 | | |||
3367 | /// <summary> | |||
3368 | /// Write any buffered data to underlying storage. | |||
3369 | /// </summary> | |||
3370 | public override void Flush() | |||
3371 | { | |||
3372 | baseStream_.Flush(); | |||
3373 | } | |||
3374 | | |||
3375 | /// <summary> | |||
3376 | /// Gets a value indicating whether the current stream supports writing. | |||
3377 | /// </summary> | |||
3378 | public override bool CanWrite { | |||
3379 | get { | |||
3380 | return baseStream_.CanWrite; | |||
3381 | } | |||
3382 | } | |||
3383 | | |||
3384 | /// <summary> | |||
3385 | /// Gets a value indicating whether the current stream supports seeking. | |||
3386 | /// </summary> | |||
3387 | public override bool CanSeek { | |||
3388 | get { | |||
3389 | return false; | |||
3390 | } | |||
3391 | } | |||
3392 | | |||
3393 | /// <summary> | |||
3394 | /// Get the length in bytes of the stream. | |||
3395 | /// </summary> | |||
3396 | public override long Length { | |||
3397 | get { | |||
3398 | return 0; | |||
3399 | } | |||
3400 | } | |||
3401 | | |||
3402 | /// <summary> | |||
3403 | /// Gets or sets the position within the current stream. | |||
3404 | /// </summary> | |||
3405 | public override long Position { | |||
3406 | get { | |||
3407 | return baseStream_.Position; | |||
3408 | } | |||
3409 | set { | |||
3410 | throw new NotImplementedException(); | |||
3411 | } | |||
3412 | } | |||
3413 | | |||
3414 | /// <summary> | |||
3415 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3416 | /// </summary> | |||
3417 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3418 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3419 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3420 | /// <returns> | |||
3421 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3422 | /// </returns> | |||
3423 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3424 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3425 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3426 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3427 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3428 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3429 | public override int Read(byte[] buffer, int offset, int count) | |||
3430 | { | |||
3431 | return 0; | |||
3432 | } | |||
3433 | | |||
3434 | /// <summary> | |||
3435 | /// Sets the position within the current stream. | |||
3436 | /// </summary> | |||
3437 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3438 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3439 | /// <returns> | |||
3440 | /// The new position within the current stream. | |||
3441 | /// </returns> | |||
3442 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3443 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3444 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3445 | public override long Seek(long offset, SeekOrigin origin) | |||
3446 | { | |||
3447 | return 0; | |||
3448 | } | |||
3449 | | |||
3450 | /// <summary> | |||
3451 | /// Sets the length of the current stream. | |||
3452 | /// </summary> | |||
3453 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3454 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3455 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3456 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3457 | public override void SetLength(long value) | |||
3458 | { | |||
3459 | } | |||
3460 | | |||
3461 | /// <summary> | |||
3462 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3463 | /// </summary> | |||
3464 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3465 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3466 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3467 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3468 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3469 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3470 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3471 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3472 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3473 | public override void Write(byte[] buffer, int offset, int count) | |||
3474 | { | |||
3475 | baseStream_.Write(buffer, offset, count); | |||
3476 | } | |||
3477 | | |||
3478 | readonly | |||
3479 | | |||
3480 | #region Instance Fields | |||
3481 | Stream baseStream_; | |||
3482 | #endregion | |||
3483 | } | |||
3484 | | |||
3485 | /// <summary> | |||
3486 | /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> | |||
3487 | /// whose data is only a part or subsection of a file. | |||
3488 | /// </summary> | |||
3489 | class PartialInputStream : Stream | |||
3490 | { | |||
3491 | #region Constructors | |||
3492 | /// <summary> | |||
3493 | /// Initialise a new instance of the <see cref="PartialInputStream"/> class. | |||
3494 | /// </summary> | |||
3495 | /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param> | |||
3496 | /// <param name="start">The start of the partial data.</param> | |||
3497 | /// <param name="length">The length of the partial data.</param> | |||
3498 | public PartialInputStream(ZipFile zipFile, long start, long length) | |||
3499 | { | |||
3500 | start_ = start; | |||
3501 | length_ = length; | |||
3502 | | |||
3503 | // Although this is the only time the zipfile is used | |||
3504 | // keeping a reference here prevents premature closure of | |||
3505 | // this zip file and thus the baseStream_. | |||
3506 | | |||
3507 | // Code like this will cause apparently random failures depending | |||
3508 | // on the size of the files and when garbage is collected. | |||
3509 | // | |||
3510 | // ZipFile z = new ZipFile (stream); | |||
3511 | // Stream reader = z.GetInputStream(0); | |||
3512 | // uses reader here.... | |||
3513 | zipFile_ = zipFile; | |||
3514 | baseStream_ = zipFile_.baseStream_; | |||
3515 | readPos_ = start; | |||
3516 | end_ = start + length; | |||
3517 | } | |||
3518 | #endregion | |||
3519 | | |||
3520 | /// <summary> | |||
3521 | /// Read a byte from this stream. | |||
3522 | /// </summary> | |||
3523 | /// <returns>Returns the byte read or -1 on end of stream.</returns> | |||
3524 | public override int ReadByte() | |||
3525 | { | |||
3526 | if (readPos_ >= end_) { | |||
3527 | // -1 is the correct value at end of stream. | |||
3528 | return -1; | |||
3529 | } | |||
3530 | | |||
3531 | lock (baseStream_) { | |||
3532 | baseStream_.Seek(readPos_++, SeekOrigin.Begin); | |||
3533 | return baseStream_.ReadByte(); | |||
3534 | } | |||
3535 | } | |||
3536 | | |||
3537 | /// <summary> | |||
3538 | /// Close this <see cref="PartialInputStream">partial input stream</see>. | |||
3539 | /// </summary> | |||
3540 | /// <remarks> | |||
3541 | /// The underlying stream is not closed. Close the parent ZipFile class to do that. | |||
3542 | /// </remarks> | |||
3543 | public override void Close() | |||
3544 | { | |||
3545 | // Do nothing at all! | |||
3546 | } | |||
3547 | | |||
3548 | /// <summary> | |||
3549 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3550 | /// </summary> | |||
3551 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3552 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3553 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3554 | /// <returns> | |||
3555 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3556 | /// </returns> | |||
3557 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3558 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3559 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3560 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3561 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3562 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3563 | public override int Read(byte[] buffer, int offset, int count) | |||
3564 | { | |||
3565 | lock (baseStream_) { | |||
3566 | if (count > end_ - readPos_) { | |||
3567 | count = (int)(end_ - readPos_); | |||
3568 | if (count == 0) { | |||
3569 | return 0; | |||
3570 | } | |||
3571 | } | |||
3572 | // Protect against Stream implementations that throw away their buffer on every Seek | |||
3573 | // (for example, Mono FileStream) | |||
3574 | if (baseStream_.Position != readPos_) { | |||
3575 | baseStream_.Seek(readPos_, SeekOrigin.Begin); | |||
3576 | } | |||
3577 | int readCount = baseStream_.Read(buffer, offset, count); | |||
3578 | if (readCount > 0) { | |||
3579 | readPos_ += readCount; | |||
3580 | } | |||
3581 | return readCount; | |||
3582 | } | |||
3583 | } | |||
3584 | | |||
3585 | /// <summary> | |||
3586 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3587 | /// </summary> | |||
3588 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3589 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3590 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3591 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3592 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3593 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3594 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3595 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3596 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3597 | public override void Write(byte[] buffer, int offset, int count) | |||
3598 | { | |||
3599 | throw new NotSupportedException(); | |||
3600 | } | |||
3601 | | |||
3602 | /// <summary> | |||
3603 | /// When overridden in a derived class, sets the length of the current stream. | |||
3604 | /// </summary> | |||
3605 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3606 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3607 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3608 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3609 | public override void SetLength(long value) | |||
3610 | { | |||
3611 | throw new NotSupportedException(); | |||
3612 | } | |||
3613 | | |||
3614 | /// <summary> | |||
3615 | /// When overridden in a derived class, sets the position within the current stream. | |||
3616 | /// </summary> | |||
3617 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3618 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3619 | /// <returns> | |||
3620 | /// The new position within the current stream. | |||
3621 | /// </returns> | |||
3622 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3623 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3624 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3625 | public override long Seek(long offset, SeekOrigin origin) | |||
3626 | { | |||
3627 | long newPos = readPos_; | |||
3628 | | |||
3629 | switch (origin) { | |||
3630 | case SeekOrigin.Begin: | |||
3631 | newPos = start_ + offset; | |||
3632 | break; | |||
3633 | | |||
3634 | case SeekOrigin.Current: | |||
3635 | newPos = readPos_ + offset; | |||
3636 | break; | |||
3637 | | |||
3638 | case SeekOrigin.End: | |||
3639 | newPos = end_ + offset; | |||
3640 | break; | |||
3641 | } | |||
3642 | | |||
3643 | if (newPos < start_) { | |||
3644 | throw new ArgumentException("Negative position is invalid"); | |||
3645 | } | |||
3646 | | |||
3647 | if (newPos >= end_) { | |||
3648 | throw new IOException("Cannot seek past end"); | |||
3649 | } | |||
3650 | readPos_ = newPos; | |||
3651 | return readPos_; | |||
3652 | } | |||
3653 | | |||
3654 | /// <summary> | |||
3655 | /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | |||
3656 | /// </summary> | |||
3657 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3658 | public override void Flush() | |||
3659 | { | |||
3660 | // Nothing to do. | |||
3661 | } | |||
3662 | | |||
3663 | /// <summary> | |||
3664 | /// Gets or sets the position within the current stream. | |||
3665 | /// </summary> | |||
3666 | /// <value></value> | |||
3667 | /// <returns>The current position within the stream.</returns> | |||
3668 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3669 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> | |||
3670 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3671 | public override long Position { | |||
3672 | get { return readPos_ - start_; } | |||
3673 | set { | |||
3674 | long newPos = start_ + value; | |||
3675 | | |||
3676 | if (newPos < start_) { | |||
3677 | throw new ArgumentException("Negative position is invalid"); | |||
3678 | } | |||
3679 | | |||
3680 | if (newPos >= end_) { | |||
3681 | throw new InvalidOperationException("Cannot seek past end"); | |||
3682 | } | |||
3683 | readPos_ = newPos; | |||
3684 | } | |||
3685 | } | |||
3686 | | |||
3687 | /// <summary> | |||
3688 | /// Gets the length in bytes of the stream. | |||
3689 | /// </summary> | |||
3690 | /// <value></value> | |||
3691 | /// <returns>A long value representing the length of the stream in bytes.</returns> | |||
3692 | /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </excep | |||
3693 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3694 | public override long Length { | |||
3695 | get { return length_; } | |||
3696 | } | |||
3697 | | |||
3698 | /// <summary> | |||
3699 | /// Gets a value indicating whether the current stream supports writing. | |||
3700 | /// </summary> | |||
3701 | /// <value>false</value> | |||
3702 | /// <returns>true if the stream supports writing; otherwise, false.</returns> | |||
3703 | public override bool CanWrite { | |||
3704 | get { return false; } | |||
3705 | } | |||
3706 | | |||
3707 | /// <summary> | |||
3708 | /// Gets a value indicating whether the current stream supports seeking. | |||
3709 | /// </summary> | |||
3710 | /// <value>true</value> | |||
3711 | /// <returns>true if the stream supports seeking; otherwise, false.</returns> | |||
3712 | public override bool CanSeek { | |||
3713 | get { return true; } | |||
3714 | } | |||
3715 | | |||
3716 | /// <summary> | |||
3717 | /// Gets a value indicating whether the current stream supports reading. | |||
3718 | /// </summary> | |||
3719 | /// <value>true.</value> | |||
3720 | /// <returns>true if the stream supports reading; otherwise, false.</returns> | |||
3721 | public override bool CanRead { | |||
3722 | get { return true; } | |||
3723 | } | |||
3724 | | |||
3725 | /// <summary> | |||
3726 | /// Gets a value that determines whether the current stream can time out. | |||
3727 | /// </summary> | |||
3728 | /// <value></value> | |||
3729 | /// <returns>A value that determines whether the current stream can time out.</returns> | |||
3730 | public override bool CanTimeout { | |||
3731 | get { return baseStream_.CanTimeout; } | |||
3732 | } | |||
3733 | #region Instance Fields | |||
3734 | ZipFile zipFile_; | |||
3735 | Stream baseStream_; | |||
3736 | long start_; | |||
3737 | long length_; | |||
3738 | long readPos_; | |||
3739 | long end_; | |||
3740 | #endregion | |||
3741 | } | |||
3742 | #endregion | |||
3743 | } | |||
3744 | | |||
3745 | #endregion | |||
3746 | | |||
3747 | #region DataSources | |||
3748 | /// <summary> | |||
3749 | /// Provides a static way to obtain a source of data for an entry. | |||
3750 | /// </summary> | |||
3751 | public interface IStaticDataSource | |||
3752 | { | |||
3753 | /// <summary> | |||
3754 | /// Get a source of data by creating a new stream. | |||
3755 | /// </summary> | |||
3756 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3757 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3758 | Stream GetSource(); | |||
3759 | } | |||
3760 | | |||
3761 | /// <summary> | |||
3762 | /// Represents a source of data that can dynamically provide | |||
3763 | /// multiple <see cref="Stream">data sources</see> based on the parameters passed. | |||
3764 | /// </summary> | |||
3765 | public interface IDynamicDataSource | |||
3766 | { | |||
3767 | /// <summary> | |||
3768 | /// Get a data source. | |||
3769 | /// </summary> | |||
3770 | /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> | |||
3771 | /// <param name="name">The name for data if known.</param> | |||
3772 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3773 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3774 | Stream GetSource(ZipEntry entry, string name); | |||
3775 | } | |||
3776 | | |||
3777 | /// <summary> | |||
3778 | /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. | |||
3779 | /// </summary> | |||
3780 | public class StaticDiskDataSource : IStaticDataSource | |||
3781 | { | |||
3782 | /// <summary> | |||
3783 | /// Initialise a new instnace of <see cref="StaticDiskDataSource"/> | |||
3784 | /// </summary> | |||
3785 | /// <param name="fileName">The name of the file to obtain data from.</param> | |||
| 0 | 3786 | public StaticDiskDataSource(string fileName) | ||
3787 | { | |||
| 0 | 3788 | fileName_ = fileName; | ||
| 0 | 3789 | } | ||
3790 | | |||
3791 | #region IDataSource Members | |||
3792 | | |||
3793 | /// <summary> | |||
3794 | /// Get a <see cref="Stream"/> providing data. | |||
3795 | /// </summary> | |||
3796 | /// <returns>Returns a <see cref="Stream"/> provising data.</returns> | |||
3797 | public Stream GetSource() | |||
3798 | { | |||
| 0 | 3799 | return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
3800 | } | |||
3801 | | |||
3802 | readonly | |||
3803 | | |||
3804 | #endregion | |||
3805 | #region Instance Fields | |||
3806 | string fileName_; | |||
3807 | #endregion | |||
3808 | } | |||
3809 | | |||
3810 | | |||
3811 | /// <summary> | |||
3812 | /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. | |||
3813 | /// </summary> | |||
3814 | public class DynamicDiskDataSource : IDynamicDataSource | |||
3815 | { | |||
3816 | | |||
3817 | #region IDataSource Members | |||
3818 | /// <summary> | |||
3819 | /// Get a <see cref="Stream"/> providing data for an entry. | |||
3820 | /// </summary> | |||
3821 | /// <param name="entry">The entry to provide data for.</param> | |||
3822 | /// <param name="name">The file name for data if known.</param> | |||
3823 | /// <returns>Returns a stream providing data; or null if not available</returns> | |||
3824 | public Stream GetSource(ZipEntry entry, string name) | |||
3825 | { | |||
3826 | Stream result = null; | |||
3827 | | |||
3828 | if (name != null) { | |||
3829 | result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3830 | } | |||
3831 | | |||
3832 | return result; | |||
3833 | } | |||
3834 | | |||
3835 | #endregion | |||
3836 | } | |||
3837 | | |||
3838 | #endregion | |||
3839 | | |||
3840 | #region Archive Storage | |||
3841 | /// <summary> | |||
3842 | /// Defines facilities for data storage when updating Zip Archives. | |||
3843 | /// </summary> | |||
3844 | public interface IArchiveStorage | |||
3845 | { | |||
3846 | /// <summary> | |||
3847 | /// Get the <see cref="FileUpdateMode"/> to apply during updates. | |||
3848 | /// </summary> | |||
3849 | FileUpdateMode UpdateMode { get; } | |||
3850 | | |||
3851 | /// <summary> | |||
3852 | /// Get an empty <see cref="Stream"/> that can be used for temporary output. | |||
3853 | /// </summary> | |||
3854 | /// <returns>Returns a temporary output <see cref="Stream"/></returns> | |||
3855 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3856 | Stream GetTemporaryOutput(); | |||
3857 | | |||
3858 | /// <summary> | |||
3859 | /// Convert a temporary output stream to a final stream. | |||
3860 | /// </summary> | |||
3861 | /// <returns>The resulting final <see cref="Stream"/></returns> | |||
3862 | /// <seealso cref="GetTemporaryOutput"/> | |||
3863 | Stream ConvertTemporaryToFinal(); | |||
3864 | | |||
3865 | /// <summary> | |||
3866 | /// Make a temporary copy of the original stream. | |||
3867 | /// </summary> | |||
3868 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
3869 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3870 | Stream MakeTemporaryCopy(Stream stream); | |||
3871 | | |||
3872 | /// <summary> | |||
3873 | /// Return a stream suitable for performing direct updates on the original source. | |||
3874 | /// </summary> | |||
3875 | /// <param name="stream">The current stream.</param> | |||
3876 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3877 | /// <remarks>This may be the current stream passed.</remarks> | |||
3878 | Stream OpenForDirectUpdate(Stream stream); | |||
3879 | | |||
3880 | /// <summary> | |||
3881 | /// Dispose of this instance. | |||
3882 | /// </summary> | |||
3883 | void Dispose(); | |||
3884 | } | |||
3885 | | |||
3886 | /// <summary> | |||
3887 | /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. | |||
3888 | /// </summary> | |||
3889 | abstract public class BaseArchiveStorage : IArchiveStorage | |||
3890 | { | |||
3891 | #region Constructors | |||
3892 | /// <summary> | |||
3893 | /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. | |||
3894 | /// </summary> | |||
3895 | /// <param name="updateMode">The update mode.</param> | |||
3896 | protected BaseArchiveStorage(FileUpdateMode updateMode) | |||
3897 | { | |||
3898 | updateMode_ = updateMode; | |||
3899 | } | |||
3900 | #endregion | |||
3901 | | |||
3902 | #region IArchiveStorage Members | |||
3903 | | |||
3904 | /// <summary> | |||
3905 | /// Gets a temporary output <see cref="Stream"/> | |||
3906 | /// </summary> | |||
3907 | /// <returns>Returns the temporary output stream.</returns> | |||
3908 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3909 | public abstract Stream GetTemporaryOutput(); | |||
3910 | | |||
3911 | /// <summary> | |||
3912 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
3913 | /// </summary> | |||
3914 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
3915 | /// the final storage for the archive.</returns> | |||
3916 | /// <seealso cref="GetTemporaryOutput"/> | |||
3917 | public abstract Stream ConvertTemporaryToFinal(); | |||
3918 | | |||
3919 | /// <summary> | |||
3920 | /// Make a temporary copy of a <see cref="Stream"/>. | |||
3921 | /// </summary> | |||
3922 | /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> | |||
3923 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3924 | public abstract Stream MakeTemporaryCopy(Stream stream); | |||
3925 | | |||
3926 | /// <summary> | |||
3927 | /// Return a stream suitable for performing direct updates on the original source. | |||
3928 | /// </summary> | |||
3929 | /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> | |||
3930 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3931 | public abstract Stream OpenForDirectUpdate(Stream stream); | |||
3932 | | |||
3933 | /// <summary> | |||
3934 | /// Disposes this instance. | |||
3935 | /// </summary> | |||
3936 | public abstract void Dispose(); | |||
3937 | | |||
3938 | /// <summary> | |||
3939 | /// Gets the update mode applicable. | |||
3940 | /// </summary> | |||
3941 | /// <value>The update mode.</value> | |||
3942 | public FileUpdateMode UpdateMode { | |||
3943 | get { | |||
3944 | return updateMode_; | |||
3945 | } | |||
3946 | } | |||
3947 | | |||
3948 | #endregion | |||
3949 | | |||
3950 | #region Instance Fields | |||
3951 | FileUpdateMode updateMode_; | |||
3952 | #endregion | |||
3953 | } | |||
3954 | | |||
3955 | /// <summary> | |||
3956 | /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. | |||
3957 | /// </summary> | |||
3958 | public class DiskArchiveStorage : BaseArchiveStorage | |||
3959 | { | |||
3960 | #region Constructors | |||
3961 | /// <summary> | |||
3962 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3963 | /// </summary> | |||
3964 | /// <param name="file">The file.</param> | |||
3965 | /// <param name="updateMode">The update mode.</param> | |||
3966 | public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) | |||
3967 | : base(updateMode) | |||
3968 | { | |||
3969 | if (file.Name == null) { | |||
3970 | throw new ZipException("Cant handle non file archives"); | |||
3971 | } | |||
3972 | | |||
3973 | fileName_ = file.Name; | |||
3974 | } | |||
3975 | | |||
3976 | /// <summary> | |||
3977 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3978 | /// </summary> | |||
3979 | /// <param name="file">The file.</param> | |||
3980 | public DiskArchiveStorage(ZipFile file) | |||
3981 | : this(file, FileUpdateMode.Safe) | |||
3982 | { | |||
3983 | } | |||
3984 | #endregion | |||
3985 | | |||
3986 | #region IArchiveStorage Members | |||
3987 | | |||
3988 | /// <summary> | |||
3989 | /// Gets a temporary output <see cref="Stream"/> for performing updates on. | |||
3990 | /// </summary> | |||
3991 | /// <returns>Returns the temporary output stream.</returns> | |||
3992 | public override Stream GetTemporaryOutput() | |||
3993 | { | |||
3994 | if (temporaryName_ != null) { | |||
3995 | temporaryName_ = GetTempFileName(temporaryName_, true); | |||
3996 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
3997 | } else { | |||
3998 | // Determine where to place files based on internal strategy. | |||
3999 | // Currently this is always done in system temp directory. | |||
4000 | temporaryName_ = Path.GetTempFileName(); | |||
4001 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
4002 | } | |||
4003 | | |||
4004 | return temporaryStream_; | |||
4005 | } | |||
4006 | | |||
4007 | /// <summary> | |||
4008 | /// Converts a temporary <see cref="Stream"/> to its final form. | |||
4009 | /// </summary> | |||
4010 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4011 | /// the final storage for the archive.</returns> | |||
4012 | public override Stream ConvertTemporaryToFinal() | |||
4013 | { | |||
4014 | if (temporaryStream_ == null) { | |||
4015 | throw new ZipException("No temporary stream has been created"); | |||
4016 | } | |||
4017 | | |||
4018 | Stream result = null; | |||
4019 | | |||
4020 | string moveTempName = GetTempFileName(fileName_, false); | |||
4021 | bool newFileCreated = false; | |||
4022 | | |||
4023 | try { | |||
4024 | temporaryStream_.Close(); | |||
4025 | File.Move(fileName_, moveTempName); | |||
4026 | File.Move(temporaryName_, fileName_); | |||
4027 | newFileCreated = true; | |||
4028 | File.Delete(moveTempName); | |||
4029 | | |||
4030 | result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
4031 | } catch (Exception) { | |||
4032 | result = null; | |||
4033 | | |||
4034 | // Try to roll back changes... | |||
4035 | if (!newFileCreated) { | |||
4036 | File.Move(moveTempName, fileName_); | |||
4037 | File.Delete(temporaryName_); | |||
4038 | } | |||
4039 | | |||
4040 | throw; | |||
4041 | } | |||
4042 | | |||
4043 | return result; | |||
4044 | } | |||
4045 | | |||
4046 | /// <summary> | |||
4047 | /// Make a temporary copy of a stream. | |||
4048 | /// </summary> | |||
4049 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4050 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4051 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4052 | { | |||
4053 | stream.Close(); | |||
4054 | | |||
4055 | temporaryName_ = GetTempFileName(fileName_, true); | |||
4056 | File.Copy(fileName_, temporaryName_, true); | |||
4057 | | |||
4058 | temporaryStream_ = new FileStream(temporaryName_, | |||
4059 | FileMode.Open, | |||
4060 | FileAccess.ReadWrite); | |||
4061 | return temporaryStream_; | |||
4062 | } | |||
4063 | | |||
4064 | /// <summary> | |||
4065 | /// Return a stream suitable for performing direct updates on the original source. | |||
4066 | /// </summary> | |||
4067 | /// <param name="stream">The current stream.</param> | |||
4068 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4069 | /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks> | |||
4070 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4071 | { | |||
4072 | Stream result; | |||
4073 | if ((stream == null) || !stream.CanWrite) { | |||
4074 | if (stream != null) { | |||
4075 | stream.Close(); | |||
4076 | } | |||
4077 | | |||
4078 | result = new FileStream(fileName_, | |||
4079 | FileMode.Open, | |||
4080 | FileAccess.ReadWrite); | |||
4081 | } else { | |||
4082 | result = stream; | |||
4083 | } | |||
4084 | | |||
4085 | return result; | |||
4086 | } | |||
4087 | | |||
4088 | /// <summary> | |||
4089 | /// Disposes this instance. | |||
4090 | /// </summary> | |||
4091 | public override void Dispose() | |||
4092 | { | |||
4093 | if (temporaryStream_ != null) { | |||
4094 | temporaryStream_.Close(); | |||
4095 | } | |||
4096 | } | |||
4097 | | |||
4098 | #endregion | |||
4099 | | |||
4100 | #region Internal routines | |||
4101 | static string GetTempFileName(string original, bool makeTempFile) | |||
4102 | { | |||
4103 | string result = null; | |||
4104 | | |||
4105 | if (original == null) { | |||
4106 | result = Path.GetTempFileName(); | |||
4107 | } else { | |||
4108 | int counter = 0; | |||
4109 | int suffixSeed = DateTime.Now.Second; | |||
4110 | | |||
4111 | while (result == null) { | |||
4112 | counter += 1; | |||
4113 | string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); | |||
4114 | if (!File.Exists(newName)) { | |||
4115 | if (makeTempFile) { | |||
4116 | try { | |||
4117 | // Try and create the file. | |||
4118 | using (FileStream stream = File.Create(newName)) { | |||
4119 | } | |||
4120 | result = newName; | |||
4121 | } catch { | |||
4122 | suffixSeed = DateTime.Now.Second; | |||
4123 | } | |||
4124 | } else { | |||
4125 | result = newName; | |||
4126 | } | |||
4127 | } | |||
4128 | } | |||
4129 | } | |||
4130 | return result; | |||
4131 | } | |||
4132 | #endregion | |||
4133 | | |||
4134 | #region Instance Fields | |||
4135 | Stream temporaryStream_; | |||
4136 | string fileName_; | |||
4137 | string temporaryName_; | |||
4138 | #endregion | |||
4139 | } | |||
4140 | | |||
4141 | /// <summary> | |||
4142 | /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. | |||
4143 | /// </summary> | |||
4144 | public class MemoryArchiveStorage : BaseArchiveStorage | |||
4145 | { | |||
4146 | #region Constructors | |||
4147 | /// <summary> | |||
4148 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4149 | /// </summary> | |||
4150 | public MemoryArchiveStorage() | |||
4151 | : base(FileUpdateMode.Direct) | |||
4152 | { | |||
4153 | } | |||
4154 | | |||
4155 | /// <summary> | |||
4156 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4157 | /// </summary> | |||
4158 | /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> | |||
4159 | /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> | |||
4160 | public MemoryArchiveStorage(FileUpdateMode updateMode) | |||
4161 | : base(updateMode) | |||
4162 | { | |||
4163 | } | |||
4164 | | |||
4165 | #endregion | |||
4166 | | |||
4167 | #region Properties | |||
4168 | /// <summary> | |||
4169 | /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. | |||
4170 | /// </summary> | |||
4171 | public MemoryStream FinalStream { | |||
4172 | get { return finalStream_; } | |||
4173 | } | |||
4174 | | |||
4175 | #endregion | |||
4176 | | |||
4177 | #region IArchiveStorage Members | |||
4178 | | |||
4179 | /// <summary> | |||
4180 | /// Gets the temporary output <see cref="Stream"/> | |||
4181 | /// </summary> | |||
4182 | /// <returns>Returns the temporary output stream.</returns> | |||
4183 | public override Stream GetTemporaryOutput() | |||
4184 | { | |||
4185 | temporaryStream_ = new MemoryStream(); | |||
4186 | return temporaryStream_; | |||
4187 | } | |||
4188 | | |||
4189 | /// <summary> | |||
4190 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
4191 | /// </summary> | |||
4192 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4193 | /// the final storage for the archive.</returns> | |||
4194 | public override Stream ConvertTemporaryToFinal() | |||
4195 | { | |||
4196 | if (temporaryStream_ == null) { | |||
4197 | throw new ZipException("No temporary stream has been created"); | |||
4198 | } | |||
4199 | | |||
4200 | finalStream_ = new MemoryStream(temporaryStream_.ToArray()); | |||
4201 | return finalStream_; | |||
4202 | } | |||
4203 | | |||
4204 | /// <summary> | |||
4205 | /// Make a temporary copy of the original stream. | |||
4206 | /// </summary> | |||
4207 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4208 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4209 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4210 | { | |||
4211 | temporaryStream_ = new MemoryStream(); | |||
4212 | stream.Position = 0; | |||
4213 | StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); | |||
4214 | return temporaryStream_; | |||
4215 | } | |||
4216 | | |||
4217 | /// <summary> | |||
4218 | /// Return a stream suitable for performing direct updates on the original source. | |||
4219 | /// </summary> | |||
4220 | /// <param name="stream">The original source stream</param> | |||
4221 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4222 | /// <remarks>If the <paramref name="stream"/> passed is not null this is used; | |||
4223 | /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> | |||
4224 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4225 | { | |||
4226 | Stream result; | |||
4227 | if ((stream == null) || !stream.CanWrite) { | |||
4228 | | |||
4229 | result = new MemoryStream(); | |||
4230 | | |||
4231 | if (stream != null) { | |||
4232 | stream.Position = 0; | |||
4233 | StreamUtils.Copy(stream, result, new byte[4096]); | |||
4234 | | |||
4235 | stream.Close(); | |||
4236 | } | |||
4237 | } else { | |||
4238 | result = stream; | |||
4239 | } | |||
4240 | | |||
4241 | return result; | |||
4242 | } | |||
4243 | | |||
4244 | /// <summary> | |||
4245 | /// Disposes this instance. | |||
4246 | /// </summary> | |||
4247 | public override void Dispose() | |||
4248 | { | |||
4249 | if (temporaryStream_ != null) { | |||
4250 | temporaryStream_.Close(); | |||
4251 | } | |||
4252 | } | |||
4253 | | |||
4254 | #endregion | |||
4255 | | |||
4256 | #region Instance Fields | |||
4257 | MemoryStream temporaryStream_; | |||
4258 | MemoryStream finalStream_; | |||
4259 | #endregion | |||
4260 | } | |||
4261 | | |||
4262 | #endregion | |||
4263 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.Compression.Streams.StreamManipulator |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\Compression\Streams\StreamManipulator.cs |
| Covered lines: | 51 |
| Uncovered lines: | 12 |
| Coverable lines: | 63 |
| Total lines: | 241 |
| Line coverage: | 80.9% |
| Branch coverage: | 67.6% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| PeekBits(...) | 3 | 100 | 100 |
| DropBits(...) | 1 | 100 | 100 |
| GetBits(...) | 2 | 0 | 0 |
| SkipToByteBoundary() | 1 | 100 | 100 |
| CopyBytes(...) | 8 | 86.36 | 80 |
| Reset() | 1 | 100 | 100 |
| SetInput(...) | 8 | 72.22 | 60 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | | |||
3 | namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams | |||
4 | { | |||
5 | /// <summary> | |||
6 | /// This class allows us to retrieve a specified number of bits from | |||
7 | /// the input buffer, as well as copy big byte blocks. | |||
8 | /// | |||
9 | /// It uses an int buffer to store up to 31 bits for direct | |||
10 | /// manipulation. This guarantees that we can get at least 16 bits, | |||
11 | /// but we only need at most 15, so this is all safe. | |||
12 | /// | |||
13 | /// There are some optimizations in this class, for example, you must | |||
14 | /// never peek more than 8 bits more than needed, and you must first | |||
15 | /// peek bits before you may drop them. This is not a general purpose | |||
16 | /// class but optimized for the behaviour of the Inflater. | |||
17 | /// | |||
18 | /// authors of the original java version : John Leuner, Jochen Hoenicke | |||
19 | /// </summary> | |||
20 | public class StreamManipulator | |||
21 | { | |||
22 | /// <summary> | |||
23 | /// Get the next sequence of bits but don't increase input pointer. bitCount must be | |||
24 | /// less or equal 16 and if this call succeeds, you must drop | |||
25 | /// at least n - 8 bits in the next call. | |||
26 | /// </summary> | |||
27 | /// <param name="bitCount">The number of bits to peek.</param> | |||
28 | /// <returns> | |||
29 | /// the value of the bits, or -1 if not enough bits available. */ | |||
30 | /// </returns> | |||
31 | public int PeekBits(int bitCount) | |||
32 | { | |||
| 11620 | 33 | if (bitsInBuffer_ < bitCount) { | ||
| 6287 | 34 | if (windowStart_ == windowEnd_) { | ||
| 396 | 35 | return -1; // ok | ||
36 | } | |||
| 5891 | 37 | buffer_ |= (uint)((window_[windowStart_++] & 0xff | | ||
| 5891 | 38 | (window_[windowStart_++] & 0xff) << 8) << bitsInBuffer_); | ||
| 5891 | 39 | bitsInBuffer_ += 16; | ||
40 | } | |||
| 11224 | 41 | return (int)(buffer_ & ((1 << bitCount) - 1)); | ||
42 | } | |||
43 | | |||
44 | /// <summary> | |||
45 | /// Drops the next n bits from the input. You should have called PeekBits | |||
46 | /// with a bigger or equal n before, to make sure that enough bits are in | |||
47 | /// the bit buffer. | |||
48 | /// </summary> | |||
49 | /// <param name="bitCount">The number of bits to drop.</param> | |||
50 | public void DropBits(int bitCount) | |||
51 | { | |||
| 11224 | 52 | buffer_ >>= bitCount; | ||
| 11224 | 53 | bitsInBuffer_ -= bitCount; | ||
| 11224 | 54 | } | ||
55 | | |||
56 | /// <summary> | |||
57 | /// Gets the next n bits and increases input pointer. This is equivalent | |||
58 | /// to <see cref="PeekBits"/> followed by <see cref="DropBits"/>, except for correct error handling. | |||
59 | /// </summary> | |||
60 | /// <param name="bitCount">The number of bits to retrieve.</param> | |||
61 | /// <returns> | |||
62 | /// the value of the bits, or -1 if not enough bits available. | |||
63 | /// </returns> | |||
64 | public int GetBits(int bitCount) | |||
65 | { | |||
| 0 | 66 | int bits = PeekBits(bitCount); | ||
| 0 | 67 | if (bits >= 0) { | ||
| 0 | 68 | DropBits(bitCount); | ||
69 | } | |||
| 0 | 70 | return bits; | ||
71 | } | |||
72 | | |||
73 | /// <summary> | |||
74 | /// Gets the number of bits available in the bit buffer. This must be | |||
75 | /// only called when a previous PeekBits() returned -1. | |||
76 | /// </summary> | |||
77 | /// <returns> | |||
78 | /// the number of bits available. | |||
79 | /// </returns> | |||
80 | public int AvailableBits { | |||
81 | get { | |||
| 23 | 82 | return bitsInBuffer_; | ||
83 | } | |||
84 | } | |||
85 | | |||
86 | /// <summary> | |||
87 | /// Gets the number of bytes available. | |||
88 | /// </summary> | |||
89 | /// <returns> | |||
90 | /// The number of bytes available. | |||
91 | /// </returns> | |||
92 | public int AvailableBytes { | |||
93 | get { | |||
| 2327 | 94 | return windowEnd_ - windowStart_ + (bitsInBuffer_ >> 3); | ||
95 | } | |||
96 | } | |||
97 | | |||
98 | /// <summary> | |||
99 | /// Skips to the next byte boundary. | |||
100 | /// </summary> | |||
101 | public void SkipToByteBoundary() | |||
102 | { | |||
| 303 | 103 | buffer_ >>= (bitsInBuffer_ & 7); | ||
| 303 | 104 | bitsInBuffer_ &= ~7; | ||
| 303 | 105 | } | ||
106 | | |||
107 | /// <summary> | |||
108 | /// Returns true when SetInput can be called | |||
109 | /// </summary> | |||
110 | public bool IsNeedingInput { | |||
111 | get { | |||
| 3355 | 112 | return windowStart_ == windowEnd_; | ||
113 | } | |||
114 | } | |||
115 | | |||
116 | /// <summary> | |||
117 | /// Copies bytes from input buffer to output buffer starting | |||
118 | /// at output[offset]. You have to make sure, that the buffer is | |||
119 | /// byte aligned. If not enough bytes are available, copies fewer | |||
120 | /// bytes. | |||
121 | /// </summary> | |||
122 | /// <param name="output"> | |||
123 | /// The buffer to copy bytes to. | |||
124 | /// </param> | |||
125 | /// <param name="offset"> | |||
126 | /// The offset in the buffer at which copying starts | |||
127 | /// </param> | |||
128 | /// <param name="length"> | |||
129 | /// The length to copy, 0 is allowed. | |||
130 | /// </param> | |||
131 | /// <returns> | |||
132 | /// The number of bytes copied, 0 if no bytes were available. | |||
133 | /// </returns> | |||
134 | /// <exception cref="ArgumentOutOfRangeException"> | |||
135 | /// Length is less than zero | |||
136 | /// </exception> | |||
137 | /// <exception cref="InvalidOperationException"> | |||
138 | /// Bit buffer isnt byte aligned | |||
139 | /// </exception> | |||
140 | public int CopyBytes(byte[] output, int offset, int length) | |||
141 | { | |||
| 2376 | 142 | if (length < 0) { | ||
| 0 | 143 | throw new ArgumentOutOfRangeException(nameof(length)); | ||
144 | } | |||
145 | | |||
| 2376 | 146 | if ((bitsInBuffer_ & 7) != 0) { | ||
147 | // bits_in_buffer may only be 0 or a multiple of 8 | |||
| 0 | 148 | throw new InvalidOperationException("Bit buffer is not byte aligned!"); | ||
149 | } | |||
150 | | |||
| 2376 | 151 | int count = 0; | ||
| 2600 | 152 | while ((bitsInBuffer_ > 0) && (length > 0)) { | ||
| 224 | 153 | output[offset++] = (byte)buffer_; | ||
| 224 | 154 | buffer_ >>= 8; | ||
| 224 | 155 | bitsInBuffer_ -= 8; | ||
| 224 | 156 | length--; | ||
| 224 | 157 | count++; | ||
158 | } | |||
159 | | |||
| 2376 | 160 | if (length == 0) { | ||
| 996 | 161 | return count; | ||
162 | } | |||
163 | | |||
| 1380 | 164 | int avail = windowEnd_ - windowStart_; | ||
| 1380 | 165 | if (length > avail) { | ||
| 0 | 166 | length = avail; | ||
167 | } | |||
| 1380 | 168 | System.Array.Copy(window_, windowStart_, output, offset, length); | ||
| 1380 | 169 | windowStart_ += length; | ||
170 | | |||
| 1380 | 171 | if (((windowStart_ - windowEnd_) & 1) != 0) { | ||
172 | // We always want an even number of bytes in input, see peekBits | |||
| 210 | 173 | buffer_ = (uint)(window_[windowStart_++] & 0xff); | ||
| 210 | 174 | bitsInBuffer_ = 8; | ||
175 | } | |||
| 1380 | 176 | return count + length; | ||
177 | } | |||
178 | | |||
179 | /// <summary> | |||
180 | /// Resets state and empties internal buffers | |||
181 | /// </summary> | |||
182 | public void Reset() | |||
183 | { | |||
| 41 | 184 | buffer_ = 0; | ||
| 41 | 185 | windowStart_ = windowEnd_ = bitsInBuffer_ = 0; | ||
| 41 | 186 | } | ||
187 | | |||
188 | /// <summary> | |||
189 | /// Add more input for consumption. | |||
190 | /// Only call when IsNeedingInput returns true | |||
191 | /// </summary> | |||
192 | /// <param name="buffer">data to be input</param> | |||
193 | /// <param name="offset">offset of first byte of input</param> | |||
194 | /// <param name="count">number of bytes of input to add.</param> | |||
195 | public void SetInput(byte[] buffer, int offset, int count) | |||
196 | { | |||
| 1433 | 197 | if (buffer == null) { | ||
| 0 | 198 | throw new ArgumentNullException(nameof(buffer)); | ||
199 | } | |||
200 | | |||
| 1433 | 201 | if (offset < 0) { | ||
| 0 | 202 | throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); | ||
203 | } | |||
204 | | |||
| 1433 | 205 | if (count < 0) { | ||
| 0 | 206 | throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); | ||
207 | } | |||
208 | | |||
| 1433 | 209 | if (windowStart_ < windowEnd_) { | ||
| 0 | 210 | throw new InvalidOperationException("Old input was not completely processed"); | ||
211 | } | |||
212 | | |||
| 1433 | 213 | int end = offset + count; | ||
214 | | |||
215 | // We want to throw an ArrayIndexOutOfBoundsException early. | |||
216 | // Note the check also handles integer wrap around. | |||
| 1433 | 217 | if ((offset > end) || (end > buffer.Length)) { | ||
| 0 | 218 | throw new ArgumentOutOfRangeException(nameof(count)); | ||
219 | } | |||
220 | | |||
| 1433 | 221 | if ((count & 1) != 0) { | ||
222 | // We always want an even number of bytes in input, see PeekBits | |||
| 214 | 223 | buffer_ |= (uint)((buffer[offset++] & 0xff) << bitsInBuffer_); | ||
| 214 | 224 | bitsInBuffer_ += 8; | ||
225 | } | |||
226 | | |||
| 1433 | 227 | window_ = buffer; | ||
| 1433 | 228 | windowStart_ = offset; | ||
| 1433 | 229 | windowEnd_ = end; | ||
| 1433 | 230 | } | ||
231 | | |||
232 | #region Instance Fields | |||
233 | private byte[] window_; | |||
234 | private int windowStart_; | |||
235 | private int windowEnd_; | |||
236 | | |||
237 | private uint buffer_; | |||
238 | private int bitsInBuffer_; | |||
239 | #endregion | |||
240 | } | |||
241 | } |
| Class: | ICSharpCode.SharpZipLib.Core.StreamUtils |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\StreamUtils.cs |
| Covered lines: | 25 |
| Uncovered lines: | 53 |
| Coverable lines: | 78 |
| Total lines: | 208 |
| Line coverage: | 32% |
| Branch coverage: | 34% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| ReadFully(...) | 1 | 100 | 100 |
| ReadFully(...) | 9 | 66.67 | 58.82 |
| Copy(...) | 7 | 76.47 | 69.23 |
| Copy(...) | 1 | 0 | 0 |
| Copy(...) | 12 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Core | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// Provides simple <see cref="Stream"/>" utilities. | |||
8 | /// </summary> | |||
9 | public sealed class StreamUtils | |||
10 | { | |||
11 | /// <summary> | |||
12 | /// Read from a <see cref="Stream"/> ensuring all the required data is read. | |||
13 | /// </summary> | |||
14 | /// <param name="stream">The stream to read.</param> | |||
15 | /// <param name="buffer">The buffer to fill.</param> | |||
16 | /// <seealso cref="ReadFully(Stream,byte[],int,int)"/> | |||
17 | static public void ReadFully(Stream stream, byte[] buffer) | |||
18 | { | |||
| 263789 | 19 | ReadFully(stream, buffer, 0, buffer.Length); | ||
| 263789 | 20 | } | ||
21 | | |||
22 | /// <summary> | |||
23 | /// Read from a <see cref="Stream"/>" ensuring all the required data is read. | |||
24 | /// </summary> | |||
25 | /// <param name="stream">The stream to read data from.</param> | |||
26 | /// <param name="buffer">The buffer to store data in.</param> | |||
27 | /// <param name="offset">The offset at which to begin storing data.</param> | |||
28 | /// <param name="count">The number of bytes of data to store.</param> | |||
29 | /// <exception cref="ArgumentNullException">Required parameter is null</exception> | |||
30 | /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> and or <paramref name="count"/> are inva | |||
31 | /// <exception cref="EndOfStreamException">End of stream is encountered before all the data has been read.</exceptio | |||
32 | static public void ReadFully(Stream stream, byte[] buffer, int offset, int count) | |||
33 | { | |||
| 329745 | 34 | if (stream == null) { | ||
| 0 | 35 | throw new ArgumentNullException(nameof(stream)); | ||
36 | } | |||
37 | | |||
| 329745 | 38 | if (buffer == null) { | ||
| 0 | 39 | throw new ArgumentNullException(nameof(buffer)); | ||
40 | } | |||
41 | | |||
42 | // Offset can equal length when buffer and count are 0. | |||
| 329745 | 43 | if ((offset < 0) || (offset > buffer.Length)) { | ||
| 0 | 44 | throw new ArgumentOutOfRangeException(nameof(offset)); | ||
45 | } | |||
46 | | |||
| 329745 | 47 | if ((count < 0) || (offset + count > buffer.Length)) { | ||
| 0 | 48 | throw new ArgumentOutOfRangeException(nameof(count)); | ||
49 | } | |||
50 | | |||
| 527678 | 51 | while (count > 0) { | ||
| 197933 | 52 | int readCount = stream.Read(buffer, offset, count); | ||
| 197933 | 53 | if (readCount <= 0) { | ||
| 0 | 54 | throw new EndOfStreamException(); | ||
55 | } | |||
| 197933 | 56 | offset += readCount; | ||
| 197933 | 57 | count -= readCount; | ||
58 | } | |||
| 329745 | 59 | } | ||
60 | | |||
61 | /// <summary> | |||
62 | /// Copy the contents of one <see cref="Stream"/> to another. | |||
63 | /// </summary> | |||
64 | /// <param name="source">The stream to source data from.</param> | |||
65 | /// <param name="destination">The stream to write data to.</param> | |||
66 | /// <param name="buffer">The buffer to use during copying.</param> | |||
67 | static public void Copy(Stream source, Stream destination, byte[] buffer) | |||
68 | { | |||
| 8 | 69 | if (source == null) { | ||
| 0 | 70 | throw new ArgumentNullException(nameof(source)); | ||
71 | } | |||
72 | | |||
| 8 | 73 | if (destination == null) { | ||
| 0 | 74 | throw new ArgumentNullException(nameof(destination)); | ||
75 | } | |||
76 | | |||
| 8 | 77 | if (buffer == null) { | ||
| 0 | 78 | throw new ArgumentNullException(nameof(buffer)); | ||
79 | } | |||
80 | | |||
81 | // Ensure a reasonable size of buffer is used without being prohibitive. | |||
| 8 | 82 | if (buffer.Length < 128) { | ||
| 0 | 83 | throw new ArgumentException("Buffer is too small", nameof(buffer)); | ||
84 | } | |||
85 | | |||
| 8 | 86 | bool copying = true; | ||
87 | | |||
| 24 | 88 | while (copying) { | ||
| 16 | 89 | int bytesRead = source.Read(buffer, 0, buffer.Length); | ||
| 16 | 90 | if (bytesRead > 0) { | ||
| 8 | 91 | destination.Write(buffer, 0, bytesRead); | ||
| 8 | 92 | } else { | ||
| 8 | 93 | destination.Flush(); | ||
| 8 | 94 | copying = false; | ||
95 | } | |||
96 | } | |||
| 8 | 97 | } | ||
98 | | |||
99 | /// <summary> | |||
100 | /// Copy the contents of one <see cref="Stream"/> to another. | |||
101 | /// </summary> | |||
102 | /// <param name="source">The stream to source data from.</param> | |||
103 | /// <param name="destination">The stream to write data to.</param> | |||
104 | /// <param name="buffer">The buffer to use during copying.</param> | |||
105 | /// <param name="progressHandler">The <see cref="ProgressHandler">progress handler delegate</see> to use.</param> | |||
106 | /// <param name="updateInterval">The minimum <see cref="TimeSpan"/> between progress updates.</param> | |||
107 | /// <param name="sender">The source for this event.</param> | |||
108 | /// <param name="name">The name to use with the event.</param> | |||
109 | /// <remarks>This form is specialised for use within #Zip to support events during archive operations.</remarks> | |||
110 | static public void Copy(Stream source, Stream destination, | |||
111 | byte[] buffer, ProgressHandler progressHandler, TimeSpan updateInterval, object sender, string name) | |||
112 | { | |||
| 0 | 113 | Copy(source, destination, buffer, progressHandler, updateInterval, sender, name, -1); | ||
| 0 | 114 | } | ||
115 | | |||
116 | /// <summary> | |||
117 | /// Copy the contents of one <see cref="Stream"/> to another. | |||
118 | /// </summary> | |||
119 | /// <param name="source">The stream to source data from.</param> | |||
120 | /// <param name="destination">The stream to write data to.</param> | |||
121 | /// <param name="buffer">The buffer to use during copying.</param> | |||
122 | /// <param name="progressHandler">The <see cref="ProgressHandler">progress handler delegate</see> to use.</param> | |||
123 | /// <param name="updateInterval">The minimum <see cref="TimeSpan"/> between progress updates.</param> | |||
124 | /// <param name="sender">The source for this event.</param> | |||
125 | /// <param name="name">The name to use with the event.</param> | |||
126 | /// <param name="fixedTarget">A predetermined fixed target value to use with progress updates. | |||
127 | /// If the value is negative the target is calculated by looking at the stream.</param> | |||
128 | /// <remarks>This form is specialised for use within #Zip to support events during archive operations.</remarks> | |||
129 | static public void Copy(Stream source, Stream destination, | |||
130 | byte[] buffer, | |||
131 | ProgressHandler progressHandler, TimeSpan updateInterval, | |||
132 | object sender, string name, long fixedTarget) | |||
133 | { | |||
| 0 | 134 | if (source == null) { | ||
| 0 | 135 | throw new ArgumentNullException(nameof(source)); | ||
136 | } | |||
137 | | |||
| 0 | 138 | if (destination == null) { | ||
| 0 | 139 | throw new ArgumentNullException(nameof(destination)); | ||
140 | } | |||
141 | | |||
| 0 | 142 | if (buffer == null) { | ||
| 0 | 143 | throw new ArgumentNullException(nameof(buffer)); | ||
144 | } | |||
145 | | |||
146 | // Ensure a reasonable size of buffer is used without being prohibitive. | |||
| 0 | 147 | if (buffer.Length < 128) { | ||
| 0 | 148 | throw new ArgumentException("Buffer is too small", nameof(buffer)); | ||
149 | } | |||
150 | | |||
| 0 | 151 | if (progressHandler == null) { | ||
| 0 | 152 | throw new ArgumentNullException(nameof(progressHandler)); | ||
153 | } | |||
154 | | |||
| 0 | 155 | bool copying = true; | ||
156 | | |||
| 0 | 157 | DateTime marker = DateTime.Now; | ||
| 0 | 158 | long processed = 0; | ||
| 0 | 159 | long target = 0; | ||
160 | | |||
| 0 | 161 | if (fixedTarget >= 0) { | ||
| 0 | 162 | target = fixedTarget; | ||
| 0 | 163 | } else if (source.CanSeek) { | ||
| 0 | 164 | target = source.Length - source.Position; | ||
165 | } | |||
166 | | |||
167 | // Always fire 0% progress.. | |||
| 0 | 168 | var args = new ProgressEventArgs(name, processed, target); | ||
| 0 | 169 | progressHandler(sender, args); | ||
170 | | |||
| 0 | 171 | bool progressFired = true; | ||
172 | | |||
| 0 | 173 | while (copying) { | ||
| 0 | 174 | int bytesRead = source.Read(buffer, 0, buffer.Length); | ||
| 0 | 175 | if (bytesRead > 0) { | ||
| 0 | 176 | processed += bytesRead; | ||
| 0 | 177 | progressFired = false; | ||
| 0 | 178 | destination.Write(buffer, 0, bytesRead); | ||
| 0 | 179 | } else { | ||
| 0 | 180 | destination.Flush(); | ||
| 0 | 181 | copying = false; | ||
182 | } | |||
183 | | |||
| 0 | 184 | if (DateTime.Now - marker > updateInterval) { | ||
| 0 | 185 | progressFired = true; | ||
| 0 | 186 | marker = DateTime.Now; | ||
| 0 | 187 | args = new ProgressEventArgs(name, processed, target); | ||
| 0 | 188 | progressHandler(sender, args); | ||
189 | | |||
| 0 | 190 | copying = args.ContinueRunning; | ||
191 | } | |||
192 | } | |||
193 | | |||
| 0 | 194 | if (!progressFired) { | ||
| 0 | 195 | args = new ProgressEventArgs(name, processed, target); | ||
| 0 | 196 | progressHandler(sender, args); | ||
197 | } | |||
| 0 | 198 | } | ||
199 | | |||
200 | /// <summary> | |||
201 | /// Initialise an instance of <see cref="StreamUtils"></see> | |||
202 | /// </summary> | |||
| 0 | 203 | private StreamUtils() | ||
204 | { | |||
205 | // Do nothing. | |||
| 0 | 206 | } | ||
207 | } | |||
208 | } |
| Class: | ICSharpCode.SharpZipLib.Tar.TarArchive |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarArchive.cs |
| Covered lines: | 46 |
| Uncovered lines: | 223 |
| Coverable lines: | 269 |
| Total lines: | 830 |
| Line coverage: | 17.1% |
| Branch coverage: | 13.6% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| OnProgressMessageEvent(...) | 2 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 2 | 85.71 | 66.67 |
| .ctor(...) | 2 | 85.71 | 66.67 |
| CreateInputTarArchive(...) | 3 | 62.5 | 60 |
| CreateInputTarArchive(...) | 3 | 60 | 60 |
| CreateOutputTarArchive(...) | 3 | 62.5 | 60 |
| CreateOutputTarArchive(...) | 3 | 60 | 60 |
| SetKeepOldFiles(...) | 2 | 0 | 0 |
| SetAsciiTranslation(...) | 2 | 0 | 0 |
| SetUserInfo(...) | 2 | 0 | 0 |
| CloseArchive() | 1 | 0 | 0 |
| ListContents() | 3 | 57.14 | 60 |
| ExtractContents(...) | 5 | 0 | 0 |
| ExtractEntry(...) | 14 | 0 | 0 |
| WriteEntry(...) | 5 | 0 | 0 |
| WriteEntryCore(...) | 19 | 0 | 0 |
| Dispose() | 1 | 100 | 100 |
| Dispose(...) | 5 | 100 | 77.78 |
| Close() | 1 | 0 | 0 |
| Finalize() | 1 | 0 | 0 |
| EnsureDirectoryExists(...) | 2 | 0 | 0 |
| IsBinary(...) | 7 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Text; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Tar | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// Used to advise clients of 'events' while processing archives | |||
9 | /// </summary> | |||
10 | public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message); | |||
11 | | |||
12 | /// <summary> | |||
13 | /// The TarArchive class implements the concept of a | |||
14 | /// 'Tape Archive'. A tar archive is a series of entries, each of | |||
15 | /// which represents a file system object. Each entry in | |||
16 | /// the archive consists of a header block followed by 0 or more data blocks. | |||
17 | /// Directory entries consist only of the header block, and are followed by entries | |||
18 | /// for the directory's contents. File entries consist of a | |||
19 | /// header followed by the number of blocks needed to | |||
20 | /// contain the file's contents. All entries are written on | |||
21 | /// block boundaries. Blocks are 512 bytes long. | |||
22 | /// | |||
23 | /// TarArchives are instantiated in either read or write mode, | |||
24 | /// based upon whether they are instantiated with an InputStream | |||
25 | /// or an OutputStream. Once instantiated TarArchives read/write | |||
26 | /// mode can not be changed. | |||
27 | /// | |||
28 | /// There is currently no support for random access to tar archives. | |||
29 | /// However, it seems that subclassing TarArchive, and using the | |||
30 | /// TarBuffer.CurrentRecord and TarBuffer.CurrentBlock | |||
31 | /// properties, this would be rather trivial. | |||
32 | /// </summary> | |||
33 | public class TarArchive : IDisposable | |||
34 | { | |||
35 | /// <summary> | |||
36 | /// Client hook allowing detailed information to be reported during processing | |||
37 | /// </summary> | |||
38 | public event ProgressMessageHandler ProgressMessageEvent; | |||
39 | | |||
40 | /// <summary> | |||
41 | /// Raises the ProgressMessage event | |||
42 | /// </summary> | |||
43 | /// <param name="entry">The <see cref="TarEntry">TarEntry</see> for this event</param> | |||
44 | /// <param name="message">message for this event. Null is no message</param> | |||
45 | protected virtual void OnProgressMessageEvent(TarEntry entry, string message) | |||
46 | { | |||
| 0 | 47 | ProgressMessageHandler handler = ProgressMessageEvent; | ||
| 0 | 48 | if (handler != null) { | ||
| 0 | 49 | handler(this, entry, message); | ||
50 | } | |||
| 0 | 51 | } | ||
52 | | |||
53 | #region Constructors | |||
54 | /// <summary> | |||
55 | /// Constructor for a default <see cref="TarArchive"/>. | |||
56 | /// </summary> | |||
| 0 | 57 | protected TarArchive() | ||
58 | { | |||
| 0 | 59 | } | ||
60 | | |||
61 | /// <summary> | |||
62 | /// Initalise a TarArchive for input. | |||
63 | /// </summary> | |||
64 | /// <param name="stream">The <see cref="TarInputStream"/> to use for input.</param> | |||
| 1 | 65 | protected TarArchive(TarInputStream stream) | ||
66 | { | |||
| 1 | 67 | if (stream == null) { | ||
| 0 | 68 | throw new ArgumentNullException(nameof(stream)); | ||
69 | } | |||
70 | | |||
| 1 | 71 | tarIn = stream; | ||
| 1 | 72 | } | ||
73 | | |||
74 | /// <summary> | |||
75 | /// Initialise a TarArchive for output. | |||
76 | /// </summary> | |||
77 | /// <param name="stream">The <see cref="TarOutputStream"/> to use for output.</param> | |||
| 1 | 78 | protected TarArchive(TarOutputStream stream) | ||
79 | { | |||
| 1 | 80 | if (stream == null) { | ||
| 0 | 81 | throw new ArgumentNullException(nameof(stream)); | ||
82 | } | |||
83 | | |||
| 1 | 84 | tarOut = stream; | ||
| 1 | 85 | } | ||
86 | #endregion | |||
87 | | |||
88 | #region Static factory methods | |||
89 | /// <summary> | |||
90 | /// The InputStream based constructors create a TarArchive for the | |||
91 | /// purposes of extracting or listing a tar archive. Thus, use | |||
92 | /// these constructors when you wish to extract files from or list | |||
93 | /// the contents of an existing tar archive. | |||
94 | /// </summary> | |||
95 | /// <param name="inputStream">The stream to retrieve archive data from.</param> | |||
96 | /// <returns>Returns a new <see cref="TarArchive"/> suitable for reading from.</returns> | |||
97 | public static TarArchive CreateInputTarArchive(Stream inputStream) | |||
98 | { | |||
| 1 | 99 | if (inputStream == null) { | ||
| 0 | 100 | throw new ArgumentNullException(nameof(inputStream)); | ||
101 | } | |||
102 | | |||
| 1 | 103 | var tarStream = inputStream as TarInputStream; | ||
104 | | |||
105 | TarArchive result; | |||
| 1 | 106 | if (tarStream != null) { | ||
| 0 | 107 | result = new TarArchive(tarStream); | ||
| 0 | 108 | } else { | ||
| 1 | 109 | result = CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor); | ||
110 | } | |||
| 1 | 111 | return result; | ||
112 | } | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Create TarArchive for reading setting block factor | |||
116 | /// </summary> | |||
117 | /// <param name="inputStream">A stream containing the tar archive contents</param> | |||
118 | /// <param name="blockFactor">The blocking factor to apply</param> | |||
119 | /// <returns>Returns a <see cref="TarArchive"/> suitable for reading.</returns> | |||
120 | public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor) | |||
121 | { | |||
| 1 | 122 | if (inputStream == null) { | ||
| 0 | 123 | throw new ArgumentNullException(nameof(inputStream)); | ||
124 | } | |||
125 | | |||
| 1 | 126 | if (inputStream is TarInputStream) { | ||
| 0 | 127 | throw new ArgumentException("TarInputStream not valid"); | ||
128 | } | |||
129 | | |||
| 1 | 130 | return new TarArchive(new TarInputStream(inputStream, blockFactor)); | ||
131 | } | |||
132 | | |||
133 | /// <summary> | |||
134 | /// Create a TarArchive for writing to, using the default blocking factor | |||
135 | /// </summary> | |||
136 | /// <param name="outputStream">The <see cref="Stream"/> to write to</param> | |||
137 | /// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns> | |||
138 | public static TarArchive CreateOutputTarArchive(Stream outputStream) | |||
139 | { | |||
| 1 | 140 | if (outputStream == null) { | ||
| 0 | 141 | throw new ArgumentNullException(nameof(outputStream)); | ||
142 | } | |||
143 | | |||
| 1 | 144 | var tarStream = outputStream as TarOutputStream; | ||
145 | | |||
146 | TarArchive result; | |||
| 1 | 147 | if (tarStream != null) { | ||
| 0 | 148 | result = new TarArchive(tarStream); | ||
| 0 | 149 | } else { | ||
| 1 | 150 | result = CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor); | ||
151 | } | |||
| 1 | 152 | return result; | ||
153 | } | |||
154 | | |||
155 | /// <summary> | |||
156 | /// Create a <see cref="TarArchive">tar archive</see> for writing. | |||
157 | /// </summary> | |||
158 | /// <param name="outputStream">The stream to write to</param> | |||
159 | /// <param name="blockFactor">The blocking factor to use for buffering.</param> | |||
160 | /// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns> | |||
161 | public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor) | |||
162 | { | |||
| 1 | 163 | if (outputStream == null) { | ||
| 0 | 164 | throw new ArgumentNullException(nameof(outputStream)); | ||
165 | } | |||
166 | | |||
| 1 | 167 | if (outputStream is TarOutputStream) { | ||
| 0 | 168 | throw new ArgumentException("TarOutputStream is not valid"); | ||
169 | } | |||
170 | | |||
| 1 | 171 | return new TarArchive(new TarOutputStream(outputStream, blockFactor)); | ||
172 | } | |||
173 | #endregion | |||
174 | | |||
175 | /// <summary> | |||
176 | /// Set the flag that determines whether existing files are | |||
177 | /// kept, or overwritten during extraction. | |||
178 | /// </summary> | |||
179 | /// <param name="keepExistingFiles"> | |||
180 | /// If true, do not overwrite existing files. | |||
181 | /// </param> | |||
182 | public void SetKeepOldFiles(bool keepExistingFiles) | |||
183 | { | |||
| 0 | 184 | if (isDisposed) { | ||
| 0 | 185 | throw new ObjectDisposedException("TarArchive"); | ||
186 | } | |||
187 | | |||
| 0 | 188 | keepOldFiles = keepExistingFiles; | ||
| 0 | 189 | } | ||
190 | | |||
191 | /// <summary> | |||
192 | /// Get/set the ascii file translation flag. If ascii file translation | |||
193 | /// is true, then the file is checked to see if it a binary file or not. | |||
194 | /// If the flag is true and the test indicates it is ascii text | |||
195 | /// file, it will be translated. The translation converts the local | |||
196 | /// operating system's concept of line ends into the UNIX line end, | |||
197 | /// '\n', which is the defacto standard for a TAR archive. This makes | |||
198 | /// text files compatible with UNIX. | |||
199 | /// </summary> | |||
200 | public bool AsciiTranslate { | |||
201 | get { | |||
| 0 | 202 | if (isDisposed) { | ||
| 0 | 203 | throw new ObjectDisposedException("TarArchive"); | ||
204 | } | |||
205 | | |||
| 0 | 206 | return asciiTranslate; | ||
207 | } | |||
208 | | |||
209 | set { | |||
| 0 | 210 | if (isDisposed) { | ||
| 0 | 211 | throw new ObjectDisposedException("TarArchive"); | ||
212 | } | |||
213 | | |||
| 0 | 214 | asciiTranslate = value; | ||
| 0 | 215 | } | ||
216 | | |||
217 | } | |||
218 | | |||
219 | /// <summary> | |||
220 | /// Set the ascii file translation flag. | |||
221 | /// </summary> | |||
222 | /// <param name= "translateAsciiFiles"> | |||
223 | /// If true, translate ascii text files. | |||
224 | /// </param> | |||
225 | [Obsolete("Use the AsciiTranslate property")] | |||
226 | public void SetAsciiTranslation(bool translateAsciiFiles) | |||
227 | { | |||
| 0 | 228 | if (isDisposed) { | ||
| 0 | 229 | throw new ObjectDisposedException("TarArchive"); | ||
230 | } | |||
231 | | |||
| 0 | 232 | asciiTranslate = translateAsciiFiles; | ||
| 0 | 233 | } | ||
234 | | |||
235 | /// <summary> | |||
236 | /// PathPrefix is added to entry names as they are written if the value is not null. | |||
237 | /// A slash character is appended after PathPrefix | |||
238 | /// </summary> | |||
239 | public string PathPrefix { | |||
240 | get { | |||
| 0 | 241 | if (isDisposed) { | ||
| 0 | 242 | throw new ObjectDisposedException("TarArchive"); | ||
243 | } | |||
244 | | |||
| 0 | 245 | return pathPrefix; | ||
246 | } | |||
247 | | |||
248 | set { | |||
| 0 | 249 | if (isDisposed) { | ||
| 0 | 250 | throw new ObjectDisposedException("TarArchive"); | ||
251 | } | |||
252 | | |||
| 0 | 253 | pathPrefix = value; | ||
| 0 | 254 | } | ||
255 | | |||
256 | } | |||
257 | | |||
258 | /// <summary> | |||
259 | /// RootPath is removed from entry names if it is found at the | |||
260 | /// beginning of the name. | |||
261 | /// </summary> | |||
262 | public string RootPath { | |||
263 | get { | |||
| 0 | 264 | if (isDisposed) { | ||
| 0 | 265 | throw new ObjectDisposedException("TarArchive"); | ||
266 | } | |||
267 | | |||
| 0 | 268 | return rootPath; | ||
269 | } | |||
270 | | |||
271 | set { | |||
| 0 | 272 | if (isDisposed) { | ||
| 0 | 273 | throw new ObjectDisposedException("TarArchive"); | ||
274 | } | |||
275 | // Convert to forward slashes for matching. Trim trailing / for correct final path | |||
| 0 | 276 | rootPath = value.Replace('\\', '/').TrimEnd('/'); | ||
| 0 | 277 | } | ||
278 | } | |||
279 | | |||
280 | /// <summary> | |||
281 | /// Set user and group information that will be used to fill in the | |||
282 | /// tar archive's entry headers. This information is based on that available | |||
283 | /// for the linux operating system, which is not always available on other | |||
284 | /// operating systems. TarArchive allows the programmer to specify values | |||
285 | /// to be used in their place. | |||
286 | /// <see cref="ApplyUserInfoOverrides"/> is set to true by this call. | |||
287 | /// </summary> | |||
288 | /// <param name="userId"> | |||
289 | /// The user id to use in the headers. | |||
290 | /// </param> | |||
291 | /// <param name="userName"> | |||
292 | /// The user name to use in the headers. | |||
293 | /// </param> | |||
294 | /// <param name="groupId"> | |||
295 | /// The group id to use in the headers. | |||
296 | /// </param> | |||
297 | /// <param name="groupName"> | |||
298 | /// The group name to use in the headers. | |||
299 | /// </param> | |||
300 | public void SetUserInfo(int userId, string userName, int groupId, string groupName) | |||
301 | { | |||
| 0 | 302 | if (isDisposed) { | ||
| 0 | 303 | throw new ObjectDisposedException("TarArchive"); | ||
304 | } | |||
305 | | |||
| 0 | 306 | this.userId = userId; | ||
| 0 | 307 | this.userName = userName; | ||
| 0 | 308 | this.groupId = groupId; | ||
| 0 | 309 | this.groupName = groupName; | ||
| 0 | 310 | applyUserInfoOverrides = true; | ||
| 0 | 311 | } | ||
312 | | |||
313 | /// <summary> | |||
314 | /// Get or set a value indicating if overrides defined by <see cref="SetUserInfo">SetUserInfo</see> should be applie | |||
315 | /// </summary> | |||
316 | /// <remarks>If overrides are not applied then the values as set in each header will be used.</remarks> | |||
317 | public bool ApplyUserInfoOverrides { | |||
318 | get { | |||
| 0 | 319 | if (isDisposed) { | ||
| 0 | 320 | throw new ObjectDisposedException("TarArchive"); | ||
321 | } | |||
322 | | |||
| 0 | 323 | return applyUserInfoOverrides; | ||
324 | } | |||
325 | | |||
326 | set { | |||
| 0 | 327 | if (isDisposed) { | ||
| 0 | 328 | throw new ObjectDisposedException("TarArchive"); | ||
329 | } | |||
330 | | |||
| 0 | 331 | applyUserInfoOverrides = value; | ||
| 0 | 332 | } | ||
333 | } | |||
334 | | |||
335 | /// <summary> | |||
336 | /// Get the archive user id. | |||
337 | /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail | |||
338 | /// on how to allow setting values on a per entry basis. | |||
339 | /// </summary> | |||
340 | /// <returns> | |||
341 | /// The current user id. | |||
342 | /// </returns> | |||
343 | public int UserId { | |||
344 | get { | |||
| 0 | 345 | if (isDisposed) { | ||
| 0 | 346 | throw new ObjectDisposedException("TarArchive"); | ||
347 | } | |||
348 | | |||
| 0 | 349 | return userId; | ||
350 | } | |||
351 | } | |||
352 | | |||
353 | /// <summary> | |||
354 | /// Get the archive user name. | |||
355 | /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail | |||
356 | /// on how to allow setting values on a per entry basis. | |||
357 | /// </summary> | |||
358 | /// <returns> | |||
359 | /// The current user name. | |||
360 | /// </returns> | |||
361 | public string UserName { | |||
362 | get { | |||
| 0 | 363 | if (isDisposed) { | ||
| 0 | 364 | throw new ObjectDisposedException("TarArchive"); | ||
365 | } | |||
366 | | |||
| 0 | 367 | return userName; | ||
368 | } | |||
369 | } | |||
370 | | |||
371 | /// <summary> | |||
372 | /// Get the archive group id. | |||
373 | /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail | |||
374 | /// on how to allow setting values on a per entry basis. | |||
375 | /// </summary> | |||
376 | /// <returns> | |||
377 | /// The current group id. | |||
378 | /// </returns> | |||
379 | public int GroupId { | |||
380 | get { | |||
| 0 | 381 | if (isDisposed) { | ||
| 0 | 382 | throw new ObjectDisposedException("TarArchive"); | ||
383 | } | |||
384 | | |||
| 0 | 385 | return groupId; | ||
386 | } | |||
387 | } | |||
388 | | |||
389 | /// <summary> | |||
390 | /// Get the archive group name. | |||
391 | /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail | |||
392 | /// on how to allow setting values on a per entry basis. | |||
393 | /// </summary> | |||
394 | /// <returns> | |||
395 | /// The current group name. | |||
396 | /// </returns> | |||
397 | public string GroupName { | |||
398 | get { | |||
| 0 | 399 | if (isDisposed) { | ||
| 0 | 400 | throw new ObjectDisposedException("TarArchive"); | ||
401 | } | |||
402 | | |||
| 0 | 403 | return groupName; | ||
404 | } | |||
405 | } | |||
406 | | |||
407 | /// <summary> | |||
408 | /// Get the archive's record size. Tar archives are composed of | |||
409 | /// a series of RECORDS each containing a number of BLOCKS. | |||
410 | /// This allowed tar archives to match the IO characteristics of | |||
411 | /// the physical device being used. Archives are expected | |||
412 | /// to be properly "blocked". | |||
413 | /// </summary> | |||
414 | /// <returns> | |||
415 | /// The record size this archive is using. | |||
416 | /// </returns> | |||
417 | public int RecordSize { | |||
418 | get { | |||
| 1 | 419 | if (isDisposed) { | ||
| 0 | 420 | throw new ObjectDisposedException("TarArchive"); | ||
421 | } | |||
422 | | |||
| 1 | 423 | if (tarIn != null) { | ||
| 0 | 424 | return tarIn.RecordSize; | ||
| 1 | 425 | } else if (tarOut != null) { | ||
| 1 | 426 | return tarOut.RecordSize; | ||
427 | } | |||
| 0 | 428 | return TarBuffer.DefaultRecordSize; | ||
429 | } | |||
430 | } | |||
431 | | |||
432 | /// <summary> | |||
433 | /// Sets the IsStreamOwner property on the underlying stream. | |||
434 | /// Set this to false to prevent the Close of the TarArchive from closing the stream. | |||
435 | /// </summary> | |||
436 | public bool IsStreamOwner { | |||
437 | set { | |||
| 0 | 438 | if (tarIn != null) { | ||
| 0 | 439 | tarIn.IsStreamOwner = value; | ||
| 0 | 440 | } else { | ||
| 0 | 441 | tarOut.IsStreamOwner = value; | ||
442 | } | |||
| 0 | 443 | } | ||
444 | } | |||
445 | | |||
446 | /// <summary> | |||
447 | /// Close the archive. | |||
448 | /// </summary> | |||
449 | [Obsolete("Use Close instead")] | |||
450 | public void CloseArchive() | |||
451 | { | |||
| 0 | 452 | Close(); | ||
| 0 | 453 | } | ||
454 | | |||
455 | /// <summary> | |||
456 | /// Perform the "list" command for the archive contents. | |||
457 | /// | |||
458 | /// NOTE That this method uses the <see cref="ProgressMessageEvent"> progress event</see> to actually list | |||
459 | /// the contents. If the progress display event is not set, nothing will be listed! | |||
460 | /// </summary> | |||
461 | public void ListContents() | |||
462 | { | |||
| 1 | 463 | if (isDisposed) { | ||
| 0 | 464 | throw new ObjectDisposedException("TarArchive"); | ||
465 | } | |||
466 | | |||
| 0 | 467 | while (true) { | ||
| 1 | 468 | TarEntry entry = tarIn.GetNextEntry(); | ||
469 | | |||
| 1 | 470 | if (entry == null) { | ||
471 | break; | |||
472 | } | |||
| 0 | 473 | OnProgressMessageEvent(entry, null); | ||
474 | } | |||
| 1 | 475 | } | ||
476 | | |||
477 | /// <summary> | |||
478 | /// Perform the "extract" command and extract the contents of the archive. | |||
479 | /// </summary> | |||
480 | /// <param name="destinationDirectory"> | |||
481 | /// The destination directory into which to extract. | |||
482 | /// </param> | |||
483 | public void ExtractContents(string destinationDirectory) | |||
484 | { | |||
| 0 | 485 | if (isDisposed) { | ||
| 0 | 486 | throw new ObjectDisposedException("TarArchive"); | ||
487 | } | |||
488 | | |||
| 0 | 489 | while (true) { | ||
| 0 | 490 | TarEntry entry = tarIn.GetNextEntry(); | ||
491 | | |||
| 0 | 492 | if (entry == null) { | ||
493 | break; | |||
494 | } | |||
495 | | |||
| 0 | 496 | if (entry.TarHeader.TypeFlag == TarHeader.LF_LINK || entry.TarHeader.TypeFlag == TarHeader.LF_SYMLINK) | ||
497 | continue; | |||
498 | | |||
| 0 | 499 | ExtractEntry(destinationDirectory, entry); | ||
500 | } | |||
| 0 | 501 | } | ||
502 | | |||
503 | /// <summary> | |||
504 | /// Extract an entry from the archive. This method assumes that the | |||
505 | /// tarIn stream has been properly set with a call to GetNextEntry(). | |||
506 | /// </summary> | |||
507 | /// <param name="destDir"> | |||
508 | /// The destination directory into which to extract. | |||
509 | /// </param> | |||
510 | /// <param name="entry"> | |||
511 | /// The TarEntry returned by tarIn.GetNextEntry(). | |||
512 | /// </param> | |||
513 | void ExtractEntry(string destDir, TarEntry entry) | |||
514 | { | |||
| 0 | 515 | OnProgressMessageEvent(entry, null); | ||
516 | | |||
| 0 | 517 | string name = entry.Name; | ||
518 | | |||
| 0 | 519 | if (Path.IsPathRooted(name)) { | ||
520 | // NOTE: | |||
521 | // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt | |||
| 0 | 522 | name = name.Substring(Path.GetPathRoot(name).Length); | ||
523 | } | |||
524 | | |||
| 0 | 525 | name = name.Replace('/', Path.DirectorySeparatorChar); | ||
526 | | |||
| 0 | 527 | string destFile = Path.Combine(destDir, name); | ||
528 | | |||
| 0 | 529 | if (entry.IsDirectory) { | ||
| 0 | 530 | EnsureDirectoryExists(destFile); | ||
| 0 | 531 | } else { | ||
| 0 | 532 | string parentDirectory = Path.GetDirectoryName(destFile); | ||
| 0 | 533 | EnsureDirectoryExists(parentDirectory); | ||
534 | | |||
| 0 | 535 | bool process = true; | ||
| 0 | 536 | var fileInfo = new FileInfo(destFile); | ||
| 0 | 537 | if (fileInfo.Exists) { | ||
| 0 | 538 | if (keepOldFiles) { | ||
| 0 | 539 | OnProgressMessageEvent(entry, "Destination file already exists"); | ||
| 0 | 540 | process = false; | ||
| 0 | 541 | } else if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0) { | ||
| 0 | 542 | OnProgressMessageEvent(entry, "Destination file already exists, and is read-only"); | ||
| 0 | 543 | process = false; | ||
544 | } | |||
545 | } | |||
546 | | |||
| 0 | 547 | if (process) { | ||
| 0 | 548 | bool asciiTrans = false; | ||
549 | | |||
| 0 | 550 | Stream outputStream = File.Create(destFile); | ||
| 0 | 551 | if (this.asciiTranslate) { | ||
| 0 | 552 | asciiTrans = !IsBinary(destFile); | ||
553 | } | |||
554 | | |||
| 0 | 555 | StreamWriter outw = null; | ||
| 0 | 556 | if (asciiTrans) { | ||
| 0 | 557 | outw = new StreamWriter(outputStream); | ||
558 | } | |||
559 | | |||
| 0 | 560 | byte[] rdbuf = new byte[32 * 1024]; | ||
561 | | |||
| 0 | 562 | while (true) { | ||
| 0 | 563 | int numRead = tarIn.Read(rdbuf, 0, rdbuf.Length); | ||
564 | | |||
| 0 | 565 | if (numRead <= 0) { | ||
566 | break; | |||
567 | } | |||
568 | | |||
| 0 | 569 | if (asciiTrans) { | ||
| 0 | 570 | for (int off = 0, b = 0; b < numRead; ++b) { | ||
| 0 | 571 | if (rdbuf[b] == 10) { | ||
| 0 | 572 | string s = Encoding.ASCII.GetString(rdbuf, off, (b - off)); | ||
| 0 | 573 | outw.WriteLine(s); | ||
| 0 | 574 | off = b + 1; | ||
575 | } | |||
576 | } | |||
| 0 | 577 | } else { | ||
| 0 | 578 | outputStream.Write(rdbuf, 0, numRead); | ||
579 | } | |||
580 | } | |||
581 | | |||
| 0 | 582 | if (asciiTrans) { | ||
| 0 | 583 | outw.Close(); | ||
| 0 | 584 | } else { | ||
| 0 | 585 | outputStream.Close(); | ||
586 | } | |||
587 | } | |||
588 | } | |||
| 0 | 589 | } | ||
590 | | |||
591 | /// <summary> | |||
592 | /// Write an entry to the archive. This method will call the putNextEntry | |||
593 | /// and then write the contents of the entry, and finally call closeEntry() | |||
594 | /// for entries that are files. For directories, it will call putNextEntry(), | |||
595 | /// and then, if the recurse flag is true, process each entry that is a | |||
596 | /// child of the directory. | |||
597 | /// </summary> | |||
598 | /// <param name="sourceEntry"> | |||
599 | /// The TarEntry representing the entry to write to the archive. | |||
600 | /// </param> | |||
601 | /// <param name="recurse"> | |||
602 | /// If true, process the children of directory entries. | |||
603 | /// </param> | |||
604 | public void WriteEntry(TarEntry sourceEntry, bool recurse) | |||
605 | { | |||
| 0 | 606 | if (sourceEntry == null) { | ||
| 0 | 607 | throw new ArgumentNullException(nameof(sourceEntry)); | ||
608 | } | |||
609 | | |||
| 0 | 610 | if (isDisposed) { | ||
| 0 | 611 | throw new ObjectDisposedException("TarArchive"); | ||
612 | } | |||
613 | | |||
614 | try { | |||
| 0 | 615 | if (recurse) { | ||
| 0 | 616 | TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName, | ||
| 0 | 617 | sourceEntry.GroupId, sourceEntry.GroupName); | ||
618 | } | |||
| 0 | 619 | WriteEntryCore(sourceEntry, recurse); | ||
| 0 | 620 | } finally { | ||
| 0 | 621 | if (recurse) { | ||
| 0 | 622 | TarHeader.RestoreSetValues(); | ||
623 | } | |||
| 0 | 624 | } | ||
| 0 | 625 | } | ||
626 | | |||
627 | /// <summary> | |||
628 | /// Write an entry to the archive. This method will call the putNextEntry | |||
629 | /// and then write the contents of the entry, and finally call closeEntry() | |||
630 | /// for entries that are files. For directories, it will call putNextEntry(), | |||
631 | /// and then, if the recurse flag is true, process each entry that is a | |||
632 | /// child of the directory. | |||
633 | /// </summary> | |||
634 | /// <param name="sourceEntry"> | |||
635 | /// The TarEntry representing the entry to write to the archive. | |||
636 | /// </param> | |||
637 | /// <param name="recurse"> | |||
638 | /// If true, process the children of directory entries. | |||
639 | /// </param> | |||
640 | void WriteEntryCore(TarEntry sourceEntry, bool recurse) | |||
641 | { | |||
| 0 | 642 | string tempFileName = null; | ||
| 0 | 643 | string entryFilename = sourceEntry.File; | ||
644 | | |||
| 0 | 645 | var entry = (TarEntry)sourceEntry.Clone(); | ||
646 | | |||
| 0 | 647 | if (applyUserInfoOverrides) { | ||
| 0 | 648 | entry.GroupId = groupId; | ||
| 0 | 649 | entry.GroupName = groupName; | ||
| 0 | 650 | entry.UserId = userId; | ||
| 0 | 651 | entry.UserName = userName; | ||
652 | } | |||
653 | | |||
| 0 | 654 | OnProgressMessageEvent(entry, null); | ||
655 | | |||
| 0 | 656 | if (asciiTranslate && !entry.IsDirectory) { | ||
657 | | |||
| 0 | 658 | if (!IsBinary(entryFilename)) { | ||
| 0 | 659 | tempFileName = Path.GetTempFileName(); | ||
660 | | |||
| 0 | 661 | using (StreamReader inStream = File.OpenText(entryFilename)) { | ||
| 0 | 662 | using (Stream outStream = File.Create(tempFileName)) { | ||
663 | | |||
| 0 | 664 | while (true) { | ||
| 0 | 665 | string line = inStream.ReadLine(); | ||
| 0 | 666 | if (line == null) { | ||
667 | break; | |||
668 | } | |||
| 0 | 669 | byte[] data = Encoding.ASCII.GetBytes(line); | ||
| 0 | 670 | outStream.Write(data, 0, data.Length); | ||
| 0 | 671 | outStream.WriteByte((byte)'\n'); | ||
672 | } | |||
673 | | |||
| 0 | 674 | outStream.Flush(); | ||
| 0 | 675 | } | ||
676 | } | |||
677 | | |||
| 0 | 678 | entry.Size = new FileInfo(tempFileName).Length; | ||
| 0 | 679 | entryFilename = tempFileName; | ||
680 | } | |||
681 | } | |||
682 | | |||
| 0 | 683 | string newName = null; | ||
684 | | |||
| 0 | 685 | if (rootPath != null) { | ||
| 0 | 686 | if (entry.Name.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)) { | ||
| 0 | 687 | newName = entry.Name.Substring(rootPath.Length + 1); | ||
688 | } | |||
689 | } | |||
690 | | |||
| 0 | 691 | if (pathPrefix != null) { | ||
| 0 | 692 | newName = (newName == null) ? pathPrefix + "/" + entry.Name : pathPrefix + "/" + newName; | ||
693 | } | |||
694 | | |||
| 0 | 695 | if (newName != null) { | ||
| 0 | 696 | entry.Name = newName; | ||
697 | } | |||
698 | | |||
| 0 | 699 | tarOut.PutNextEntry(entry); | ||
700 | | |||
| 0 | 701 | if (entry.IsDirectory) { | ||
| 0 | 702 | if (recurse) { | ||
| 0 | 703 | TarEntry[] list = entry.GetDirectoryEntries(); | ||
| 0 | 704 | for (int i = 0; i < list.Length; ++i) { | ||
| 0 | 705 | WriteEntryCore(list[i], recurse); | ||
706 | } | |||
707 | } | |||
| 0 | 708 | } else { | ||
| 0 | 709 | using (Stream inputStream = File.OpenRead(entryFilename)) { | ||
| 0 | 710 | byte[] localBuffer = new byte[32 * 1024]; | ||
| 0 | 711 | while (true) { | ||
| 0 | 712 | int numRead = inputStream.Read(localBuffer, 0, localBuffer.Length); | ||
713 | | |||
| 0 | 714 | if (numRead <= 0) { | ||
| 0 | 715 | break; | ||
716 | } | |||
717 | | |||
| 0 | 718 | tarOut.Write(localBuffer, 0, numRead); | ||
719 | } | |||
720 | } | |||
721 | | |||
| 0 | 722 | if (!string.IsNullOrEmpty(tempFileName)) { | ||
| 0 | 723 | File.Delete(tempFileName); | ||
724 | } | |||
725 | | |||
| 0 | 726 | tarOut.CloseEntry(); | ||
727 | } | |||
| 0 | 728 | } | ||
729 | | |||
730 | /// <summary> | |||
731 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. | |||
732 | /// </summary> | |||
733 | public void Dispose() | |||
734 | { | |||
| 2 | 735 | Dispose(true); | ||
| 2 | 736 | GC.SuppressFinalize(this); | ||
| 2 | 737 | } | ||
738 | | |||
739 | /// <summary> | |||
740 | /// Releases the unmanaged resources used by the FileStream and optionally releases the managed resources. | |||
741 | /// </summary> | |||
742 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
743 | /// false to release only unmanaged resources.</param> | |||
744 | protected virtual void Dispose(bool disposing) | |||
745 | { | |||
| 2 | 746 | if (!isDisposed) { | ||
| 2 | 747 | isDisposed = true; | ||
| 2 | 748 | if (disposing) { | ||
| 2 | 749 | if (tarOut != null) { | ||
| 1 | 750 | tarOut.Flush(); | ||
| 1 | 751 | tarOut.Close(); | ||
752 | } | |||
753 | | |||
| 2 | 754 | if (tarIn != null) { | ||
| 1 | 755 | tarIn.Close(); | ||
756 | } | |||
757 | } | |||
758 | } | |||
| 2 | 759 | } | ||
760 | | |||
761 | /// <summary> | |||
762 | /// Closes the archive and releases any associated resources. | |||
763 | /// </summary> | |||
764 | public virtual void Close() | |||
765 | { | |||
| 0 | 766 | Dispose(true); | ||
| 0 | 767 | } | ||
768 | | |||
769 | /// <summary> | |||
770 | /// Ensures that resources are freed and other cleanup operations are performed | |||
771 | /// when the garbage collector reclaims the <see cref="TarArchive"/>. | |||
772 | /// </summary> | |||
773 | ~TarArchive() | |||
774 | { | |||
| 0 | 775 | Dispose(false); | ||
| 0 | 776 | } | ||
777 | | |||
778 | static void EnsureDirectoryExists(string directoryName) | |||
779 | { | |||
| 0 | 780 | if (!Directory.Exists(directoryName)) { | ||
781 | try { | |||
| 0 | 782 | Directory.CreateDirectory(directoryName); | ||
| 0 | 783 | } catch (Exception e) { | ||
| 0 | 784 | throw new TarException("Exception creating directory '" + directoryName + "', " + e.Message, e); | ||
785 | } | |||
786 | } | |||
| 0 | 787 | } | ||
788 | | |||
789 | // TODO: TarArchive - Is there a better way to test for a text file? | |||
790 | // It no longer reads entire files into memory but is still a weak test! | |||
791 | // This assumes that byte values 0-7, 14-31 or 255 are binary | |||
792 | // and that all non text files contain one of these values | |||
793 | static bool IsBinary(string filename) | |||
794 | { | |||
| 0 | 795 | using (FileStream fs = File.OpenRead(filename)) { | ||
| 0 | 796 | int sampleSize = Math.Min(4096, (int)fs.Length); | ||
| 0 | 797 | byte[] content = new byte[sampleSize]; | ||
798 | | |||
| 0 | 799 | int bytesRead = fs.Read(content, 0, sampleSize); | ||
800 | | |||
| 0 | 801 | for (int i = 0; i < bytesRead; ++i) { | ||
| 0 | 802 | byte b = content[i]; | ||
| 0 | 803 | if ((b < 8) || ((b > 13) && (b < 32)) || (b == 255)) { | ||
| 0 | 804 | return true; | ||
805 | } | |||
806 | } | |||
| 0 | 807 | } | ||
| 0 | 808 | return false; | ||
| 0 | 809 | } | ||
810 | | |||
811 | #region Instance Fields | |||
812 | bool keepOldFiles; | |||
813 | bool asciiTranslate; | |||
814 | | |||
815 | int userId; | |||
| 2 | 816 | string userName = string.Empty; | ||
817 | int groupId; | |||
| 2 | 818 | string groupName = string.Empty; | ||
819 | | |||
820 | string rootPath; | |||
821 | string pathPrefix; | |||
822 | | |||
823 | bool applyUserInfoOverrides; | |||
824 | | |||
825 | TarInputStream tarIn; | |||
826 | TarOutputStream tarOut; | |||
827 | bool isDisposed; | |||
828 | #endregion | |||
829 | } | |||
830 | } |
| Class: | ICSharpCode.SharpZipLib.Tar.TarBuffer |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarBuffer.cs |
| Covered lines: | 96 |
| Uncovered lines: | 50 |
| Coverable lines: | 146 |
| Total lines: | 551 |
| Line coverage: | 65.7% |
| Branch coverage: | 50% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| GetRecordSize() | 1 | 0 | 0 |
| GetBlockFactor() | 1 | 0 | 0 |
| .ctor() | 1 | 100 | 100 |
| CreateInputTarBuffer(...) | 2 | 0 | 0 |
| CreateInputTarBuffer(...) | 3 | 77.78 | 60 |
| CreateOutputTarBuffer(...) | 2 | 0 | 0 |
| CreateOutputTarBuffer(...) | 3 | 77.78 | 60 |
| Initialize(...) | 2 | 100 | 100 |
| IsEOFBlock(...) | 5 | 0 | 0 |
| IsEndOfArchiveBlock(...) | 5 | 80 | 77.78 |
| SkipBlock() | 4 | 0 | 0 |
| ReadBlock() | 4 | 77.78 | 57.14 |
| ReadRecord() | 4 | 91.67 | 71.43 |
| GetCurrentBlockNum() | 1 | 0 | 0 |
| GetCurrentRecordNum() | 1 | 0 | 0 |
| WriteBlock(...) | 5 | 66.67 | 66.67 |
| WriteBlock(...) | 7 | 64.29 | 61.54 |
| WriteRecord() | 2 | 85.71 | 66.67 |
| WriteFinalRecord() | 3 | 87.5 | 60 |
| Close() | 5 | 100 | 88.89 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Tar | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// The TarBuffer class implements the tar archive concept | |||
8 | /// of a buffered input stream. This concept goes back to the | |||
9 | /// days of blocked tape drives and special io devices. In the | |||
10 | /// C# universe, the only real function that this class | |||
11 | /// performs is to ensure that files have the correct "record" | |||
12 | /// size, or other tars will complain. | |||
13 | /// <p> | |||
14 | /// You should never have a need to access this class directly. | |||
15 | /// TarBuffers are created by Tar IO Streams. | |||
16 | /// </p> | |||
17 | /// </summary> | |||
18 | public class TarBuffer | |||
19 | { | |||
20 | | |||
21 | /* A quote from GNU tar man file on blocking and records | |||
22 | A `tar' archive file contains a series of blocks. Each block | |||
23 | contains `BLOCKSIZE' bytes. Although this format may be thought of as | |||
24 | being on magnetic tape, other media are often used. | |||
25 | | |||
26 | Each file archived is represented by a header block which describes | |||
27 | the file, followed by zero or more blocks which give the contents of | |||
28 | the file. At the end of the archive file there may be a block filled | |||
29 | with binary zeros as an end-of-file marker. A reasonable system should | |||
30 | write a block of zeros at the end, but must not assume that such a | |||
31 | block exists when reading an archive. | |||
32 | | |||
33 | The blocks may be "blocked" for physical I/O operations. Each | |||
34 | record of N blocks is written with a single 'write ()' | |||
35 | operation. On magnetic tapes, the result of such a write is a single | |||
36 | record. When writing an archive, the last record of blocks should be | |||
37 | written at the full size, with blocks after the zero block containing | |||
38 | all zeros. When reading an archive, a reasonable system should | |||
39 | properly handle an archive whose last record is shorter than the rest, | |||
40 | or which contains garbage records after a zero block. | |||
41 | */ | |||
42 | | |||
43 | #region Constants | |||
44 | /// <summary> | |||
45 | /// The size of a block in a tar archive in bytes. | |||
46 | /// </summary> | |||
47 | /// <remarks>This is 512 bytes.</remarks> | |||
48 | public const int BlockSize = 512; | |||
49 | | |||
50 | /// <summary> | |||
51 | /// The number of blocks in a default record. | |||
52 | /// </summary> | |||
53 | /// <remarks> | |||
54 | /// The default value is 20 blocks per record. | |||
55 | /// </remarks> | |||
56 | public const int DefaultBlockFactor = 20; | |||
57 | | |||
58 | /// <summary> | |||
59 | /// The size in bytes of a default record. | |||
60 | /// </summary> | |||
61 | /// <remarks> | |||
62 | /// The default size is 10KB. | |||
63 | /// </remarks> | |||
64 | public const int DefaultRecordSize = BlockSize * DefaultBlockFactor; | |||
65 | #endregion | |||
66 | | |||
67 | /// <summary> | |||
68 | /// Get the record size for this buffer | |||
69 | /// </summary> | |||
70 | /// <value>The record size in bytes. | |||
71 | /// This is equal to the <see cref="BlockFactor"/> multiplied by the <see cref="BlockSize"/></value> | |||
72 | public int RecordSize { | |||
73 | get { | |||
| 360 | 74 | return recordSize; | ||
75 | } | |||
76 | } | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Get the TAR Buffer's record size. | |||
80 | /// </summary> | |||
81 | /// <returns>The record size in bytes. | |||
82 | /// This is equal to the <see cref="BlockFactor"/> multiplied by the <see cref="BlockSize"/></returns> | |||
83 | [Obsolete("Use RecordSize property instead")] | |||
84 | public int GetRecordSize() | |||
85 | { | |||
| 0 | 86 | return recordSize; | ||
87 | } | |||
88 | | |||
89 | /// <summary> | |||
90 | /// Get the Blocking factor for the buffer | |||
91 | /// </summary> | |||
92 | /// <value>This is the number of blocks in each record.</value> | |||
93 | public int BlockFactor { | |||
94 | get { | |||
| 4198 | 95 | return blockFactor; | ||
96 | } | |||
97 | } | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Get the TAR Buffer's block factor | |||
101 | /// </summary> | |||
102 | /// <returns>The block factor; the number of blocks per record.</returns> | |||
103 | [Obsolete("Use BlockFactor property instead")] | |||
104 | public int GetBlockFactor() | |||
105 | { | |||
| 0 | 106 | return blockFactor; | ||
107 | } | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Construct a default TarBuffer | |||
111 | /// </summary> | |||
| 78 | 112 | protected TarBuffer() | ||
113 | { | |||
| 78 | 114 | } | ||
115 | | |||
116 | /// <summary> | |||
117 | /// Create TarBuffer for reading with default BlockFactor | |||
118 | /// </summary> | |||
119 | /// <param name="inputStream">Stream to buffer</param> | |||
120 | /// <returns>A new <see cref="TarBuffer"/> suitable for input.</returns> | |||
121 | public static TarBuffer CreateInputTarBuffer(Stream inputStream) | |||
122 | { | |||
| 0 | 123 | if (inputStream == null) { | ||
| 0 | 124 | throw new ArgumentNullException(nameof(inputStream)); | ||
125 | } | |||
126 | | |||
| 0 | 127 | return CreateInputTarBuffer(inputStream, DefaultBlockFactor); | ||
128 | } | |||
129 | | |||
130 | /// <summary> | |||
131 | /// Construct TarBuffer for reading inputStream setting BlockFactor | |||
132 | /// </summary> | |||
133 | /// <param name="inputStream">Stream to buffer</param> | |||
134 | /// <param name="blockFactor">Blocking factor to apply</param> | |||
135 | /// <returns>A new <see cref="TarBuffer"/> suitable for input.</returns> | |||
136 | public static TarBuffer CreateInputTarBuffer(Stream inputStream, int blockFactor) | |||
137 | { | |||
| 5 | 138 | if (inputStream == null) { | ||
| 0 | 139 | throw new ArgumentNullException(nameof(inputStream)); | ||
140 | } | |||
141 | | |||
| 5 | 142 | if (blockFactor <= 0) { | ||
| 0 | 143 | throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative"); | ||
144 | } | |||
145 | | |||
| 5 | 146 | var tarBuffer = new TarBuffer(); | ||
| 5 | 147 | tarBuffer.inputStream = inputStream; | ||
| 5 | 148 | tarBuffer.outputStream = null; | ||
| 5 | 149 | tarBuffer.Initialize(blockFactor); | ||
150 | | |||
| 5 | 151 | return tarBuffer; | ||
152 | } | |||
153 | | |||
154 | /// <summary> | |||
155 | /// Construct TarBuffer for writing with default BlockFactor | |||
156 | /// </summary> | |||
157 | /// <param name="outputStream">output stream for buffer</param> | |||
158 | /// <returns>A new <see cref="TarBuffer"/> suitable for output.</returns> | |||
159 | public static TarBuffer CreateOutputTarBuffer(Stream outputStream) | |||
160 | { | |||
| 0 | 161 | if (outputStream == null) { | ||
| 0 | 162 | throw new ArgumentNullException(nameof(outputStream)); | ||
163 | } | |||
164 | | |||
| 0 | 165 | return CreateOutputTarBuffer(outputStream, DefaultBlockFactor); | ||
166 | } | |||
167 | | |||
168 | /// <summary> | |||
169 | /// Construct TarBuffer for writing Tar output to streams. | |||
170 | /// </summary> | |||
171 | /// <param name="outputStream">Output stream to write to.</param> | |||
172 | /// <param name="blockFactor">Blocking factor to apply</param> | |||
173 | /// <returns>A new <see cref="TarBuffer"/> suitable for output.</returns> | |||
174 | public static TarBuffer CreateOutputTarBuffer(Stream outputStream, int blockFactor) | |||
175 | { | |||
| 73 | 176 | if (outputStream == null) { | ||
| 0 | 177 | throw new ArgumentNullException(nameof(outputStream)); | ||
178 | } | |||
179 | | |||
| 73 | 180 | if (blockFactor <= 0) { | ||
| 0 | 181 | throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative"); | ||
182 | } | |||
183 | | |||
| 73 | 184 | var tarBuffer = new TarBuffer(); | ||
| 73 | 185 | tarBuffer.inputStream = null; | ||
| 73 | 186 | tarBuffer.outputStream = outputStream; | ||
| 73 | 187 | tarBuffer.Initialize(blockFactor); | ||
188 | | |||
| 73 | 189 | return tarBuffer; | ||
190 | } | |||
191 | | |||
192 | /// <summary> | |||
193 | /// Initialization common to all constructors. | |||
194 | /// </summary> | |||
195 | void Initialize(int archiveBlockFactor) | |||
196 | { | |||
| 78 | 197 | blockFactor = archiveBlockFactor; | ||
| 78 | 198 | recordSize = archiveBlockFactor * BlockSize; | ||
| 78 | 199 | recordBuffer = new byte[RecordSize]; | ||
200 | | |||
| 78 | 201 | if (inputStream != null) { | ||
| 5 | 202 | currentRecordIndex = -1; | ||
| 5 | 203 | currentBlockIndex = BlockFactor; | ||
| 5 | 204 | } else { | ||
| 73 | 205 | currentRecordIndex = 0; | ||
| 73 | 206 | currentBlockIndex = 0; | ||
207 | } | |||
| 73 | 208 | } | ||
209 | | |||
210 | /// <summary> | |||
211 | /// Determine if an archive block indicates End of Archive. End of | |||
212 | /// archive is indicated by a block that consists entirely of null bytes. | |||
213 | /// All remaining blocks for the record should also be null's | |||
214 | /// However some older tars only do a couple of null blocks (Old GNU tar for one) | |||
215 | /// and also partial records | |||
216 | /// </summary> | |||
217 | /// <param name = "block">The data block to check.</param> | |||
218 | /// <returns>Returns true if the block is an EOF block; false otherwise.</returns> | |||
219 | [Obsolete("Use IsEndOfArchiveBlock instead")] | |||
220 | public bool IsEOFBlock(byte[] block) | |||
221 | { | |||
| 0 | 222 | if (block == null) { | ||
| 0 | 223 | throw new ArgumentNullException(nameof(block)); | ||
224 | } | |||
225 | | |||
| 0 | 226 | if (block.Length != BlockSize) { | ||
| 0 | 227 | throw new ArgumentException("block length is invalid"); | ||
228 | } | |||
229 | | |||
| 0 | 230 | for (int i = 0; i < BlockSize; ++i) { | ||
| 0 | 231 | if (block[i] != 0) { | ||
| 0 | 232 | return false; | ||
233 | } | |||
234 | } | |||
235 | | |||
| 0 | 236 | return true; | ||
237 | } | |||
238 | | |||
239 | | |||
240 | /// <summary> | |||
241 | /// Determine if an archive block indicates the End of an Archive has been reached. | |||
242 | /// End of archive is indicated by a block that consists entirely of null bytes. | |||
243 | /// All remaining blocks for the record should also be null's | |||
244 | /// However some older tars only do a couple of null blocks (Old GNU tar for one) | |||
245 | /// and also partial records | |||
246 | /// </summary> | |||
247 | /// <param name = "block">The data block to check.</param> | |||
248 | /// <returns>Returns true if the block is an EOF block; false otherwise.</returns> | |||
249 | public static bool IsEndOfArchiveBlock(byte[] block) | |||
250 | { | |||
| 3 | 251 | if (block == null) { | ||
| 0 | 252 | throw new ArgumentNullException(nameof(block)); | ||
253 | } | |||
254 | | |||
| 3 | 255 | if (block.Length != BlockSize) { | ||
| 0 | 256 | throw new ArgumentException("block length is invalid"); | ||
257 | } | |||
258 | | |||
| 1030 | 259 | for (int i = 0; i < BlockSize; ++i) { | ||
| 514 | 260 | if (block[i] != 0) { | ||
| 2 | 261 | return false; | ||
262 | } | |||
263 | } | |||
264 | | |||
| 1 | 265 | return true; | ||
266 | } | |||
267 | | |||
268 | /// <summary> | |||
269 | /// Skip over a block on the input stream. | |||
270 | /// </summary> | |||
271 | public void SkipBlock() | |||
272 | { | |||
| 0 | 273 | if (inputStream == null) { | ||
| 0 | 274 | throw new TarException("no input stream defined"); | ||
275 | } | |||
276 | | |||
| 0 | 277 | if (currentBlockIndex >= BlockFactor) { | ||
| 0 | 278 | if (!ReadRecord()) { | ||
| 0 | 279 | throw new TarException("Failed to read a record"); | ||
280 | } | |||
281 | } | |||
282 | | |||
| 0 | 283 | currentBlockIndex++; | ||
| 0 | 284 | } | ||
285 | | |||
286 | /// <summary> | |||
287 | /// Read a block from the input stream. | |||
288 | /// </summary> | |||
289 | /// <returns> | |||
290 | /// The block of data read. | |||
291 | /// </returns> | |||
292 | public byte[] ReadBlock() | |||
293 | { | |||
| 3 | 294 | if (inputStream == null) { | ||
| 0 | 295 | throw new TarException("TarBuffer.ReadBlock - no input stream defined"); | ||
296 | } | |||
297 | | |||
| 3 | 298 | if (currentBlockIndex >= BlockFactor) { | ||
| 3 | 299 | if (!ReadRecord()) { | ||
| 0 | 300 | throw new TarException("Failed to read a record"); | ||
301 | } | |||
302 | } | |||
303 | | |||
| 3 | 304 | byte[] result = new byte[BlockSize]; | ||
305 | | |||
| 3 | 306 | Array.Copy(recordBuffer, (currentBlockIndex * BlockSize), result, 0, BlockSize); | ||
| 3 | 307 | currentBlockIndex++; | ||
| 3 | 308 | return result; | ||
309 | } | |||
310 | | |||
311 | /// <summary> | |||
312 | /// Read a record from data stream. | |||
313 | /// </summary> | |||
314 | /// <returns> | |||
315 | /// false if End-Of-File, else true. | |||
316 | /// </returns> | |||
317 | bool ReadRecord() | |||
318 | { | |||
| 3 | 319 | if (inputStream == null) { | ||
| 0 | 320 | throw new TarException("no input stream stream defined"); | ||
321 | } | |||
322 | | |||
| 3 | 323 | currentBlockIndex = 0; | ||
324 | | |||
| 3 | 325 | int offset = 0; | ||
| 3 | 326 | int bytesNeeded = RecordSize; | ||
327 | | |||
| 6 | 328 | while (bytesNeeded > 0) { | ||
| 3 | 329 | long numBytes = inputStream.Read(recordBuffer, offset, bytesNeeded); | ||
330 | | |||
331 | // | |||
332 | // NOTE | |||
333 | // We have found EOF, and the record is not full! | |||
334 | // | |||
335 | // This is a broken archive. It does not follow the standard | |||
336 | // blocking algorithm. However, because we are generous, and | |||
337 | // it requires little effort, we will simply ignore the error | |||
338 | // and continue as if the entire record were read. This does | |||
339 | // not appear to break anything upstream. We used to return | |||
340 | // false in this case. | |||
341 | // | |||
342 | // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix. | |||
343 | // | |||
| 3 | 344 | if (numBytes <= 0) { | ||
345 | break; | |||
346 | } | |||
347 | | |||
| 3 | 348 | offset += (int)numBytes; | ||
| 3 | 349 | bytesNeeded -= (int)numBytes; | ||
350 | } | |||
351 | | |||
| 3 | 352 | currentRecordIndex++; | ||
| 3 | 353 | return true; | ||
354 | } | |||
355 | | |||
356 | /// <summary> | |||
357 | /// Get the current block number, within the current record, zero based. | |||
358 | /// </summary> | |||
359 | /// <remarks>Block numbers are zero based values</remarks> | |||
360 | /// <seealso cref="RecordSize"/> | |||
361 | public int CurrentBlock { | |||
| 0 | 362 | get { return currentBlockIndex; } | ||
363 | } | |||
364 | | |||
365 | /// <summary> | |||
366 | /// Get/set flag indicating ownership of the underlying stream. | |||
367 | /// When the flag is true <see cref="Close"></see> will close the underlying stream also. | |||
368 | /// </summary> | |||
369 | public bool IsStreamOwner { | |||
| 0 | 370 | get { return isStreamOwner_; } | ||
| 4 | 371 | set { isStreamOwner_ = value; } | ||
372 | } | |||
373 | | |||
374 | /// <summary> | |||
375 | /// Get the current block number, within the current record, zero based. | |||
376 | /// </summary> | |||
377 | /// <returns> | |||
378 | /// The current zero based block number. | |||
379 | /// </returns> | |||
380 | /// <remarks> | |||
381 | /// The absolute block number = (<see cref="GetCurrentRecordNum">record number</see> * <see cref="BlockFactor">block | |||
382 | /// </remarks> | |||
383 | [Obsolete("Use CurrentBlock property instead")] | |||
384 | public int GetCurrentBlockNum() | |||
385 | { | |||
| 0 | 386 | return currentBlockIndex; | ||
387 | } | |||
388 | | |||
389 | /// <summary> | |||
390 | /// Get the current record number. | |||
391 | /// </summary> | |||
392 | /// <returns> | |||
393 | /// The current zero based record number. | |||
394 | /// </returns> | |||
395 | public int CurrentRecord { | |||
| 0 | 396 | get { return currentRecordIndex; } | ||
397 | } | |||
398 | | |||
399 | /// <summary> | |||
400 | /// Get the current record number. | |||
401 | /// </summary> | |||
402 | /// <returns> | |||
403 | /// The current zero based record number. | |||
404 | /// </returns> | |||
405 | [Obsolete("Use CurrentRecord property instead")] | |||
406 | public int GetCurrentRecordNum() | |||
407 | { | |||
| 0 | 408 | return currentRecordIndex; | ||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Write a block of data to the archive. | |||
413 | /// </summary> | |||
414 | /// <param name="block"> | |||
415 | /// The data to write to the archive. | |||
416 | /// </param> | |||
417 | public void WriteBlock(byte[] block) | |||
418 | { | |||
| 148 | 419 | if (block == null) { | ||
| 0 | 420 | throw new ArgumentNullException(nameof(block)); | ||
421 | } | |||
422 | | |||
| 148 | 423 | if (outputStream == null) { | ||
| 0 | 424 | throw new TarException("TarBuffer.WriteBlock - no output stream defined"); | ||
425 | } | |||
426 | | |||
| 148 | 427 | if (block.Length != BlockSize) { | ||
| 0 | 428 | string errorText = string.Format("TarBuffer.WriteBlock - block to write has length '{0}' which is not the block | ||
| 0 | 429 | block.Length, BlockSize); | ||
| 0 | 430 | throw new TarException(errorText); | ||
431 | } | |||
432 | | |||
| 148 | 433 | if (currentBlockIndex >= BlockFactor) { | ||
| 4 | 434 | WriteRecord(); | ||
435 | } | |||
436 | | |||
| 148 | 437 | Array.Copy(block, 0, recordBuffer, (currentBlockIndex * BlockSize), BlockSize); | ||
| 148 | 438 | currentBlockIndex++; | ||
| 148 | 439 | } | ||
440 | | |||
441 | /// <summary> | |||
442 | /// Write an archive record to the archive, where the record may be | |||
443 | /// inside of a larger array buffer. The buffer must be "offset plus | |||
444 | /// record size" long. | |||
445 | /// </summary> | |||
446 | /// <param name="buffer"> | |||
447 | /// The buffer containing the record data to write. | |||
448 | /// </param> | |||
449 | /// <param name="offset"> | |||
450 | /// The offset of the record data within buffer. | |||
451 | /// </param> | |||
452 | public void WriteBlock(byte[] buffer, int offset) | |||
453 | { | |||
| 4042 | 454 | if (buffer == null) { | ||
| 0 | 455 | throw new ArgumentNullException(nameof(buffer)); | ||
456 | } | |||
457 | | |||
| 4042 | 458 | if (outputStream == null) { | ||
| 0 | 459 | throw new TarException("TarBuffer.WriteBlock - no output stream stream defined"); | ||
460 | } | |||
461 | | |||
| 4042 | 462 | if ((offset < 0) || (offset >= buffer.Length)) { | ||
| 0 | 463 | throw new ArgumentOutOfRangeException(nameof(offset)); | ||
464 | } | |||
465 | | |||
| 4042 | 466 | if ((offset + BlockSize) > buffer.Length) { | ||
| 0 | 467 | string errorText = string.Format("TarBuffer.WriteBlock - record has length '{0}' with offset '{1}' which is less | ||
| 0 | 468 | buffer.Length, offset, recordSize); | ||
| 0 | 469 | throw new TarException(errorText); | ||
470 | } | |||
471 | | |||
| 4042 | 472 | if (currentBlockIndex >= BlockFactor) { | ||
| 128 | 473 | WriteRecord(); | ||
474 | } | |||
475 | | |||
| 4042 | 476 | Array.Copy(buffer, offset, recordBuffer, (currentBlockIndex * BlockSize), BlockSize); | ||
477 | | |||
| 4042 | 478 | currentBlockIndex++; | ||
| 4042 | 479 | } | ||
480 | | |||
481 | /// <summary> | |||
482 | /// Write a TarBuffer record to the archive. | |||
483 | /// </summary> | |||
484 | void WriteRecord() | |||
485 | { | |||
| 205 | 486 | if (outputStream == null) { | ||
| 0 | 487 | throw new TarException("TarBuffer.WriteRecord no output stream defined"); | ||
488 | } | |||
489 | | |||
| 205 | 490 | outputStream.Write(recordBuffer, 0, RecordSize); | ||
| 205 | 491 | outputStream.Flush(); | ||
492 | | |||
| 205 | 493 | currentBlockIndex = 0; | ||
| 205 | 494 | currentRecordIndex++; | ||
| 205 | 495 | } | ||
496 | | |||
497 | /// <summary> | |||
498 | /// WriteFinalRecord writes the current record buffer to output any unwritten data is present. | |||
499 | /// </summary> | |||
500 | /// <remarks>Any trailing bytes are set to zero which is by definition correct behaviour | |||
501 | /// for the end of a tar stream.</remarks> | |||
502 | void WriteFinalRecord() | |||
503 | { | |||
| 73 | 504 | if (outputStream == null) { | ||
| 0 | 505 | throw new TarException("TarBuffer.WriteFinalRecord no output stream defined"); | ||
506 | } | |||
507 | | |||
| 73 | 508 | if (currentBlockIndex > 0) { | ||
| 73 | 509 | int dataBytes = currentBlockIndex * BlockSize; | ||
| 73 | 510 | Array.Clear(recordBuffer, dataBytes, RecordSize - dataBytes); | ||
| 73 | 511 | WriteRecord(); | ||
512 | } | |||
513 | | |||
| 73 | 514 | outputStream.Flush(); | ||
| 73 | 515 | } | ||
516 | | |||
517 | /// <summary> | |||
518 | /// Close the TarBuffer. If this is an output buffer, also flush the | |||
519 | /// current block before closing. | |||
520 | /// </summary> | |||
521 | public void Close() | |||
522 | { | |||
| 78 | 523 | if (outputStream != null) { | ||
| 73 | 524 | WriteFinalRecord(); | ||
525 | | |||
| 73 | 526 | if (isStreamOwner_) { | ||
| 72 | 527 | outputStream.Close(); | ||
528 | } | |||
| 73 | 529 | outputStream = null; | ||
| 78 | 530 | } else if (inputStream != null) { | ||
| 5 | 531 | if (isStreamOwner_) { | ||
| 4 | 532 | inputStream.Close(); | ||
533 | } | |||
| 5 | 534 | inputStream = null; | ||
535 | } | |||
| 5 | 536 | } | ||
537 | | |||
538 | #region Instance Fields | |||
539 | Stream inputStream; | |||
540 | Stream outputStream; | |||
541 | | |||
542 | byte[] recordBuffer; | |||
543 | int currentBlockIndex; | |||
544 | int currentRecordIndex; | |||
545 | | |||
| 78 | 546 | int recordSize = DefaultRecordSize; | ||
| 78 | 547 | int blockFactor = DefaultBlockFactor; | ||
| 78 | 548 | bool isStreamOwner_ = true; | ||
549 | #endregion | |||
550 | } | |||
551 | } |
| Class: | ICSharpCode.SharpZipLib.Tar.TarEntry |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarEntry.cs |
| Covered lines: | 61 |
| Uncovered lines: | 61 |
| Coverable lines: | 122 |
| Total lines: | 496 |
| Line coverage: | 50% |
| Branch coverage: | 21.4% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 2 | 80 | 66.67 |
| Clone() | 1 | 100 | 100 |
| CreateTarEntry(...) | 1 | 100 | 100 |
| CreateEntryFromFile(...) | 1 | 0 | 0 |
| Equals(...) | 2 | 0 | 0 |
| GetHashCode() | 1 | 0 | 0 |
| IsDescendent(...) | 2 | 0 | 0 |
| SetIds(...) | 1 | 0 | 0 |
| SetNames(...) | 1 | 0 | 0 |
| GetFileTarHeader(...) | 8 | 0 | 0 |
| GetDirectoryEntries() | 4 | 0 | 0 |
| WriteEntryHeader(...) | 1 | 100 | 100 |
| AdjustEntryName(...) | 1 | 0 | 0 |
| NameTarHeader(...) | 7 | 88.89 | 55.56 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Tar | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// This class represents an entry in a Tar archive. It consists | |||
8 | /// of the entry's header, as well as the entry's File. Entries | |||
9 | /// can be instantiated in one of three ways, depending on how | |||
10 | /// they are to be used. | |||
11 | /// <p> | |||
12 | /// TarEntries that are created from the header bytes read from | |||
13 | /// an archive are instantiated with the TarEntry( byte[] ) | |||
14 | /// constructor. These entries will be used when extracting from | |||
15 | /// or listing the contents of an archive. These entries have their | |||
16 | /// header filled in using the header bytes. They also set the File | |||
17 | /// to null, since they reference an archive entry not a file.</p> | |||
18 | /// <p> | |||
19 | /// TarEntries that are created from files that are to be written | |||
20 | /// into an archive are instantiated with the CreateEntryFromFile(string) | |||
21 | /// pseudo constructor. These entries have their header filled in using | |||
22 | /// the File's information. They also keep a reference to the File | |||
23 | /// for convenience when writing entries.</p> | |||
24 | /// <p> | |||
25 | /// Finally, TarEntries can be constructed from nothing but a name. | |||
26 | /// This allows the programmer to construct the entry by hand, for | |||
27 | /// instance when only an InputStream is available for writing to | |||
28 | /// the archive, and the header information is constructed from | |||
29 | /// other information. In this case the header fields are set to | |||
30 | /// defaults and the File is set to null.</p> | |||
31 | /// <see cref="TarHeader"/> | |||
32 | /// </summary> | |||
33 | public class TarEntry : ICloneable | |||
34 | { | |||
35 | #region Constructors | |||
36 | /// <summary> | |||
37 | /// Initialise a default instance of <see cref="TarEntry"/>. | |||
38 | /// </summary> | |||
| 79 | 39 | private TarEntry() | ||
40 | { | |||
| 79 | 41 | header = new TarHeader(); | ||
| 79 | 42 | } | ||
43 | | |||
44 | /// <summary> | |||
45 | /// Construct an entry from an archive's header bytes. File is set | |||
46 | /// to null. | |||
47 | /// </summary> | |||
48 | /// <param name = "headerBuffer"> | |||
49 | /// The header bytes from a tar archive entry. | |||
50 | /// </param> | |||
| 1 | 51 | public TarEntry(byte[] headerBuffer) | ||
52 | { | |||
| 1 | 53 | header = new TarHeader(); | ||
| 1 | 54 | header.ParseBuffer(headerBuffer); | ||
| 1 | 55 | } | ||
56 | | |||
57 | /// <summary> | |||
58 | /// Construct a TarEntry using the <paramref name="header">header</paramref> provided | |||
59 | /// </summary> | |||
60 | /// <param name="header">Header details for entry</param> | |||
| 1 | 61 | public TarEntry(TarHeader header) | ||
62 | { | |||
| 1 | 63 | if (header == null) { | ||
| 0 | 64 | throw new ArgumentNullException(nameof(header)); | ||
65 | } | |||
66 | | |||
| 1 | 67 | this.header = (TarHeader)header.Clone(); | ||
| 1 | 68 | } | ||
69 | #endregion | |||
70 | | |||
71 | #region ICloneable Members | |||
72 | /// <summary> | |||
73 | /// Clone this tar entry. | |||
74 | /// </summary> | |||
75 | /// <returns>Returns a clone of this entry.</returns> | |||
76 | public object Clone() | |||
77 | { | |||
| 1 | 78 | var entry = new TarEntry(); | ||
| 1 | 79 | entry.file = file; | ||
| 1 | 80 | entry.header = (TarHeader)header.Clone(); | ||
| 1 | 81 | entry.Name = Name; | ||
| 1 | 82 | return entry; | ||
83 | } | |||
84 | #endregion | |||
85 | | |||
86 | /// <summary> | |||
87 | /// Construct an entry with only a <paramref name="name">name</paramref>. | |||
88 | /// This allows the programmer to construct the entry's header "by hand". | |||
89 | /// </summary> | |||
90 | /// <param name="name">The name to use for the entry</param> | |||
91 | /// <returns>Returns the newly created <see cref="TarEntry"/></returns> | |||
92 | public static TarEntry CreateTarEntry(string name) | |||
93 | { | |||
| 78 | 94 | var entry = new TarEntry(); | ||
| 78 | 95 | TarEntry.NameTarHeader(entry.header, name); | ||
| 78 | 96 | return entry; | ||
97 | } | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Construct an entry for a file. File is set to file, and the | |||
101 | /// header is constructed from information from the file. | |||
102 | /// </summary> | |||
103 | /// <param name = "fileName">The file name that the entry represents.</param> | |||
104 | /// <returns>Returns the newly created <see cref="TarEntry"/></returns> | |||
105 | public static TarEntry CreateEntryFromFile(string fileName) | |||
106 | { | |||
| 0 | 107 | var entry = new TarEntry(); | ||
| 0 | 108 | entry.GetFileTarHeader(entry.header, fileName); | ||
| 0 | 109 | return entry; | ||
110 | } | |||
111 | | |||
112 | /// <summary> | |||
113 | /// Determine if the two entries are equal. Equality is determined | |||
114 | /// by the header names being equal. | |||
115 | /// </summary> | |||
116 | /// <param name="obj">The <see cref="Object"/> to compare with the current Object.</param> | |||
117 | /// <returns> | |||
118 | /// True if the entries are equal; false if not. | |||
119 | /// </returns> | |||
120 | public override bool Equals(object obj) | |||
121 | { | |||
| 0 | 122 | var localEntry = obj as TarEntry; | ||
123 | | |||
| 0 | 124 | if (localEntry != null) { | ||
| 0 | 125 | return Name.Equals(localEntry.Name); | ||
126 | } | |||
| 0 | 127 | return false; | ||
128 | } | |||
129 | | |||
130 | /// <summary> | |||
131 | /// Derive a Hash value for the current <see cref="Object"/> | |||
132 | /// </summary> | |||
133 | /// <returns>A Hash code for the current <see cref="Object"/></returns> | |||
134 | public override int GetHashCode() | |||
135 | { | |||
| 0 | 136 | return Name.GetHashCode(); | ||
137 | } | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Determine if the given entry is a descendant of this entry. | |||
141 | /// Descendancy is determined by the name of the descendant | |||
142 | /// starting with this entry's name. | |||
143 | /// </summary> | |||
144 | /// <param name = "toTest"> | |||
145 | /// Entry to be checked as a descendent of this. | |||
146 | /// </param> | |||
147 | /// <returns> | |||
148 | /// True if entry is a descendant of this. | |||
149 | /// </returns> | |||
150 | public bool IsDescendent(TarEntry toTest) | |||
151 | { | |||
| 0 | 152 | if (toTest == null) { | ||
| 0 | 153 | throw new ArgumentNullException(nameof(toTest)); | ||
154 | } | |||
155 | | |||
| 0 | 156 | return toTest.Name.StartsWith(Name, StringComparison.Ordinal); | ||
157 | } | |||
158 | | |||
159 | /// <summary> | |||
160 | /// Get this entry's header. | |||
161 | /// </summary> | |||
162 | /// <returns> | |||
163 | /// This entry's TarHeader. | |||
164 | /// </returns> | |||
165 | public TarHeader TarHeader { | |||
166 | get { | |||
| 78 | 167 | return header; | ||
168 | } | |||
169 | } | |||
170 | | |||
171 | /// <summary> | |||
172 | /// Get/Set this entry's name. | |||
173 | /// </summary> | |||
174 | public string Name { | |||
175 | get { | |||
| 73 | 176 | return header.Name; | ||
177 | } | |||
178 | set { | |||
| 2 | 179 | header.Name = value; | ||
| 1 | 180 | } | ||
181 | } | |||
182 | | |||
183 | /// <summary> | |||
184 | /// Get/set this entry's user id. | |||
185 | /// </summary> | |||
186 | public int UserId { | |||
187 | get { | |||
| 0 | 188 | return header.UserId; | ||
189 | } | |||
190 | set { | |||
| 0 | 191 | header.UserId = value; | ||
| 0 | 192 | } | ||
193 | } | |||
194 | | |||
195 | /// <summary> | |||
196 | /// Get/set this entry's group id. | |||
197 | /// </summary> | |||
198 | public int GroupId { | |||
199 | get { | |||
| 2 | 200 | return header.GroupId; | ||
201 | } | |||
202 | set { | |||
| 1 | 203 | header.GroupId = value; | ||
| 1 | 204 | } | ||
205 | } | |||
206 | | |||
207 | /// <summary> | |||
208 | /// Get/set this entry's user name. | |||
209 | /// </summary> | |||
210 | public string UserName { | |||
211 | get { | |||
| 2 | 212 | return header.UserName; | ||
213 | } | |||
214 | set { | |||
| 2 | 215 | header.UserName = value; | ||
| 2 | 216 | } | ||
217 | } | |||
218 | | |||
219 | /// <summary> | |||
220 | /// Get/set this entry's group name. | |||
221 | /// </summary> | |||
222 | public string GroupName { | |||
223 | get { | |||
| 3 | 224 | return header.GroupName; | ||
225 | } | |||
226 | set { | |||
| 2 | 227 | header.GroupName = value; | ||
| 2 | 228 | } | ||
229 | } | |||
230 | | |||
231 | /// <summary> | |||
232 | /// Convenience method to set this entry's group and user ids. | |||
233 | /// </summary> | |||
234 | /// <param name="userId"> | |||
235 | /// This entry's new user id. | |||
236 | /// </param> | |||
237 | /// <param name="groupId"> | |||
238 | /// This entry's new group id. | |||
239 | /// </param> | |||
240 | public void SetIds(int userId, int groupId) | |||
241 | { | |||
| 0 | 242 | UserId = userId; | ||
| 0 | 243 | GroupId = groupId; | ||
| 0 | 244 | } | ||
245 | | |||
246 | /// <summary> | |||
247 | /// Convenience method to set this entry's group and user names. | |||
248 | /// </summary> | |||
249 | /// <param name="userName"> | |||
250 | /// This entry's new user name. | |||
251 | /// </param> | |||
252 | /// <param name="groupName"> | |||
253 | /// This entry's new group name. | |||
254 | /// </param> | |||
255 | public void SetNames(string userName, string groupName) | |||
256 | { | |||
| 0 | 257 | UserName = userName; | ||
| 0 | 258 | GroupName = groupName; | ||
| 0 | 259 | } | ||
260 | | |||
261 | /// <summary> | |||
262 | /// Get/Set the modification time for this entry | |||
263 | /// </summary> | |||
264 | public DateTime ModTime { | |||
265 | get { | |||
| 2 | 266 | return header.ModTime; | ||
267 | } | |||
268 | set { | |||
| 2 | 269 | header.ModTime = value; | ||
| 1 | 270 | } | ||
271 | } | |||
272 | | |||
273 | /// <summary> | |||
274 | /// Get this entry's file. | |||
275 | /// </summary> | |||
276 | /// <returns> | |||
277 | /// This entry's file. | |||
278 | /// </returns> | |||
279 | public string File { | |||
280 | get { | |||
| 2 | 281 | return file; | ||
282 | } | |||
283 | } | |||
284 | | |||
285 | /// <summary> | |||
286 | /// Get/set this entry's recorded file size. | |||
287 | /// </summary> | |||
288 | public long Size { | |||
289 | get { | |||
| 73 | 290 | return header.Size; | ||
291 | } | |||
292 | set { | |||
| 70 | 293 | header.Size = value; | ||
| 69 | 294 | } | ||
295 | } | |||
296 | | |||
297 | /// <summary> | |||
298 | /// Return true if this entry represents a directory, false otherwise | |||
299 | /// </summary> | |||
300 | /// <returns> | |||
301 | /// True if this entry is a directory. | |||
302 | /// </returns> | |||
303 | public bool IsDirectory { | |||
304 | get { | |||
| 72 | 305 | if (file != null) { | ||
| 0 | 306 | return Directory.Exists(file); | ||
307 | } | |||
308 | | |||
| 72 | 309 | if (header != null) { | ||
| 72 | 310 | if ((header.TypeFlag == TarHeader.LF_DIR) || Name.EndsWith("/", StringComparison.Ordinal)) { | ||
| 0 | 311 | return true; | ||
312 | } | |||
313 | } | |||
| 72 | 314 | return false; | ||
315 | } | |||
316 | } | |||
317 | | |||
318 | /// <summary> | |||
319 | /// Fill in a TarHeader with information from a File. | |||
320 | /// </summary> | |||
321 | /// <param name="header"> | |||
322 | /// The TarHeader to fill in. | |||
323 | /// </param> | |||
324 | /// <param name="file"> | |||
325 | /// The file from which to get the header information. | |||
326 | /// </param> | |||
327 | public void GetFileTarHeader(TarHeader header, string file) | |||
328 | { | |||
| 0 | 329 | if (header == null) { | ||
| 0 | 330 | throw new ArgumentNullException(nameof(header)); | ||
331 | } | |||
332 | | |||
| 0 | 333 | if (file == null) { | ||
| 0 | 334 | throw new ArgumentNullException(nameof(file)); | ||
335 | } | |||
336 | | |||
| 0 | 337 | this.file = file; | ||
338 | | |||
339 | // bugfix from torhovl from #D forum: | |||
| 0 | 340 | string name = file; | ||
341 | | |||
342 | // 23-Jan-2004 GnuTar allows device names in path where the name is not local to the current directory | |||
| 0 | 343 | if (name.IndexOf(Environment.CurrentDirectory, StringComparison.Ordinal) == 0) { | ||
| 0 | 344 | name = name.Substring(Environment.CurrentDirectory.Length); | ||
345 | } | |||
346 | | |||
347 | /* | |||
348 | if (Path.DirectorySeparatorChar == '\\') | |||
349 | { | |||
350 | // check if the OS is Windows | |||
351 | // Strip off drive letters! | |||
352 | if (name.Length > 2) | |||
353 | { | |||
354 | char ch1 = name[0]; | |||
355 | char ch2 = name[1]; | |||
356 | | |||
357 | if (ch2 == ':' && Char.IsLetter(ch1)) | |||
358 | { | |||
359 | name = name.Substring(2); | |||
360 | } | |||
361 | } | |||
362 | } | |||
363 | */ | |||
364 | | |||
| 0 | 365 | name = name.Replace(Path.DirectorySeparatorChar, '/'); | ||
366 | | |||
367 | // No absolute pathnames | |||
368 | // Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\", | |||
369 | // so we loop on starting /'s. | |||
| 0 | 370 | while (name.StartsWith("/", StringComparison.Ordinal)) { | ||
| 0 | 371 | name = name.Substring(1); | ||
372 | } | |||
373 | | |||
| 0 | 374 | header.LinkName = String.Empty; | ||
| 0 | 375 | header.Name = name; | ||
376 | | |||
| 0 | 377 | if (Directory.Exists(file)) { | ||
| 0 | 378 | header.Mode = 1003; // Magic number for security access for a UNIX filesystem | ||
| 0 | 379 | header.TypeFlag = TarHeader.LF_DIR; | ||
| 0 | 380 | if ((header.Name.Length == 0) || header.Name[header.Name.Length - 1] != '/') { | ||
| 0 | 381 | header.Name = header.Name + "/"; | ||
382 | } | |||
383 | | |||
| 0 | 384 | header.Size = 0; | ||
| 0 | 385 | } else { | ||
| 0 | 386 | header.Mode = 33216; // Magic number for security access for a UNIX filesystem | ||
| 0 | 387 | header.TypeFlag = TarHeader.LF_NORMAL; | ||
| 0 | 388 | header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length; | ||
389 | } | |||
390 | | |||
| 0 | 391 | header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime() | ||
| 0 | 392 | header.DevMajor = 0; | ||
| 0 | 393 | header.DevMinor = 0; | ||
| 0 | 394 | } | ||
395 | | |||
396 | /// <summary> | |||
397 | /// Get entries for all files present in this entries directory. | |||
398 | /// If this entry doesnt represent a directory zero entries are returned. | |||
399 | /// </summary> | |||
400 | /// <returns> | |||
401 | /// An array of TarEntry's for this entry's children. | |||
402 | /// </returns> | |||
403 | public TarEntry[] GetDirectoryEntries() | |||
404 | { | |||
| 0 | 405 | if ((file == null) || !Directory.Exists(file)) { | ||
| 0 | 406 | return new TarEntry[0]; | ||
407 | } | |||
408 | | |||
| 0 | 409 | string[] list = Directory.GetFileSystemEntries(file); | ||
| 0 | 410 | TarEntry[] result = new TarEntry[list.Length]; | ||
411 | | |||
| 0 | 412 | for (int i = 0; i < list.Length; ++i) { | ||
| 0 | 413 | result[i] = TarEntry.CreateEntryFromFile(list[i]); | ||
414 | } | |||
415 | | |||
| 0 | 416 | return result; | ||
417 | } | |||
418 | | |||
419 | /// <summary> | |||
420 | /// Write an entry's header information to a header buffer. | |||
421 | /// </summary> | |||
422 | /// <param name = "outBuffer"> | |||
423 | /// The tar entry header buffer to fill in. | |||
424 | /// </param> | |||
425 | public void WriteEntryHeader(byte[] outBuffer) | |||
426 | { | |||
| 70 | 427 | header.WriteHeader(outBuffer); | ||
| 70 | 428 | } | ||
429 | | |||
430 | /// <summary> | |||
431 | /// Convenience method that will modify an entry's name directly | |||
432 | /// in place in an entry header buffer byte array. | |||
433 | /// </summary> | |||
434 | /// <param name="buffer"> | |||
435 | /// The buffer containing the entry header to modify. | |||
436 | /// </param> | |||
437 | /// <param name="newName"> | |||
438 | /// The new name to place into the header buffer. | |||
439 | /// </param> | |||
440 | static public void AdjustEntryName(byte[] buffer, string newName) | |||
441 | { | |||
| 0 | 442 | TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN); | ||
| 0 | 443 | } | ||
444 | | |||
445 | /// <summary> | |||
446 | /// Fill in a TarHeader given only the entry's name. | |||
447 | /// </summary> | |||
448 | /// <param name="header"> | |||
449 | /// The TarHeader to fill in. | |||
450 | /// </param> | |||
451 | /// <param name="name"> | |||
452 | /// The tar entry name. | |||
453 | /// </param> | |||
454 | static public void NameTarHeader(TarHeader header, string name) | |||
455 | { | |||
| 78 | 456 | if (header == null) { | ||
| 0 | 457 | throw new ArgumentNullException(nameof(header)); | ||
458 | } | |||
459 | | |||
| 78 | 460 | if (name == null) { | ||
| 0 | 461 | throw new ArgumentNullException(nameof(name)); | ||
462 | } | |||
463 | | |||
| 78 | 464 | bool isDir = name.EndsWith("/", StringComparison.Ordinal); | ||
465 | | |||
| 78 | 466 | header.Name = name; | ||
| 78 | 467 | header.Mode = isDir ? 1003 : 33216; | ||
| 78 | 468 | header.UserId = 0; | ||
| 78 | 469 | header.GroupId = 0; | ||
| 78 | 470 | header.Size = 0; | ||
471 | | |||
| 78 | 472 | header.ModTime = DateTime.UtcNow; | ||
473 | | |||
| 78 | 474 | header.TypeFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL; | ||
475 | | |||
| 78 | 476 | header.LinkName = String.Empty; | ||
| 78 | 477 | header.UserName = String.Empty; | ||
| 78 | 478 | header.GroupName = String.Empty; | ||
479 | | |||
| 78 | 480 | header.DevMajor = 0; | ||
| 78 | 481 | header.DevMinor = 0; | ||
| 78 | 482 | } | ||
483 | | |||
484 | #region Instance Fields | |||
485 | /// <summary> | |||
486 | /// The name of the file this entry represents or null if the entry is not based on a file. | |||
487 | /// </summary> | |||
488 | string file; | |||
489 | | |||
490 | /// <summary> | |||
491 | /// The entry's header information. | |||
492 | /// </summary> | |||
493 | TarHeader header; | |||
494 | #endregion | |||
495 | } | |||
496 | } |
| Class: | ICSharpCode.SharpZipLib.Tar.TarException |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarException.cs |
| Covered lines: | 2 |
| Uncovered lines: | 6 |
| Coverable lines: | 8 |
| Total lines: | 48 |
| Line coverage: | 25% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Runtime.Serialization; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Tar | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// TarException represents exceptions specific to Tar classes and code. | |||
8 | /// </summary> | |||
9 | [Serializable] | |||
10 | public class TarException : SharpZipBaseException | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Deserialization constructor | |||
14 | /// </summary> | |||
15 | /// <param name="info"><see cref="SerializationInfo"/> for this constructor</param> | |||
16 | /// <param name="context"><see cref="StreamingContext"/> for this constructor</param> | |||
17 | protected TarException(SerializationInfo info, StreamingContext context) | |||
| 0 | 18 | : base(info, context) | ||
19 | { | |||
| 0 | 20 | } | ||
21 | | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="TarException" />. | |||
24 | /// </summary> | |||
| 0 | 25 | public TarException() | ||
26 | { | |||
| 0 | 27 | } | ||
28 | | |||
29 | /// <summary> | |||
30 | /// Initialise a new instance of <see cref="TarException" /> with its message string. | |||
31 | /// </summary> | |||
32 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
33 | public TarException(string message) | |||
| 1 | 34 | : base(message) | ||
35 | { | |||
| 1 | 36 | } | ||
37 | | |||
38 | /// <summary> | |||
39 | /// Initialise a new instance of <see cref="TarException" />. | |||
40 | /// </summary> | |||
41 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
42 | /// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param> | |||
43 | public TarException(string message, Exception innerException) | |||
| 0 | 44 | : base(message, innerException) | ||
45 | { | |||
| 0 | 46 | } | ||
47 | } | |||
48 | } |
| Class: | ICSharpCode.SharpZipLib.Tar.TarHeader |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarHeader.cs |
| Covered lines: | 209 |
| Uncovered lines: | 63 |
| Coverable lines: | 272 |
| Total lines: | 1077 |
| Line coverage: | 76.8% |
| Branch coverage: | 66.9% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| GetName() | 1 | 0 | 0 |
| Clone() | 1 | 100 | 100 |
| ParseBuffer(...) | 4 | 63.16 | 42.86 |
| WriteHeader(...) | 6 | 89.66 | 72.73 |
| GetHashCode() | 1 | 0 | 0 |
| Equals(...) | 16 | 83.33 | 66.67 |
| SetValueDefaults(...) | 1 | 0 | 0 |
| RestoreSetValues() | 1 | 0 | 0 |
| ParseBinaryOrOctal(...) | 3 | 25 | 40 |
| ParseOctal(...) | 8 | 93.33 | 73.33 |
| ParseName(...) | 7 | 73.33 | 69.23 |
| GetNameBytes(...) | 3 | 0 | 0 |
| GetNameBytes(...) | 6 | 83.33 | 72.73 |
| GetNameBytes(...) | 3 | 0 | 0 |
| GetNameBytes(...) | 3 | 60 | 60 |
| GetAsciiBytes(...) | 5 | 77.78 | 77.78 |
| GetOctalBytes(...) | 6 | 93.33 | 90.91 |
| GetBinaryOrOctalBytes(...) | 3 | 22.22 | 40 |
| GetCheckSumOctalBytes(...) | 1 | 100 | 100 |
| ComputeCheckSum(...) | 2 | 100 | 100 |
| MakeCheckSum(...) | 4 | 100 | 100 |
| GetCTime(...) | 1 | 100 | 100 |
| GetDateTimeFromCTime(...) | 1 | 50 | 100 |
| .cctor() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Text; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Tar | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// This class encapsulates the Tar Entry Header used in Tar Archives. | |||
8 | /// The class also holds a number of tar constants, used mostly in headers. | |||
9 | /// </summary> | |||
10 | /// <remarks> | |||
11 | /// The tar format and its POSIX successor PAX have a long history which makes for compatability | |||
12 | /// issues when creating and reading files. | |||
13 | /// | |||
14 | /// This is further complicated by a large number of programs with variations on formats | |||
15 | /// One common issue is the handling of names longer than 100 characters. | |||
16 | /// GNU style long names are currently supported. | |||
17 | /// | |||
18 | /// This is the ustar (Posix 1003.1) header. | |||
19 | /// | |||
20 | /// struct header | |||
21 | /// { | |||
22 | /// char t_name[100]; // 0 Filename | |||
23 | /// char t_mode[8]; // 100 Permissions | |||
24 | /// char t_uid[8]; // 108 Numerical User ID | |||
25 | /// char t_gid[8]; // 116 Numerical Group ID | |||
26 | /// char t_size[12]; // 124 Filesize | |||
27 | /// char t_mtime[12]; // 136 st_mtime | |||
28 | /// char t_chksum[8]; // 148 Checksum | |||
29 | /// char t_typeflag; // 156 Type of File | |||
30 | /// char t_linkname[100]; // 157 Target of Links | |||
31 | /// char t_magic[6]; // 257 "ustar" or other... | |||
32 | /// char t_version[2]; // 263 Version fixed to 00 | |||
33 | /// char t_uname[32]; // 265 User Name | |||
34 | /// char t_gname[32]; // 297 Group Name | |||
35 | /// char t_devmajor[8]; // 329 Major for devices | |||
36 | /// char t_devminor[8]; // 337 Minor for devices | |||
37 | /// char t_prefix[155]; // 345 Prefix for t_name | |||
38 | /// char t_mfill[12]; // 500 Filler up to 512 | |||
39 | /// }; | |||
40 | /// </remarks> | |||
41 | public class TarHeader : ICloneable | |||
42 | { | |||
43 | #region Constants | |||
44 | /// <summary> | |||
45 | /// The length of the name field in a header buffer. | |||
46 | /// </summary> | |||
47 | public const int NAMELEN = 100; | |||
48 | | |||
49 | /// <summary> | |||
50 | /// The length of the mode field in a header buffer. | |||
51 | /// </summary> | |||
52 | public const int MODELEN = 8; | |||
53 | | |||
54 | /// <summary> | |||
55 | /// The length of the user id field in a header buffer. | |||
56 | /// </summary> | |||
57 | public const int UIDLEN = 8; | |||
58 | | |||
59 | /// <summary> | |||
60 | /// The length of the group id field in a header buffer. | |||
61 | /// </summary> | |||
62 | public const int GIDLEN = 8; | |||
63 | | |||
64 | /// <summary> | |||
65 | /// The length of the checksum field in a header buffer. | |||
66 | /// </summary> | |||
67 | public const int CHKSUMLEN = 8; | |||
68 | | |||
69 | /// <summary> | |||
70 | /// Offset of checksum in a header buffer. | |||
71 | /// </summary> | |||
72 | public const int CHKSUMOFS = 148; | |||
73 | | |||
74 | /// <summary> | |||
75 | /// The length of the size field in a header buffer. | |||
76 | /// </summary> | |||
77 | public const int SIZELEN = 12; | |||
78 | | |||
79 | /// <summary> | |||
80 | /// The length of the magic field in a header buffer. | |||
81 | /// </summary> | |||
82 | public const int MAGICLEN = 6; | |||
83 | | |||
84 | /// <summary> | |||
85 | /// The length of the version field in a header buffer. | |||
86 | /// </summary> | |||
87 | public const int VERSIONLEN = 2; | |||
88 | | |||
89 | /// <summary> | |||
90 | /// The length of the modification time field in a header buffer. | |||
91 | /// </summary> | |||
92 | public const int MODTIMELEN = 12; | |||
93 | | |||
94 | /// <summary> | |||
95 | /// The length of the user name field in a header buffer. | |||
96 | /// </summary> | |||
97 | public const int UNAMELEN = 32; | |||
98 | | |||
99 | /// <summary> | |||
100 | /// The length of the group name field in a header buffer. | |||
101 | /// </summary> | |||
102 | public const int GNAMELEN = 32; | |||
103 | | |||
104 | /// <summary> | |||
105 | /// The length of the devices field in a header buffer. | |||
106 | /// </summary> | |||
107 | public const int DEVLEN = 8; | |||
108 | | |||
109 | /// <summary> | |||
110 | /// The length of the name prefix field in a header buffer. | |||
111 | /// </summary> | |||
112 | public const int PREFIXLEN = 155; | |||
113 | | |||
114 | // | |||
115 | // LF_ constants represent the "type" of an entry | |||
116 | // | |||
117 | | |||
118 | /// <summary> | |||
119 | /// The "old way" of indicating a normal file. | |||
120 | /// </summary> | |||
121 | public const byte LF_OLDNORM = 0; | |||
122 | | |||
123 | /// <summary> | |||
124 | /// Normal file type. | |||
125 | /// </summary> | |||
126 | public const byte LF_NORMAL = (byte)'0'; | |||
127 | | |||
128 | /// <summary> | |||
129 | /// Link file type. | |||
130 | /// </summary> | |||
131 | public const byte LF_LINK = (byte)'1'; | |||
132 | | |||
133 | /// <summary> | |||
134 | /// Symbolic link file type. | |||
135 | /// </summary> | |||
136 | public const byte LF_SYMLINK = (byte)'2'; | |||
137 | | |||
138 | /// <summary> | |||
139 | /// Character device file type. | |||
140 | /// </summary> | |||
141 | public const byte LF_CHR = (byte)'3'; | |||
142 | | |||
143 | /// <summary> | |||
144 | /// Block device file type. | |||
145 | /// </summary> | |||
146 | public const byte LF_BLK = (byte)'4'; | |||
147 | | |||
148 | /// <summary> | |||
149 | /// Directory file type. | |||
150 | /// </summary> | |||
151 | public const byte LF_DIR = (byte)'5'; | |||
152 | | |||
153 | /// <summary> | |||
154 | /// FIFO (pipe) file type. | |||
155 | /// </summary> | |||
156 | public const byte LF_FIFO = (byte)'6'; | |||
157 | | |||
158 | /// <summary> | |||
159 | /// Contiguous file type. | |||
160 | /// </summary> | |||
161 | public const byte LF_CONTIG = (byte)'7'; | |||
162 | | |||
163 | /// <summary> | |||
164 | /// Posix.1 2001 global extended header | |||
165 | /// </summary> | |||
166 | public const byte LF_GHDR = (byte)'g'; | |||
167 | | |||
168 | /// <summary> | |||
169 | /// Posix.1 2001 extended header | |||
170 | /// </summary> | |||
171 | public const byte LF_XHDR = (byte)'x'; | |||
172 | | |||
173 | // POSIX allows for upper case ascii type as extensions | |||
174 | | |||
175 | /// <summary> | |||
176 | /// Solaris access control list file type | |||
177 | /// </summary> | |||
178 | public const byte LF_ACL = (byte)'A'; | |||
179 | | |||
180 | /// <summary> | |||
181 | /// GNU dir dump file type | |||
182 | /// This is a dir entry that contains the names of files that were in the | |||
183 | /// dir at the time the dump was made | |||
184 | /// </summary> | |||
185 | public const byte LF_GNU_DUMPDIR = (byte)'D'; | |||
186 | | |||
187 | /// <summary> | |||
188 | /// Solaris Extended Attribute File | |||
189 | /// </summary> | |||
190 | public const byte LF_EXTATTR = (byte)'E'; | |||
191 | | |||
192 | /// <summary> | |||
193 | /// Inode (metadata only) no file content | |||
194 | /// </summary> | |||
195 | public const byte LF_META = (byte)'I'; | |||
196 | | |||
197 | /// <summary> | |||
198 | /// Identifies the next file on the tape as having a long link name | |||
199 | /// </summary> | |||
200 | public const byte LF_GNU_LONGLINK = (byte)'K'; | |||
201 | | |||
202 | /// <summary> | |||
203 | /// Identifies the next file on the tape as having a long name | |||
204 | /// </summary> | |||
205 | public const byte LF_GNU_LONGNAME = (byte)'L'; | |||
206 | | |||
207 | /// <summary> | |||
208 | /// Continuation of a file that began on another volume | |||
209 | /// </summary> | |||
210 | public const byte LF_GNU_MULTIVOL = (byte)'M'; | |||
211 | | |||
212 | /// <summary> | |||
213 | /// For storing filenames that dont fit in the main header (old GNU) | |||
214 | /// </summary> | |||
215 | public const byte LF_GNU_NAMES = (byte)'N'; | |||
216 | | |||
217 | /// <summary> | |||
218 | /// GNU Sparse file | |||
219 | /// </summary> | |||
220 | public const byte LF_GNU_SPARSE = (byte)'S'; | |||
221 | | |||
222 | /// <summary> | |||
223 | /// GNU Tape/volume header ignore on extraction | |||
224 | /// </summary> | |||
225 | public const byte LF_GNU_VOLHDR = (byte)'V'; | |||
226 | | |||
227 | /// <summary> | |||
228 | /// The magic tag representing a POSIX tar archive. (includes trailing NULL) | |||
229 | /// </summary> | |||
230 | public const string TMAGIC = "ustar "; | |||
231 | | |||
232 | /// <summary> | |||
233 | /// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it | |||
234 | /// </summary> | |||
235 | public const string GNU_TMAGIC = "ustar "; | |||
236 | | |||
237 | const long timeConversionFactor = 10000000L; // 1 tick == 100 nanoseconds | |||
| 1 | 238 | readonly static DateTime dateTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0); | ||
239 | #endregion | |||
240 | | |||
241 | #region Constructors | |||
242 | | |||
243 | /// <summary> | |||
244 | /// Initialise a default TarHeader instance | |||
245 | /// </summary> | |||
| 84 | 246 | public TarHeader() | ||
247 | { | |||
| 84 | 248 | Magic = TMAGIC; | ||
| 84 | 249 | Version = " "; | ||
250 | | |||
| 84 | 251 | Name = ""; | ||
| 84 | 252 | LinkName = ""; | ||
253 | | |||
| 84 | 254 | UserId = defaultUserId; | ||
| 84 | 255 | GroupId = defaultGroupId; | ||
| 84 | 256 | UserName = defaultUser; | ||
| 84 | 257 | GroupName = defaultGroupName; | ||
| 84 | 258 | Size = 0; | ||
| 84 | 259 | } | ||
260 | | |||
261 | #endregion | |||
262 | | |||
263 | #region Properties | |||
264 | /// <summary> | |||
265 | /// Get/set the name for this tar entry. | |||
266 | /// </summary> | |||
267 | /// <exception cref="ArgumentNullException">Thrown when attempting to set the property to null.</exception> | |||
268 | public string Name { | |||
| 214 | 269 | get { return name; } | ||
270 | set { | |||
| 166 | 271 | if (value == null) { | ||
| 1 | 272 | throw new ArgumentNullException(nameof(value)); | ||
273 | } | |||
| 165 | 274 | name = value; | ||
| 165 | 275 | } | ||
276 | } | |||
277 | | |||
278 | /// <summary> | |||
279 | /// Get the name of this entry. | |||
280 | /// </summary> | |||
281 | /// <returns>The entry's name.</returns> | |||
282 | [Obsolete("Use the Name property instead", true)] | |||
283 | public string GetName() | |||
284 | { | |||
| 0 | 285 | return name; | ||
286 | } | |||
287 | | |||
288 | /// <summary> | |||
289 | /// Get/set the entry's Unix style permission mode. | |||
290 | /// </summary> | |||
291 | public int Mode { | |||
| 1 | 292 | get { return mode; } | ||
| 162 | 293 | set { mode = value; } | ||
294 | } | |||
295 | | |||
296 | | |||
297 | /// <summary> | |||
298 | /// The entry's user id. | |||
299 | /// </summary> | |||
300 | /// <remarks> | |||
301 | /// This is only directly relevant to unix systems. | |||
302 | /// The default is zero. | |||
303 | /// </remarks> | |||
304 | public int UserId { | |||
| 125 | 305 | get { return userId; } | ||
| 334 | 306 | set { userId = value; } | ||
307 | } | |||
308 | | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Get/set the entry's group id. | |||
312 | /// </summary> | |||
313 | /// <remarks> | |||
314 | /// This is only directly relevant to linux/unix systems. | |||
315 | /// The default value is zero. | |||
316 | /// </remarks> | |||
317 | public int GroupId { | |||
| 125 | 318 | get { return groupId; } | ||
| 336 | 319 | set { groupId = value; } | ||
320 | } | |||
321 | | |||
322 | | |||
323 | /// <summary> | |||
324 | /// Get/set the entry's size. | |||
325 | /// </summary> | |||
326 | /// <exception cref="ArgumentOutOfRangeException">Thrown when setting the size to less than zero.</exception> | |||
327 | public long Size { | |||
| 195 | 328 | get { return size; } | ||
329 | set { | |||
| 237 | 330 | if (value < 0) { | ||
| 1 | 331 | throw new ArgumentOutOfRangeException(nameof(value), "Cannot be less than zero"); | ||
332 | } | |||
| 236 | 333 | size = value; | ||
| 236 | 334 | } | ||
335 | } | |||
336 | | |||
337 | | |||
338 | /// <summary> | |||
339 | /// Get/set the entry's modification time. | |||
340 | /// </summary> | |||
341 | /// <remarks> | |||
342 | /// The modification time is only accurate to within a second. | |||
343 | /// </remarks> | |||
344 | /// <exception cref="ArgumentOutOfRangeException">Thrown when setting the date time to less than 1/1/1970.</exceptio | |||
345 | public DateTime ModTime { | |||
| 121 | 346 | get { return modTime; } | ||
347 | set { | |||
| 85 | 348 | if (value < dateTime1970) { | ||
| 1 | 349 | throw new ArgumentOutOfRangeException(nameof(value), "ModTime cannot be before Jan 1st 1970"); | ||
350 | } | |||
| 84 | 351 | modTime = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second); | ||
| 84 | 352 | } | ||
353 | } | |||
354 | | |||
355 | | |||
356 | /// <summary> | |||
357 | /// Get the entry's checksum. This is only valid/updated after writing or reading an entry. | |||
358 | /// </summary> | |||
359 | public int Checksum { | |||
| 51 | 360 | get { return checksum; } | ||
361 | } | |||
362 | | |||
363 | | |||
364 | /// <summary> | |||
365 | /// Get value of true if the header checksum is valid, false otherwise. | |||
366 | /// </summary> | |||
367 | public bool IsChecksumValid { | |||
| 3 | 368 | get { return isChecksumValid; } | ||
369 | } | |||
370 | | |||
371 | | |||
372 | /// <summary> | |||
373 | /// Get/set the entry's type flag. | |||
374 | /// </summary> | |||
375 | public byte TypeFlag { | |||
| 334 | 376 | get { return typeFlag; } | ||
| 166 | 377 | set { typeFlag = value; } | ||
378 | } | |||
379 | | |||
380 | | |||
381 | /// <summary> | |||
382 | /// The entry's link name. | |||
383 | /// </summary> | |||
384 | /// <exception cref="ArgumentNullException">Thrown when attempting to set LinkName to null.</exception> | |||
385 | public string LinkName { | |||
| 120 | 386 | get { return linkName; } | ||
387 | set { | |||
| 170 | 388 | if (value == null) { | ||
| 1 | 389 | throw new ArgumentNullException(nameof(value)); | ||
390 | } | |||
| 169 | 391 | linkName = value; | ||
| 169 | 392 | } | ||
393 | } | |||
394 | | |||
395 | | |||
396 | /// <summary> | |||
397 | /// Get/set the entry's magic tag. | |||
398 | /// </summary> | |||
399 | /// <exception cref="ArgumentNullException">Thrown when attempting to set Magic to null.</exception> | |||
400 | public string Magic { | |||
| 116 | 401 | get { return magic; } | ||
402 | set { | |||
| 90 | 403 | if (value == null) { | ||
| 1 | 404 | throw new ArgumentNullException(nameof(value)); | ||
405 | } | |||
| 89 | 406 | magic = value; | ||
| 89 | 407 | } | ||
408 | } | |||
409 | | |||
410 | | |||
411 | /// <summary> | |||
412 | /// The entry's version. | |||
413 | /// </summary> | |||
414 | /// <exception cref="ArgumentNullException">Thrown when attempting to set Version to null.</exception> | |||
415 | public string Version { | |||
416 | get { | |||
| 111 | 417 | return version; | ||
418 | } | |||
419 | | |||
420 | set { | |||
| 87 | 421 | if (value == null) { | ||
| 1 | 422 | throw new ArgumentNullException(nameof(value)); | ||
423 | } | |||
| 86 | 424 | version = value; | ||
| 86 | 425 | } | ||
426 | } | |||
427 | | |||
428 | | |||
429 | /// <summary> | |||
430 | /// The entry's user name. | |||
431 | /// </summary> | |||
432 | public string UserName { | |||
| 111 | 433 | get { return userName; } | ||
434 | set { | |||
| 166 | 435 | if (value != null) { | ||
| 81 | 436 | userName = value.Substring(0, Math.Min(UNAMELEN, value.Length)); | ||
| 81 | 437 | } else { | ||
| 85 | 438 | string currentUser = Environment.UserName; | ||
| 85 | 439 | if (currentUser.Length > UNAMELEN) { | ||
| 0 | 440 | currentUser = currentUser.Substring(0, UNAMELEN); | ||
441 | } | |||
| 85 | 442 | userName = currentUser; | ||
443 | } | |||
| 85 | 444 | } | ||
445 | } | |||
446 | | |||
447 | | |||
448 | /// <summary> | |||
449 | /// Get/set the entry's group name. | |||
450 | /// </summary> | |||
451 | /// <remarks> | |||
452 | /// This is only directly relevant to unix systems. | |||
453 | /// </remarks> | |||
454 | public string GroupName { | |||
| 110 | 455 | get { return groupName; } | ||
456 | set { | |||
| 166 | 457 | if (value == null) { | ||
| 1 | 458 | groupName = "None"; | ||
| 1 | 459 | } else { | ||
| 165 | 460 | groupName = value; | ||
461 | } | |||
| 165 | 462 | } | ||
463 | } | |||
464 | | |||
465 | | |||
466 | /// <summary> | |||
467 | /// Get/set the entry's major device number. | |||
468 | /// </summary> | |||
469 | public int DevMajor { | |||
| 36 | 470 | get { return devMajor; } | ||
| 162 | 471 | set { devMajor = value; } | ||
472 | } | |||
473 | | |||
474 | | |||
475 | /// <summary> | |||
476 | /// Get/set the entry's minor device number. | |||
477 | /// </summary> | |||
478 | public int DevMinor { | |||
| 34 | 479 | get { return devMinor; } | ||
| 162 | 480 | set { devMinor = value; } | ||
481 | } | |||
482 | | |||
483 | #endregion | |||
484 | | |||
485 | #region ICloneable Members | |||
486 | /// <summary> | |||
487 | /// Create a new <see cref="TarHeader"/> that is a copy of the current instance. | |||
488 | /// </summary> | |||
489 | /// <returns>A new <see cref="Object"/> that is a copy of the current instance.</returns> | |||
490 | public object Clone() | |||
491 | { | |||
| 2 | 492 | return MemberwiseClone(); | ||
493 | } | |||
494 | #endregion | |||
495 | | |||
496 | /// <summary> | |||
497 | /// Parse TarHeader information from a header buffer. | |||
498 | /// </summary> | |||
499 | /// <param name = "header"> | |||
500 | /// The tar entry header buffer to get information from. | |||
501 | /// </param> | |||
502 | public void ParseBuffer(byte[] header) | |||
503 | { | |||
| 3 | 504 | if (header == null) { | ||
| 0 | 505 | throw new ArgumentNullException(nameof(header)); | ||
506 | } | |||
507 | | |||
| 3 | 508 | int offset = 0; | ||
509 | | |||
| 3 | 510 | name = ParseName(header, offset, NAMELEN).ToString(); | ||
| 3 | 511 | offset += NAMELEN; | ||
512 | | |||
| 3 | 513 | mode = (int)ParseOctal(header, offset, MODELEN); | ||
| 3 | 514 | offset += MODELEN; | ||
515 | | |||
| 3 | 516 | UserId = (int)ParseOctal(header, offset, UIDLEN); | ||
| 3 | 517 | offset += UIDLEN; | ||
518 | | |||
| 3 | 519 | GroupId = (int)ParseOctal(header, offset, GIDLEN); | ||
| 3 | 520 | offset += GIDLEN; | ||
521 | | |||
| 3 | 522 | Size = ParseBinaryOrOctal(header, offset, SIZELEN); | ||
| 3 | 523 | offset += SIZELEN; | ||
524 | | |||
| 3 | 525 | ModTime = GetDateTimeFromCTime(ParseOctal(header, offset, MODTIMELEN)); | ||
| 3 | 526 | offset += MODTIMELEN; | ||
527 | | |||
| 3 | 528 | checksum = (int)ParseOctal(header, offset, CHKSUMLEN); | ||
| 3 | 529 | offset += CHKSUMLEN; | ||
530 | | |||
| 3 | 531 | TypeFlag = header[offset++]; | ||
532 | | |||
| 3 | 533 | LinkName = ParseName(header, offset, NAMELEN).ToString(); | ||
| 3 | 534 | offset += NAMELEN; | ||
535 | | |||
| 3 | 536 | Magic = ParseName(header, offset, MAGICLEN).ToString(); | ||
| 3 | 537 | offset += MAGICLEN; | ||
538 | | |||
| 3 | 539 | if (Magic == "ustar") | ||
540 | { | |||
| 0 | 541 | Version = ParseName(header, offset, VERSIONLEN).ToString(); | ||
| 0 | 542 | offset += VERSIONLEN; | ||
543 | | |||
| 0 | 544 | UserName = ParseName(header, offset, UNAMELEN).ToString(); | ||
| 0 | 545 | offset += UNAMELEN; | ||
546 | | |||
| 0 | 547 | GroupName = ParseName(header, offset, GNAMELEN).ToString(); | ||
| 0 | 548 | offset += GNAMELEN; | ||
549 | | |||
| 0 | 550 | DevMajor = (int) ParseOctal(header, offset, DEVLEN); | ||
| 0 | 551 | offset += DEVLEN; | ||
552 | | |||
| 0 | 553 | DevMinor = (int) ParseOctal(header, offset, DEVLEN); | ||
| 0 | 554 | offset += DEVLEN; | ||
555 | | |||
| 0 | 556 | string prefix = ParseName(header, offset, PREFIXLEN).ToString(); | ||
| 0 | 557 | if (!string.IsNullOrEmpty(prefix)) Name = prefix + '/' + Name; | ||
558 | } | |||
559 | | |||
| 3 | 560 | isChecksumValid = Checksum == TarHeader.MakeCheckSum(header); | ||
| 3 | 561 | } | ||
562 | | |||
563 | /// <summary> | |||
564 | /// 'Write' header information to buffer provided, updating the <see cref="Checksum">check sum</see>. | |||
565 | /// </summary> | |||
566 | /// <param name="outBuffer">output buffer for header information</param> | |||
567 | public void WriteHeader(byte[] outBuffer) | |||
568 | { | |||
| 70 | 569 | if (outBuffer == null) { | ||
| 0 | 570 | throw new ArgumentNullException(nameof(outBuffer)); | ||
571 | } | |||
572 | | |||
| 70 | 573 | int offset = 0; | ||
574 | | |||
| 70 | 575 | offset = GetNameBytes(Name, outBuffer, offset, NAMELEN); | ||
| 70 | 576 | offset = GetOctalBytes(mode, outBuffer, offset, MODELEN); | ||
| 70 | 577 | offset = GetOctalBytes(UserId, outBuffer, offset, UIDLEN); | ||
| 70 | 578 | offset = GetOctalBytes(GroupId, outBuffer, offset, GIDLEN); | ||
579 | | |||
| 70 | 580 | offset = GetBinaryOrOctalBytes(Size, outBuffer, offset, SIZELEN); | ||
| 70 | 581 | offset = GetOctalBytes(GetCTime(ModTime), outBuffer, offset, MODTIMELEN); | ||
582 | | |||
| 70 | 583 | int csOffset = offset; | ||
| 1260 | 584 | for (int c = 0; c < CHKSUMLEN; ++c) { | ||
| 560 | 585 | outBuffer[offset++] = (byte)' '; | ||
586 | } | |||
587 | | |||
| 70 | 588 | outBuffer[offset++] = TypeFlag; | ||
589 | | |||
| 70 | 590 | offset = GetNameBytes(LinkName, outBuffer, offset, NAMELEN); | ||
| 70 | 591 | offset = GetAsciiBytes(Magic, 0, outBuffer, offset, MAGICLEN); | ||
| 70 | 592 | offset = GetNameBytes(Version, outBuffer, offset, VERSIONLEN); | ||
| 70 | 593 | offset = GetNameBytes(UserName, outBuffer, offset, UNAMELEN); | ||
| 70 | 594 | offset = GetNameBytes(GroupName, outBuffer, offset, GNAMELEN); | ||
595 | | |||
| 70 | 596 | if ((TypeFlag == LF_CHR) || (TypeFlag == LF_BLK)) { | ||
| 0 | 597 | offset = GetOctalBytes(DevMajor, outBuffer, offset, DEVLEN); | ||
| 0 | 598 | offset = GetOctalBytes(DevMinor, outBuffer, offset, DEVLEN); | ||
599 | } | |||
600 | | |||
| 12880 | 601 | for (; offset < outBuffer.Length;) { | ||
| 12810 | 602 | outBuffer[offset++] = 0; | ||
603 | } | |||
604 | | |||
| 70 | 605 | checksum = ComputeCheckSum(outBuffer); | ||
606 | | |||
| 70 | 607 | GetCheckSumOctalBytes(checksum, outBuffer, csOffset, CHKSUMLEN); | ||
| 70 | 608 | isChecksumValid = true; | ||
| 70 | 609 | } | ||
610 | | |||
611 | /// <summary> | |||
612 | /// Get a hash code for the current object. | |||
613 | /// </summary> | |||
614 | /// <returns>A hash code for the current object.</returns> | |||
615 | public override int GetHashCode() | |||
616 | { | |||
| 0 | 617 | return Name.GetHashCode(); | ||
618 | } | |||
619 | | |||
620 | /// <summary> | |||
621 | /// Determines if this instance is equal to the specified object. | |||
622 | /// </summary> | |||
623 | /// <param name="obj">The object to compare with.</param> | |||
624 | /// <returns>true if the objects are equal, false otherwise.</returns> | |||
625 | public override bool Equals(object obj) | |||
626 | { | |||
| 29 | 627 | var localHeader = obj as TarHeader; | ||
628 | | |||
629 | bool result; | |||
| 29 | 630 | if (localHeader != null) { | ||
| 29 | 631 | result = (name == localHeader.name) | ||
| 29 | 632 | && (mode == localHeader.mode) | ||
| 29 | 633 | && (UserId == localHeader.UserId) | ||
| 29 | 634 | && (GroupId == localHeader.GroupId) | ||
| 29 | 635 | && (Size == localHeader.Size) | ||
| 29 | 636 | && (ModTime == localHeader.ModTime) | ||
| 29 | 637 | && (Checksum == localHeader.Checksum) | ||
| 29 | 638 | && (TypeFlag == localHeader.TypeFlag) | ||
| 29 | 639 | && (LinkName == localHeader.LinkName) | ||
| 29 | 640 | && (Magic == localHeader.Magic) | ||
| 29 | 641 | && (Version == localHeader.Version) | ||
| 29 | 642 | && (UserName == localHeader.UserName) | ||
| 29 | 643 | && (GroupName == localHeader.GroupName) | ||
| 29 | 644 | && (DevMajor == localHeader.DevMajor) | ||
| 29 | 645 | && (DevMinor == localHeader.DevMinor); | ||
| 29 | 646 | } else { | ||
| 0 | 647 | result = false; | ||
648 | } | |||
| 29 | 649 | return result; | ||
650 | } | |||
651 | | |||
652 | /// <summary> | |||
653 | /// Set defaults for values used when constructing a TarHeader instance. | |||
654 | /// </summary> | |||
655 | /// <param name="userId">Value to apply as a default for userId.</param> | |||
656 | /// <param name="userName">Value to apply as a default for userName.</param> | |||
657 | /// <param name="groupId">Value to apply as a default for groupId.</param> | |||
658 | /// <param name="groupName">Value to apply as a default for groupName.</param> | |||
659 | static internal void SetValueDefaults(int userId, string userName, int groupId, string groupName) | |||
660 | { | |||
| 0 | 661 | defaultUserId = userIdAsSet = userId; | ||
| 0 | 662 | defaultUser = userNameAsSet = userName; | ||
| 0 | 663 | defaultGroupId = groupIdAsSet = groupId; | ||
| 0 | 664 | defaultGroupName = groupNameAsSet = groupName; | ||
| 0 | 665 | } | ||
666 | | |||
667 | static internal void RestoreSetValues() | |||
668 | { | |||
| 0 | 669 | defaultUserId = userIdAsSet; | ||
| 0 | 670 | defaultUser = userNameAsSet; | ||
| 0 | 671 | defaultGroupId = groupIdAsSet; | ||
| 0 | 672 | defaultGroupName = groupNameAsSet; | ||
| 0 | 673 | } | ||
674 | | |||
675 | // Return value that may be stored in octal or binary. Length must exceed 8. | |||
676 | // | |||
677 | static private long ParseBinaryOrOctal(byte[] header, int offset, int length) | |||
678 | { | |||
| 3 | 679 | if (header[offset] >= 0x80) { | ||
680 | // File sizes over 8GB are stored in 8 right-justified bytes of binary indicated by setting the high-order bit o | |||
| 0 | 681 | long result = 0; | ||
| 0 | 682 | for (int pos = length - 8; pos < length; pos++) { | ||
| 0 | 683 | result = result << 8 | header[offset + pos]; | ||
684 | } | |||
| 0 | 685 | return result; | ||
686 | } | |||
| 3 | 687 | return ParseOctal(header, offset, length); | ||
688 | } | |||
689 | | |||
690 | /// <summary> | |||
691 | /// Parse an octal string from a header buffer. | |||
692 | /// </summary> | |||
693 | /// <param name = "header">The header buffer from which to parse.</param> | |||
694 | /// <param name = "offset">The offset into the buffer from which to parse.</param> | |||
695 | /// <param name = "length">The number of header bytes to parse.</param> | |||
696 | /// <returns>The long equivalent of the octal string.</returns> | |||
697 | static public long ParseOctal(byte[] header, int offset, int length) | |||
698 | { | |||
| 18 | 699 | if (header == null) { | ||
| 0 | 700 | throw new ArgumentNullException(nameof(header)); | ||
701 | } | |||
702 | | |||
| 18 | 703 | long result = 0; | ||
| 18 | 704 | bool stillPadding = true; | ||
705 | | |||
| 18 | 706 | int end = offset + length; | ||
| 330 | 707 | for (int i = offset; i < end; ++i) { | ||
| 165 | 708 | if (header[i] == 0) { | ||
709 | break; | |||
710 | } | |||
711 | | |||
| 147 | 712 | if (header[i] == (byte)' ' || header[i] == '0') { | ||
| 102 | 713 | if (stillPadding) { | ||
714 | continue; | |||
715 | } | |||
716 | | |||
| 15 | 717 | if (header[i] == (byte)' ') { | ||
718 | break; | |||
719 | } | |||
720 | } | |||
721 | | |||
| 60 | 722 | stillPadding = false; | ||
723 | | |||
| 60 | 724 | result = (result << 3) + (header[i] - '0'); | ||
725 | } | |||
726 | | |||
| 18 | 727 | return result; | ||
728 | } | |||
729 | | |||
730 | /// <summary> | |||
731 | /// Parse a name from a header buffer. | |||
732 | /// </summary> | |||
733 | /// <param name="header"> | |||
734 | /// The header buffer from which to parse. | |||
735 | /// </param> | |||
736 | /// <param name="offset"> | |||
737 | /// The offset into the buffer from which to parse. | |||
738 | /// </param> | |||
739 | /// <param name="length"> | |||
740 | /// The number of header bytes to parse. | |||
741 | /// </param> | |||
742 | /// <returns> | |||
743 | /// The name parsed. | |||
744 | /// </returns> | |||
745 | static public StringBuilder ParseName(byte[] header, int offset, int length) | |||
746 | { | |||
| 9 | 747 | if (header == null) { | ||
| 0 | 748 | throw new ArgumentNullException(nameof(header)); | ||
749 | } | |||
750 | | |||
| 9 | 751 | if (offset < 0) { | ||
| 0 | 752 | throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be less than zero"); | ||
753 | } | |||
754 | | |||
| 9 | 755 | if (length < 0) { | ||
| 0 | 756 | throw new ArgumentOutOfRangeException(nameof(length), "Cannot be less than zero"); | ||
757 | } | |||
758 | | |||
| 9 | 759 | if (offset + length > header.Length) { | ||
| 0 | 760 | throw new ArgumentException("Exceeds header size", nameof(length)); | ||
761 | } | |||
762 | | |||
| 9 | 763 | var result = new StringBuilder(length); | ||
764 | | |||
| 108 | 765 | for (int i = offset; i < offset + length; ++i) { | ||
| 51 | 766 | if (header[i] == 0) { | ||
767 | break; | |||
768 | } | |||
| 45 | 769 | result.Append((char)header[i]); | ||
770 | } | |||
771 | | |||
| 9 | 772 | return result; | ||
773 | } | |||
774 | | |||
775 | /// <summary> | |||
776 | /// Add <paramref name="name">name</paramref> to the buffer as a collection of bytes | |||
777 | /// </summary> | |||
778 | /// <param name="name">The name to add</param> | |||
779 | /// <param name="nameOffset">The offset of the first character</param> | |||
780 | /// <param name="buffer">The buffer to add to</param> | |||
781 | /// <param name="bufferOffset">The index of the first byte to add</param> | |||
782 | /// <param name="length">The number of characters/bytes to add</param> | |||
783 | /// <returns>The next free index in the <paramref name="buffer"/></returns> | |||
784 | public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buffer, int bufferOffset, int length) | |||
785 | { | |||
| 0 | 786 | if (name == null) { | ||
| 0 | 787 | throw new ArgumentNullException(nameof(name)); | ||
788 | } | |||
789 | | |||
| 0 | 790 | if (buffer == null) { | ||
| 0 | 791 | throw new ArgumentNullException(nameof(buffer)); | ||
792 | } | |||
793 | | |||
| 0 | 794 | return GetNameBytes(name.ToString(), nameOffset, buffer, bufferOffset, length); | ||
795 | } | |||
796 | | |||
797 | /// <summary> | |||
798 | /// Add <paramref name="name">name</paramref> to the buffer as a collection of bytes | |||
799 | /// </summary> | |||
800 | /// <param name="name">The name to add</param> | |||
801 | /// <param name="nameOffset">The offset of the first character</param> | |||
802 | /// <param name="buffer">The buffer to add to</param> | |||
803 | /// <param name="bufferOffset">The index of the first byte to add</param> | |||
804 | /// <param name="length">The number of characters/bytes to add</param> | |||
805 | /// <returns>The next free index in the <paramref name="buffer"/></returns> | |||
806 | public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length) | |||
807 | { | |||
| 350 | 808 | if (name == null) { | ||
| 0 | 809 | throw new ArgumentNullException(nameof(name)); | ||
810 | } | |||
811 | | |||
| 350 | 812 | if (buffer == null) { | ||
| 0 | 813 | throw new ArgumentNullException(nameof(buffer)); | ||
814 | } | |||
815 | | |||
816 | int i; | |||
817 | | |||
| 2100 | 818 | for (i = 0 ; i < length && nameOffset + i < name.Length; ++i) { | ||
| 700 | 819 | buffer[bufferOffset + i] = (byte)name[nameOffset + i]; | ||
820 | } | |||
821 | | |||
| 36190 | 822 | for (; i < length; ++i) { | ||
| 17920 | 823 | buffer[bufferOffset + i] = 0; | ||
824 | } | |||
825 | | |||
| 350 | 826 | return bufferOffset + length; | ||
827 | } | |||
828 | | |||
829 | /// <summary> | |||
830 | /// Add an entry name to the buffer | |||
831 | /// </summary> | |||
832 | /// <param name="name"> | |||
833 | /// The name to add | |||
834 | /// </param> | |||
835 | /// <param name="buffer"> | |||
836 | /// The buffer to add to | |||
837 | /// </param> | |||
838 | /// <param name="offset"> | |||
839 | /// The offset into the buffer from which to start adding | |||
840 | /// </param> | |||
841 | /// <param name="length"> | |||
842 | /// The number of header bytes to add | |||
843 | /// </param> | |||
844 | /// <returns> | |||
845 | /// The index of the next free byte in the buffer | |||
846 | /// </returns> | |||
847 | public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length) | |||
848 | { | |||
849 | | |||
| 0 | 850 | if (name == null) { | ||
| 0 | 851 | throw new ArgumentNullException(nameof(name)); | ||
852 | } | |||
853 | | |||
| 0 | 854 | if (buffer == null) { | ||
| 0 | 855 | throw new ArgumentNullException(nameof(buffer)); | ||
856 | } | |||
857 | | |||
| 0 | 858 | return GetNameBytes(name.ToString(), 0, buffer, offset, length); | ||
859 | } | |||
860 | | |||
861 | /// <summary> | |||
862 | /// Add an entry name to the buffer | |||
863 | /// </summary> | |||
864 | /// <param name="name">The name to add</param> | |||
865 | /// <param name="buffer">The buffer to add to</param> | |||
866 | /// <param name="offset">The offset into the buffer from which to start adding</param> | |||
867 | /// <param name="length">The number of header bytes to add</param> | |||
868 | /// <returns>The index of the next free byte in the buffer</returns> | |||
869 | public static int GetNameBytes(string name, byte[] buffer, int offset, int length) | |||
870 | { | |||
871 | | |||
| 350 | 872 | if (name == null) { | ||
| 0 | 873 | throw new ArgumentNullException(nameof(name)); | ||
874 | } | |||
875 | | |||
| 350 | 876 | if (buffer == null) { | ||
| 0 | 877 | throw new ArgumentNullException(nameof(buffer)); | ||
878 | } | |||
879 | | |||
| 350 | 880 | return GetNameBytes(name, 0, buffer, offset, length); | ||
881 | } | |||
882 | | |||
883 | /// <summary> | |||
884 | /// Add a string to a buffer as a collection of ascii bytes. | |||
885 | /// </summary> | |||
886 | /// <param name="toAdd">The string to add</param> | |||
887 | /// <param name="nameOffset">The offset of the first character to add.</param> | |||
888 | /// <param name="buffer">The buffer to add to.</param> | |||
889 | /// <param name="bufferOffset">The offset to start adding at.</param> | |||
890 | /// <param name="length">The number of ascii characters to add.</param> | |||
891 | /// <returns>The next free index in the buffer.</returns> | |||
892 | public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length) | |||
893 | { | |||
| 70 | 894 | if (toAdd == null) { | ||
| 0 | 895 | throw new ArgumentNullException(nameof(toAdd)); | ||
896 | } | |||
897 | | |||
| 70 | 898 | if (buffer == null) { | ||
| 0 | 899 | throw new ArgumentNullException(nameof(buffer)); | ||
900 | } | |||
901 | | |||
| 980 | 902 | for (int i = 0; i < length && nameOffset + i < toAdd.Length; ++i) { | ||
| 420 | 903 | buffer[bufferOffset + i] = (byte)toAdd[nameOffset + i]; | ||
904 | } | |||
| 70 | 905 | return bufferOffset + length; | ||
906 | } | |||
907 | | |||
908 | /// <summary> | |||
909 | /// Put an octal representation of a value into a buffer | |||
910 | /// </summary> | |||
911 | /// <param name = "value"> | |||
912 | /// the value to be converted to octal | |||
913 | /// </param> | |||
914 | /// <param name = "buffer"> | |||
915 | /// buffer to store the octal string | |||
916 | /// </param> | |||
917 | /// <param name = "offset"> | |||
918 | /// The offset into the buffer where the value starts | |||
919 | /// </param> | |||
920 | /// <param name = "length"> | |||
921 | /// The length of the octal string to create | |||
922 | /// </param> | |||
923 | /// <returns> | |||
924 | /// The offset of the character next byte after the octal string | |||
925 | /// </returns> | |||
926 | public static int GetOctalBytes(long value, byte[] buffer, int offset, int length) | |||
927 | { | |||
| 420 | 928 | if (buffer == null) { | ||
| 0 | 929 | throw new ArgumentNullException(nameof(buffer)); | ||
930 | } | |||
931 | | |||
| 420 | 932 | int localIndex = length - 1; | ||
933 | | |||
934 | // Either a space or null is valid here. We use NULL as per GNUTar | |||
| 420 | 935 | buffer[offset + localIndex] = 0; | ||
| 420 | 936 | --localIndex; | ||
937 | | |||
| 420 | 938 | if (value > 0) { | ||
| 4218 | 939 | for (long v = value; (localIndex >= 0) && (v > 0); --localIndex) { | ||
| 1831 | 940 | buffer[offset + localIndex] = (byte)((byte)'0' + (byte)(v & 7)); | ||
| 1831 | 941 | v >>= 3; | ||
942 | } | |||
943 | } | |||
944 | | |||
| 3618 | 945 | for (; localIndex >= 0; --localIndex) { | ||
| 1599 | 946 | buffer[offset + localIndex] = (byte)'0'; | ||
947 | } | |||
948 | | |||
| 420 | 949 | return offset + length; | ||
950 | } | |||
951 | | |||
952 | /// <summary> | |||
953 | /// Put an octal or binary representation of a value into a buffer | |||
954 | /// </summary> | |||
955 | /// <param name = "value">Value to be convert to octal</param> | |||
956 | /// <param name = "buffer">The buffer to update</param> | |||
957 | /// <param name = "offset">The offset into the buffer to store the value</param> | |||
958 | /// <param name = "length">The length of the octal string. Must be 12.</param> | |||
959 | /// <returns>Index of next byte</returns> | |||
960 | private static int GetBinaryOrOctalBytes(long value, byte[] buffer, int offset, int length) | |||
961 | { | |||
| 70 | 962 | if (value > 0x1FFFFFFFF) { // Octal 77777777777 (11 digits) | ||
963 | // Put value as binary, right-justified into the buffer. Set high order bit of left-most byte. | |||
| 0 | 964 | for (int pos = length - 1; pos > 0; pos--) { | ||
| 0 | 965 | buffer[offset + pos] = (byte)value; | ||
| 0 | 966 | value = value >> 8; | ||
967 | } | |||
| 0 | 968 | buffer[offset] = 0x80; | ||
| 0 | 969 | return offset + length; | ||
970 | } | |||
| 70 | 971 | return GetOctalBytes(value, buffer, offset, length); | ||
972 | } | |||
973 | | |||
974 | /// <summary> | |||
975 | /// Add the checksum integer to header buffer. | |||
976 | /// </summary> | |||
977 | /// <param name = "value"></param> | |||
978 | /// <param name = "buffer">The header buffer to set the checksum for</param> | |||
979 | /// <param name = "offset">The offset into the buffer for the checksum</param> | |||
980 | /// <param name = "length">The number of header bytes to update. | |||
981 | /// It's formatted differently from the other fields: it has 6 digits, a | |||
982 | /// null, then a space -- rather than digits, a space, then a null. | |||
983 | /// The final space is already there, from checksumming | |||
984 | /// </param> | |||
985 | /// <returns>The modified buffer offset</returns> | |||
986 | static void GetCheckSumOctalBytes(long value, byte[] buffer, int offset, int length) | |||
987 | { | |||
| 70 | 988 | GetOctalBytes(value, buffer, offset, length - 1); | ||
| 70 | 989 | } | ||
990 | | |||
991 | /// <summary> | |||
992 | /// Compute the checksum for a tar entry header. | |||
993 | /// The checksum field must be all spaces prior to this happening | |||
994 | /// </summary> | |||
995 | /// <param name = "buffer">The tar entry's header buffer.</param> | |||
996 | /// <returns>The computed checksum.</returns> | |||
997 | static int ComputeCheckSum(byte[] buffer) | |||
998 | { | |||
| 70 | 999 | int sum = 0; | ||
| 71820 | 1000 | for (int i = 0; i < buffer.Length; ++i) { | ||
| 35840 | 1001 | sum += buffer[i]; | ||
1002 | } | |||
| 70 | 1003 | return sum; | ||
1004 | } | |||
1005 | | |||
1006 | /// <summary> | |||
1007 | /// Make a checksum for a tar entry ignoring the checksum contents. | |||
1008 | /// </summary> | |||
1009 | /// <param name = "buffer">The tar entry's header buffer.</param> | |||
1010 | /// <returns>The checksum for the buffer</returns> | |||
1011 | static int MakeCheckSum(byte[] buffer) | |||
1012 | { | |||
| 3 | 1013 | int sum = 0; | ||
| 894 | 1014 | for (int i = 0; i < CHKSUMOFS; ++i) { | ||
| 444 | 1015 | sum += buffer[i]; | ||
1016 | } | |||
1017 | | |||
| 54 | 1018 | for (int i = 0; i < CHKSUMLEN; ++i) { | ||
| 24 | 1019 | sum += (byte)' '; | ||
1020 | } | |||
1021 | | |||
| 2142 | 1022 | for (int i = CHKSUMOFS + CHKSUMLEN; i < buffer.Length; ++i) { | ||
| 1068 | 1023 | sum += buffer[i]; | ||
1024 | } | |||
| 3 | 1025 | return sum; | ||
1026 | } | |||
1027 | | |||
1028 | static int GetCTime(DateTime dateTime) | |||
1029 | { | |||
| 70 | 1030 | return unchecked((int)((dateTime.Ticks - dateTime1970.Ticks) / timeConversionFactor)); | ||
1031 | } | |||
1032 | | |||
1033 | static DateTime GetDateTimeFromCTime(long ticks) | |||
1034 | { | |||
1035 | DateTime result; | |||
1036 | | |||
1037 | try { | |||
| 3 | 1038 | result = new DateTime(dateTime1970.Ticks + ticks * timeConversionFactor); | ||
| 3 | 1039 | } catch (ArgumentOutOfRangeException) { | ||
| 0 | 1040 | result = dateTime1970; | ||
| 0 | 1041 | } | ||
| 3 | 1042 | return result; | ||
1043 | } | |||
1044 | | |||
1045 | #region Instance Fields | |||
1046 | string name; | |||
1047 | int mode; | |||
1048 | int userId; | |||
1049 | int groupId; | |||
1050 | long size; | |||
1051 | DateTime modTime; | |||
1052 | int checksum; | |||
1053 | bool isChecksumValid; | |||
1054 | byte typeFlag; | |||
1055 | string linkName; | |||
1056 | string magic; | |||
1057 | string version; | |||
1058 | string userName; | |||
1059 | string groupName; | |||
1060 | int devMajor; | |||
1061 | int devMinor; | |||
1062 | #endregion | |||
1063 | | |||
1064 | #region Class Fields | |||
1065 | // Values used during recursive operations. | |||
1066 | static internal int userIdAsSet; | |||
1067 | static internal int groupIdAsSet; | |||
1068 | static internal string userNameAsSet; | |||
| 1 | 1069 | static internal string groupNameAsSet = "None"; | ||
1070 | | |||
1071 | static internal int defaultUserId; | |||
1072 | static internal int defaultGroupId; | |||
| 1 | 1073 | static internal string defaultGroupName = "None"; | ||
1074 | static internal string defaultUser; | |||
1075 | #endregion | |||
1076 | } | |||
1077 | } |
| Class: | ICSharpCode.SharpZipLib.Tar.TarInputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarInputStream.cs |
| Covered lines: | 40 |
| Uncovered lines: | 118 |
| Coverable lines: | 158 |
| Total lines: | 626 |
| Line coverage: | 25.3% |
| Branch coverage: | 22% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| Flush() | 1 | 0 | 0 |
| Seek(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| Write(...) | 1 | 0 | 0 |
| WriteByte(...) | 1 | 0 | 0 |
| ReadByte() | 2 | 0 | 0 |
| Read(...) | 10 | 0 | 0 |
| Close() | 1 | 100 | 100 |
| SetEntryFactory(...) | 1 | 0 | 0 |
| GetRecordSize() | 1 | 0 | 0 |
| Skip(...) | 4 | 0 | 0 |
| Mark(...) | 1 | 0 | 0 |
| Reset() | 1 | 0 | 0 |
| GetNextEntry() | 20 | 42.86 | 41.03 |
| CopyEntryContents(...) | 2 | 0 | 0 |
| SkipToNextEntry() | 2 | 0 | 0 |
| CreateEntry(...) | 1 | 0 | 0 |
| CreateEntryFromFile(...) | 1 | 0 | 0 |
| CreateEntry(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Text; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Tar | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// The TarInputStream reads a UNIX tar archive as an InputStream. | |||
9 | /// methods are provided to position at each successive entry in | |||
10 | /// the archive, and the read each entry as a normal input stream | |||
11 | /// using read(). | |||
12 | /// </summary> | |||
13 | public class TarInputStream : Stream | |||
14 | { | |||
15 | #region Constructors | |||
16 | /// <summary> | |||
17 | /// Construct a TarInputStream with default block factor | |||
18 | /// </summary> | |||
19 | /// <param name="inputStream">stream to source data from</param> | |||
20 | public TarInputStream(Stream inputStream) | |||
| 4 | 21 | : this(inputStream, TarBuffer.DefaultBlockFactor) | ||
22 | { | |||
| 4 | 23 | } | ||
24 | | |||
25 | /// <summary> | |||
26 | /// Construct a TarInputStream with user specified block factor | |||
27 | /// </summary> | |||
28 | /// <param name="inputStream">stream to source data from</param> | |||
29 | /// <param name="blockFactor">block factor to apply to archive</param> | |||
| 5 | 30 | public TarInputStream(Stream inputStream, int blockFactor) | ||
31 | { | |||
| 5 | 32 | this.inputStream = inputStream; | ||
| 5 | 33 | tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor); | ||
| 5 | 34 | } | ||
35 | | |||
36 | #endregion | |||
37 | | |||
38 | /// <summary> | |||
39 | /// Get/set flag indicating ownership of the underlying stream. | |||
40 | /// When the flag is true <see cref="Close"></see> will close the underlying stream also. | |||
41 | /// </summary> | |||
42 | public bool IsStreamOwner { | |||
| 0 | 43 | get { return tarBuffer.IsStreamOwner; } | ||
| 2 | 44 | set { tarBuffer.IsStreamOwner = value; } | ||
45 | } | |||
46 | | |||
47 | #region Stream Overrides | |||
48 | /// <summary> | |||
49 | /// Gets a value indicating whether the current stream supports reading | |||
50 | /// </summary> | |||
51 | public override bool CanRead { | |||
52 | get { | |||
| 0 | 53 | return inputStream.CanRead; | ||
54 | } | |||
55 | } | |||
56 | | |||
57 | /// <summary> | |||
58 | /// Gets a value indicating whether the current stream supports seeking | |||
59 | /// This property always returns false. | |||
60 | /// </summary> | |||
61 | public override bool CanSeek { | |||
62 | get { | |||
| 0 | 63 | return false; | ||
64 | } | |||
65 | } | |||
66 | | |||
67 | /// <summary> | |||
68 | /// Gets a value indicating if the stream supports writing. | |||
69 | /// This property always returns false. | |||
70 | /// </summary> | |||
71 | public override bool CanWrite { | |||
72 | get { | |||
| 0 | 73 | return false; | ||
74 | } | |||
75 | } | |||
76 | | |||
77 | /// <summary> | |||
78 | /// The length in bytes of the stream | |||
79 | /// </summary> | |||
80 | public override long Length { | |||
81 | get { | |||
| 0 | 82 | return inputStream.Length; | ||
83 | } | |||
84 | } | |||
85 | | |||
86 | /// <summary> | |||
87 | /// Gets or sets the position within the stream. | |||
88 | /// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException | |||
89 | /// </summary> | |||
90 | /// <exception cref="NotSupportedException">Any attempt to set position</exception> | |||
91 | public override long Position { | |||
92 | get { | |||
| 0 | 93 | return inputStream.Position; | ||
94 | } | |||
95 | set { | |||
| 0 | 96 | throw new NotSupportedException("TarInputStream Seek not supported"); | ||
97 | } | |||
98 | } | |||
99 | | |||
100 | /// <summary> | |||
101 | /// Flushes the baseInputStream | |||
102 | /// </summary> | |||
103 | public override void Flush() | |||
104 | { | |||
| 0 | 105 | inputStream.Flush(); | ||
| 0 | 106 | } | ||
107 | | |||
108 | /// <summary> | |||
109 | /// Set the streams position. This operation is not supported and will throw a NotSupportedException | |||
110 | /// </summary> | |||
111 | /// <param name="offset">The offset relative to the origin to seek to.</param> | |||
112 | /// <param name="origin">The <see cref="SeekOrigin"/> to start seeking from.</param> | |||
113 | /// <returns>The new position in the stream.</returns> | |||
114 | /// <exception cref="NotSupportedException">Any access</exception> | |||
115 | public override long Seek(long offset, SeekOrigin origin) | |||
116 | { | |||
| 0 | 117 | throw new NotSupportedException("TarInputStream Seek not supported"); | ||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Sets the length of the stream | |||
122 | /// This operation is not supported and will throw a NotSupportedException | |||
123 | /// </summary> | |||
124 | /// <param name="value">The new stream length.</param> | |||
125 | /// <exception cref="NotSupportedException">Any access</exception> | |||
126 | public override void SetLength(long value) | |||
127 | { | |||
| 0 | 128 | throw new NotSupportedException("TarInputStream SetLength not supported"); | ||
129 | } | |||
130 | | |||
131 | /// <summary> | |||
132 | /// Writes a block of bytes to this stream using data from a buffer. | |||
133 | /// This operation is not supported and will throw a NotSupportedException | |||
134 | /// </summary> | |||
135 | /// <param name="buffer">The buffer containing bytes to write.</param> | |||
136 | /// <param name="offset">The offset in the buffer of the frist byte to write.</param> | |||
137 | /// <param name="count">The number of bytes to write.</param> | |||
138 | /// <exception cref="NotSupportedException">Any access</exception> | |||
139 | public override void Write(byte[] buffer, int offset, int count) | |||
140 | { | |||
| 0 | 141 | throw new NotSupportedException("TarInputStream Write not supported"); | ||
142 | } | |||
143 | | |||
144 | /// <summary> | |||
145 | /// Writes a byte to the current position in the file stream. | |||
146 | /// This operation is not supported and will throw a NotSupportedException | |||
147 | /// </summary> | |||
148 | /// <param name="value">The byte value to write.</param> | |||
149 | /// <exception cref="NotSupportedException">Any access</exception> | |||
150 | public override void WriteByte(byte value) | |||
151 | { | |||
| 0 | 152 | throw new NotSupportedException("TarInputStream WriteByte not supported"); | ||
153 | } | |||
154 | /// <summary> | |||
155 | /// Reads a byte from the current tar archive entry. | |||
156 | /// </summary> | |||
157 | /// <returns>A byte cast to an int; -1 if the at the end of the stream.</returns> | |||
158 | public override int ReadByte() | |||
159 | { | |||
| 0 | 160 | byte[] oneByteBuffer = new byte[1]; | ||
| 0 | 161 | int num = Read(oneByteBuffer, 0, 1); | ||
| 0 | 162 | if (num <= 0) { | ||
163 | // return -1 to indicate that no byte was read. | |||
| 0 | 164 | return -1; | ||
165 | } | |||
| 0 | 166 | return oneByteBuffer[0]; | ||
167 | } | |||
168 | | |||
169 | /// <summary> | |||
170 | /// Reads bytes from the current tar archive entry. | |||
171 | /// | |||
172 | /// This method is aware of the boundaries of the current | |||
173 | /// entry in the archive and will deal with them appropriately | |||
174 | /// </summary> | |||
175 | /// <param name="buffer"> | |||
176 | /// The buffer into which to place bytes read. | |||
177 | /// </param> | |||
178 | /// <param name="offset"> | |||
179 | /// The offset at which to place bytes read. | |||
180 | /// </param> | |||
181 | /// <param name="count"> | |||
182 | /// The number of bytes to read. | |||
183 | /// </param> | |||
184 | /// <returns> | |||
185 | /// The number of bytes read, or 0 at end of stream/EOF. | |||
186 | /// </returns> | |||
187 | public override int Read(byte[] buffer, int offset, int count) | |||
188 | { | |||
| 0 | 189 | if (buffer == null) { | ||
| 0 | 190 | throw new ArgumentNullException(nameof(buffer)); | ||
191 | } | |||
192 | | |||
| 0 | 193 | int totalRead = 0; | ||
194 | | |||
| 0 | 195 | if (entryOffset >= entrySize) { | ||
| 0 | 196 | return 0; | ||
197 | } | |||
198 | | |||
| 0 | 199 | long numToRead = count; | ||
200 | | |||
| 0 | 201 | if ((numToRead + entryOffset) > entrySize) { | ||
| 0 | 202 | numToRead = entrySize - entryOffset; | ||
203 | } | |||
204 | | |||
| 0 | 205 | if (readBuffer != null) { | ||
| 0 | 206 | int sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int)numToRead; | ||
207 | | |||
| 0 | 208 | Array.Copy(readBuffer, 0, buffer, offset, sz); | ||
209 | | |||
| 0 | 210 | if (sz >= readBuffer.Length) { | ||
| 0 | 211 | readBuffer = null; | ||
| 0 | 212 | } else { | ||
| 0 | 213 | int newLen = readBuffer.Length - sz; | ||
| 0 | 214 | byte[] newBuf = new byte[newLen]; | ||
| 0 | 215 | Array.Copy(readBuffer, sz, newBuf, 0, newLen); | ||
| 0 | 216 | readBuffer = newBuf; | ||
217 | } | |||
218 | | |||
| 0 | 219 | totalRead += sz; | ||
| 0 | 220 | numToRead -= sz; | ||
| 0 | 221 | offset += sz; | ||
222 | } | |||
223 | | |||
| 0 | 224 | while (numToRead > 0) { | ||
| 0 | 225 | byte[] rec = tarBuffer.ReadBlock(); | ||
| 0 | 226 | if (rec == null) { | ||
227 | // Unexpected EOF! | |||
| 0 | 228 | throw new TarException("unexpected EOF with " + numToRead + " bytes unread"); | ||
229 | } | |||
230 | | |||
| 0 | 231 | var sz = (int)numToRead; | ||
| 0 | 232 | int recLen = rec.Length; | ||
233 | | |||
| 0 | 234 | if (recLen > sz) { | ||
| 0 | 235 | Array.Copy(rec, 0, buffer, offset, sz); | ||
| 0 | 236 | readBuffer = new byte[recLen - sz]; | ||
| 0 | 237 | Array.Copy(rec, sz, readBuffer, 0, recLen - sz); | ||
| 0 | 238 | } else { | ||
| 0 | 239 | sz = recLen; | ||
| 0 | 240 | Array.Copy(rec, 0, buffer, offset, recLen); | ||
241 | } | |||
242 | | |||
| 0 | 243 | totalRead += sz; | ||
| 0 | 244 | numToRead -= sz; | ||
| 0 | 245 | offset += sz; | ||
246 | } | |||
247 | | |||
| 0 | 248 | entryOffset += totalRead; | ||
249 | | |||
| 0 | 250 | return totalRead; | ||
251 | } | |||
252 | | |||
253 | /// <summary> | |||
254 | /// Closes this stream. Calls the TarBuffer's close() method. | |||
255 | /// The underlying stream is closed by the TarBuffer. | |||
256 | /// </summary> | |||
257 | public override void Close() | |||
258 | { | |||
| 5 | 259 | tarBuffer.Close(); | ||
| 5 | 260 | } | ||
261 | | |||
262 | #endregion | |||
263 | | |||
264 | /// <summary> | |||
265 | /// Set the entry factory for this instance. | |||
266 | /// </summary> | |||
267 | /// <param name="factory">The factory for creating new entries</param> | |||
268 | public void SetEntryFactory(IEntryFactory factory) | |||
269 | { | |||
| 0 | 270 | entryFactory = factory; | ||
| 0 | 271 | } | ||
272 | | |||
273 | /// <summary> | |||
274 | /// Get the record size being used by this stream's TarBuffer. | |||
275 | /// </summary> | |||
276 | public int RecordSize { | |||
| 0 | 277 | get { return tarBuffer.RecordSize; } | ||
278 | } | |||
279 | | |||
280 | /// <summary> | |||
281 | /// Get the record size being used by this stream's TarBuffer. | |||
282 | /// </summary> | |||
283 | /// <returns> | |||
284 | /// TarBuffer record size. | |||
285 | /// </returns> | |||
286 | [Obsolete("Use RecordSize property instead")] | |||
287 | public int GetRecordSize() | |||
288 | { | |||
| 0 | 289 | return tarBuffer.RecordSize; | ||
290 | } | |||
291 | | |||
292 | /// <summary> | |||
293 | /// Get the available data that can be read from the current | |||
294 | /// entry in the archive. This does not indicate how much data | |||
295 | /// is left in the entire archive, only in the current entry. | |||
296 | /// This value is determined from the entry's size header field | |||
297 | /// and the amount of data already read from the current entry. | |||
298 | /// </summary> | |||
299 | /// <returns> | |||
300 | /// The number of available bytes for the current entry. | |||
301 | /// </returns> | |||
302 | public long Available { | |||
303 | get { | |||
| 0 | 304 | return entrySize - entryOffset; | ||
305 | } | |||
306 | } | |||
307 | | |||
308 | /// <summary> | |||
309 | /// Skip bytes in the input buffer. This skips bytes in the | |||
310 | /// current entry's data, not the entire archive, and will | |||
311 | /// stop at the end of the current entry's data if the number | |||
312 | /// to skip extends beyond that point. | |||
313 | /// </summary> | |||
314 | /// <param name="skipCount"> | |||
315 | /// The number of bytes to skip. | |||
316 | /// </param> | |||
317 | public void Skip(long skipCount) | |||
318 | { | |||
319 | // TODO: REVIEW efficiency of TarInputStream.Skip | |||
320 | // This is horribly inefficient, but it ensures that we | |||
321 | // properly skip over bytes via the TarBuffer... | |||
322 | // | |||
| 0 | 323 | byte[] skipBuf = new byte[8 * 1024]; | ||
324 | | |||
| 0 | 325 | for (long num = skipCount; num > 0;) { | ||
| 0 | 326 | int toRead = num > skipBuf.Length ? skipBuf.Length : (int)num; | ||
| 0 | 327 | int numRead = Read(skipBuf, 0, toRead); | ||
328 | | |||
| 0 | 329 | if (numRead == -1) { | ||
330 | break; | |||
331 | } | |||
332 | | |||
| 0 | 333 | num -= numRead; | ||
334 | } | |||
| 0 | 335 | } | ||
336 | | |||
337 | /// <summary> | |||
338 | /// Return a value of true if marking is supported; false otherwise. | |||
339 | /// </summary> | |||
340 | /// <remarks>Currently marking is not supported, the return value is always false.</remarks> | |||
341 | public bool IsMarkSupported { | |||
342 | get { | |||
| 0 | 343 | return false; | ||
344 | } | |||
345 | } | |||
346 | | |||
347 | /// <summary> | |||
348 | /// Since we do not support marking just yet, we do nothing. | |||
349 | /// </summary> | |||
350 | /// <param name ="markLimit"> | |||
351 | /// The limit to mark. | |||
352 | /// </param> | |||
353 | public void Mark(int markLimit) | |||
354 | { | |||
| 0 | 355 | } | ||
356 | | |||
357 | /// <summary> | |||
358 | /// Since we do not support marking just yet, we do nothing. | |||
359 | /// </summary> | |||
360 | public void Reset() | |||
361 | { | |||
| 0 | 362 | } | ||
363 | | |||
364 | /// <summary> | |||
365 | /// Get the next entry in this tar archive. This will skip | |||
366 | /// over any remaining data in the current entry, if there | |||
367 | /// is one, and place the input stream at the header of the | |||
368 | /// next entry, and read the header and instantiate a new | |||
369 | /// TarEntry from the header bytes and return that entry. | |||
370 | /// If there are no more entries in the archive, null will | |||
371 | /// be returned to indicate that the end of the archive has | |||
372 | /// been reached. | |||
373 | /// </summary> | |||
374 | /// <returns> | |||
375 | /// The next TarEntry in the archive, or null. | |||
376 | /// </returns> | |||
377 | public TarEntry GetNextEntry() | |||
378 | { | |||
| 3 | 379 | if (hasHitEOF) { | ||
| 0 | 380 | return null; | ||
381 | } | |||
382 | | |||
| 3 | 383 | if (currentEntry != null) { | ||
| 0 | 384 | SkipToNextEntry(); | ||
385 | } | |||
386 | | |||
| 3 | 387 | byte[] headerBuf = tarBuffer.ReadBlock(); | ||
388 | | |||
| 3 | 389 | if (headerBuf == null) { | ||
| 0 | 390 | hasHitEOF = true; | ||
| 0 | 391 | } else | ||
| 3 | 392 | hasHitEOF |= TarBuffer.IsEndOfArchiveBlock(headerBuf); | ||
393 | | |||
| 3 | 394 | if (hasHitEOF) { | ||
| 1 | 395 | currentEntry = null; | ||
| 1 | 396 | } else { | ||
397 | try { | |||
| 2 | 398 | var header = new TarHeader(); | ||
| 2 | 399 | header.ParseBuffer(headerBuf); | ||
| 2 | 400 | if (!header.IsChecksumValid) { | ||
| 1 | 401 | throw new TarException("Header checksum is invalid"); | ||
402 | } | |||
| 1 | 403 | this.entryOffset = 0; | ||
| 1 | 404 | this.entrySize = header.Size; | ||
405 | | |||
| 1 | 406 | StringBuilder longName = null; | ||
407 | | |||
| 1 | 408 | if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME) { | ||
409 | | |||
| 0 | 410 | byte[] nameBuffer = new byte[TarBuffer.BlockSize]; | ||
| 0 | 411 | long numToRead = this.entrySize; | ||
412 | | |||
| 0 | 413 | longName = new StringBuilder(); | ||
414 | | |||
| 0 | 415 | while (numToRead > 0) { | ||
| 0 | 416 | int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead | ||
417 | | |||
| 0 | 418 | if (numRead == -1) { | ||
| 0 | 419 | throw new InvalidHeaderException("Failed to read long name entry"); | ||
420 | } | |||
421 | | |||
| 0 | 422 | longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString()); | ||
| 0 | 423 | numToRead -= numRead; | ||
424 | } | |||
425 | | |||
| 0 | 426 | SkipToNextEntry(); | ||
| 0 | 427 | headerBuf = this.tarBuffer.ReadBlock(); | ||
| 1 | 428 | } else if (header.TypeFlag == TarHeader.LF_GHDR) { // POSIX global extended header | ||
429 | // Ignore things we dont understand completely for now | |||
| 0 | 430 | SkipToNextEntry(); | ||
| 0 | 431 | headerBuf = this.tarBuffer.ReadBlock(); | ||
| 1 | 432 | } else if (header.TypeFlag == TarHeader.LF_XHDR) { // POSIX extended header | ||
433 | // Ignore things we dont understand completely for now | |||
| 0 | 434 | SkipToNextEntry(); | ||
| 0 | 435 | headerBuf = this.tarBuffer.ReadBlock(); | ||
| 1 | 436 | } else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) { | ||
437 | // TODO: could show volume name when verbose | |||
| 0 | 438 | SkipToNextEntry(); | ||
| 0 | 439 | headerBuf = this.tarBuffer.ReadBlock(); | ||
| 1 | 440 | } else if (header.TypeFlag != TarHeader.LF_NORMAL && | ||
| 1 | 441 | header.TypeFlag != TarHeader.LF_OLDNORM && | ||
| 1 | 442 | header.TypeFlag != TarHeader.LF_LINK && | ||
| 1 | 443 | header.TypeFlag != TarHeader.LF_SYMLINK && | ||
| 1 | 444 | header.TypeFlag != TarHeader.LF_DIR) { | ||
445 | // Ignore things we dont understand completely for now | |||
| 0 | 446 | SkipToNextEntry(); | ||
| 0 | 447 | headerBuf = tarBuffer.ReadBlock(); | ||
448 | } | |||
449 | | |||
| 1 | 450 | if (entryFactory == null) { | ||
| 1 | 451 | currentEntry = new TarEntry(headerBuf); | ||
| 1 | 452 | if (longName != null) { | ||
| 0 | 453 | currentEntry.Name = longName.ToString(); | ||
454 | } | |||
| 0 | 455 | } else { | ||
| 0 | 456 | currentEntry = entryFactory.CreateEntry(headerBuf); | ||
457 | } | |||
458 | | |||
459 | // Magic was checked here for 'ustar' but there are multiple valid possibilities | |||
460 | // so this is not done anymore. | |||
461 | | |||
| 1 | 462 | entryOffset = 0; | ||
463 | | |||
464 | // TODO: Review How do we resolve this discrepancy?! | |||
| 1 | 465 | entrySize = this.currentEntry.Size; | ||
| 1 | 466 | } catch (InvalidHeaderException ex) { | ||
| 0 | 467 | entrySize = 0; | ||
| 0 | 468 | entryOffset = 0; | ||
| 0 | 469 | currentEntry = null; | ||
| 0 | 470 | string errorText = string.Format("Bad header in record {0} block {1} {2}", | ||
| 0 | 471 | tarBuffer.CurrentRecord, tarBuffer.CurrentBlock, ex.Message); | ||
| 0 | 472 | throw new InvalidHeaderException(errorText); | ||
473 | } | |||
474 | } | |||
| 2 | 475 | return currentEntry; | ||
476 | } | |||
477 | | |||
478 | /// <summary> | |||
479 | /// Copies the contents of the current tar archive entry directly into | |||
480 | /// an output stream. | |||
481 | /// </summary> | |||
482 | /// <param name="outputStream"> | |||
483 | /// The OutputStream into which to write the entry's data. | |||
484 | /// </param> | |||
485 | public void CopyEntryContents(Stream outputStream) | |||
486 | { | |||
| 0 | 487 | byte[] tempBuffer = new byte[32 * 1024]; | ||
488 | | |||
| 0 | 489 | while (true) { | ||
| 0 | 490 | int numRead = Read(tempBuffer, 0, tempBuffer.Length); | ||
| 0 | 491 | if (numRead <= 0) { | ||
492 | break; | |||
493 | } | |||
| 0 | 494 | outputStream.Write(tempBuffer, 0, numRead); | ||
495 | } | |||
| 0 | 496 | } | ||
497 | | |||
498 | void SkipToNextEntry() | |||
499 | { | |||
| 0 | 500 | long numToSkip = entrySize - entryOffset; | ||
501 | | |||
| 0 | 502 | if (numToSkip > 0) { | ||
| 0 | 503 | Skip(numToSkip); | ||
504 | } | |||
505 | | |||
| 0 | 506 | readBuffer = null; | ||
| 0 | 507 | } | ||
508 | | |||
509 | /// <summary> | |||
510 | /// This interface is provided, along with the method <see cref="SetEntryFactory"/>, to allow | |||
511 | /// the programmer to have their own <see cref="TarEntry"/> subclass instantiated for the | |||
512 | /// entries return from <see cref="GetNextEntry"/>. | |||
513 | /// </summary> | |||
514 | public interface IEntryFactory | |||
515 | { | |||
516 | /// <summary> | |||
517 | /// Create an entry based on name alone | |||
518 | /// </summary> | |||
519 | /// <param name="name"> | |||
520 | /// Name of the new EntryPointNotFoundException to create | |||
521 | /// </param> | |||
522 | /// <returns>created TarEntry or descendant class</returns> | |||
523 | TarEntry CreateEntry(string name); | |||
524 | | |||
525 | /// <summary> | |||
526 | /// Create an instance based on an actual file | |||
527 | /// </summary> | |||
528 | /// <param name="fileName"> | |||
529 | /// Name of file to represent in the entry | |||
530 | /// </param> | |||
531 | /// <returns> | |||
532 | /// Created TarEntry or descendant class | |||
533 | /// </returns> | |||
534 | TarEntry CreateEntryFromFile(string fileName); | |||
535 | | |||
536 | /// <summary> | |||
537 | /// Create a tar entry based on the header information passed | |||
538 | /// </summary> | |||
539 | /// <param name="headerBuffer"> | |||
540 | /// Buffer containing header information to create an an entry from. | |||
541 | /// </param> | |||
542 | /// <returns> | |||
543 | /// Created TarEntry or descendant class | |||
544 | /// </returns> | |||
545 | TarEntry CreateEntry(byte[] headerBuffer); | |||
546 | } | |||
547 | | |||
548 | /// <summary> | |||
549 | /// Standard entry factory class creating instances of the class TarEntry | |||
550 | /// </summary> | |||
551 | public class EntryFactoryAdapter : IEntryFactory | |||
552 | { | |||
553 | /// <summary> | |||
554 | /// Create a <see cref="TarEntry"/> based on named | |||
555 | /// </summary> | |||
556 | /// <param name="name">The name to use for the entry</param> | |||
557 | /// <returns>A new <see cref="TarEntry"/></returns> | |||
558 | public TarEntry CreateEntry(string name) | |||
559 | { | |||
| 0 | 560 | return TarEntry.CreateTarEntry(name); | ||
561 | } | |||
562 | | |||
563 | /// <summary> | |||
564 | /// Create a tar entry with details obtained from <paramref name="fileName">file</paramref> | |||
565 | /// </summary> | |||
566 | /// <param name="fileName">The name of the file to retrieve details from.</param> | |||
567 | /// <returns>A new <see cref="TarEntry"/></returns> | |||
568 | public TarEntry CreateEntryFromFile(string fileName) | |||
569 | { | |||
| 0 | 570 | return TarEntry.CreateEntryFromFile(fileName); | ||
571 | } | |||
572 | | |||
573 | /// <summary> | |||
574 | /// Create an entry based on details in <paramref name="headerBuffer">header</paramref> | |||
575 | /// </summary> | |||
576 | /// <param name="headerBuffer">The buffer containing entry details.</param> | |||
577 | /// <returns>A new <see cref="TarEntry"/></returns> | |||
578 | public TarEntry CreateEntry(byte[] headerBuffer) | |||
579 | { | |||
| 0 | 580 | return new TarEntry(headerBuffer); | ||
581 | } | |||
582 | } | |||
583 | | |||
584 | #region Instance Fields | |||
585 | /// <summary> | |||
586 | /// Flag set when last block has been read | |||
587 | /// </summary> | |||
588 | protected bool hasHitEOF; | |||
589 | | |||
590 | /// <summary> | |||
591 | /// Size of this entry as recorded in header | |||
592 | /// </summary> | |||
593 | protected long entrySize; | |||
594 | | |||
595 | /// <summary> | |||
596 | /// Number of bytes read for this entry so far | |||
597 | /// </summary> | |||
598 | protected long entryOffset; | |||
599 | | |||
600 | /// <summary> | |||
601 | /// Buffer used with calls to <code>Read()</code> | |||
602 | /// </summary> | |||
603 | protected byte[] readBuffer; | |||
604 | | |||
605 | /// <summary> | |||
606 | /// Working buffer | |||
607 | /// </summary> | |||
608 | protected TarBuffer tarBuffer; | |||
609 | | |||
610 | /// <summary> | |||
611 | /// Current entry being read | |||
612 | /// </summary> | |||
613 | TarEntry currentEntry; | |||
614 | | |||
615 | /// <summary> | |||
616 | /// Factory used to create TarEntry or descendant class instance | |||
617 | /// </summary> | |||
618 | protected IEntryFactory entryFactory; | |||
619 | | |||
620 | /// <summary> | |||
621 | /// Stream used as the source of input data. | |||
622 | /// </summary> | |||
623 | readonly Stream inputStream; | |||
624 | #endregion | |||
625 | } | |||
626 | } |
| Class: | ICSharpCode.SharpZipLib.Tar.TarOutputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Tar\TarOutputStream.cs |
| Covered lines: | 64 |
| Uncovered lines: | 54 |
| Coverable lines: | 118 |
| Total lines: | 442 |
| Line coverage: | 54.2% |
| Branch coverage: | 58.3% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 2 | 87.5 | 66.67 |
| Seek(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| ReadByte() | 1 | 0 | 0 |
| Read(...) | 1 | 0 | 0 |
| Flush() | 1 | 100 | 100 |
| Finish() | 2 | 100 | 100 |
| Close() | 2 | 100 | 66.67 |
| GetRecordSize() | 1 | 0 | 0 |
| PutNextEntry(...) | 5 | 26.92 | 44.44 |
| CloseEntry() | 3 | 77.78 | 60 |
| WriteByte(...) | 1 | 100 | 100 |
| Write(...) | 10 | 59.46 | 68.42 |
| WriteEofBlock() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Tar | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// The TarOutputStream writes a UNIX tar archive as an OutputStream. | |||
8 | /// Methods are provided to put entries, and then write their contents | |||
9 | /// by writing to this stream using write(). | |||
10 | /// </summary> | |||
11 | /// public | |||
12 | public class TarOutputStream : Stream | |||
13 | { | |||
14 | #region Constructors | |||
15 | /// <summary> | |||
16 | /// Construct TarOutputStream using default block factor | |||
17 | /// </summary> | |||
18 | /// <param name="outputStream">stream to write to</param> | |||
19 | public TarOutputStream(Stream outputStream) | |||
| 3 | 20 | : this(outputStream, TarBuffer.DefaultBlockFactor) | ||
21 | { | |||
| 3 | 22 | } | ||
23 | | |||
24 | /// <summary> | |||
25 | /// Construct TarOutputStream with user specified block factor | |||
26 | /// </summary> | |||
27 | /// <param name="outputStream">stream to write to</param> | |||
28 | /// <param name="blockFactor">blocking factor</param> | |||
| 73 | 29 | public TarOutputStream(Stream outputStream, int blockFactor) | ||
30 | { | |||
| 73 | 31 | if (outputStream == null) { | ||
| 0 | 32 | throw new ArgumentNullException(nameof(outputStream)); | ||
33 | } | |||
34 | | |||
| 73 | 35 | this.outputStream = outputStream; | ||
| 73 | 36 | buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor); | ||
37 | | |||
| 73 | 38 | assemblyBuffer = new byte[TarBuffer.BlockSize]; | ||
| 73 | 39 | blockBuffer = new byte[TarBuffer.BlockSize]; | ||
| 73 | 40 | } | ||
41 | #endregion | |||
42 | | |||
43 | /// <summary> | |||
44 | /// Get/set flag indicating ownership of the underlying stream. | |||
45 | /// When the flag is true <see cref="Close"></see> will close the underlying stream also. | |||
46 | /// </summary> | |||
47 | public bool IsStreamOwner { | |||
| 0 | 48 | get { return buffer.IsStreamOwner; } | ||
| 2 | 49 | set { buffer.IsStreamOwner = value; } | ||
50 | } | |||
51 | | |||
52 | /// <summary> | |||
53 | /// true if the stream supports reading; otherwise, false. | |||
54 | /// </summary> | |||
55 | public override bool CanRead { | |||
56 | get { | |||
| 0 | 57 | return outputStream.CanRead; | ||
58 | } | |||
59 | } | |||
60 | | |||
61 | /// <summary> | |||
62 | /// true if the stream supports seeking; otherwise, false. | |||
63 | /// </summary> | |||
64 | public override bool CanSeek { | |||
65 | get { | |||
| 0 | 66 | return outputStream.CanSeek; | ||
67 | } | |||
68 | } | |||
69 | | |||
70 | /// <summary> | |||
71 | /// true if stream supports writing; otherwise, false. | |||
72 | /// </summary> | |||
73 | public override bool CanWrite { | |||
74 | get { | |||
| 0 | 75 | return outputStream.CanWrite; | ||
76 | } | |||
77 | } | |||
78 | | |||
79 | /// <summary> | |||
80 | /// length of stream in bytes | |||
81 | /// </summary> | |||
82 | public override long Length { | |||
83 | get { | |||
| 0 | 84 | return outputStream.Length; | ||
85 | } | |||
86 | } | |||
87 | | |||
88 | /// <summary> | |||
89 | /// gets or sets the position within the current stream. | |||
90 | /// </summary> | |||
91 | public override long Position { | |||
92 | get { | |||
| 0 | 93 | return outputStream.Position; | ||
94 | } | |||
95 | set { | |||
| 0 | 96 | outputStream.Position = value; | ||
| 0 | 97 | } | ||
98 | } | |||
99 | | |||
100 | /// <summary> | |||
101 | /// set the position within the current stream | |||
102 | /// </summary> | |||
103 | /// <param name="offset">The offset relative to the <paramref name="origin"/> to seek to</param> | |||
104 | /// <param name="origin">The <see cref="SeekOrigin"/> to seek from.</param> | |||
105 | /// <returns>The new position in the stream.</returns> | |||
106 | public override long Seek(long offset, SeekOrigin origin) | |||
107 | { | |||
| 0 | 108 | return outputStream.Seek(offset, origin); | ||
109 | } | |||
110 | | |||
111 | /// <summary> | |||
112 | /// Set the length of the current stream | |||
113 | /// </summary> | |||
114 | /// <param name="value">The new stream length.</param> | |||
115 | public override void SetLength(long value) | |||
116 | { | |||
| 0 | 117 | outputStream.SetLength(value); | ||
| 0 | 118 | } | ||
119 | | |||
120 | /// <summary> | |||
121 | /// Read a byte from the stream and advance the position within the stream | |||
122 | /// by one byte or returns -1 if at the end of the stream. | |||
123 | /// </summary> | |||
124 | /// <returns>The byte value or -1 if at end of stream</returns> | |||
125 | public override int ReadByte() | |||
126 | { | |||
| 0 | 127 | return outputStream.ReadByte(); | ||
128 | } | |||
129 | | |||
130 | /// <summary> | |||
131 | /// read bytes from the current stream and advance the position within the | |||
132 | /// stream by the number of bytes read. | |||
133 | /// </summary> | |||
134 | /// <param name="buffer">The buffer to store read bytes in.</param> | |||
135 | /// <param name="offset">The index into the buffer to being storing bytes at.</param> | |||
136 | /// <param name="count">The desired number of bytes to read.</param> | |||
137 | /// <returns>The total number of bytes read, or zero if at the end of the stream. | |||
138 | /// The number of bytes may be less than the <paramref name="count">count</paramref> | |||
139 | /// requested if data is not avialable.</returns> | |||
140 | public override int Read(byte[] buffer, int offset, int count) | |||
141 | { | |||
| 0 | 142 | return outputStream.Read(buffer, offset, count); | ||
143 | } | |||
144 | | |||
145 | /// <summary> | |||
146 | /// All buffered data is written to destination | |||
147 | /// </summary> | |||
148 | public override void Flush() | |||
149 | { | |||
| 1 | 150 | outputStream.Flush(); | ||
| 1 | 151 | } | ||
152 | | |||
153 | /// <summary> | |||
154 | /// Ends the TAR archive without closing the underlying OutputStream. | |||
155 | /// The result is that the EOF block of nulls is written. | |||
156 | /// </summary> | |||
157 | public void Finish() | |||
158 | { | |||
| 73 | 159 | if (IsEntryOpen) { | ||
| 5 | 160 | CloseEntry(); | ||
161 | } | |||
| 73 | 162 | WriteEofBlock(); | ||
| 73 | 163 | } | ||
164 | | |||
165 | /// <summary> | |||
166 | /// Ends the TAR archive and closes the underlying OutputStream. | |||
167 | /// </summary> | |||
168 | /// <remarks>This means that Finish() is called followed by calling the | |||
169 | /// TarBuffer's Close().</remarks> | |||
170 | public override void Close() | |||
171 | { | |||
| 73 | 172 | if (!isClosed) { | ||
| 73 | 173 | isClosed = true; | ||
| 73 | 174 | Finish(); | ||
| 73 | 175 | buffer.Close(); | ||
176 | } | |||
| 73 | 177 | } | ||
178 | | |||
179 | /// <summary> | |||
180 | /// Get the record size being used by this stream's TarBuffer. | |||
181 | /// </summary> | |||
182 | public int RecordSize { | |||
| 1 | 183 | get { return buffer.RecordSize; } | ||
184 | } | |||
185 | | |||
186 | /// <summary> | |||
187 | /// Get the record size being used by this stream's TarBuffer. | |||
188 | /// </summary> | |||
189 | /// <returns> | |||
190 | /// The TarBuffer record size. | |||
191 | /// </returns> | |||
192 | [Obsolete("Use RecordSize property instead")] | |||
193 | public int GetRecordSize() | |||
194 | { | |||
| 0 | 195 | return buffer.RecordSize; | ||
196 | } | |||
197 | | |||
198 | /// <summary> | |||
199 | /// Get a value indicating wether an entry is open, requiring more data to be written. | |||
200 | /// </summary> | |||
201 | bool IsEntryOpen { | |||
| 73 | 202 | get { return (currBytes < currSize); } | ||
203 | | |||
204 | } | |||
205 | | |||
206 | /// <summary> | |||
207 | /// Put an entry on the output stream. This writes the entry's | |||
208 | /// header and positions the output stream for writing | |||
209 | /// the contents of the entry. Once this method is called, the | |||
210 | /// stream is ready for calls to write() to write the entry's | |||
211 | /// contents. Once the contents are written, closeEntry() | |||
212 | /// <B>MUST</B> be called to ensure that all buffered data | |||
213 | /// is completely written to the output stream. | |||
214 | /// </summary> | |||
215 | /// <param name="entry"> | |||
216 | /// The TarEntry to be written to the archive. | |||
217 | /// </param> | |||
218 | public void PutNextEntry(TarEntry entry) | |||
219 | { | |||
| 70 | 220 | if (entry == null) { | ||
| 0 | 221 | throw new ArgumentNullException(nameof(entry)); | ||
222 | } | |||
223 | | |||
| 70 | 224 | if (entry.TarHeader.Name.Length > TarHeader.NAMELEN) { | ||
| 0 | 225 | var longHeader = new TarHeader(); | ||
| 0 | 226 | longHeader.TypeFlag = TarHeader.LF_GNU_LONGNAME; | ||
| 0 | 227 | longHeader.Name = longHeader.Name + "././@LongLink"; | ||
| 0 | 228 | longHeader.Mode = 420;//644 by default | ||
| 0 | 229 | longHeader.UserId = entry.UserId; | ||
| 0 | 230 | longHeader.GroupId = entry.GroupId; | ||
| 0 | 231 | longHeader.GroupName = entry.GroupName; | ||
| 0 | 232 | longHeader.UserName = entry.UserName; | ||
| 0 | 233 | longHeader.LinkName = ""; | ||
| 0 | 234 | longHeader.Size = entry.TarHeader.Name.Length + 1; // Plus one to avoid dropping last char | ||
235 | | |||
| 0 | 236 | longHeader.WriteHeader(blockBuffer); | ||
| 0 | 237 | buffer.WriteBlock(blockBuffer); // Add special long filename header block | ||
238 | | |||
| 0 | 239 | int nameCharIndex = 0; | ||
240 | | |||
| 0 | 241 | while (nameCharIndex < entry.TarHeader.Name.Length) { | ||
| 0 | 242 | Array.Clear(blockBuffer, 0, blockBuffer.Length); | ||
| 0 | 243 | TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0, TarBuffer.BlockSize); | ||
| 0 | 244 | nameCharIndex += TarBuffer.BlockSize; | ||
| 0 | 245 | buffer.WriteBlock(blockBuffer); | ||
246 | } | |||
247 | } | |||
248 | | |||
| 70 | 249 | entry.WriteEntryHeader(blockBuffer); | ||
| 70 | 250 | buffer.WriteBlock(blockBuffer); | ||
251 | | |||
| 70 | 252 | currBytes = 0; | ||
253 | | |||
| 70 | 254 | currSize = entry.IsDirectory ? 0 : entry.Size; | ||
| 70 | 255 | } | ||
256 | | |||
257 | /// <summary> | |||
258 | /// Close an entry. This method MUST be called for all file | |||
259 | /// entries that contain data. The reason is that we must | |||
260 | /// buffer data written to the stream in order to satisfy | |||
261 | /// the buffer's block based writes. Thus, there may be | |||
262 | /// data fragments still being assembled that must be written | |||
263 | /// to the output stream before this entry is closed and the | |||
264 | /// next entry written. | |||
265 | /// </summary> | |||
266 | public void CloseEntry() | |||
267 | { | |||
| 5 | 268 | if (assemblyBufferLength > 0) { | ||
| 5 | 269 | Array.Clear(assemblyBuffer, assemblyBufferLength, assemblyBuffer.Length - assemblyBufferLength); | ||
270 | | |||
| 5 | 271 | buffer.WriteBlock(assemblyBuffer); | ||
272 | | |||
| 5 | 273 | currBytes += assemblyBufferLength; | ||
| 5 | 274 | assemblyBufferLength = 0; | ||
275 | } | |||
276 | | |||
| 5 | 277 | if (currBytes < currSize) { | ||
| 0 | 278 | string errorText = string.Format( | ||
| 0 | 279 | "Entry closed at '{0}' before the '{1}' bytes specified in the header were written", | ||
| 0 | 280 | currBytes, currSize); | ||
| 0 | 281 | throw new TarException(errorText); | ||
282 | } | |||
| 5 | 283 | } | ||
284 | | |||
285 | /// <summary> | |||
286 | /// Writes a byte to the current tar archive entry. | |||
287 | /// This method simply calls Write(byte[], int, int). | |||
288 | /// </summary> | |||
289 | /// <param name="value"> | |||
290 | /// The byte to be written. | |||
291 | /// </param> | |||
292 | public override void WriteByte(byte value) | |||
293 | { | |||
| 45 | 294 | Write(new byte[] { value }, 0, 1); | ||
| 45 | 295 | } | ||
296 | | |||
297 | /// <summary> | |||
298 | /// Writes bytes to the current tar archive entry. This method | |||
299 | /// is aware of the current entry and will throw an exception if | |||
300 | /// you attempt to write bytes past the length specified for the | |||
301 | /// current entry. The method is also (painfully) aware of the | |||
302 | /// record buffering required by TarBuffer, and manages buffers | |||
303 | /// that are not a multiple of recordsize in length, including | |||
304 | /// assembling records from small buffers. | |||
305 | /// </summary> | |||
306 | /// <param name = "buffer"> | |||
307 | /// The buffer to write to the archive. | |||
308 | /// </param> | |||
309 | /// <param name = "offset"> | |||
310 | /// The offset in the buffer from which to get bytes. | |||
311 | /// </param> | |||
312 | /// <param name = "count"> | |||
313 | /// The number of bytes to write. | |||
314 | /// </param> | |||
315 | public override void Write(byte[] buffer, int offset, int count) | |||
316 | { | |||
| 4087 | 317 | if (buffer == null) { | ||
| 0 | 318 | throw new ArgumentNullException(nameof(buffer)); | ||
319 | } | |||
320 | | |||
| 4087 | 321 | if (offset < 0) { | ||
| 0 | 322 | throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); | ||
323 | } | |||
324 | | |||
| 4087 | 325 | if (buffer.Length - offset < count) { | ||
| 0 | 326 | throw new ArgumentException("offset and count combination is invalid"); | ||
327 | } | |||
328 | | |||
| 4087 | 329 | if (count < 0) { | ||
| 0 | 330 | throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); | ||
331 | } | |||
332 | | |||
| 4087 | 333 | if ((currBytes + count) > currSize) { | ||
| 0 | 334 | string errorText = string.Format("request to write '{0}' bytes exceeds size in header of '{1}' bytes", | ||
| 0 | 335 | count, this.currSize); | ||
| 0 | 336 | throw new ArgumentOutOfRangeException(nameof(count), errorText); | ||
337 | } | |||
338 | | |||
339 | // | |||
340 | // We have to deal with assembly!!! | |||
341 | // The programmer can be writing little 32 byte chunks for all | |||
342 | // we know, and we must assemble complete blocks for writing. | |||
343 | // TODO REVIEW Maybe this should be in TarBuffer? Could that help to | |||
344 | // eliminate some of the buffer copying. | |||
345 | // | |||
| 4087 | 346 | if (assemblyBufferLength > 0) { | ||
| 40 | 347 | if ((assemblyBufferLength + count) >= blockBuffer.Length) { | ||
| 0 | 348 | int aLen = blockBuffer.Length - assemblyBufferLength; | ||
349 | | |||
| 0 | 350 | Array.Copy(assemblyBuffer, 0, blockBuffer, 0, assemblyBufferLength); | ||
| 0 | 351 | Array.Copy(buffer, offset, blockBuffer, assemblyBufferLength, aLen); | ||
352 | | |||
| 0 | 353 | this.buffer.WriteBlock(blockBuffer); | ||
354 | | |||
| 0 | 355 | currBytes += blockBuffer.Length; | ||
356 | | |||
| 0 | 357 | offset += aLen; | ||
| 0 | 358 | count -= aLen; | ||
359 | | |||
| 0 | 360 | assemblyBufferLength = 0; | ||
| 0 | 361 | } else { | ||
| 40 | 362 | Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count); | ||
| 40 | 363 | offset += count; | ||
| 40 | 364 | assemblyBufferLength += count; | ||
| 40 | 365 | count -= count; | ||
366 | } | |||
367 | } | |||
368 | | |||
369 | // | |||
370 | // When we get here we have EITHER: | |||
371 | // o An empty "assembly" buffer. | |||
372 | // o No bytes to write (count == 0) | |||
373 | // | |||
| 8129 | 374 | while (count > 0) { | ||
| 4047 | 375 | if (count < blockBuffer.Length) { | ||
| 5 | 376 | Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count); | ||
| 5 | 377 | assemblyBufferLength += count; | ||
| 5 | 378 | break; | ||
379 | } | |||
380 | | |||
| 4042 | 381 | this.buffer.WriteBlock(buffer, offset); | ||
382 | | |||
| 4042 | 383 | int bufferLength = blockBuffer.Length; | ||
| 4042 | 384 | currBytes += bufferLength; | ||
| 4042 | 385 | count -= bufferLength; | ||
| 4042 | 386 | offset += bufferLength; | ||
387 | } | |||
| 4082 | 388 | } | ||
389 | | |||
390 | /// <summary> | |||
391 | /// Write an EOF (end of archive) block to the tar archive. | |||
392 | /// An EOF block consists of all zeros. | |||
393 | /// </summary> | |||
394 | void WriteEofBlock() | |||
395 | { | |||
| 73 | 396 | Array.Clear(blockBuffer, 0, blockBuffer.Length); | ||
| 73 | 397 | buffer.WriteBlock(blockBuffer); | ||
| 73 | 398 | } | ||
399 | | |||
400 | #region Instance Fields | |||
401 | /// <summary> | |||
402 | /// bytes written for this entry so far | |||
403 | /// </summary> | |||
404 | long currBytes; | |||
405 | | |||
406 | /// <summary> | |||
407 | /// current 'Assembly' buffer length | |||
408 | /// </summary> | |||
409 | int assemblyBufferLength; | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Flag indicating wether this instance has been closed or not. | |||
413 | /// </summary> | |||
414 | bool isClosed; | |||
415 | | |||
416 | /// <summary> | |||
417 | /// Size for the current entry | |||
418 | /// </summary> | |||
419 | protected long currSize; | |||
420 | | |||
421 | /// <summary> | |||
422 | /// single block working buffer | |||
423 | /// </summary> | |||
424 | protected byte[] blockBuffer; | |||
425 | | |||
426 | /// <summary> | |||
427 | /// 'Assembly' buffer used to assemble data before writing | |||
428 | /// </summary> | |||
429 | protected byte[] assemblyBuffer; | |||
430 | | |||
431 | /// <summary> | |||
432 | /// TarBuffer used to provide correct blocking factor | |||
433 | /// </summary> | |||
434 | protected TarBuffer buffer; | |||
435 | | |||
436 | /// <summary> | |||
437 | /// the destination stream for the archive contents | |||
438 | /// </summary> | |||
439 | protected Stream outputStream; | |||
440 | #endregion | |||
441 | } | |||
442 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.TestStatus |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs |
| Covered lines: | 7 |
| Uncovered lines: | 13 |
| Coverable lines: | 20 |
| Total lines: | 4263 |
| Line coverage: | 35% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| AddError() | 1 | 100 | 100 |
| SetOperation(...) | 1 | 0 | 0 |
| SetEntry(...) | 1 | 0 | 0 |
| SetBytesTested(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.IO; | |||
4 | using System.Text; | |||
5 | using System.Globalization; | |||
6 | using System.Security.Cryptography; | |||
7 | using ICSharpCode.SharpZipLib.Encryption; | |||
8 | using ICSharpCode.SharpZipLib.Core; | |||
9 | using ICSharpCode.SharpZipLib.Checksum; | |||
10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
11 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
12 | | |||
13 | namespace ICSharpCode.SharpZipLib.Zip | |||
14 | { | |||
15 | #region Keys Required Event Args | |||
16 | /// <summary> | |||
17 | /// Arguments used with KeysRequiredEvent | |||
18 | /// </summary> | |||
19 | public class KeysRequiredEventArgs : EventArgs | |||
20 | { | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
24 | /// </summary> | |||
25 | /// <param name="name">The name of the file for which keys are required.</param> | |||
26 | public KeysRequiredEventArgs(string name) | |||
27 | { | |||
28 | fileName = name; | |||
29 | } | |||
30 | | |||
31 | /// <summary> | |||
32 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
33 | /// </summary> | |||
34 | /// <param name="name">The name of the file for which keys are required.</param> | |||
35 | /// <param name="keyValue">The current key value.</param> | |||
36 | public KeysRequiredEventArgs(string name, byte[] keyValue) | |||
37 | { | |||
38 | fileName = name; | |||
39 | key = keyValue; | |||
40 | } | |||
41 | | |||
42 | #endregion | |||
43 | #region Properties | |||
44 | /// <summary> | |||
45 | /// Gets the name of the file for which keys are required. | |||
46 | /// </summary> | |||
47 | public string FileName { | |||
48 | get { return fileName; } | |||
49 | } | |||
50 | | |||
51 | /// <summary> | |||
52 | /// Gets or sets the key value | |||
53 | /// </summary> | |||
54 | public byte[] Key { | |||
55 | get { return key; } | |||
56 | set { key = value; } | |||
57 | } | |||
58 | #endregion | |||
59 | | |||
60 | #region Instance Fields | |||
61 | string fileName; | |||
62 | byte[] key; | |||
63 | #endregion | |||
64 | } | |||
65 | #endregion | |||
66 | | |||
67 | #region Test Definitions | |||
68 | /// <summary> | |||
69 | /// The strategy to apply to testing. | |||
70 | /// </summary> | |||
71 | public enum TestStrategy | |||
72 | { | |||
73 | /// <summary> | |||
74 | /// Find the first error only. | |||
75 | /// </summary> | |||
76 | FindFirstError, | |||
77 | /// <summary> | |||
78 | /// Find all possible errors. | |||
79 | /// </summary> | |||
80 | FindAllErrors, | |||
81 | } | |||
82 | | |||
83 | /// <summary> | |||
84 | /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. | |||
85 | /// </summary> | |||
86 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
87 | public enum TestOperation | |||
88 | { | |||
89 | /// <summary> | |||
90 | /// Setting up testing. | |||
91 | /// </summary> | |||
92 | Initialising, | |||
93 | | |||
94 | /// <summary> | |||
95 | /// Testing an individual entries header | |||
96 | /// </summary> | |||
97 | EntryHeader, | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Testing an individual entries data | |||
101 | /// </summary> | |||
102 | EntryData, | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Testing an individual entry has completed. | |||
106 | /// </summary> | |||
107 | EntryComplete, | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Running miscellaneous tests | |||
111 | /// </summary> | |||
112 | MiscellaneousTests, | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Testing is complete | |||
116 | /// </summary> | |||
117 | Complete, | |||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. | |||
122 | /// </summary> | |||
123 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
124 | public class TestStatus | |||
125 | { | |||
126 | #region Constructors | |||
127 | /// <summary> | |||
128 | /// Initialise a new instance of <see cref="TestStatus"/> | |||
129 | /// </summary> | |||
130 | /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> | |||
| 96 | 131 | public TestStatus(ZipFile file) | ||
132 | { | |||
| 96 | 133 | file_ = file; | ||
| 96 | 134 | } | ||
135 | #endregion | |||
136 | | |||
137 | #region Properties | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Get the current <see cref="TestOperation"/> in progress. | |||
141 | /// </summary> | |||
142 | public TestOperation Operation { | |||
| 0 | 143 | get { return operation_; } | ||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get the <see cref="ZipFile"/> this status is applicable to. | |||
148 | /// </summary> | |||
149 | public ZipFile File { | |||
| 0 | 150 | get { return file_; } | ||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Get the current/last entry tested. | |||
155 | /// </summary> | |||
156 | public ZipEntry Entry { | |||
| 0 | 157 | get { return entry_; } | ||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Get the number of errors detected so far. | |||
162 | /// </summary> | |||
163 | public int ErrorCount { | |||
| 96 | 164 | get { return errorCount_; } | ||
165 | } | |||
166 | | |||
167 | /// <summary> | |||
168 | /// Get the number of bytes tested so far for the current entry. | |||
169 | /// </summary> | |||
170 | public long BytesTested { | |||
| 0 | 171 | get { return bytesTested_; } | ||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Get a value indicating wether the last entry test was valid. | |||
176 | /// </summary> | |||
177 | public bool EntryValid { | |||
| 0 | 178 | get { return entryValid_; } | ||
179 | } | |||
180 | #endregion | |||
181 | | |||
182 | #region Internal API | |||
183 | internal void AddError() | |||
184 | { | |||
| 1 | 185 | errorCount_++; | ||
| 1 | 186 | entryValid_ = false; | ||
| 1 | 187 | } | ||
188 | | |||
189 | internal void SetOperation(TestOperation operation) | |||
190 | { | |||
| 0 | 191 | operation_ = operation; | ||
| 0 | 192 | } | ||
193 | | |||
194 | internal void SetEntry(ZipEntry entry) | |||
195 | { | |||
| 0 | 196 | entry_ = entry; | ||
| 0 | 197 | entryValid_ = true; | ||
| 0 | 198 | bytesTested_ = 0; | ||
| 0 | 199 | } | ||
200 | | |||
201 | internal void SetBytesTested(long value) | |||
202 | { | |||
| 0 | 203 | bytesTested_ = value; | ||
| 0 | 204 | } | ||
205 | #endregion | |||
206 | | |||
207 | #region Instance Fields | |||
208 | ZipFile file_; | |||
209 | ZipEntry entry_; | |||
210 | bool entryValid_; | |||
211 | int errorCount_; | |||
212 | long bytesTested_; | |||
213 | TestOperation operation_; | |||
214 | #endregion | |||
215 | } | |||
216 | | |||
217 | /// <summary> | |||
218 | /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if | |||
219 | /// </summary> | |||
220 | /// <remarks>If the message is non-null an error has occured. If the message is null | |||
221 | /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> | |||
222 | public delegate void ZipTestResultHandler(TestStatus status, string message); | |||
223 | #endregion | |||
224 | | |||
225 | #region Update Definitions | |||
226 | /// <summary> | |||
227 | /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. | |||
228 | /// </summary> | |||
229 | public enum FileUpdateMode | |||
230 | { | |||
231 | /// <summary> | |||
232 | /// Perform all updates on temporary files ensuring that the original file is saved. | |||
233 | /// </summary> | |||
234 | Safe, | |||
235 | /// <summary> | |||
236 | /// Update the archive directly, which is faster but less safe. | |||
237 | /// </summary> | |||
238 | Direct, | |||
239 | } | |||
240 | #endregion | |||
241 | | |||
242 | #region ZipFile Class | |||
243 | /// <summary> | |||
244 | /// This class represents a Zip archive. You can ask for the contained | |||
245 | /// entries, or get an input stream for a file entry. The entry is | |||
246 | /// automatically decompressed. | |||
247 | /// | |||
248 | /// You can also update the archive adding or deleting entries. | |||
249 | /// | |||
250 | /// This class is thread safe for input: You can open input streams for arbitrary | |||
251 | /// entries in different threads. | |||
252 | /// <br/> | |||
253 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
254 | /// </summary> | |||
255 | /// <example> | |||
256 | /// <code> | |||
257 | /// using System; | |||
258 | /// using System.Text; | |||
259 | /// using System.Collections; | |||
260 | /// using System.IO; | |||
261 | /// | |||
262 | /// using ICSharpCode.SharpZipLib.Zip; | |||
263 | /// | |||
264 | /// class MainClass | |||
265 | /// { | |||
266 | /// static public void Main(string[] args) | |||
267 | /// { | |||
268 | /// using (ZipFile zFile = new ZipFile(args[0])) { | |||
269 | /// Console.WriteLine("Listing of : " + zFile.Name); | |||
270 | /// Console.WriteLine(""); | |||
271 | /// Console.WriteLine("Raw Size Size Date Time Name"); | |||
272 | /// Console.WriteLine("-------- -------- -------- ------ ---------"); | |||
273 | /// foreach (ZipEntry e in zFile) { | |||
274 | /// if ( e.IsFile ) { | |||
275 | /// DateTime d = e.DateTime; | |||
276 | /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, | |||
277 | /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), | |||
278 | /// e.Name); | |||
279 | /// } | |||
280 | /// } | |||
281 | /// } | |||
282 | /// } | |||
283 | /// } | |||
284 | /// </code> | |||
285 | /// </example> | |||
286 | public class ZipFile : IEnumerable, IDisposable | |||
287 | { | |||
288 | #region KeyHandling | |||
289 | | |||
290 | /// <summary> | |||
291 | /// Delegate for handling keys/password setting during compresion/decompression. | |||
292 | /// </summary> | |||
293 | public delegate void KeysRequiredEventHandler( | |||
294 | object sender, | |||
295 | KeysRequiredEventArgs e | |||
296 | ); | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Event handler for handling encryption keys. | |||
300 | /// </summary> | |||
301 | public KeysRequiredEventHandler KeysRequired; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Handles getting of encryption keys when required. | |||
305 | /// </summary> | |||
306 | /// <param name="fileName">The file for which encryption keys are required.</param> | |||
307 | void OnKeysRequired(string fileName) | |||
308 | { | |||
309 | if (KeysRequired != null) { | |||
310 | var krea = new KeysRequiredEventArgs(fileName, key); | |||
311 | KeysRequired(this, krea); | |||
312 | key = krea.Key; | |||
313 | } | |||
314 | } | |||
315 | | |||
316 | /// <summary> | |||
317 | /// Get/set the encryption key value. | |||
318 | /// </summary> | |||
319 | byte[] Key { | |||
320 | get { return key; } | |||
321 | set { key = value; } | |||
322 | } | |||
323 | | |||
324 | /// <summary> | |||
325 | /// Password to be used for encrypting/decrypting files. | |||
326 | /// </summary> | |||
327 | /// <remarks>Set to null if no password is required.</remarks> | |||
328 | public string Password { | |||
329 | set { | |||
330 | if (string.IsNullOrEmpty(value)) { | |||
331 | key = null; | |||
332 | } else { | |||
333 | rawPassword_ = value; | |||
334 | key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); | |||
335 | } | |||
336 | } | |||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Get a value indicating wether encryption keys are currently available. | |||
341 | /// </summary> | |||
342 | bool HaveKeys { | |||
343 | get { return key != null; } | |||
344 | } | |||
345 | #endregion | |||
346 | | |||
347 | #region Constructors | |||
348 | /// <summary> | |||
349 | /// Opens a Zip file with the given name for reading. | |||
350 | /// </summary> | |||
351 | /// <param name="name">The name of the file to open.</param> | |||
352 | /// <exception cref="ArgumentNullException">The argument supplied is null.</exception> | |||
353 | /// <exception cref="IOException"> | |||
354 | /// An i/o error occurs | |||
355 | /// </exception> | |||
356 | /// <exception cref="ZipException"> | |||
357 | /// The file doesn't contain a valid zip archive. | |||
358 | /// </exception> | |||
359 | public ZipFile(string name) | |||
360 | { | |||
361 | if (name == null) { | |||
362 | throw new ArgumentNullException(nameof(name)); | |||
363 | } | |||
364 | | |||
365 | name_ = name; | |||
366 | | |||
367 | baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
368 | isStreamOwner = true; | |||
369 | | |||
370 | try { | |||
371 | ReadEntries(); | |||
372 | } catch { | |||
373 | DisposeInternal(true); | |||
374 | throw; | |||
375 | } | |||
376 | } | |||
377 | | |||
378 | /// <summary> | |||
379 | /// Opens a Zip file reading the given <see cref="FileStream"/>. | |||
380 | /// </summary> | |||
381 | /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> | |||
382 | /// <exception cref="ArgumentNullException">The supplied argument is null.</exception> | |||
383 | /// <exception cref="IOException"> | |||
384 | /// An i/o error occurs. | |||
385 | /// </exception> | |||
386 | /// <exception cref="ZipException"> | |||
387 | /// The file doesn't contain a valid zip archive. | |||
388 | /// </exception> | |||
389 | public ZipFile(FileStream file) | |||
390 | { | |||
391 | if (file == null) { | |||
392 | throw new ArgumentNullException(nameof(file)); | |||
393 | } | |||
394 | | |||
395 | if (!file.CanSeek) { | |||
396 | throw new ArgumentException("Stream is not seekable", nameof(file)); | |||
397 | } | |||
398 | | |||
399 | baseStream_ = file; | |||
400 | name_ = file.Name; | |||
401 | isStreamOwner = true; | |||
402 | | |||
403 | try { | |||
404 | ReadEntries(); | |||
405 | } catch { | |||
406 | DisposeInternal(true); | |||
407 | throw; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Opens a Zip file reading the given <see cref="Stream"/>. | |||
413 | /// </summary> | |||
414 | /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> | |||
415 | /// <exception cref="IOException"> | |||
416 | /// An i/o error occurs | |||
417 | /// </exception> | |||
418 | /// <exception cref="ZipException"> | |||
419 | /// The stream doesn't contain a valid zip archive.<br/> | |||
420 | /// </exception> | |||
421 | /// <exception cref="ArgumentException"> | |||
422 | /// The <see cref="Stream">stream</see> doesnt support seeking. | |||
423 | /// </exception> | |||
424 | /// <exception cref="ArgumentNullException"> | |||
425 | /// The <see cref="Stream">stream</see> argument is null. | |||
426 | /// </exception> | |||
427 | public ZipFile(Stream stream) | |||
428 | { | |||
429 | if (stream == null) { | |||
430 | throw new ArgumentNullException(nameof(stream)); | |||
431 | } | |||
432 | | |||
433 | if (!stream.CanSeek) { | |||
434 | throw new ArgumentException("Stream is not seekable", nameof(stream)); | |||
435 | } | |||
436 | | |||
437 | baseStream_ = stream; | |||
438 | isStreamOwner = true; | |||
439 | | |||
440 | if (baseStream_.Length > 0) { | |||
441 | try { | |||
442 | ReadEntries(); | |||
443 | } catch { | |||
444 | DisposeInternal(true); | |||
445 | throw; | |||
446 | } | |||
447 | } else { | |||
448 | entries_ = new ZipEntry[0]; | |||
449 | isNewArchive_ = true; | |||
450 | } | |||
451 | } | |||
452 | | |||
453 | /// <summary> | |||
454 | /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. | |||
455 | /// </summary> | |||
456 | internal ZipFile() | |||
457 | { | |||
458 | entries_ = new ZipEntry[0]; | |||
459 | isNewArchive_ = true; | |||
460 | } | |||
461 | | |||
462 | #endregion | |||
463 | | |||
464 | #region Destructors and Closing | |||
465 | /// <summary> | |||
466 | /// Finalize this instance. | |||
467 | /// </summary> | |||
468 | ~ZipFile() | |||
469 | { | |||
470 | Dispose(false); | |||
471 | } | |||
472 | | |||
473 | /// <summary> | |||
474 | /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying | |||
475 | /// Once closed, no further instance methods should be called. | |||
476 | /// </summary> | |||
477 | /// <exception cref="System.IO.IOException"> | |||
478 | /// An i/o error occurs. | |||
479 | /// </exception> | |||
480 | public void Close() | |||
481 | { | |||
482 | DisposeInternal(true); | |||
483 | GC.SuppressFinalize(this); | |||
484 | } | |||
485 | | |||
486 | #endregion | |||
487 | | |||
488 | #region Creators | |||
489 | /// <summary> | |||
490 | /// Create a new <see cref="ZipFile"/> whose data will be stored in a file. | |||
491 | /// </summary> | |||
492 | /// <param name="fileName">The name of the archive to create.</param> | |||
493 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
494 | /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception> | |||
495 | public static ZipFile Create(string fileName) | |||
496 | { | |||
497 | if (fileName == null) { | |||
498 | throw new ArgumentNullException(nameof(fileName)); | |||
499 | } | |||
500 | | |||
501 | FileStream fs = File.Create(fileName); | |||
502 | | |||
503 | var result = new ZipFile(); | |||
504 | result.name_ = fileName; | |||
505 | result.baseStream_ = fs; | |||
506 | result.isStreamOwner = true; | |||
507 | return result; | |||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. | |||
512 | /// </summary> | |||
513 | /// <param name="outStream">The stream providing data storage.</param> | |||
514 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
515 | /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception> | |||
516 | /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception> | |||
517 | public static ZipFile Create(Stream outStream) | |||
518 | { | |||
519 | if (outStream == null) { | |||
520 | throw new ArgumentNullException(nameof(outStream)); | |||
521 | } | |||
522 | | |||
523 | if (!outStream.CanWrite) { | |||
524 | throw new ArgumentException("Stream is not writeable", nameof(outStream)); | |||
525 | } | |||
526 | | |||
527 | if (!outStream.CanSeek) { | |||
528 | throw new ArgumentException("Stream is not seekable", nameof(outStream)); | |||
529 | } | |||
530 | | |||
531 | var result = new ZipFile(); | |||
532 | result.baseStream_ = outStream; | |||
533 | return result; | |||
534 | } | |||
535 | | |||
536 | #endregion | |||
537 | | |||
538 | #region Properties | |||
539 | /// <summary> | |||
540 | /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. | |||
541 | /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. | |||
542 | /// </summary> | |||
543 | /// <remarks> | |||
544 | /// The default value is true in all cases. | |||
545 | /// </remarks> | |||
546 | public bool IsStreamOwner { | |||
547 | get { return isStreamOwner; } | |||
548 | set { isStreamOwner = value; } | |||
549 | } | |||
550 | | |||
551 | /// <summary> | |||
552 | /// Get a value indicating wether | |||
553 | /// this archive is embedded in another file or not. | |||
554 | /// </summary> | |||
555 | public bool IsEmbeddedArchive { | |||
556 | // Not strictly correct in all circumstances currently | |||
557 | get { return offsetOfFirstEntry > 0; } | |||
558 | } | |||
559 | | |||
560 | /// <summary> | |||
561 | /// Get a value indicating that this archive is a new one. | |||
562 | /// </summary> | |||
563 | public bool IsNewArchive { | |||
564 | get { return isNewArchive_; } | |||
565 | } | |||
566 | | |||
567 | /// <summary> | |||
568 | /// Gets the comment for the zip file. | |||
569 | /// </summary> | |||
570 | public string ZipFileComment { | |||
571 | get { return comment_; } | |||
572 | } | |||
573 | | |||
574 | /// <summary> | |||
575 | /// Gets the name of this zip file. | |||
576 | /// </summary> | |||
577 | public string Name { | |||
578 | get { return name_; } | |||
579 | } | |||
580 | | |||
581 | /// <summary> | |||
582 | /// Gets the number of entries in this zip file. | |||
583 | /// </summary> | |||
584 | /// <exception cref="InvalidOperationException"> | |||
585 | /// The Zip file has been closed. | |||
586 | /// </exception> | |||
587 | [Obsolete("Use the Count property instead")] | |||
588 | public int Size { | |||
589 | get { | |||
590 | return entries_.Length; | |||
591 | } | |||
592 | } | |||
593 | | |||
594 | /// <summary> | |||
595 | /// Get the number of entries contained in this <see cref="ZipFile"/>. | |||
596 | /// </summary> | |||
597 | public long Count { | |||
598 | get { | |||
599 | return entries_.Length; | |||
600 | } | |||
601 | } | |||
602 | | |||
603 | /// <summary> | |||
604 | /// Indexer property for ZipEntries | |||
605 | /// </summary> | |||
606 | [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] | |||
607 | public ZipEntry this[int index] { | |||
608 | get { | |||
609 | return (ZipEntry)entries_[index].Clone(); | |||
610 | } | |||
611 | } | |||
612 | | |||
613 | #endregion | |||
614 | | |||
615 | #region Input Handling | |||
616 | /// <summary> | |||
617 | /// Gets an enumerator for the Zip entries in this Zip file. | |||
618 | /// </summary> | |||
619 | /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> | |||
620 | /// <exception cref="ObjectDisposedException"> | |||
621 | /// The Zip file has been closed. | |||
622 | /// </exception> | |||
623 | public IEnumerator GetEnumerator() | |||
624 | { | |||
625 | if (isDisposed_) { | |||
626 | throw new ObjectDisposedException("ZipFile"); | |||
627 | } | |||
628 | | |||
629 | return new ZipEntryEnumerator(entries_); | |||
630 | } | |||
631 | | |||
632 | /// <summary> | |||
633 | /// Return the index of the entry with a matching name | |||
634 | /// </summary> | |||
635 | /// <param name="name">Entry name to find</param> | |||
636 | /// <param name="ignoreCase">If true the comparison is case insensitive</param> | |||
637 | /// <returns>The index position of the matching entry or -1 if not found</returns> | |||
638 | /// <exception cref="ObjectDisposedException"> | |||
639 | /// The Zip file has been closed. | |||
640 | /// </exception> | |||
641 | public int FindEntry(string name, bool ignoreCase) | |||
642 | { | |||
643 | if (isDisposed_) { | |||
644 | throw new ObjectDisposedException("ZipFile"); | |||
645 | } | |||
646 | | |||
647 | // TODO: This will be slow as the next ice age for huge archives! | |||
648 | for (int i = 0; i < entries_.Length; i++) { | |||
649 | if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) { | |||
650 | return i; | |||
651 | } | |||
652 | } | |||
653 | return -1; | |||
654 | } | |||
655 | | |||
656 | /// <summary> | |||
657 | /// Searches for a zip entry in this archive with the given name. | |||
658 | /// String comparisons are case insensitive | |||
659 | /// </summary> | |||
660 | /// <param name="name"> | |||
661 | /// The name to find. May contain directory components separated by slashes ('/'). | |||
662 | /// </param> | |||
663 | /// <returns> | |||
664 | /// A clone of the zip entry, or null if no entry with that name exists. | |||
665 | /// </returns> | |||
666 | /// <exception cref="ObjectDisposedException"> | |||
667 | /// The Zip file has been closed. | |||
668 | /// </exception> | |||
669 | public ZipEntry GetEntry(string name) | |||
670 | { | |||
671 | if (isDisposed_) { | |||
672 | throw new ObjectDisposedException("ZipFile"); | |||
673 | } | |||
674 | | |||
675 | int index = FindEntry(name, true); | |||
676 | return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; | |||
677 | } | |||
678 | | |||
679 | /// <summary> | |||
680 | /// Gets an input stream for reading the given zip entry data in an uncompressed form. | |||
681 | /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). | |||
682 | /// </summary> | |||
683 | /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> | |||
684 | /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> | |||
685 | /// <exception cref="ObjectDisposedException"> | |||
686 | /// The ZipFile has already been closed | |||
687 | /// </exception> | |||
688 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
689 | /// The compression method for the entry is unknown | |||
690 | /// </exception> | |||
691 | /// <exception cref="IndexOutOfRangeException"> | |||
692 | /// The entry is not found in the ZipFile | |||
693 | /// </exception> | |||
694 | public Stream GetInputStream(ZipEntry entry) | |||
695 | { | |||
696 | if (entry == null) { | |||
697 | throw new ArgumentNullException(nameof(entry)); | |||
698 | } | |||
699 | | |||
700 | if (isDisposed_) { | |||
701 | throw new ObjectDisposedException("ZipFile"); | |||
702 | } | |||
703 | | |||
704 | long index = entry.ZipFileIndex; | |||
705 | if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { | |||
706 | index = FindEntry(entry.Name, true); | |||
707 | if (index < 0) { | |||
708 | throw new ZipException("Entry cannot be found"); | |||
709 | } | |||
710 | } | |||
711 | return GetInputStream(index); | |||
712 | } | |||
713 | | |||
714 | /// <summary> | |||
715 | /// Creates an input stream reading a zip entry | |||
716 | /// </summary> | |||
717 | /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> | |||
718 | /// <returns> | |||
719 | /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> | |||
720 | /// </returns> | |||
721 | /// <exception cref="ObjectDisposedException"> | |||
722 | /// The ZipFile has already been closed | |||
723 | /// </exception> | |||
724 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
725 | /// The compression method for the entry is unknown | |||
726 | /// </exception> | |||
727 | /// <exception cref="IndexOutOfRangeException"> | |||
728 | /// The entry is not found in the ZipFile | |||
729 | /// </exception> | |||
730 | public Stream GetInputStream(long entryIndex) | |||
731 | { | |||
732 | if (isDisposed_) { | |||
733 | throw new ObjectDisposedException("ZipFile"); | |||
734 | } | |||
735 | | |||
736 | long start = LocateEntry(entries_[entryIndex]); | |||
737 | CompressionMethod method = entries_[entryIndex].CompressionMethod; | |||
738 | Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); | |||
739 | | |||
740 | if (entries_[entryIndex].IsCrypted == true) { | |||
741 | result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); | |||
742 | if (result == null) { | |||
743 | throw new ZipException("Unable to decrypt this entry"); | |||
744 | } | |||
745 | } | |||
746 | | |||
747 | switch (method) { | |||
748 | case CompressionMethod.Stored: | |||
749 | // read as is. | |||
750 | break; | |||
751 | | |||
752 | case CompressionMethod.Deflated: | |||
753 | // No need to worry about ownership and closing as underlying stream close does nothing. | |||
754 | result = new InflaterInputStream(result, new Inflater(true)); | |||
755 | break; | |||
756 | | |||
757 | default: | |||
758 | throw new ZipException("Unsupported compression method " + method); | |||
759 | } | |||
760 | | |||
761 | return result; | |||
762 | } | |||
763 | | |||
764 | #endregion | |||
765 | | |||
766 | #region Archive Testing | |||
767 | /// <summary> | |||
768 | /// Test an archive for integrity/validity | |||
769 | /// </summary> | |||
770 | /// <param name="testData">Perform low level data Crc check</param> | |||
771 | /// <returns>true if all tests pass, false otherwise</returns> | |||
772 | /// <remarks>Testing will terminate on the first error found.</remarks> | |||
773 | public bool TestArchive(bool testData) | |||
774 | { | |||
775 | return TestArchive(testData, TestStrategy.FindFirstError, null); | |||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Test an archive for integrity/validity | |||
780 | /// </summary> | |||
781 | /// <param name="testData">Perform low level data Crc check</param> | |||
782 | /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> | |||
783 | /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> | |||
784 | /// <returns>true if all tests pass, false otherwise</returns> | |||
785 | /// <exception cref="ObjectDisposedException">The object has already been closed.</exception> | |||
786 | public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) | |||
787 | { | |||
788 | if (isDisposed_) { | |||
789 | throw new ObjectDisposedException("ZipFile"); | |||
790 | } | |||
791 | | |||
792 | var status = new TestStatus(this); | |||
793 | | |||
794 | if (resultHandler != null) { | |||
795 | resultHandler(status, null); | |||
796 | } | |||
797 | | |||
798 | HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; | |||
799 | | |||
800 | bool testing = true; | |||
801 | | |||
802 | try { | |||
803 | int entryIndex = 0; | |||
804 | | |||
805 | while (testing && (entryIndex < Count)) { | |||
806 | if (resultHandler != null) { | |||
807 | status.SetEntry(this[entryIndex]); | |||
808 | status.SetOperation(TestOperation.EntryHeader); | |||
809 | resultHandler(status, null); | |||
810 | } | |||
811 | | |||
812 | try { | |||
813 | TestLocalHeader(this[entryIndex], test); | |||
814 | } catch (ZipException ex) { | |||
815 | status.AddError(); | |||
816 | | |||
817 | if (resultHandler != null) { | |||
818 | resultHandler(status, | |||
819 | string.Format("Exception during test - '{0}'", ex.Message)); | |||
820 | } | |||
821 | | |||
822 | testing &= strategy != TestStrategy.FindFirstError; | |||
823 | } | |||
824 | | |||
825 | if (testing && testData && this[entryIndex].IsFile) { | |||
826 | if (resultHandler != null) { | |||
827 | status.SetOperation(TestOperation.EntryData); | |||
828 | resultHandler(status, null); | |||
829 | } | |||
830 | | |||
831 | var crc = new Crc32(); | |||
832 | | |||
833 | using (Stream entryStream = this.GetInputStream(this[entryIndex])) { | |||
834 | | |||
835 | byte[] buffer = new byte[4096]; | |||
836 | long totalBytes = 0; | |||
837 | int bytesRead; | |||
838 | while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { | |||
839 | crc.Update(buffer, 0, bytesRead); | |||
840 | | |||
841 | if (resultHandler != null) { | |||
842 | totalBytes += bytesRead; | |||
843 | status.SetBytesTested(totalBytes); | |||
844 | resultHandler(status, null); | |||
845 | } | |||
846 | } | |||
847 | } | |||
848 | | |||
849 | if (this[entryIndex].Crc != crc.Value) { | |||
850 | status.AddError(); | |||
851 | | |||
852 | if (resultHandler != null) { | |||
853 | resultHandler(status, "CRC mismatch"); | |||
854 | } | |||
855 | | |||
856 | testing &= strategy != TestStrategy.FindFirstError; | |||
857 | } | |||
858 | | |||
859 | if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
860 | var helper = new ZipHelperStream(baseStream_); | |||
861 | var data = new DescriptorData(); | |||
862 | helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); | |||
863 | if (this[entryIndex].Crc != data.Crc) { | |||
864 | status.AddError(); | |||
865 | } | |||
866 | | |||
867 | if (this[entryIndex].CompressedSize != data.CompressedSize) { | |||
868 | status.AddError(); | |||
869 | } | |||
870 | | |||
871 | if (this[entryIndex].Size != data.Size) { | |||
872 | status.AddError(); | |||
873 | } | |||
874 | } | |||
875 | } | |||
876 | | |||
877 | if (resultHandler != null) { | |||
878 | status.SetOperation(TestOperation.EntryComplete); | |||
879 | resultHandler(status, null); | |||
880 | } | |||
881 | | |||
882 | entryIndex += 1; | |||
883 | } | |||
884 | | |||
885 | if (resultHandler != null) { | |||
886 | status.SetOperation(TestOperation.MiscellaneousTests); | |||
887 | resultHandler(status, null); | |||
888 | } | |||
889 | | |||
890 | // TODO: the 'Corrina Johns' test where local headers are missing from | |||
891 | // the central directory. They are therefore invisible to many archivers. | |||
892 | } catch (Exception ex) { | |||
893 | status.AddError(); | |||
894 | | |||
895 | if (resultHandler != null) { | |||
896 | resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); | |||
897 | } | |||
898 | } | |||
899 | | |||
900 | if (resultHandler != null) { | |||
901 | status.SetOperation(TestOperation.Complete); | |||
902 | status.SetEntry(null); | |||
903 | resultHandler(status, null); | |||
904 | } | |||
905 | | |||
906 | return (status.ErrorCount == 0); | |||
907 | } | |||
908 | | |||
909 | [Flags] | |||
910 | enum HeaderTest | |||
911 | { | |||
912 | Extract = 0x01, // Check that this header represents an entry whose data can be extracted | |||
913 | Header = 0x02, // Check that this header contents are valid | |||
914 | } | |||
915 | | |||
916 | /// <summary> | |||
917 | /// Test a local header against that provided from the central directory | |||
918 | /// </summary> | |||
919 | /// <param name="entry"> | |||
920 | /// The entry to test against | |||
921 | /// </param> | |||
922 | /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> | |||
923 | /// <returns>The offset of the entries data in the file</returns> | |||
924 | long TestLocalHeader(ZipEntry entry, HeaderTest tests) | |||
925 | { | |||
926 | lock (baseStream_) { | |||
927 | bool testHeader = (tests & HeaderTest.Header) != 0; | |||
928 | bool testData = (tests & HeaderTest.Extract) != 0; | |||
929 | | |||
930 | baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); | |||
931 | if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { | |||
932 | throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset) | |||
933 | } | |||
934 | | |||
935 | var extractVersion = (short)(ReadLEUshort() & 0x00ff); | |||
936 | var localFlags = (short)ReadLEUshort(); | |||
937 | var compressionMethod = (short)ReadLEUshort(); | |||
938 | var fileTime = (short)ReadLEUshort(); | |||
939 | var fileDate = (short)ReadLEUshort(); | |||
940 | uint crcValue = ReadLEUint(); | |||
941 | long compressedSize = ReadLEUint(); | |||
942 | long size = ReadLEUint(); | |||
943 | int storedNameLength = ReadLEUshort(); | |||
944 | int extraDataLength = ReadLEUshort(); | |||
945 | | |||
946 | byte[] nameData = new byte[storedNameLength]; | |||
947 | StreamUtils.ReadFully(baseStream_, nameData); | |||
948 | | |||
949 | byte[] extraData = new byte[extraDataLength]; | |||
950 | StreamUtils.ReadFully(baseStream_, extraData); | |||
951 | | |||
952 | var localExtraData = new ZipExtraData(extraData); | |||
953 | | |||
954 | // Extra data / zip64 checks | |||
955 | if (localExtraData.Find(1)) { | |||
956 | // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 | |||
957 | // and size or compressedSize = MaxValue, due to rogue creators. | |||
958 | | |||
959 | size = localExtraData.ReadLong(); | |||
960 | compressedSize = localExtraData.ReadLong(); | |||
961 | | |||
962 | if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
963 | // These may be valid if patched later | |||
964 | if ((size != -1) && (size != entry.Size)) { | |||
965 | throw new ZipException("Size invalid for descriptor"); | |||
966 | } | |||
967 | | |||
968 | if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) { | |||
969 | throw new ZipException("Compressed size invalid for descriptor"); | |||
970 | } | |||
971 | } | |||
972 | } else { | |||
973 | // No zip64 extra data but entry requires it. | |||
974 | if ((extractVersion >= ZipConstants.VersionZip64) && | |||
975 | (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) { | |||
976 | throw new ZipException("Required Zip64 extended information missing"); | |||
977 | } | |||
978 | } | |||
979 | | |||
980 | if (testData) { | |||
981 | if (entry.IsFile) { | |||
982 | if (!entry.IsCompressionMethodSupported()) { | |||
983 | throw new ZipException("Compression method not supported"); | |||
984 | } | |||
985 | | |||
986 | if ((extractVersion > ZipConstants.VersionMadeBy) | |||
987 | || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { | |||
988 | throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract | |||
989 | } | |||
990 | | |||
991 | if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance | |||
992 | throw new ZipException("The library does not support the zip version required to extract this entry"); | |||
993 | } | |||
994 | } | |||
995 | } | |||
996 | | |||
997 | if (testHeader) { | |||
998 | if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. | |||
999 | (extractVersion != 10) && | |||
1000 | (extractVersion != 11) && | |||
1001 | (extractVersion != 20) && | |||
1002 | (extractVersion != 21) && | |||
1003 | (extractVersion != 25) && | |||
1004 | (extractVersion != 27) && | |||
1005 | (extractVersion != 45) && | |||
1006 | (extractVersion != 46) && | |||
1007 | (extractVersion != 50) && | |||
1008 | (extractVersion != 51) && | |||
1009 | (extractVersion != 52) && | |||
1010 | (extractVersion != 61) && | |||
1011 | (extractVersion != 62) && | |||
1012 | (extractVersion != 63) | |||
1013 | ) { | |||
1014 | throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersi | |||
1015 | } | |||
1016 | | |||
1017 | // Local entry flags dont have reserved bit set on. | |||
1018 | if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R | |||
1019 | throw new ZipException("Reserved bit flags cannot be set."); | |||
1020 | } | |||
1021 | | |||
1022 | // Encryption requires extract version >= 20 | |||
1023 | if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { | |||
1024 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0}) | |||
1025 | } | |||
1026 | | |||
1027 | // Strong encryption requires encryption flag to be set and extract version >= 50. | |||
1028 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1029 | if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) { | |||
1030 | throw new ZipException("Strong encryption flag set but encryption flag is not set"); | |||
1031 | } | |||
1032 | | |||
1033 | if (extractVersion < 50) { | |||
1034 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0 | |||
1035 | } | |||
1036 | } | |||
1037 | | |||
1038 | // Patched entries require extract version >= 27 | |||
1039 | if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { | |||
1040 | throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); | |||
1041 | } | |||
1042 | | |||
1043 | // Central header flags match local entry flags. | |||
1044 | if (localFlags != entry.Flags) { | |||
1045 | throw new ZipException("Central header/local header flags mismatch"); | |||
1046 | } | |||
1047 | | |||
1048 | // Central header compression method matches local entry | |||
1049 | if (entry.CompressionMethod != (CompressionMethod)compressionMethod) { | |||
1050 | throw new ZipException("Central header/local header compression method mismatch"); | |||
1051 | } | |||
1052 | | |||
1053 | if (entry.Version != extractVersion) { | |||
1054 | throw new ZipException("Extract version mismatch"); | |||
1055 | } | |||
1056 | | |||
1057 | // Strong encryption and extract version match | |||
1058 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | |||
1059 | if (extractVersion < 62) { | |||
1060 | throw new ZipException("Strong encryption flag set but version not high enough"); | |||
1061 | } | |||
1062 | } | |||
1063 | | |||
1064 | if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) { | |||
1065 | if ((fileTime != 0) || (fileDate != 0)) { | |||
1066 | throw new ZipException("Header masked set but date/time values non-zero"); | |||
1067 | } | |||
1068 | } | |||
1069 | | |||
1070 | if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) { | |||
1071 | if (crcValue != (uint)entry.Crc) { | |||
1072 | throw new ZipException("Central header/local header crc mismatch"); | |||
1073 | } | |||
1074 | } | |||
1075 | | |||
1076 | // Crc valid for empty entry. | |||
1077 | // This will also apply to streamed entries where size isnt known and the header cant be patched | |||
1078 | if ((size == 0) && (compressedSize == 0)) { | |||
1079 | if (crcValue != 0) { | |||
1080 | throw new ZipException("Invalid CRC for empty entry"); | |||
1081 | } | |||
1082 | } | |||
1083 | | |||
1084 | // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS str | |||
1085 | // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably | |||
1086 | if (entry.Name.Length > storedNameLength) { | |||
1087 | throw new ZipException("File name length mismatch"); | |||
1088 | } | |||
1089 | | |||
1090 | // Name data has already been read convert it and compare. | |||
1091 | string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); | |||
1092 | | |||
1093 | // Central directory and local entry name match | |||
1094 | if (localName != entry.Name) { | |||
1095 | throw new ZipException("Central header and local header file name mismatch"); | |||
1096 | } | |||
1097 | | |||
1098 | // Directories have zero actual size but can have compressed size | |||
1099 | if (entry.IsDirectory) { | |||
1100 | if (size > 0) { | |||
1101 | throw new ZipException("Directory cannot have size"); | |||
1102 | } | |||
1103 | | |||
1104 | // There may be other cases where the compressed size can be greater than this? | |||
1105 | // If so until details are known we will be strict. | |||
1106 | if (entry.IsCrypted) { | |||
1107 | if (compressedSize > ZipConstants.CryptoHeaderSize + 2) { | |||
1108 | throw new ZipException("Directory compressed size invalid"); | |||
1109 | } | |||
1110 | } else if (compressedSize > 2) { | |||
1111 | // When not compressed the directory size can validly be 2 bytes | |||
1112 | // if the true size wasnt known when data was originally being written. | |||
1113 | // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes | |||
1114 | throw new ZipException("Directory compressed size invalid"); | |||
1115 | } | |||
1116 | } | |||
1117 | | |||
1118 | if (!ZipNameTransform.IsValidName(localName, true)) { | |||
1119 | throw new ZipException("Name is invalid"); | |||
1120 | } | |||
1121 | } | |||
1122 | | |||
1123 | // Tests that apply to both data and header. | |||
1124 | | |||
1125 | // Size can be verified only if it is known in the local header. | |||
1126 | // it will always be known in the central header. | |||
1127 | if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || | |||
1128 | ((size > 0 || compressedSize > 0) && entry.Size > 0)) { | |||
1129 | | |||
1130 | if ((size != 0) | |||
1131 | && (size != entry.Size)) { | |||
1132 | throw new ZipException( | |||
1133 | string.Format("Size mismatch between central header({0}) and local header({1})", | |||
1134 | entry.Size, size)); | |||
1135 | } | |||
1136 | | |||
1137 | if ((compressedSize != 0) | |||
1138 | && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) { | |||
1139 | throw new ZipException( | |||
1140 | string.Format("Compressed size mismatch between central header({0}) and local header({1})", | |||
1141 | entry.CompressedSize, compressedSize)); | |||
1142 | } | |||
1143 | } | |||
1144 | | |||
1145 | int extraLength = storedNameLength + extraDataLength; | |||
1146 | return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; | |||
1147 | } | |||
1148 | } | |||
1149 | | |||
1150 | #endregion | |||
1151 | | |||
1152 | #region Updating | |||
1153 | | |||
1154 | const int DefaultBufferSize = 4096; | |||
1155 | | |||
1156 | /// <summary> | |||
1157 | /// The kind of update to apply. | |||
1158 | /// </summary> | |||
1159 | enum UpdateCommand | |||
1160 | { | |||
1161 | Copy, // Copy original file contents. | |||
1162 | Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. | |||
1163 | Add, // Add a new file to the archive. | |||
1164 | } | |||
1165 | | |||
1166 | #region Properties | |||
1167 | /// <summary> | |||
1168 | /// Get / set the <see cref="INameTransform"/> to apply to names when updating. | |||
1169 | /// </summary> | |||
1170 | public INameTransform NameTransform { | |||
1171 | get { | |||
1172 | return updateEntryFactory_.NameTransform; | |||
1173 | } | |||
1174 | | |||
1175 | set { | |||
1176 | updateEntryFactory_.NameTransform = value; | |||
1177 | } | |||
1178 | } | |||
1179 | | |||
1180 | /// <summary> | |||
1181 | /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values | |||
1182 | /// during updates. | |||
1183 | /// </summary> | |||
1184 | public IEntryFactory EntryFactory { | |||
1185 | get { | |||
1186 | return updateEntryFactory_; | |||
1187 | } | |||
1188 | | |||
1189 | set { | |||
1190 | if (value == null) { | |||
1191 | updateEntryFactory_ = new ZipEntryFactory(); | |||
1192 | } else { | |||
1193 | updateEntryFactory_ = value; | |||
1194 | } | |||
1195 | } | |||
1196 | } | |||
1197 | | |||
1198 | /// <summary> | |||
1199 | /// Get /set the buffer size to be used when updating this zip file. | |||
1200 | /// </summary> | |||
1201 | public int BufferSize { | |||
1202 | get { return bufferSize_; } | |||
1203 | set { | |||
1204 | if (value < 1024) { | |||
1205 | throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); | |||
1206 | } | |||
1207 | | |||
1208 | if (bufferSize_ != value) { | |||
1209 | bufferSize_ = value; | |||
1210 | copyBuffer_ = null; | |||
1211 | } | |||
1212 | } | |||
1213 | } | |||
1214 | | |||
1215 | /// <summary> | |||
1216 | /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. | |||
1217 | /// </summary> | |||
1218 | public bool IsUpdating { | |||
1219 | get { return updates_ != null; } | |||
1220 | } | |||
1221 | | |||
1222 | /// <summary> | |||
1223 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
1224 | /// </summary> | |||
1225 | public UseZip64 UseZip64 { | |||
1226 | get { return useZip64_; } | |||
1227 | set { useZip64_ = value; } | |||
1228 | } | |||
1229 | | |||
1230 | #endregion | |||
1231 | | |||
1232 | #region Immediate updating | |||
1233 | // TBD: Direct form of updating | |||
1234 | // | |||
1235 | // public void Update(IEntryMatcher deleteMatcher) | |||
1236 | // { | |||
1237 | // } | |||
1238 | // | |||
1239 | // public void Update(IScanner addScanner) | |||
1240 | // { | |||
1241 | // } | |||
1242 | #endregion | |||
1243 | | |||
1244 | #region Deferred Updating | |||
1245 | /// <summary> | |||
1246 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1247 | /// </summary> | |||
1248 | /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</p | |||
1249 | /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param | |||
1250 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1251 | /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception> | |||
1252 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1253 | public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) | |||
1254 | { | |||
1255 | if (archiveStorage == null) { | |||
1256 | throw new ArgumentNullException(nameof(archiveStorage)); | |||
1257 | } | |||
1258 | | |||
1259 | if (dataSource == null) { | |||
1260 | throw new ArgumentNullException(nameof(dataSource)); | |||
1261 | } | |||
1262 | | |||
1263 | if (isDisposed_) { | |||
1264 | throw new ObjectDisposedException("ZipFile"); | |||
1265 | } | |||
1266 | | |||
1267 | if (IsEmbeddedArchive) { | |||
1268 | throw new ZipException("Cannot update embedded/SFX archives"); | |||
1269 | } | |||
1270 | | |||
1271 | archiveStorage_ = archiveStorage; | |||
1272 | updateDataSource_ = dataSource; | |||
1273 | | |||
1274 | // NOTE: the baseStream_ may not currently support writing or seeking. | |||
1275 | | |||
1276 | updateIndex_ = new Hashtable(); | |||
1277 | | |||
1278 | updates_ = new ArrayList(entries_.Length); | |||
1279 | foreach (ZipEntry entry in entries_) { | |||
1280 | int index = updates_.Add(new ZipUpdate(entry)); | |||
1281 | updateIndex_.Add(entry.Name, index); | |||
1282 | } | |||
1283 | | |||
1284 | // We must sort by offset before using offset's calculated sizes | |||
1285 | updates_.Sort(new UpdateComparer()); | |||
1286 | | |||
1287 | int idx = 0; | |||
1288 | foreach (ZipUpdate update in updates_) { | |||
1289 | //If last entry, there is no next entry offset to use | |||
1290 | if (idx == updates_.Count - 1) | |||
1291 | break; | |||
1292 | | |||
1293 | update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; | |||
1294 | idx++; | |||
1295 | } | |||
1296 | updateCount_ = updates_.Count; | |||
1297 | | |||
1298 | contentsEdited_ = false; | |||
1299 | commentEdited_ = false; | |||
1300 | newComment_ = null; | |||
1301 | } | |||
1302 | | |||
1303 | /// <summary> | |||
1304 | /// Begin updating to this <see cref="ZipFile"/> archive. | |||
1305 | /// </summary> | |||
1306 | /// <param name="archiveStorage">The storage to use during the update.</param> | |||
1307 | public void BeginUpdate(IArchiveStorage archiveStorage) | |||
1308 | { | |||
1309 | BeginUpdate(archiveStorage, new DynamicDiskDataSource()); | |||
1310 | } | |||
1311 | | |||
1312 | /// <summary> | |||
1313 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1314 | /// </summary> | |||
1315 | /// <seealso cref="BeginUpdate(IArchiveStorage)"/> | |||
1316 | /// <seealso cref="CommitUpdate"></seealso> | |||
1317 | /// <seealso cref="AbortUpdate"></seealso> | |||
1318 | public void BeginUpdate() | |||
1319 | { | |||
1320 | if (Name == null) { | |||
1321 | BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); | |||
1322 | } else { | |||
1323 | BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); | |||
1324 | } | |||
1325 | } | |||
1326 | | |||
1327 | /// <summary> | |||
1328 | /// Commit current updates, updating this archive. | |||
1329 | /// </summary> | |||
1330 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1331 | /// <seealso cref="AbortUpdate"></seealso> | |||
1332 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1333 | public void CommitUpdate() | |||
1334 | { | |||
1335 | if (isDisposed_) { | |||
1336 | throw new ObjectDisposedException("ZipFile"); | |||
1337 | } | |||
1338 | | |||
1339 | CheckUpdating(); | |||
1340 | | |||
1341 | try { | |||
1342 | updateIndex_.Clear(); | |||
1343 | updateIndex_ = null; | |||
1344 | | |||
1345 | if (contentsEdited_) { | |||
1346 | RunUpdates(); | |||
1347 | } else if (commentEdited_) { | |||
1348 | UpdateCommentOnly(); | |||
1349 | } else { | |||
1350 | // Create an empty archive if none existed originally. | |||
1351 | if (entries_.Length == 0) { | |||
1352 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
1353 | using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) { | |||
1354 | zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); | |||
1355 | } | |||
1356 | } | |||
1357 | } | |||
1358 | | |||
1359 | } finally { | |||
1360 | PostUpdateCleanup(); | |||
1361 | } | |||
1362 | } | |||
1363 | | |||
1364 | /// <summary> | |||
1365 | /// Abort updating leaving the archive unchanged. | |||
1366 | /// </summary> | |||
1367 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1368 | /// <seealso cref="CommitUpdate"></seealso> | |||
1369 | public void AbortUpdate() | |||
1370 | { | |||
1371 | PostUpdateCleanup(); | |||
1372 | } | |||
1373 | | |||
1374 | /// <summary> | |||
1375 | /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. | |||
1376 | /// </summary> | |||
1377 | /// <param name="comment">The comment to record.</param> | |||
1378 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1379 | public void SetComment(string comment) | |||
1380 | { | |||
1381 | if (isDisposed_) { | |||
1382 | throw new ObjectDisposedException("ZipFile"); | |||
1383 | } | |||
1384 | | |||
1385 | CheckUpdating(); | |||
1386 | | |||
1387 | newComment_ = new ZipString(comment); | |||
1388 | | |||
1389 | if (newComment_.RawLength > 0xffff) { | |||
1390 | newComment_ = null; | |||
1391 | throw new ZipException("Comment length exceeds maximum - 65535"); | |||
1392 | } | |||
1393 | | |||
1394 | // We dont take account of the original and current comment appearing to be the same | |||
1395 | // as encoding may be different. | |||
1396 | commentEdited_ = true; | |||
1397 | } | |||
1398 | | |||
1399 | #endregion | |||
1400 | | |||
1401 | #region Adding Entries | |||
1402 | | |||
1403 | void AddUpdate(ZipUpdate update) | |||
1404 | { | |||
1405 | contentsEdited_ = true; | |||
1406 | | |||
1407 | int index = FindExistingUpdate(update.Entry.Name); | |||
1408 | | |||
1409 | if (index >= 0) { | |||
1410 | if (updates_[index] == null) { | |||
1411 | updateCount_ += 1; | |||
1412 | } | |||
1413 | | |||
1414 | // Direct replacement is faster than delete and add. | |||
1415 | updates_[index] = update; | |||
1416 | } else { | |||
1417 | index = updates_.Add(update); | |||
1418 | updateCount_ += 1; | |||
1419 | updateIndex_.Add(update.Entry.Name, index); | |||
1420 | } | |||
1421 | } | |||
1422 | | |||
1423 | /// <summary> | |||
1424 | /// Add a new entry to the archive. | |||
1425 | /// </summary> | |||
1426 | /// <param name="fileName">The name of the file to add.</param> | |||
1427 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1428 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> | |||
1429 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1430 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1431 | /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception> | |||
1432 | public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) | |||
1433 | { | |||
1434 | if (fileName == null) { | |||
1435 | throw new ArgumentNullException(nameof(fileName)); | |||
1436 | } | |||
1437 | | |||
1438 | if (isDisposed_) { | |||
1439 | throw new ObjectDisposedException("ZipFile"); | |||
1440 | } | |||
1441 | | |||
1442 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1443 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1444 | } | |||
1445 | | |||
1446 | CheckUpdating(); | |||
1447 | contentsEdited_ = true; | |||
1448 | | |||
1449 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1450 | entry.IsUnicodeText = useUnicodeText; | |||
1451 | entry.CompressionMethod = compressionMethod; | |||
1452 | | |||
1453 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1454 | } | |||
1455 | | |||
1456 | /// <summary> | |||
1457 | /// Add a new entry to the archive. | |||
1458 | /// </summary> | |||
1459 | /// <param name="fileName">The name of the file to add.</param> | |||
1460 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1461 | /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception> | |||
1462 | /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception> | |||
1463 | public void Add(string fileName, CompressionMethod compressionMethod) | |||
1464 | { | |||
1465 | if (fileName == null) { | |||
1466 | throw new ArgumentNullException(nameof(fileName)); | |||
1467 | } | |||
1468 | | |||
1469 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | |||
1470 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | |||
1471 | } | |||
1472 | | |||
1473 | CheckUpdating(); | |||
1474 | contentsEdited_ = true; | |||
1475 | | |||
1476 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | |||
1477 | entry.CompressionMethod = compressionMethod; | |||
1478 | AddUpdate(new ZipUpdate(fileName, entry)); | |||
1479 | } | |||
1480 | | |||
1481 | /// <summary> | |||
1482 | /// Add a file to the archive. | |||
1483 | /// </summary> | |||
1484 | /// <param name="fileName">The name of the file to add.</param> | |||
1485 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1486 | public void Add(string fileName) | |||
1487 | { | |||
1488 | if (fileName == null) { | |||
1489 | throw new ArgumentNullException(nameof(fileName)); | |||
1490 | } | |||
1491 | | |||
1492 | CheckUpdating(); | |||
1493 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); | |||
1494 | } | |||
1495 | | |||
1496 | /// <summary> | |||
1497 | /// Add a file to the archive. | |||
1498 | /// </summary> | |||
1499 | /// <param name="fileName">The name of the file to add.</param> | |||
1500 | /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param> | |||
1501 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1502 | public void Add(string fileName, string entryName) | |||
1503 | { | |||
1504 | if (fileName == null) { | |||
1505 | throw new ArgumentNullException(nameof(fileName)); | |||
1506 | } | |||
1507 | | |||
1508 | if (entryName == null) { | |||
1509 | throw new ArgumentNullException(nameof(entryName)); | |||
1510 | } | |||
1511 | | |||
1512 | CheckUpdating(); | |||
1513 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); | |||
1514 | } | |||
1515 | | |||
1516 | | |||
1517 | /// <summary> | |||
1518 | /// Add a file entry with data. | |||
1519 | /// </summary> | |||
1520 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1521 | /// <param name="entryName">The name to give to the entry.</param> | |||
1522 | public void Add(IStaticDataSource dataSource, string entryName) | |||
1523 | { | |||
1524 | if (dataSource == null) { | |||
1525 | throw new ArgumentNullException(nameof(dataSource)); | |||
1526 | } | |||
1527 | | |||
1528 | if (entryName == null) { | |||
1529 | throw new ArgumentNullException(nameof(entryName)); | |||
1530 | } | |||
1531 | | |||
1532 | CheckUpdating(); | |||
1533 | AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); | |||
1534 | } | |||
1535 | | |||
1536 | /// <summary> | |||
1537 | /// Add a file entry with data. | |||
1538 | /// </summary> | |||
1539 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1540 | /// <param name="entryName">The name to give to the entry.</param> | |||
1541 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1542 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
1543 | { | |||
1544 | if (dataSource == null) { | |||
1545 | throw new ArgumentNullException(nameof(dataSource)); | |||
1546 | } | |||
1547 | | |||
1548 | if (entryName == null) { | |||
1549 | throw new ArgumentNullException(nameof(entryName)); | |||
1550 | } | |||
1551 | | |||
1552 | CheckUpdating(); | |||
1553 | | |||
1554 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1555 | entry.CompressionMethod = compressionMethod; | |||
1556 | | |||
1557 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1558 | } | |||
1559 | | |||
1560 | /// <summary> | |||
1561 | /// Add a file entry with data. | |||
1562 | /// </summary> | |||
1563 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1564 | /// <param name="entryName">The name to give to the entry.</param> | |||
1565 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1566 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> | |||
1567 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicode | |||
1568 | { | |||
1569 | if (dataSource == null) { | |||
1570 | throw new ArgumentNullException(nameof(dataSource)); | |||
1571 | } | |||
1572 | | |||
1573 | if (entryName == null) { | |||
1574 | throw new ArgumentNullException(nameof(entryName)); | |||
1575 | } | |||
1576 | | |||
1577 | CheckUpdating(); | |||
1578 | | |||
1579 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | |||
1580 | entry.IsUnicodeText = useUnicodeText; | |||
1581 | entry.CompressionMethod = compressionMethod; | |||
1582 | | |||
1583 | AddUpdate(new ZipUpdate(dataSource, entry)); | |||
1584 | } | |||
1585 | | |||
1586 | /// <summary> | |||
1587 | /// Add a <see cref="ZipEntry"/> that contains no data. | |||
1588 | /// </summary> | |||
1589 | /// <param name="entry">The entry to add.</param> | |||
1590 | /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> | |||
1591 | public void Add(ZipEntry entry) | |||
1592 | { | |||
1593 | if (entry == null) { | |||
1594 | throw new ArgumentNullException(nameof(entry)); | |||
1595 | } | |||
1596 | | |||
1597 | CheckUpdating(); | |||
1598 | | |||
1599 | if ((entry.Size != 0) || (entry.CompressedSize != 0)) { | |||
1600 | throw new ZipException("Entry cannot have any data"); | |||
1601 | } | |||
1602 | | |||
1603 | AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); | |||
1604 | } | |||
1605 | | |||
1606 | /// <summary> | |||
1607 | /// Add a directory entry to the archive. | |||
1608 | /// </summary> | |||
1609 | /// <param name="directoryName">The directory to add.</param> | |||
1610 | public void AddDirectory(string directoryName) | |||
1611 | { | |||
1612 | if (directoryName == null) { | |||
1613 | throw new ArgumentNullException(nameof(directoryName)); | |||
1614 | } | |||
1615 | | |||
1616 | CheckUpdating(); | |||
1617 | | |||
1618 | ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); | |||
1619 | AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); | |||
1620 | } | |||
1621 | | |||
1622 | #endregion | |||
1623 | | |||
1624 | #region Modifying Entries | |||
1625 | /* Modify not yet ready for public consumption. | |||
1626 | Direct modification of an entry should not overwrite original data before its read. | |||
1627 | Safe mode is trivial in this sense. | |||
1628 | public void Modify(ZipEntry original, ZipEntry updated) | |||
1629 | { | |||
1630 | if ( original == null ) { | |||
1631 | throw new ArgumentNullException("original"); | |||
1632 | } | |||
1633 | | |||
1634 | if ( updated == null ) { | |||
1635 | throw new ArgumentNullException("updated"); | |||
1636 | } | |||
1637 | | |||
1638 | CheckUpdating(); | |||
1639 | contentsEdited_ = true; | |||
1640 | updates_.Add(new ZipUpdate(original, updated)); | |||
1641 | } | |||
1642 | */ | |||
1643 | #endregion | |||
1644 | | |||
1645 | #region Deleting Entries | |||
1646 | /// <summary> | |||
1647 | /// Delete an entry by name | |||
1648 | /// </summary> | |||
1649 | /// <param name="fileName">The filename to delete</param> | |||
1650 | /// <returns>True if the entry was found and deleted; false otherwise.</returns> | |||
1651 | public bool Delete(string fileName) | |||
1652 | { | |||
1653 | if (fileName == null) { | |||
1654 | throw new ArgumentNullException(nameof(fileName)); | |||
1655 | } | |||
1656 | | |||
1657 | CheckUpdating(); | |||
1658 | | |||
1659 | bool result = false; | |||
1660 | int index = FindExistingUpdate(fileName); | |||
1661 | if ((index >= 0) && (updates_[index] != null)) { | |||
1662 | result = true; | |||
1663 | contentsEdited_ = true; | |||
1664 | updates_[index] = null; | |||
1665 | updateCount_ -= 1; | |||
1666 | } else { | |||
1667 | throw new ZipException("Cannot find entry to delete"); | |||
1668 | } | |||
1669 | return result; | |||
1670 | } | |||
1671 | | |||
1672 | /// <summary> | |||
1673 | /// Delete a <see cref="ZipEntry"/> from the archive. | |||
1674 | /// </summary> | |||
1675 | /// <param name="entry">The entry to delete.</param> | |||
1676 | public void Delete(ZipEntry entry) | |||
1677 | { | |||
1678 | if (entry == null) { | |||
1679 | throw new ArgumentNullException(nameof(entry)); | |||
1680 | } | |||
1681 | | |||
1682 | CheckUpdating(); | |||
1683 | | |||
1684 | int index = FindExistingUpdate(entry); | |||
1685 | if (index >= 0) { | |||
1686 | contentsEdited_ = true; | |||
1687 | updates_[index] = null; | |||
1688 | updateCount_ -= 1; | |||
1689 | } else { | |||
1690 | throw new ZipException("Cannot find entry to delete"); | |||
1691 | } | |||
1692 | } | |||
1693 | | |||
1694 | #endregion | |||
1695 | | |||
1696 | #region Update Support | |||
1697 | | |||
1698 | #region Writing Values/Headers | |||
1699 | void WriteLEShort(int value) | |||
1700 | { | |||
1701 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1702 | baseStream_.WriteByte((byte)((value >> 8) & 0xff)); | |||
1703 | } | |||
1704 | | |||
1705 | /// <summary> | |||
1706 | /// Write an unsigned short in little endian byte order. | |||
1707 | /// </summary> | |||
1708 | void WriteLEUshort(ushort value) | |||
1709 | { | |||
1710 | baseStream_.WriteByte((byte)(value & 0xff)); | |||
1711 | baseStream_.WriteByte((byte)(value >> 8)); | |||
1712 | } | |||
1713 | | |||
1714 | /// <summary> | |||
1715 | /// Write an int in little endian byte order. | |||
1716 | /// </summary> | |||
1717 | void WriteLEInt(int value) | |||
1718 | { | |||
1719 | WriteLEShort(value & 0xffff); | |||
1720 | WriteLEShort(value >> 16); | |||
1721 | } | |||
1722 | | |||
1723 | /// <summary> | |||
1724 | /// Write an unsigned int in little endian byte order. | |||
1725 | /// </summary> | |||
1726 | void WriteLEUint(uint value) | |||
1727 | { | |||
1728 | WriteLEUshort((ushort)(value & 0xffff)); | |||
1729 | WriteLEUshort((ushort)(value >> 16)); | |||
1730 | } | |||
1731 | | |||
1732 | /// <summary> | |||
1733 | /// Write a long in little endian byte order. | |||
1734 | /// </summary> | |||
1735 | void WriteLeLong(long value) | |||
1736 | { | |||
1737 | WriteLEInt((int)(value & 0xffffffff)); | |||
1738 | WriteLEInt((int)(value >> 32)); | |||
1739 | } | |||
1740 | | |||
1741 | void WriteLEUlong(ulong value) | |||
1742 | { | |||
1743 | WriteLEUint((uint)(value & 0xffffffff)); | |||
1744 | WriteLEUint((uint)(value >> 32)); | |||
1745 | } | |||
1746 | | |||
1747 | void WriteLocalEntryHeader(ZipUpdate update) | |||
1748 | { | |||
1749 | ZipEntry entry = update.OutEntry; | |||
1750 | | |||
1751 | // TODO: Local offset will require adjusting for multi-disk zip files. | |||
1752 | entry.Offset = baseStream_.Position; | |||
1753 | | |||
1754 | // TODO: Need to clear any entry flags that dont make sense or throw an exception here. | |||
1755 | if (update.Command != UpdateCommand.Copy) { | |||
1756 | if (entry.CompressionMethod == CompressionMethod.Deflated) { | |||
1757 | if (entry.Size == 0) { | |||
1758 | // No need to compress - no data. | |||
1759 | entry.CompressedSize = entry.Size; | |||
1760 | entry.Crc = 0; | |||
1761 | entry.CompressionMethod = CompressionMethod.Stored; | |||
1762 | } | |||
1763 | } else if (entry.CompressionMethod == CompressionMethod.Stored) { | |||
1764 | entry.Flags &= ~(int)GeneralBitFlags.Descriptor; | |||
1765 | } | |||
1766 | | |||
1767 | if (HaveKeys) { | |||
1768 | entry.IsCrypted = true; | |||
1769 | if (entry.Crc < 0) { | |||
1770 | entry.Flags |= (int)GeneralBitFlags.Descriptor; | |||
1771 | } | |||
1772 | } else { | |||
1773 | entry.IsCrypted = false; | |||
1774 | } | |||
1775 | | |||
1776 | switch (useZip64_) { | |||
1777 | case UseZip64.Dynamic: | |||
1778 | if (entry.Size < 0) { | |||
1779 | entry.ForceZip64(); | |||
1780 | } | |||
1781 | break; | |||
1782 | | |||
1783 | case UseZip64.On: | |||
1784 | entry.ForceZip64(); | |||
1785 | break; | |||
1786 | | |||
1787 | case UseZip64.Off: | |||
1788 | // Do nothing. The entry itself may be using Zip64 independantly. | |||
1789 | break; | |||
1790 | } | |||
1791 | } | |||
1792 | | |||
1793 | // Write the local file header | |||
1794 | WriteLEInt(ZipConstants.LocalHeaderSignature); | |||
1795 | | |||
1796 | WriteLEShort(entry.Version); | |||
1797 | WriteLEShort(entry.Flags); | |||
1798 | | |||
1799 | WriteLEShort((byte)entry.CompressionMethod); | |||
1800 | WriteLEInt((int)entry.DosTime); | |||
1801 | | |||
1802 | if (!entry.HasCrc) { | |||
1803 | // Note patch address for updating CRC later. | |||
1804 | update.CrcPatchOffset = baseStream_.Position; | |||
1805 | WriteLEInt((int)0); | |||
1806 | } else { | |||
1807 | WriteLEInt(unchecked((int)entry.Crc)); | |||
1808 | } | |||
1809 | | |||
1810 | if (entry.LocalHeaderRequiresZip64) { | |||
1811 | WriteLEInt(-1); | |||
1812 | WriteLEInt(-1); | |||
1813 | } else { | |||
1814 | if ((entry.CompressedSize < 0) || (entry.Size < 0)) { | |||
1815 | update.SizePatchOffset = baseStream_.Position; | |||
1816 | } | |||
1817 | | |||
1818 | WriteLEInt((int)entry.CompressedSize); | |||
1819 | WriteLEInt((int)entry.Size); | |||
1820 | } | |||
1821 | | |||
1822 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1823 | | |||
1824 | if (name.Length > 0xFFFF) { | |||
1825 | throw new ZipException("Entry name too long."); | |||
1826 | } | |||
1827 | | |||
1828 | var ed = new ZipExtraData(entry.ExtraData); | |||
1829 | | |||
1830 | if (entry.LocalHeaderRequiresZip64) { | |||
1831 | ed.StartNewEntry(); | |||
1832 | | |||
1833 | // Local entry header always includes size and compressed size. | |||
1834 | // NOTE the order of these fields is reversed when compared to the normal headers! | |||
1835 | ed.AddLeLong(entry.Size); | |||
1836 | ed.AddLeLong(entry.CompressedSize); | |||
1837 | ed.AddNewEntry(1); | |||
1838 | } else { | |||
1839 | ed.Delete(1); | |||
1840 | } | |||
1841 | | |||
1842 | entry.ExtraData = ed.GetEntryData(); | |||
1843 | | |||
1844 | WriteLEShort(name.Length); | |||
1845 | WriteLEShort(entry.ExtraData.Length); | |||
1846 | | |||
1847 | if (name.Length > 0) { | |||
1848 | baseStream_.Write(name, 0, name.Length); | |||
1849 | } | |||
1850 | | |||
1851 | if (entry.LocalHeaderRequiresZip64) { | |||
1852 | if (!ed.Find(1)) { | |||
1853 | throw new ZipException("Internal error cannot find extra data"); | |||
1854 | } | |||
1855 | | |||
1856 | update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; | |||
1857 | } | |||
1858 | | |||
1859 | if (entry.ExtraData.Length > 0) { | |||
1860 | baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); | |||
1861 | } | |||
1862 | } | |||
1863 | | |||
1864 | int WriteCentralDirectoryHeader(ZipEntry entry) | |||
1865 | { | |||
1866 | if (entry.CompressedSize < 0) { | |||
1867 | throw new ZipException("Attempt to write central directory entry with unknown csize"); | |||
1868 | } | |||
1869 | | |||
1870 | if (entry.Size < 0) { | |||
1871 | throw new ZipException("Attempt to write central directory entry with unknown size"); | |||
1872 | } | |||
1873 | | |||
1874 | if (entry.Crc < 0) { | |||
1875 | throw new ZipException("Attempt to write central directory entry with unknown crc"); | |||
1876 | } | |||
1877 | | |||
1878 | // Write the central file header | |||
1879 | WriteLEInt(ZipConstants.CentralHeaderSignature); | |||
1880 | | |||
1881 | // Version made by | |||
1882 | WriteLEShort(ZipConstants.VersionMadeBy); | |||
1883 | | |||
1884 | // Version required to extract | |||
1885 | WriteLEShort(entry.Version); | |||
1886 | | |||
1887 | WriteLEShort(entry.Flags); | |||
1888 | | |||
1889 | unchecked { | |||
1890 | WriteLEShort((byte)entry.CompressionMethod); | |||
1891 | WriteLEInt((int)entry.DosTime); | |||
1892 | WriteLEInt((int)entry.Crc); | |||
1893 | } | |||
1894 | | |||
1895 | if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { | |||
1896 | WriteLEInt(-1); | |||
1897 | } else { | |||
1898 | WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); | |||
1899 | } | |||
1900 | | |||
1901 | if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { | |||
1902 | WriteLEInt(-1); | |||
1903 | } else { | |||
1904 | WriteLEInt((int)entry.Size); | |||
1905 | } | |||
1906 | | |||
1907 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | |||
1908 | | |||
1909 | if (name.Length > 0xFFFF) { | |||
1910 | throw new ZipException("Entry name is too long."); | |||
1911 | } | |||
1912 | | |||
1913 | WriteLEShort(name.Length); | |||
1914 | | |||
1915 | // Central header extra data is different to local header version so regenerate. | |||
1916 | var ed = new ZipExtraData(entry.ExtraData); | |||
1917 | | |||
1918 | if (entry.CentralHeaderRequiresZip64) { | |||
1919 | ed.StartNewEntry(); | |||
1920 | | |||
1921 | if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1922 | ed.AddLeLong(entry.Size); | |||
1923 | } | |||
1924 | | |||
1925 | if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | |||
1926 | ed.AddLeLong(entry.CompressedSize); | |||
1927 | } | |||
1928 | | |||
1929 | if (entry.Offset >= 0xffffffff) { | |||
1930 | ed.AddLeLong(entry.Offset); | |||
1931 | } | |||
1932 | | |||
1933 | // Number of disk on which this file starts isnt supported and is never written here. | |||
1934 | ed.AddNewEntry(1); | |||
1935 | } else { | |||
1936 | // Should have already be done when local header was added. | |||
1937 | ed.Delete(1); | |||
1938 | } | |||
1939 | | |||
1940 | byte[] centralExtraData = ed.GetEntryData(); | |||
1941 | | |||
1942 | WriteLEShort(centralExtraData.Length); | |||
1943 | WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); | |||
1944 | | |||
1945 | WriteLEShort(0); // disk number | |||
1946 | WriteLEShort(0); // internal file attributes | |||
1947 | | |||
1948 | // External file attributes... | |||
1949 | if (entry.ExternalFileAttributes != -1) { | |||
1950 | WriteLEInt(entry.ExternalFileAttributes); | |||
1951 | } else { | |||
1952 | if (entry.IsDirectory) { | |||
1953 | WriteLEUint(16); | |||
1954 | } else { | |||
1955 | WriteLEUint(0); | |||
1956 | } | |||
1957 | } | |||
1958 | | |||
1959 | if (entry.Offset >= 0xffffffff) { | |||
1960 | WriteLEUint(0xffffffff); | |||
1961 | } else { | |||
1962 | WriteLEUint((uint)(int)entry.Offset); | |||
1963 | } | |||
1964 | | |||
1965 | if (name.Length > 0) { | |||
1966 | baseStream_.Write(name, 0, name.Length); | |||
1967 | } | |||
1968 | | |||
1969 | if (centralExtraData.Length > 0) { | |||
1970 | baseStream_.Write(centralExtraData, 0, centralExtraData.Length); | |||
1971 | } | |||
1972 | | |||
1973 | byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; | |||
1974 | | |||
1975 | if (rawComment.Length > 0) { | |||
1976 | baseStream_.Write(rawComment, 0, rawComment.Length); | |||
1977 | } | |||
1978 | | |||
1979 | return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; | |||
1980 | } | |||
1981 | #endregion | |||
1982 | | |||
1983 | void PostUpdateCleanup() | |||
1984 | { | |||
1985 | updateDataSource_ = null; | |||
1986 | updates_ = null; | |||
1987 | updateIndex_ = null; | |||
1988 | | |||
1989 | if (archiveStorage_ != null) { | |||
1990 | archiveStorage_.Dispose(); | |||
1991 | archiveStorage_ = null; | |||
1992 | } | |||
1993 | } | |||
1994 | | |||
1995 | string GetTransformedFileName(string name) | |||
1996 | { | |||
1997 | INameTransform transform = NameTransform; | |||
1998 | return (transform != null) ? | |||
1999 | transform.TransformFile(name) : | |||
2000 | name; | |||
2001 | } | |||
2002 | | |||
2003 | string GetTransformedDirectoryName(string name) | |||
2004 | { | |||
2005 | INameTransform transform = NameTransform; | |||
2006 | return (transform != null) ? | |||
2007 | transform.TransformDirectory(name) : | |||
2008 | name; | |||
2009 | } | |||
2010 | | |||
2011 | /// <summary> | |||
2012 | /// Get a raw memory buffer. | |||
2013 | /// </summary> | |||
2014 | /// <returns>Returns a raw memory buffer.</returns> | |||
2015 | byte[] GetBuffer() | |||
2016 | { | |||
2017 | if (copyBuffer_ == null) { | |||
2018 | copyBuffer_ = new byte[bufferSize_]; | |||
2019 | } | |||
2020 | return copyBuffer_; | |||
2021 | } | |||
2022 | | |||
2023 | void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) | |||
2024 | { | |||
2025 | int bytesToCopy = GetDescriptorSize(update); | |||
2026 | | |||
2027 | if (bytesToCopy > 0) { | |||
2028 | byte[] buffer = GetBuffer(); | |||
2029 | | |||
2030 | while (bytesToCopy > 0) { | |||
2031 | int readSize = Math.Min(buffer.Length, bytesToCopy); | |||
2032 | | |||
2033 | int bytesRead = source.Read(buffer, 0, readSize); | |||
2034 | if (bytesRead > 0) { | |||
2035 | dest.Write(buffer, 0, bytesRead); | |||
2036 | bytesToCopy -= bytesRead; | |||
2037 | } else { | |||
2038 | throw new ZipException("Unxpected end of stream"); | |||
2039 | } | |||
2040 | } | |||
2041 | } | |||
2042 | } | |||
2043 | | |||
2044 | void CopyBytes(ZipUpdate update, Stream destination, Stream source, | |||
2045 | long bytesToCopy, bool updateCrc) | |||
2046 | { | |||
2047 | if (destination == source) { | |||
2048 | throw new InvalidOperationException("Destination and source are the same"); | |||
2049 | } | |||
2050 | | |||
2051 | // NOTE: Compressed size is updated elsewhere. | |||
2052 | var crc = new Crc32(); | |||
2053 | byte[] buffer = GetBuffer(); | |||
2054 | | |||
2055 | long targetBytes = bytesToCopy; | |||
2056 | long totalBytesRead = 0; | |||
2057 | | |||
2058 | int bytesRead; | |||
2059 | do { | |||
2060 | int readSize = buffer.Length; | |||
2061 | | |||
2062 | if (bytesToCopy < readSize) { | |||
2063 | readSize = (int)bytesToCopy; | |||
2064 | } | |||
2065 | | |||
2066 | bytesRead = source.Read(buffer, 0, readSize); | |||
2067 | if (bytesRead > 0) { | |||
2068 | if (updateCrc) { | |||
2069 | crc.Update(buffer, 0, bytesRead); | |||
2070 | } | |||
2071 | destination.Write(buffer, 0, bytesRead); | |||
2072 | bytesToCopy -= bytesRead; | |||
2073 | totalBytesRead += bytesRead; | |||
2074 | } | |||
2075 | } | |||
2076 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2077 | | |||
2078 | if (totalBytesRead != targetBytes) { | |||
2079 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2080 | } | |||
2081 | | |||
2082 | if (updateCrc) { | |||
2083 | update.OutEntry.Crc = crc.Value; | |||
2084 | } | |||
2085 | } | |||
2086 | | |||
2087 | /// <summary> | |||
2088 | /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. | |||
2089 | /// </summary> | |||
2090 | /// <param name="update">The update to get the size for.</param> | |||
2091 | /// <returns>The descriptor size, zero if there isnt one.</returns> | |||
2092 | int GetDescriptorSize(ZipUpdate update) | |||
2093 | { | |||
2094 | int result = 0; | |||
2095 | if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | |||
2096 | result = ZipConstants.DataDescriptorSize - 4; | |||
2097 | if (update.Entry.LocalHeaderRequiresZip64) { | |||
2098 | result = ZipConstants.Zip64DataDescriptorSize - 4; | |||
2099 | } | |||
2100 | } | |||
2101 | return result; | |||
2102 | } | |||
2103 | | |||
2104 | void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) | |||
2105 | { | |||
2106 | int bytesToCopy = GetDescriptorSize(update); | |||
2107 | | |||
2108 | while (bytesToCopy > 0) { | |||
2109 | var readSize = (int)bytesToCopy; | |||
2110 | byte[] buffer = GetBuffer(); | |||
2111 | | |||
2112 | stream.Position = sourcePosition; | |||
2113 | int bytesRead = stream.Read(buffer, 0, readSize); | |||
2114 | if (bytesRead > 0) { | |||
2115 | stream.Position = destinationPosition; | |||
2116 | stream.Write(buffer, 0, bytesRead); | |||
2117 | bytesToCopy -= bytesRead; | |||
2118 | destinationPosition += bytesRead; | |||
2119 | sourcePosition += bytesRead; | |||
2120 | } else { | |||
2121 | throw new ZipException("Unxpected end of stream"); | |||
2122 | } | |||
2123 | } | |||
2124 | } | |||
2125 | | |||
2126 | void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou | |||
2127 | { | |||
2128 | long bytesToCopy = update.Entry.CompressedSize; | |||
2129 | | |||
2130 | // NOTE: Compressed size is updated elsewhere. | |||
2131 | var crc = new Crc32(); | |||
2132 | byte[] buffer = GetBuffer(); | |||
2133 | | |||
2134 | long targetBytes = bytesToCopy; | |||
2135 | long totalBytesRead = 0; | |||
2136 | | |||
2137 | int bytesRead; | |||
2138 | do { | |||
2139 | int readSize = buffer.Length; | |||
2140 | | |||
2141 | if (bytesToCopy < readSize) { | |||
2142 | readSize = (int)bytesToCopy; | |||
2143 | } | |||
2144 | | |||
2145 | stream.Position = sourcePosition; | |||
2146 | bytesRead = stream.Read(buffer, 0, readSize); | |||
2147 | if (bytesRead > 0) { | |||
2148 | if (updateCrc) { | |||
2149 | crc.Update(buffer, 0, bytesRead); | |||
2150 | } | |||
2151 | stream.Position = destinationPosition; | |||
2152 | stream.Write(buffer, 0, bytesRead); | |||
2153 | | |||
2154 | destinationPosition += bytesRead; | |||
2155 | sourcePosition += bytesRead; | |||
2156 | bytesToCopy -= bytesRead; | |||
2157 | totalBytesRead += bytesRead; | |||
2158 | } | |||
2159 | } | |||
2160 | while ((bytesRead > 0) && (bytesToCopy > 0)); | |||
2161 | | |||
2162 | if (totalBytesRead != targetBytes) { | |||
2163 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | |||
2164 | } | |||
2165 | | |||
2166 | if (updateCrc) { | |||
2167 | update.OutEntry.Crc = crc.Value; | |||
2168 | } | |||
2169 | } | |||
2170 | | |||
2171 | int FindExistingUpdate(ZipEntry entry) | |||
2172 | { | |||
2173 | int result = -1; | |||
2174 | string convertedName = GetTransformedFileName(entry.Name); | |||
2175 | | |||
2176 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2177 | result = (int)updateIndex_[convertedName]; | |||
2178 | } | |||
2179 | /* | |||
2180 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2181 | // for CF? | |||
2182 | for (int index = 0; index < updates_.Count; ++index) | |||
2183 | { | |||
2184 | ZipUpdate zu = ( ZipUpdate )updates_[index]; | |||
2185 | if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && | |||
2186 | (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { | |||
2187 | result = index; | |||
2188 | break; | |||
2189 | } | |||
2190 | } | |||
2191 | */ | |||
2192 | return result; | |||
2193 | } | |||
2194 | | |||
2195 | int FindExistingUpdate(string fileName) | |||
2196 | { | |||
2197 | int result = -1; | |||
2198 | | |||
2199 | string convertedName = GetTransformedFileName(fileName); | |||
2200 | | |||
2201 | if (updateIndex_.ContainsKey(convertedName)) { | |||
2202 | result = (int)updateIndex_[convertedName]; | |||
2203 | } | |||
2204 | | |||
2205 | /* | |||
2206 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2207 | // for CF? | |||
2208 | for ( int index = 0; index < updates_.Count; ++index ) { | |||
2209 | if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, | |||
2210 | true, CultureInfo.InvariantCulture) == 0 ) { | |||
2211 | result = index; | |||
2212 | break; | |||
2213 | } | |||
2214 | } | |||
2215 | */ | |||
2216 | | |||
2217 | return result; | |||
2218 | } | |||
2219 | | |||
2220 | /// <summary> | |||
2221 | /// Get an output stream for the specified <see cref="ZipEntry"/> | |||
2222 | /// </summary> | |||
2223 | /// <param name="entry">The entry to get an output stream for.</param> | |||
2224 | /// <returns>The output stream obtained for the entry.</returns> | |||
2225 | Stream GetOutputStream(ZipEntry entry) | |||
2226 | { | |||
2227 | Stream result = baseStream_; | |||
2228 | | |||
2229 | if (entry.IsCrypted == true) { | |||
2230 | result = CreateAndInitEncryptionStream(result, entry); | |||
2231 | } | |||
2232 | | |||
2233 | switch (entry.CompressionMethod) { | |||
2234 | case CompressionMethod.Stored: | |||
2235 | result = new UncompressedStream(result); | |||
2236 | break; | |||
2237 | | |||
2238 | case CompressionMethod.Deflated: | |||
2239 | var dos = new DeflaterOutputStream(result, new Deflater(9, true)); | |||
2240 | dos.IsStreamOwner = false; | |||
2241 | result = dos; | |||
2242 | break; | |||
2243 | | |||
2244 | default: | |||
2245 | throw new ZipException("Unknown compression method " + entry.CompressionMethod); | |||
2246 | } | |||
2247 | return result; | |||
2248 | } | |||
2249 | | |||
2250 | void AddEntry(ZipFile workFile, ZipUpdate update) | |||
2251 | { | |||
2252 | Stream source = null; | |||
2253 | | |||
2254 | if (update.Entry.IsFile) { | |||
2255 | source = update.GetSource(); | |||
2256 | | |||
2257 | if (source == null) { | |||
2258 | source = updateDataSource_.GetSource(update.Entry, update.Filename); | |||
2259 | } | |||
2260 | } | |||
2261 | | |||
2262 | if (source != null) { | |||
2263 | using (source) { | |||
2264 | long sourceStreamLength = source.Length; | |||
2265 | if (update.OutEntry.Size < 0) { | |||
2266 | update.OutEntry.Size = sourceStreamLength; | |||
2267 | } else { | |||
2268 | // Check for errant entries. | |||
2269 | if (update.OutEntry.Size != sourceStreamLength) { | |||
2270 | throw new ZipException("Entry size/stream size mismatch"); | |||
2271 | } | |||
2272 | } | |||
2273 | | |||
2274 | workFile.WriteLocalEntryHeader(update); | |||
2275 | | |||
2276 | long dataStart = workFile.baseStream_.Position; | |||
2277 | | |||
2278 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2279 | CopyBytes(update, output, source, sourceStreamLength, true); | |||
2280 | } | |||
2281 | | |||
2282 | long dataEnd = workFile.baseStream_.Position; | |||
2283 | update.OutEntry.CompressedSize = dataEnd - dataStart; | |||
2284 | | |||
2285 | if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) { | |||
2286 | var helper = new ZipHelperStream(workFile.baseStream_); | |||
2287 | helper.WriteDataDescriptor(update.OutEntry); | |||
2288 | } | |||
2289 | } | |||
2290 | } else { | |||
2291 | workFile.WriteLocalEntryHeader(update); | |||
2292 | update.OutEntry.CompressedSize = 0; | |||
2293 | } | |||
2294 | | |||
2295 | } | |||
2296 | | |||
2297 | void ModifyEntry(ZipFile workFile, ZipUpdate update) | |||
2298 | { | |||
2299 | workFile.WriteLocalEntryHeader(update); | |||
2300 | long dataStart = workFile.baseStream_.Position; | |||
2301 | | |||
2302 | // TODO: This is slow if the changes don't effect the data!! | |||
2303 | if (update.Entry.IsFile && (update.Filename != null)) { | |||
2304 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | |||
2305 | using (Stream source = this.GetInputStream(update.Entry)) { | |||
2306 | CopyBytes(update, output, source, source.Length, true); | |||
2307 | } | |||
2308 | } | |||
2309 | } | |||
2310 | | |||
2311 | long dataEnd = workFile.baseStream_.Position; | |||
2312 | update.Entry.CompressedSize = dataEnd - dataStart; | |||
2313 | } | |||
2314 | | |||
2315 | void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) | |||
2316 | { | |||
2317 | bool skipOver = false || update.Entry.Offset == destinationPosition; | |||
2318 | | |||
2319 | if (!skipOver) { | |||
2320 | baseStream_.Position = destinationPosition; | |||
2321 | workFile.WriteLocalEntryHeader(update); | |||
2322 | destinationPosition = baseStream_.Position; | |||
2323 | } | |||
2324 | | |||
2325 | long sourcePosition = 0; | |||
2326 | | |||
2327 | const int NameLengthOffset = 26; | |||
2328 | | |||
2329 | // TODO: Add base for SFX friendly handling | |||
2330 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2331 | | |||
2332 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2333 | | |||
2334 | // Clumsy way of handling retrieving the original name and extra data length for now. | |||
2335 | // TODO: Stop re-reading name and data length in CopyEntryDirect. | |||
2336 | uint nameLength = ReadLEUshort(); | |||
2337 | uint extraLength = ReadLEUshort(); | |||
2338 | | |||
2339 | sourcePosition = baseStream_.Position + nameLength + extraLength; | |||
2340 | | |||
2341 | if (skipOver) { | |||
2342 | if (update.OffsetBasedSize != -1) | |||
2343 | destinationPosition += update.OffsetBasedSize; | |||
2344 | else | |||
2345 | // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) ar | |||
2346 | // WinZip produces a warning on these entries: | |||
2347 | // "caution: value of lrec.csize (compressed size) changed from ..." | |||
2348 | destinationPosition += | |||
2349 | (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size | |||
2350 | update.Entry.CompressedSize + GetDescriptorSize(update); | |||
2351 | } else { | |||
2352 | if (update.Entry.CompressedSize > 0) { | |||
2353 | CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); | |||
2354 | } | |||
2355 | CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); | |||
2356 | } | |||
2357 | } | |||
2358 | | |||
2359 | void CopyEntry(ZipFile workFile, ZipUpdate update) | |||
2360 | { | |||
2361 | workFile.WriteLocalEntryHeader(update); | |||
2362 | | |||
2363 | if (update.Entry.CompressedSize > 0) { | |||
2364 | const int NameLengthOffset = 26; | |||
2365 | | |||
2366 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | |||
2367 | | |||
2368 | // TODO: This wont work for SFX files! | |||
2369 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | |||
2370 | | |||
2371 | uint nameLength = ReadLEUshort(); | |||
2372 | uint extraLength = ReadLEUshort(); | |||
2373 | | |||
2374 | baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); | |||
2375 | | |||
2376 | CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); | |||
2377 | } | |||
2378 | CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); | |||
2379 | } | |||
2380 | | |||
2381 | void Reopen(Stream source) | |||
2382 | { | |||
2383 | if (source == null) { | |||
2384 | throw new ZipException("Failed to reopen archive - no source"); | |||
2385 | } | |||
2386 | | |||
2387 | isNewArchive_ = false; | |||
2388 | baseStream_ = source; | |||
2389 | ReadEntries(); | |||
2390 | } | |||
2391 | | |||
2392 | void Reopen() | |||
2393 | { | |||
2394 | if (Name == null) { | |||
2395 | throw new InvalidOperationException("Name is not known cannot Reopen"); | |||
2396 | } | |||
2397 | | |||
2398 | Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); | |||
2399 | } | |||
2400 | | |||
2401 | void UpdateCommentOnly() | |||
2402 | { | |||
2403 | long baseLength = baseStream_.Length; | |||
2404 | | |||
2405 | ZipHelperStream updateFile = null; | |||
2406 | | |||
2407 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2408 | Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); | |||
2409 | updateFile = new ZipHelperStream(copyStream); | |||
2410 | updateFile.IsStreamOwner = true; | |||
2411 | | |||
2412 | baseStream_.Close(); | |||
2413 | baseStream_ = null; | |||
2414 | } else { | |||
2415 | if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2416 | // TODO: archiveStorage wasnt originally intended for this use. | |||
2417 | // Need to revisit this to tidy up handling as archive storage currently doesnt | |||
2418 | // handle the original stream well. | |||
2419 | // The problem is when using an existing zip archive with an in memory archive storage. | |||
2420 | // The open stream wont support writing but the memory storage should open the same file not an in memory one. | |||
2421 | | |||
2422 | // Need to tidy up the archive storage interface and contract basically. | |||
2423 | baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); | |||
2424 | updateFile = new ZipHelperStream(baseStream_); | |||
2425 | } else { | |||
2426 | baseStream_.Close(); | |||
2427 | baseStream_ = null; | |||
2428 | updateFile = new ZipHelperStream(Name); | |||
2429 | } | |||
2430 | } | |||
2431 | | |||
2432 | using (updateFile) { | |||
2433 | long locatedCentralDirOffset = | |||
2434 | updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2435 | baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2436 | if (locatedCentralDirOffset < 0) { | |||
2437 | throw new ZipException("Cannot find central directory"); | |||
2438 | } | |||
2439 | | |||
2440 | const int CentralHeaderCommentSizeOffset = 16; | |||
2441 | updateFile.Position += CentralHeaderCommentSizeOffset; | |||
2442 | | |||
2443 | byte[] rawComment = newComment_.RawComment; | |||
2444 | | |||
2445 | updateFile.WriteLEShort(rawComment.Length); | |||
2446 | updateFile.Write(rawComment, 0, rawComment.Length); | |||
2447 | updateFile.SetLength(updateFile.Position); | |||
2448 | } | |||
2449 | | |||
2450 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | |||
2451 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2452 | } else { | |||
2453 | ReadEntries(); | |||
2454 | } | |||
2455 | } | |||
2456 | | |||
2457 | /// <summary> | |||
2458 | /// Class used to sort updates. | |||
2459 | /// </summary> | |||
2460 | class UpdateComparer : IComparer | |||
2461 | { | |||
2462 | /// <summary> | |||
2463 | /// Compares two objects and returns a value indicating whether one is | |||
2464 | /// less than, equal to or greater than the other. | |||
2465 | /// </summary> | |||
2466 | /// <param name="x">First object to compare</param> | |||
2467 | /// <param name="y">Second object to compare.</param> | |||
2468 | /// <returns>Compare result.</returns> | |||
2469 | public int Compare( | |||
2470 | object x, | |||
2471 | object y) | |||
2472 | { | |||
2473 | var zx = x as ZipUpdate; | |||
2474 | var zy = y as ZipUpdate; | |||
2475 | | |||
2476 | int result; | |||
2477 | | |||
2478 | if (zx == null) { | |||
2479 | if (zy == null) { | |||
2480 | result = 0; | |||
2481 | } else { | |||
2482 | result = -1; | |||
2483 | } | |||
2484 | } else if (zy == null) { | |||
2485 | result = 1; | |||
2486 | } else { | |||
2487 | int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2488 | int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; | |||
2489 | | |||
2490 | result = xCmdValue - yCmdValue; | |||
2491 | if (result == 0) { | |||
2492 | long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; | |||
2493 | if (offsetDiff < 0) { | |||
2494 | result = -1; | |||
2495 | } else if (offsetDiff == 0) { | |||
2496 | result = 0; | |||
2497 | } else { | |||
2498 | result = 1; | |||
2499 | } | |||
2500 | } | |||
2501 | } | |||
2502 | return result; | |||
2503 | } | |||
2504 | } | |||
2505 | | |||
2506 | void RunUpdates() | |||
2507 | { | |||
2508 | long sizeEntries = 0; | |||
2509 | long endOfStream = 0; | |||
2510 | bool directUpdate = false; | |||
2511 | long destinationPosition = 0; // NOT SFX friendly | |||
2512 | | |||
2513 | ZipFile workFile; | |||
2514 | | |||
2515 | if (IsNewArchive) { | |||
2516 | workFile = this; | |||
2517 | workFile.baseStream_.Position = 0; | |||
2518 | directUpdate = true; | |||
2519 | } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | |||
2520 | workFile = this; | |||
2521 | workFile.baseStream_.Position = 0; | |||
2522 | directUpdate = true; | |||
2523 | | |||
2524 | // Sort the updates by offset within copies/modifies, then adds. | |||
2525 | // This ensures that data required by copies will not be overwritten. | |||
2526 | updates_.Sort(new UpdateComparer()); | |||
2527 | } else { | |||
2528 | workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); | |||
2529 | workFile.UseZip64 = UseZip64; | |||
2530 | | |||
2531 | if (key != null) { | |||
2532 | workFile.key = (byte[])key.Clone(); | |||
2533 | } | |||
2534 | } | |||
2535 | | |||
2536 | try { | |||
2537 | foreach (ZipUpdate update in updates_) { | |||
2538 | if (update != null) { | |||
2539 | switch (update.Command) { | |||
2540 | case UpdateCommand.Copy: | |||
2541 | if (directUpdate) { | |||
2542 | CopyEntryDirect(workFile, update, ref destinationPosition); | |||
2543 | } else { | |||
2544 | CopyEntry(workFile, update); | |||
2545 | } | |||
2546 | break; | |||
2547 | | |||
2548 | case UpdateCommand.Modify: | |||
2549 | // TODO: Direct modifying of an entry will take some legwork. | |||
2550 | ModifyEntry(workFile, update); | |||
2551 | break; | |||
2552 | | |||
2553 | case UpdateCommand.Add: | |||
2554 | if (!IsNewArchive && directUpdate) { | |||
2555 | workFile.baseStream_.Position = destinationPosition; | |||
2556 | } | |||
2557 | | |||
2558 | AddEntry(workFile, update); | |||
2559 | | |||
2560 | if (directUpdate) { | |||
2561 | destinationPosition = workFile.baseStream_.Position; | |||
2562 | } | |||
2563 | break; | |||
2564 | } | |||
2565 | } | |||
2566 | } | |||
2567 | | |||
2568 | if (!IsNewArchive && directUpdate) { | |||
2569 | workFile.baseStream_.Position = destinationPosition; | |||
2570 | } | |||
2571 | | |||
2572 | long centralDirOffset = workFile.baseStream_.Position; | |||
2573 | | |||
2574 | foreach (ZipUpdate update in updates_) { | |||
2575 | if (update != null) { | |||
2576 | sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); | |||
2577 | } | |||
2578 | } | |||
2579 | | |||
2580 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | |||
2581 | using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) { | |||
2582 | zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); | |||
2583 | } | |||
2584 | | |||
2585 | endOfStream = workFile.baseStream_.Position; | |||
2586 | | |||
2587 | // And now patch entries... | |||
2588 | foreach (ZipUpdate update in updates_) { | |||
2589 | if (update != null) { | |||
2590 | // If the size of the entry is zero leave the crc as 0 as well. | |||
2591 | // The calculated crc will be all bits on... | |||
2592 | if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { | |||
2593 | workFile.baseStream_.Position = update.CrcPatchOffset; | |||
2594 | workFile.WriteLEInt((int)update.OutEntry.Crc); | |||
2595 | } | |||
2596 | | |||
2597 | if (update.SizePatchOffset > 0) { | |||
2598 | workFile.baseStream_.Position = update.SizePatchOffset; | |||
2599 | if (update.OutEntry.LocalHeaderRequiresZip64) { | |||
2600 | workFile.WriteLeLong(update.OutEntry.Size); | |||
2601 | workFile.WriteLeLong(update.OutEntry.CompressedSize); | |||
2602 | } else { | |||
2603 | workFile.WriteLEInt((int)update.OutEntry.CompressedSize); | |||
2604 | workFile.WriteLEInt((int)update.OutEntry.Size); | |||
2605 | } | |||
2606 | } | |||
2607 | } | |||
2608 | } | |||
2609 | } catch { | |||
2610 | workFile.Close(); | |||
2611 | if (!directUpdate && (workFile.Name != null)) { | |||
2612 | File.Delete(workFile.Name); | |||
2613 | } | |||
2614 | throw; | |||
2615 | } | |||
2616 | | |||
2617 | if (directUpdate) { | |||
2618 | workFile.baseStream_.SetLength(endOfStream); | |||
2619 | workFile.baseStream_.Flush(); | |||
2620 | isNewArchive_ = false; | |||
2621 | ReadEntries(); | |||
2622 | } else { | |||
2623 | baseStream_.Close(); | |||
2624 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | |||
2625 | } | |||
2626 | } | |||
2627 | | |||
2628 | void CheckUpdating() | |||
2629 | { | |||
2630 | if (updates_ == null) { | |||
2631 | throw new InvalidOperationException("BeginUpdate has not been called"); | |||
2632 | } | |||
2633 | } | |||
2634 | | |||
2635 | #endregion | |||
2636 | | |||
2637 | #region ZipUpdate class | |||
2638 | /// <summary> | |||
2639 | /// Represents a pending update to a Zip file. | |||
2640 | /// </summary> | |||
2641 | class ZipUpdate | |||
2642 | { | |||
2643 | #region Constructors | |||
2644 | public ZipUpdate(string fileName, ZipEntry entry) | |||
2645 | { | |||
2646 | command_ = UpdateCommand.Add; | |||
2647 | entry_ = entry; | |||
2648 | filename_ = fileName; | |||
2649 | } | |||
2650 | | |||
2651 | [Obsolete] | |||
2652 | public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) | |||
2653 | { | |||
2654 | command_ = UpdateCommand.Add; | |||
2655 | entry_ = new ZipEntry(entryName); | |||
2656 | entry_.CompressionMethod = compressionMethod; | |||
2657 | filename_ = fileName; | |||
2658 | } | |||
2659 | | |||
2660 | [Obsolete] | |||
2661 | public ZipUpdate(string fileName, string entryName) | |||
2662 | : this(fileName, entryName, CompressionMethod.Deflated) | |||
2663 | { | |||
2664 | // Do nothing. | |||
2665 | } | |||
2666 | | |||
2667 | [Obsolete] | |||
2668 | public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
2669 | { | |||
2670 | command_ = UpdateCommand.Add; | |||
2671 | entry_ = new ZipEntry(entryName); | |||
2672 | entry_.CompressionMethod = compressionMethod; | |||
2673 | dataSource_ = dataSource; | |||
2674 | } | |||
2675 | | |||
2676 | public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) | |||
2677 | { | |||
2678 | command_ = UpdateCommand.Add; | |||
2679 | entry_ = entry; | |||
2680 | dataSource_ = dataSource; | |||
2681 | } | |||
2682 | | |||
2683 | public ZipUpdate(ZipEntry original, ZipEntry updated) | |||
2684 | { | |||
2685 | throw new ZipException("Modify not currently supported"); | |||
2686 | /* | |||
2687 | command_ = UpdateCommand.Modify; | |||
2688 | entry_ = ( ZipEntry )original.Clone(); | |||
2689 | outEntry_ = ( ZipEntry )updated.Clone(); | |||
2690 | */ | |||
2691 | } | |||
2692 | | |||
2693 | public ZipUpdate(UpdateCommand command, ZipEntry entry) | |||
2694 | { | |||
2695 | command_ = command; | |||
2696 | entry_ = (ZipEntry)entry.Clone(); | |||
2697 | } | |||
2698 | | |||
2699 | | |||
2700 | /// <summary> | |||
2701 | /// Copy an existing entry. | |||
2702 | /// </summary> | |||
2703 | /// <param name="entry">The existing entry to copy.</param> | |||
2704 | public ZipUpdate(ZipEntry entry) | |||
2705 | : this(UpdateCommand.Copy, entry) | |||
2706 | { | |||
2707 | // Do nothing. | |||
2708 | } | |||
2709 | #endregion | |||
2710 | | |||
2711 | /// <summary> | |||
2712 | /// Get the <see cref="ZipEntry"/> for this update. | |||
2713 | /// </summary> | |||
2714 | /// <remarks>This is the source or original entry.</remarks> | |||
2715 | public ZipEntry Entry { | |||
2716 | get { return entry_; } | |||
2717 | } | |||
2718 | | |||
2719 | /// <summary> | |||
2720 | /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. | |||
2721 | /// </summary> | |||
2722 | public ZipEntry OutEntry { | |||
2723 | get { | |||
2724 | if (outEntry_ == null) { | |||
2725 | outEntry_ = (ZipEntry)entry_.Clone(); | |||
2726 | } | |||
2727 | | |||
2728 | return outEntry_; | |||
2729 | } | |||
2730 | } | |||
2731 | | |||
2732 | /// <summary> | |||
2733 | /// Get the command for this update. | |||
2734 | /// </summary> | |||
2735 | public UpdateCommand Command { | |||
2736 | get { return command_; } | |||
2737 | } | |||
2738 | | |||
2739 | /// <summary> | |||
2740 | /// Get the filename if any for this update. Null if none exists. | |||
2741 | /// </summary> | |||
2742 | public string Filename { | |||
2743 | get { return filename_; } | |||
2744 | } | |||
2745 | | |||
2746 | /// <summary> | |||
2747 | /// Get/set the location of the size patch for this update. | |||
2748 | /// </summary> | |||
2749 | public long SizePatchOffset { | |||
2750 | get { return sizePatchOffset_; } | |||
2751 | set { sizePatchOffset_ = value; } | |||
2752 | } | |||
2753 | | |||
2754 | /// <summary> | |||
2755 | /// Get /set the location of the crc patch for this update. | |||
2756 | /// </summary> | |||
2757 | public long CrcPatchOffset { | |||
2758 | get { return crcPatchOffset_; } | |||
2759 | set { crcPatchOffset_ = value; } | |||
2760 | } | |||
2761 | | |||
2762 | /// <summary> | |||
2763 | /// Get/set the size calculated by offset. | |||
2764 | /// Specifically, the difference between this and next entry's starting offset. | |||
2765 | /// </summary> | |||
2766 | public long OffsetBasedSize { | |||
2767 | get { return _offsetBasedSize; } | |||
2768 | set { _offsetBasedSize = value; } | |||
2769 | } | |||
2770 | | |||
2771 | public Stream GetSource() | |||
2772 | { | |||
2773 | Stream result = null; | |||
2774 | if (dataSource_ != null) { | |||
2775 | result = dataSource_.GetSource(); | |||
2776 | } | |||
2777 | | |||
2778 | return result; | |||
2779 | } | |||
2780 | | |||
2781 | #region Instance Fields | |||
2782 | ZipEntry entry_; | |||
2783 | ZipEntry outEntry_; | |||
2784 | UpdateCommand command_; | |||
2785 | IStaticDataSource dataSource_; | |||
2786 | string filename_; | |||
2787 | long sizePatchOffset_ = -1; | |||
2788 | long crcPatchOffset_ = -1; | |||
2789 | long _offsetBasedSize = -1; | |||
2790 | #endregion | |||
2791 | } | |||
2792 | | |||
2793 | #endregion | |||
2794 | #endregion | |||
2795 | | |||
2796 | #region Disposing | |||
2797 | | |||
2798 | #region IDisposable Members | |||
2799 | void IDisposable.Dispose() | |||
2800 | { | |||
2801 | Close(); | |||
2802 | } | |||
2803 | #endregion | |||
2804 | | |||
2805 | void DisposeInternal(bool disposing) | |||
2806 | { | |||
2807 | if (!isDisposed_) { | |||
2808 | isDisposed_ = true; | |||
2809 | entries_ = new ZipEntry[0]; | |||
2810 | | |||
2811 | if (IsStreamOwner && (baseStream_ != null)) { | |||
2812 | lock (baseStream_) { | |||
2813 | baseStream_.Close(); | |||
2814 | } | |||
2815 | } | |||
2816 | | |||
2817 | PostUpdateCleanup(); | |||
2818 | } | |||
2819 | } | |||
2820 | | |||
2821 | /// <summary> | |||
2822 | /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. | |||
2823 | /// </summary> | |||
2824 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
2825 | /// false to release only unmanaged resources.</param> | |||
2826 | protected virtual void Dispose(bool disposing) | |||
2827 | { | |||
2828 | DisposeInternal(disposing); | |||
2829 | } | |||
2830 | | |||
2831 | #endregion | |||
2832 | | |||
2833 | #region Internal routines | |||
2834 | #region Reading | |||
2835 | /// <summary> | |||
2836 | /// Read an unsigned short in little endian byte order. | |||
2837 | /// </summary> | |||
2838 | /// <returns>Returns the value read.</returns> | |||
2839 | /// <exception cref="EndOfStreamException"> | |||
2840 | /// The stream ends prematurely | |||
2841 | /// </exception> | |||
2842 | ushort ReadLEUshort() | |||
2843 | { | |||
2844 | int data1 = baseStream_.ReadByte(); | |||
2845 | | |||
2846 | if (data1 < 0) { | |||
2847 | throw new EndOfStreamException("End of stream"); | |||
2848 | } | |||
2849 | | |||
2850 | int data2 = baseStream_.ReadByte(); | |||
2851 | | |||
2852 | if (data2 < 0) { | |||
2853 | throw new EndOfStreamException("End of stream"); | |||
2854 | } | |||
2855 | | |||
2856 | | |||
2857 | return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); | |||
2858 | } | |||
2859 | | |||
2860 | /// <summary> | |||
2861 | /// Read a uint in little endian byte order. | |||
2862 | /// </summary> | |||
2863 | /// <returns>Returns the value read.</returns> | |||
2864 | /// <exception cref="IOException"> | |||
2865 | /// An i/o error occurs. | |||
2866 | /// </exception> | |||
2867 | /// <exception cref="System.IO.EndOfStreamException"> | |||
2868 | /// The file ends prematurely | |||
2869 | /// </exception> | |||
2870 | uint ReadLEUint() | |||
2871 | { | |||
2872 | return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); | |||
2873 | } | |||
2874 | | |||
2875 | ulong ReadLEUlong() | |||
2876 | { | |||
2877 | return ReadLEUint() | ((ulong)ReadLEUint() << 32); | |||
2878 | } | |||
2879 | | |||
2880 | #endregion | |||
2881 | // NOTE this returns the offset of the first byte after the signature. | |||
2882 | long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
2883 | { | |||
2884 | using (ZipHelperStream les = new ZipHelperStream(baseStream_)) { | |||
2885 | return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); | |||
2886 | } | |||
2887 | } | |||
2888 | | |||
2889 | /// <summary> | |||
2890 | /// Search for and read the central directory of a zip file filling the entries array. | |||
2891 | /// </summary> | |||
2892 | /// <exception cref="System.IO.IOException"> | |||
2893 | /// An i/o error occurs. | |||
2894 | /// </exception> | |||
2895 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
2896 | /// The central directory is malformed or cannot be found | |||
2897 | /// </exception> | |||
2898 | void ReadEntries() | |||
2899 | { | |||
2900 | // Search for the End Of Central Directory. When a zip comment is | |||
2901 | // present the directory will start earlier | |||
2902 | // | |||
2903 | // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. | |||
2904 | // This should be compatible with both SFX and ZIP files but has only been tested for Zip files | |||
2905 | // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then | |||
2906 | // this could be invalid. | |||
2907 | // Could also speed this up by reading memory in larger blocks. | |||
2908 | | |||
2909 | if (baseStream_.CanSeek == false) { | |||
2910 | throw new ZipException("ZipFile stream must be seekable"); | |||
2911 | } | |||
2912 | | |||
2913 | long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | |||
2914 | baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | |||
2915 | | |||
2916 | if (locatedEndOfCentralDir < 0) { | |||
2917 | throw new ZipException("Cannot find central directory"); | |||
2918 | } | |||
2919 | | |||
2920 | // Read end of central directory record | |||
2921 | ushort thisDiskNumber = ReadLEUshort(); | |||
2922 | ushort startCentralDirDisk = ReadLEUshort(); | |||
2923 | ulong entriesForThisDisk = ReadLEUshort(); | |||
2924 | ulong entriesForWholeCentralDir = ReadLEUshort(); | |||
2925 | ulong centralDirSize = ReadLEUint(); | |||
2926 | long offsetOfCentralDir = ReadLEUint(); | |||
2927 | uint commentSize = ReadLEUshort(); | |||
2928 | | |||
2929 | if (commentSize > 0) { | |||
2930 | byte[] comment = new byte[commentSize]; | |||
2931 | | |||
2932 | StreamUtils.ReadFully(baseStream_, comment); | |||
2933 | comment_ = ZipConstants.ConvertToString(comment); | |||
2934 | } else { | |||
2935 | comment_ = string.Empty; | |||
2936 | } | |||
2937 | | |||
2938 | bool isZip64 = false; | |||
2939 | | |||
2940 | // Check if zip64 header information is required. | |||
2941 | if ((thisDiskNumber == 0xffff) || | |||
2942 | (startCentralDirDisk == 0xffff) || | |||
2943 | (entriesForThisDisk == 0xffff) || | |||
2944 | (entriesForWholeCentralDir == 0xffff) || | |||
2945 | (centralDirSize == 0xffffffff) || | |||
2946 | (offsetOfCentralDir == 0xffffffff)) { | |||
2947 | isZip64 = true; | |||
2948 | | |||
2949 | long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, | |||
2950 | if (offset < 0) { | |||
2951 | throw new ZipException("Cannot find Zip64 locator"); | |||
2952 | } | |||
2953 | | |||
2954 | // number of the disk with the start of the zip64 end of central directory 4 bytes | |||
2955 | // relative offset of the zip64 end of central directory record 8 bytes | |||
2956 | // total number of disks 4 bytes | |||
2957 | ReadLEUint(); // startDisk64 is not currently used | |||
2958 | ulong offset64 = ReadLEUlong(); | |||
2959 | uint totalDisks = ReadLEUint(); | |||
2960 | | |||
2961 | baseStream_.Position = (long)offset64; | |||
2962 | long sig64 = ReadLEUint(); | |||
2963 | | |||
2964 | if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { | |||
2965 | throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); | |||
2966 | } | |||
2967 | | |||
2968 | // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. | |||
2969 | ulong recordSize = ReadLEUlong(); | |||
2970 | int versionMadeBy = ReadLEUshort(); | |||
2971 | int versionToExtract = ReadLEUshort(); | |||
2972 | uint thisDisk = ReadLEUint(); | |||
2973 | uint centralDirDisk = ReadLEUint(); | |||
2974 | entriesForThisDisk = ReadLEUlong(); | |||
2975 | entriesForWholeCentralDir = ReadLEUlong(); | |||
2976 | centralDirSize = ReadLEUlong(); | |||
2977 | offsetOfCentralDir = (long)ReadLEUlong(); | |||
2978 | | |||
2979 | // NOTE: zip64 extensible data sector (variable size) is ignored. | |||
2980 | } | |||
2981 | | |||
2982 | entries_ = new ZipEntry[entriesForThisDisk]; | |||
2983 | | |||
2984 | // SFX/embedded support, find the offset of the first entry vis the start of the stream | |||
2985 | // This applies to Zip files that are appended to the end of an SFX stub. | |||
2986 | // Or are appended as a resource to an executable. | |||
2987 | // Zip files created by some archivers have the offsets altered to reflect the true offsets | |||
2988 | // and so dont require any adjustment here... | |||
2989 | // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? | |||
2990 | if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) { | |||
2991 | offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); | |||
2992 | if (offsetOfFirstEntry <= 0) { | |||
2993 | throw new ZipException("Invalid embedded zip archive"); | |||
2994 | } | |||
2995 | } | |||
2996 | | |||
2997 | baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); | |||
2998 | | |||
2999 | for (ulong i = 0; i < entriesForThisDisk; i++) { | |||
3000 | if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { | |||
3001 | throw new ZipException("Wrong Central Directory signature"); | |||
3002 | } | |||
3003 | | |||
3004 | int versionMadeBy = ReadLEUshort(); | |||
3005 | int versionToExtract = ReadLEUshort(); | |||
3006 | int bitFlags = ReadLEUshort(); | |||
3007 | int method = ReadLEUshort(); | |||
3008 | uint dostime = ReadLEUint(); | |||
3009 | uint crc = ReadLEUint(); | |||
3010 | var csize = (long)ReadLEUint(); | |||
3011 | var size = (long)ReadLEUint(); | |||
3012 | int nameLen = ReadLEUshort(); | |||
3013 | int extraLen = ReadLEUshort(); | |||
3014 | int commentLen = ReadLEUshort(); | |||
3015 | | |||
3016 | int diskStartNo = ReadLEUshort(); // Not currently used | |||
3017 | int internalAttributes = ReadLEUshort(); // Not currently used | |||
3018 | | |||
3019 | uint externalAttributes = ReadLEUint(); | |||
3020 | long offset = ReadLEUint(); | |||
3021 | | |||
3022 | byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; | |||
3023 | | |||
3024 | StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); | |||
3025 | string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); | |||
3026 | | |||
3027 | var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); | |||
3028 | entry.Crc = crc & 0xffffffffL; | |||
3029 | entry.Size = size & 0xffffffffL; | |||
3030 | entry.CompressedSize = csize & 0xffffffffL; | |||
3031 | entry.Flags = bitFlags; | |||
3032 | entry.DosTime = (uint)dostime; | |||
3033 | entry.ZipFileIndex = (long)i; | |||
3034 | entry.Offset = offset; | |||
3035 | entry.ExternalFileAttributes = (int)externalAttributes; | |||
3036 | | |||
3037 | if ((bitFlags & 8) == 0) { | |||
3038 | entry.CryptoCheckValue = (byte)(crc >> 24); | |||
3039 | } else { | |||
3040 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | |||
3041 | } | |||
3042 | | |||
3043 | if (extraLen > 0) { | |||
3044 | byte[] extra = new byte[extraLen]; | |||
3045 | StreamUtils.ReadFully(baseStream_, extra); | |||
3046 | entry.ExtraData = extra; | |||
3047 | } | |||
3048 | | |||
3049 | entry.ProcessExtraData(false); | |||
3050 | | |||
3051 | if (commentLen > 0) { | |||
3052 | StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); | |||
3053 | entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); | |||
3054 | } | |||
3055 | | |||
3056 | entries_[i] = entry; | |||
3057 | } | |||
3058 | } | |||
3059 | | |||
3060 | /// <summary> | |||
3061 | /// Locate the data for a given entry. | |||
3062 | /// </summary> | |||
3063 | /// <returns> | |||
3064 | /// The start offset of the data. | |||
3065 | /// </returns> | |||
3066 | /// <exception cref="System.IO.EndOfStreamException"> | |||
3067 | /// The stream ends prematurely | |||
3068 | /// </exception> | |||
3069 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
3070 | /// The local header signature is invalid, the entry and central header file name lengths are different | |||
3071 | /// or the local and entry compression methods dont match | |||
3072 | /// </exception> | |||
3073 | long LocateEntry(ZipEntry entry) | |||
3074 | { | |||
3075 | return TestLocalHeader(entry, HeaderTest.Extract); | |||
3076 | } | |||
3077 | | |||
3078 | Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) | |||
3079 | { | |||
3080 | CryptoStream result = null; | |||
3081 | | |||
3082 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3083 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3084 | var classicManaged = new PkzipClassicManaged(); | |||
3085 | | |||
3086 | OnKeysRequired(entry.Name); | |||
3087 | if (HaveKeys == false) { | |||
3088 | throw new ZipException("No password available for encrypted stream"); | |||
3089 | } | |||
3090 | | |||
3091 | result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); | |||
3092 | CheckClassicPassword(result, entry); | |||
3093 | } else { | |||
3094 | if (entry.Version == ZipConstants.VERSION_AES) { | |||
3095 | // | |||
3096 | OnKeysRequired(entry.Name); | |||
3097 | if (HaveKeys == false) { | |||
3098 | throw new ZipException("No password available for AES encrypted stream"); | |||
3099 | } | |||
3100 | int saltLen = entry.AESSaltLen; | |||
3101 | byte[] saltBytes = new byte[saltLen]; | |||
3102 | int saltIn = baseStream.Read(saltBytes, 0, saltLen); | |||
3103 | if (saltIn != saltLen) | |||
3104 | throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); | |||
3105 | // | |||
3106 | byte[] pwdVerifyRead = new byte[2]; | |||
3107 | baseStream.Read(pwdVerifyRead, 0, 2); | |||
3108 | int blockSize = entry.AESKeySize / 8; // bits to bytes | |||
3109 | | |||
3110 | var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); | |||
3111 | byte[] pwdVerifyCalc = decryptor.PwdVerifier; | |||
3112 | if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) | |||
3113 | throw new ZipException("Invalid password for AES"); | |||
3114 | result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); | |||
3115 | } else { | |||
3116 | throw new ZipException("Decryption method not supported"); | |||
3117 | } | |||
3118 | } | |||
3119 | | |||
3120 | return result; | |||
3121 | } | |||
3122 | | |||
3123 | Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) | |||
3124 | { | |||
3125 | CryptoStream result = null; | |||
3126 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | |||
3127 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | |||
3128 | var classicManaged = new PkzipClassicManaged(); | |||
3129 | | |||
3130 | OnKeysRequired(entry.Name); | |||
3131 | if (HaveKeys == false) { | |||
3132 | throw new ZipException("No password available for encrypted stream"); | |||
3133 | } | |||
3134 | | |||
3135 | // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream | |||
3136 | // which doesnt do this. | |||
3137 | result = new CryptoStream(new UncompressedStream(baseStream), | |||
3138 | classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); | |||
3139 | | |||
3140 | if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { | |||
3141 | WriteEncryptionHeader(result, entry.DosTime << 16); | |||
3142 | } else { | |||
3143 | WriteEncryptionHeader(result, entry.Crc); | |||
3144 | } | |||
3145 | } | |||
3146 | return result; | |||
3147 | } | |||
3148 | | |||
3149 | static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) | |||
3150 | { | |||
3151 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3152 | StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); | |||
3153 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | |||
3154 | throw new ZipException("Invalid password"); | |||
3155 | } | |||
3156 | } | |||
3157 | | |||
3158 | static void WriteEncryptionHeader(Stream stream, long crcValue) | |||
3159 | { | |||
3160 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | |||
3161 | var rnd = new Random(); | |||
3162 | rnd.NextBytes(cryptBuffer); | |||
3163 | cryptBuffer[11] = (byte)(crcValue >> 24); | |||
3164 | stream.Write(cryptBuffer, 0, cryptBuffer.Length); | |||
3165 | } | |||
3166 | | |||
3167 | #endregion | |||
3168 | | |||
3169 | #region Instance Fields | |||
3170 | bool isDisposed_; | |||
3171 | string name_; | |||
3172 | string comment_; | |||
3173 | string rawPassword_; | |||
3174 | Stream baseStream_; | |||
3175 | bool isStreamOwner; | |||
3176 | long offsetOfFirstEntry; | |||
3177 | ZipEntry[] entries_; | |||
3178 | byte[] key; | |||
3179 | bool isNewArchive_; | |||
3180 | | |||
3181 | // Default is dynamic which is not backwards compatible and can cause problems | |||
3182 | // with XP's built in compression which cant read Zip64 archives. | |||
3183 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
3184 | // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. | |||
3185 | UseZip64 useZip64_ = UseZip64.Dynamic; | |||
3186 | | |||
3187 | #region Zip Update Instance Fields | |||
3188 | ArrayList updates_; | |||
3189 | long updateCount_; // Count is managed manually as updates_ can contain nulls! | |||
3190 | Hashtable updateIndex_; | |||
3191 | IArchiveStorage archiveStorage_; | |||
3192 | IDynamicDataSource updateDataSource_; | |||
3193 | bool contentsEdited_; | |||
3194 | int bufferSize_ = DefaultBufferSize; | |||
3195 | byte[] copyBuffer_; | |||
3196 | ZipString newComment_; | |||
3197 | bool commentEdited_; | |||
3198 | IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); | |||
3199 | #endregion | |||
3200 | #endregion | |||
3201 | | |||
3202 | #region Support Classes | |||
3203 | /// <summary> | |||
3204 | /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. | |||
3205 | /// </summary> | |||
3206 | class ZipString | |||
3207 | { | |||
3208 | #region Constructors | |||
3209 | /// <summary> | |||
3210 | /// Initialise a <see cref="ZipString"/> with a string. | |||
3211 | /// </summary> | |||
3212 | /// <param name="comment">The textual string form.</param> | |||
3213 | public ZipString(string comment) | |||
3214 | { | |||
3215 | comment_ = comment; | |||
3216 | isSourceString_ = true; | |||
3217 | } | |||
3218 | | |||
3219 | /// <summary> | |||
3220 | /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. | |||
3221 | /// </summary> | |||
3222 | /// <param name="rawString"></param> | |||
3223 | public ZipString(byte[] rawString) | |||
3224 | { | |||
3225 | rawComment_ = rawString; | |||
3226 | } | |||
3227 | #endregion | |||
3228 | | |||
3229 | /// <summary> | |||
3230 | /// Get a value indicating the original source of data for this instance. | |||
3231 | /// True if the source was a string; false if the source was binary data. | |||
3232 | /// </summary> | |||
3233 | public bool IsSourceString { | |||
3234 | get { return isSourceString_; } | |||
3235 | } | |||
3236 | | |||
3237 | /// <summary> | |||
3238 | /// Get the length of the comment when represented as raw bytes. | |||
3239 | /// </summary> | |||
3240 | public int RawLength { | |||
3241 | get { | |||
3242 | MakeBytesAvailable(); | |||
3243 | return rawComment_.Length; | |||
3244 | } | |||
3245 | } | |||
3246 | | |||
3247 | /// <summary> | |||
3248 | /// Get the comment in its 'raw' form as plain bytes. | |||
3249 | /// </summary> | |||
3250 | public byte[] RawComment { | |||
3251 | get { | |||
3252 | MakeBytesAvailable(); | |||
3253 | return (byte[])rawComment_.Clone(); | |||
3254 | } | |||
3255 | } | |||
3256 | | |||
3257 | /// <summary> | |||
3258 | /// Reset the comment to its initial state. | |||
3259 | /// </summary> | |||
3260 | public void Reset() | |||
3261 | { | |||
3262 | if (isSourceString_) { | |||
3263 | rawComment_ = null; | |||
3264 | } else { | |||
3265 | comment_ = null; | |||
3266 | } | |||
3267 | } | |||
3268 | | |||
3269 | void MakeTextAvailable() | |||
3270 | { | |||
3271 | if (comment_ == null) { | |||
3272 | comment_ = ZipConstants.ConvertToString(rawComment_); | |||
3273 | } | |||
3274 | } | |||
3275 | | |||
3276 | void MakeBytesAvailable() | |||
3277 | { | |||
3278 | if (rawComment_ == null) { | |||
3279 | rawComment_ = ZipConstants.ConvertToArray(comment_); | |||
3280 | } | |||
3281 | } | |||
3282 | | |||
3283 | /// <summary> | |||
3284 | /// Implicit conversion of comment to a string. | |||
3285 | /// </summary> | |||
3286 | /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> | |||
3287 | /// <returns>The textual equivalent for the input value.</returns> | |||
3288 | static public implicit operator string(ZipString zipString) | |||
3289 | { | |||
3290 | zipString.MakeTextAvailable(); | |||
3291 | return zipString.comment_; | |||
3292 | } | |||
3293 | | |||
3294 | #region Instance Fields | |||
3295 | string comment_; | |||
3296 | byte[] rawComment_; | |||
3297 | bool isSourceString_; | |||
3298 | #endregion | |||
3299 | } | |||
3300 | | |||
3301 | /// <summary> | |||
3302 | /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> | |||
3303 | /// </summary> | |||
3304 | class ZipEntryEnumerator : IEnumerator | |||
3305 | { | |||
3306 | #region Constructors | |||
3307 | public ZipEntryEnumerator(ZipEntry[] entries) | |||
3308 | { | |||
3309 | array = entries; | |||
3310 | } | |||
3311 | | |||
3312 | #endregion | |||
3313 | #region IEnumerator Members | |||
3314 | public object Current { | |||
3315 | get { | |||
3316 | return array[index]; | |||
3317 | } | |||
3318 | } | |||
3319 | | |||
3320 | public void Reset() | |||
3321 | { | |||
3322 | index = -1; | |||
3323 | } | |||
3324 | | |||
3325 | public bool MoveNext() | |||
3326 | { | |||
3327 | return (++index < array.Length); | |||
3328 | } | |||
3329 | #endregion | |||
3330 | #region Instance Fields | |||
3331 | ZipEntry[] array; | |||
3332 | int index = -1; | |||
3333 | #endregion | |||
3334 | } | |||
3335 | | |||
3336 | /// <summary> | |||
3337 | /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data | |||
3338 | /// to and flush, but cannot read, seek or do anything else to. | |||
3339 | /// </summary> | |||
3340 | class UncompressedStream : Stream | |||
3341 | { | |||
3342 | #region Constructors | |||
3343 | public UncompressedStream(Stream baseStream) | |||
3344 | { | |||
3345 | baseStream_ = baseStream; | |||
3346 | } | |||
3347 | | |||
3348 | #endregion | |||
3349 | | |||
3350 | /// <summary> | |||
3351 | /// Close this stream instance. | |||
3352 | /// </summary> | |||
3353 | public override void Close() | |||
3354 | { | |||
3355 | // Do nothing | |||
3356 | } | |||
3357 | | |||
3358 | /// <summary> | |||
3359 | /// Gets a value indicating whether the current stream supports reading. | |||
3360 | /// </summary> | |||
3361 | public override bool CanRead { | |||
3362 | get { | |||
3363 | return false; | |||
3364 | } | |||
3365 | } | |||
3366 | | |||
3367 | /// <summary> | |||
3368 | /// Write any buffered data to underlying storage. | |||
3369 | /// </summary> | |||
3370 | public override void Flush() | |||
3371 | { | |||
3372 | baseStream_.Flush(); | |||
3373 | } | |||
3374 | | |||
3375 | /// <summary> | |||
3376 | /// Gets a value indicating whether the current stream supports writing. | |||
3377 | /// </summary> | |||
3378 | public override bool CanWrite { | |||
3379 | get { | |||
3380 | return baseStream_.CanWrite; | |||
3381 | } | |||
3382 | } | |||
3383 | | |||
3384 | /// <summary> | |||
3385 | /// Gets a value indicating whether the current stream supports seeking. | |||
3386 | /// </summary> | |||
3387 | public override bool CanSeek { | |||
3388 | get { | |||
3389 | return false; | |||
3390 | } | |||
3391 | } | |||
3392 | | |||
3393 | /// <summary> | |||
3394 | /// Get the length in bytes of the stream. | |||
3395 | /// </summary> | |||
3396 | public override long Length { | |||
3397 | get { | |||
3398 | return 0; | |||
3399 | } | |||
3400 | } | |||
3401 | | |||
3402 | /// <summary> | |||
3403 | /// Gets or sets the position within the current stream. | |||
3404 | /// </summary> | |||
3405 | public override long Position { | |||
3406 | get { | |||
3407 | return baseStream_.Position; | |||
3408 | } | |||
3409 | set { | |||
3410 | throw new NotImplementedException(); | |||
3411 | } | |||
3412 | } | |||
3413 | | |||
3414 | /// <summary> | |||
3415 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3416 | /// </summary> | |||
3417 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3418 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3419 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3420 | /// <returns> | |||
3421 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3422 | /// </returns> | |||
3423 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3424 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3425 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3426 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3427 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3428 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3429 | public override int Read(byte[] buffer, int offset, int count) | |||
3430 | { | |||
3431 | return 0; | |||
3432 | } | |||
3433 | | |||
3434 | /// <summary> | |||
3435 | /// Sets the position within the current stream. | |||
3436 | /// </summary> | |||
3437 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3438 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3439 | /// <returns> | |||
3440 | /// The new position within the current stream. | |||
3441 | /// </returns> | |||
3442 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3443 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3444 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3445 | public override long Seek(long offset, SeekOrigin origin) | |||
3446 | { | |||
3447 | return 0; | |||
3448 | } | |||
3449 | | |||
3450 | /// <summary> | |||
3451 | /// Sets the length of the current stream. | |||
3452 | /// </summary> | |||
3453 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3454 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3455 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3456 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3457 | public override void SetLength(long value) | |||
3458 | { | |||
3459 | } | |||
3460 | | |||
3461 | /// <summary> | |||
3462 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3463 | /// </summary> | |||
3464 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3465 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3466 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3467 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3468 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3469 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3470 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3471 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3472 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3473 | public override void Write(byte[] buffer, int offset, int count) | |||
3474 | { | |||
3475 | baseStream_.Write(buffer, offset, count); | |||
3476 | } | |||
3477 | | |||
3478 | readonly | |||
3479 | | |||
3480 | #region Instance Fields | |||
3481 | Stream baseStream_; | |||
3482 | #endregion | |||
3483 | } | |||
3484 | | |||
3485 | /// <summary> | |||
3486 | /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> | |||
3487 | /// whose data is only a part or subsection of a file. | |||
3488 | /// </summary> | |||
3489 | class PartialInputStream : Stream | |||
3490 | { | |||
3491 | #region Constructors | |||
3492 | /// <summary> | |||
3493 | /// Initialise a new instance of the <see cref="PartialInputStream"/> class. | |||
3494 | /// </summary> | |||
3495 | /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param> | |||
3496 | /// <param name="start">The start of the partial data.</param> | |||
3497 | /// <param name="length">The length of the partial data.</param> | |||
3498 | public PartialInputStream(ZipFile zipFile, long start, long length) | |||
3499 | { | |||
3500 | start_ = start; | |||
3501 | length_ = length; | |||
3502 | | |||
3503 | // Although this is the only time the zipfile is used | |||
3504 | // keeping a reference here prevents premature closure of | |||
3505 | // this zip file and thus the baseStream_. | |||
3506 | | |||
3507 | // Code like this will cause apparently random failures depending | |||
3508 | // on the size of the files and when garbage is collected. | |||
3509 | // | |||
3510 | // ZipFile z = new ZipFile (stream); | |||
3511 | // Stream reader = z.GetInputStream(0); | |||
3512 | // uses reader here.... | |||
3513 | zipFile_ = zipFile; | |||
3514 | baseStream_ = zipFile_.baseStream_; | |||
3515 | readPos_ = start; | |||
3516 | end_ = start + length; | |||
3517 | } | |||
3518 | #endregion | |||
3519 | | |||
3520 | /// <summary> | |||
3521 | /// Read a byte from this stream. | |||
3522 | /// </summary> | |||
3523 | /// <returns>Returns the byte read or -1 on end of stream.</returns> | |||
3524 | public override int ReadByte() | |||
3525 | { | |||
3526 | if (readPos_ >= end_) { | |||
3527 | // -1 is the correct value at end of stream. | |||
3528 | return -1; | |||
3529 | } | |||
3530 | | |||
3531 | lock (baseStream_) { | |||
3532 | baseStream_.Seek(readPos_++, SeekOrigin.Begin); | |||
3533 | return baseStream_.ReadByte(); | |||
3534 | } | |||
3535 | } | |||
3536 | | |||
3537 | /// <summary> | |||
3538 | /// Close this <see cref="PartialInputStream">partial input stream</see>. | |||
3539 | /// </summary> | |||
3540 | /// <remarks> | |||
3541 | /// The underlying stream is not closed. Close the parent ZipFile class to do that. | |||
3542 | /// </remarks> | |||
3543 | public override void Close() | |||
3544 | { | |||
3545 | // Do nothing at all! | |||
3546 | } | |||
3547 | | |||
3548 | /// <summary> | |||
3549 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3550 | /// </summary> | |||
3551 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3552 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3553 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3554 | /// <returns> | |||
3555 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3556 | /// </returns> | |||
3557 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3558 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3559 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3560 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3561 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3562 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3563 | public override int Read(byte[] buffer, int offset, int count) | |||
3564 | { | |||
3565 | lock (baseStream_) { | |||
3566 | if (count > end_ - readPos_) { | |||
3567 | count = (int)(end_ - readPos_); | |||
3568 | if (count == 0) { | |||
3569 | return 0; | |||
3570 | } | |||
3571 | } | |||
3572 | // Protect against Stream implementations that throw away their buffer on every Seek | |||
3573 | // (for example, Mono FileStream) | |||
3574 | if (baseStream_.Position != readPos_) { | |||
3575 | baseStream_.Seek(readPos_, SeekOrigin.Begin); | |||
3576 | } | |||
3577 | int readCount = baseStream_.Read(buffer, offset, count); | |||
3578 | if (readCount > 0) { | |||
3579 | readPos_ += readCount; | |||
3580 | } | |||
3581 | return readCount; | |||
3582 | } | |||
3583 | } | |||
3584 | | |||
3585 | /// <summary> | |||
3586 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3587 | /// </summary> | |||
3588 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3589 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3590 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3591 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3592 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3593 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3594 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3595 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3596 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3597 | public override void Write(byte[] buffer, int offset, int count) | |||
3598 | { | |||
3599 | throw new NotSupportedException(); | |||
3600 | } | |||
3601 | | |||
3602 | /// <summary> | |||
3603 | /// When overridden in a derived class, sets the length of the current stream. | |||
3604 | /// </summary> | |||
3605 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3606 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3607 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3608 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3609 | public override void SetLength(long value) | |||
3610 | { | |||
3611 | throw new NotSupportedException(); | |||
3612 | } | |||
3613 | | |||
3614 | /// <summary> | |||
3615 | /// When overridden in a derived class, sets the position within the current stream. | |||
3616 | /// </summary> | |||
3617 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3618 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3619 | /// <returns> | |||
3620 | /// The new position within the current stream. | |||
3621 | /// </returns> | |||
3622 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3623 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3624 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3625 | public override long Seek(long offset, SeekOrigin origin) | |||
3626 | { | |||
3627 | long newPos = readPos_; | |||
3628 | | |||
3629 | switch (origin) { | |||
3630 | case SeekOrigin.Begin: | |||
3631 | newPos = start_ + offset; | |||
3632 | break; | |||
3633 | | |||
3634 | case SeekOrigin.Current: | |||
3635 | newPos = readPos_ + offset; | |||
3636 | break; | |||
3637 | | |||
3638 | case SeekOrigin.End: | |||
3639 | newPos = end_ + offset; | |||
3640 | break; | |||
3641 | } | |||
3642 | | |||
3643 | if (newPos < start_) { | |||
3644 | throw new ArgumentException("Negative position is invalid"); | |||
3645 | } | |||
3646 | | |||
3647 | if (newPos >= end_) { | |||
3648 | throw new IOException("Cannot seek past end"); | |||
3649 | } | |||
3650 | readPos_ = newPos; | |||
3651 | return readPos_; | |||
3652 | } | |||
3653 | | |||
3654 | /// <summary> | |||
3655 | /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | |||
3656 | /// </summary> | |||
3657 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3658 | public override void Flush() | |||
3659 | { | |||
3660 | // Nothing to do. | |||
3661 | } | |||
3662 | | |||
3663 | /// <summary> | |||
3664 | /// Gets or sets the position within the current stream. | |||
3665 | /// </summary> | |||
3666 | /// <value></value> | |||
3667 | /// <returns>The current position within the stream.</returns> | |||
3668 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3669 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> | |||
3670 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3671 | public override long Position { | |||
3672 | get { return readPos_ - start_; } | |||
3673 | set { | |||
3674 | long newPos = start_ + value; | |||
3675 | | |||
3676 | if (newPos < start_) { | |||
3677 | throw new ArgumentException("Negative position is invalid"); | |||
3678 | } | |||
3679 | | |||
3680 | if (newPos >= end_) { | |||
3681 | throw new InvalidOperationException("Cannot seek past end"); | |||
3682 | } | |||
3683 | readPos_ = newPos; | |||
3684 | } | |||
3685 | } | |||
3686 | | |||
3687 | /// <summary> | |||
3688 | /// Gets the length in bytes of the stream. | |||
3689 | /// </summary> | |||
3690 | /// <value></value> | |||
3691 | /// <returns>A long value representing the length of the stream in bytes.</returns> | |||
3692 | /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </excep | |||
3693 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3694 | public override long Length { | |||
3695 | get { return length_; } | |||
3696 | } | |||
3697 | | |||
3698 | /// <summary> | |||
3699 | /// Gets a value indicating whether the current stream supports writing. | |||
3700 | /// </summary> | |||
3701 | /// <value>false</value> | |||
3702 | /// <returns>true if the stream supports writing; otherwise, false.</returns> | |||
3703 | public override bool CanWrite { | |||
3704 | get { return false; } | |||
3705 | } | |||
3706 | | |||
3707 | /// <summary> | |||
3708 | /// Gets a value indicating whether the current stream supports seeking. | |||
3709 | /// </summary> | |||
3710 | /// <value>true</value> | |||
3711 | /// <returns>true if the stream supports seeking; otherwise, false.</returns> | |||
3712 | public override bool CanSeek { | |||
3713 | get { return true; } | |||
3714 | } | |||
3715 | | |||
3716 | /// <summary> | |||
3717 | /// Gets a value indicating whether the current stream supports reading. | |||
3718 | /// </summary> | |||
3719 | /// <value>true.</value> | |||
3720 | /// <returns>true if the stream supports reading; otherwise, false.</returns> | |||
3721 | public override bool CanRead { | |||
3722 | get { return true; } | |||
3723 | } | |||
3724 | | |||
3725 | /// <summary> | |||
3726 | /// Gets a value that determines whether the current stream can time out. | |||
3727 | /// </summary> | |||
3728 | /// <value></value> | |||
3729 | /// <returns>A value that determines whether the current stream can time out.</returns> | |||
3730 | public override bool CanTimeout { | |||
3731 | get { return baseStream_.CanTimeout; } | |||
3732 | } | |||
3733 | #region Instance Fields | |||
3734 | ZipFile zipFile_; | |||
3735 | Stream baseStream_; | |||
3736 | long start_; | |||
3737 | long length_; | |||
3738 | long readPos_; | |||
3739 | long end_; | |||
3740 | #endregion | |||
3741 | } | |||
3742 | #endregion | |||
3743 | } | |||
3744 | | |||
3745 | #endregion | |||
3746 | | |||
3747 | #region DataSources | |||
3748 | /// <summary> | |||
3749 | /// Provides a static way to obtain a source of data for an entry. | |||
3750 | /// </summary> | |||
3751 | public interface IStaticDataSource | |||
3752 | { | |||
3753 | /// <summary> | |||
3754 | /// Get a source of data by creating a new stream. | |||
3755 | /// </summary> | |||
3756 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3757 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3758 | Stream GetSource(); | |||
3759 | } | |||
3760 | | |||
3761 | /// <summary> | |||
3762 | /// Represents a source of data that can dynamically provide | |||
3763 | /// multiple <see cref="Stream">data sources</see> based on the parameters passed. | |||
3764 | /// </summary> | |||
3765 | public interface IDynamicDataSource | |||
3766 | { | |||
3767 | /// <summary> | |||
3768 | /// Get a data source. | |||
3769 | /// </summary> | |||
3770 | /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> | |||
3771 | /// <param name="name">The name for data if known.</param> | |||
3772 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3773 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3774 | Stream GetSource(ZipEntry entry, string name); | |||
3775 | } | |||
3776 | | |||
3777 | /// <summary> | |||
3778 | /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. | |||
3779 | /// </summary> | |||
3780 | public class StaticDiskDataSource : IStaticDataSource | |||
3781 | { | |||
3782 | /// <summary> | |||
3783 | /// Initialise a new instnace of <see cref="StaticDiskDataSource"/> | |||
3784 | /// </summary> | |||
3785 | /// <param name="fileName">The name of the file to obtain data from.</param> | |||
3786 | public StaticDiskDataSource(string fileName) | |||
3787 | { | |||
3788 | fileName_ = fileName; | |||
3789 | } | |||
3790 | | |||
3791 | #region IDataSource Members | |||
3792 | | |||
3793 | /// <summary> | |||
3794 | /// Get a <see cref="Stream"/> providing data. | |||
3795 | /// </summary> | |||
3796 | /// <returns>Returns a <see cref="Stream"/> provising data.</returns> | |||
3797 | public Stream GetSource() | |||
3798 | { | |||
3799 | return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3800 | } | |||
3801 | | |||
3802 | readonly | |||
3803 | | |||
3804 | #endregion | |||
3805 | #region Instance Fields | |||
3806 | string fileName_; | |||
3807 | #endregion | |||
3808 | } | |||
3809 | | |||
3810 | | |||
3811 | /// <summary> | |||
3812 | /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. | |||
3813 | /// </summary> | |||
3814 | public class DynamicDiskDataSource : IDynamicDataSource | |||
3815 | { | |||
3816 | | |||
3817 | #region IDataSource Members | |||
3818 | /// <summary> | |||
3819 | /// Get a <see cref="Stream"/> providing data for an entry. | |||
3820 | /// </summary> | |||
3821 | /// <param name="entry">The entry to provide data for.</param> | |||
3822 | /// <param name="name">The file name for data if known.</param> | |||
3823 | /// <returns>Returns a stream providing data; or null if not available</returns> | |||
3824 | public Stream GetSource(ZipEntry entry, string name) | |||
3825 | { | |||
3826 | Stream result = null; | |||
3827 | | |||
3828 | if (name != null) { | |||
3829 | result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3830 | } | |||
3831 | | |||
3832 | return result; | |||
3833 | } | |||
3834 | | |||
3835 | #endregion | |||
3836 | } | |||
3837 | | |||
3838 | #endregion | |||
3839 | | |||
3840 | #region Archive Storage | |||
3841 | /// <summary> | |||
3842 | /// Defines facilities for data storage when updating Zip Archives. | |||
3843 | /// </summary> | |||
3844 | public interface IArchiveStorage | |||
3845 | { | |||
3846 | /// <summary> | |||
3847 | /// Get the <see cref="FileUpdateMode"/> to apply during updates. | |||
3848 | /// </summary> | |||
3849 | FileUpdateMode UpdateMode { get; } | |||
3850 | | |||
3851 | /// <summary> | |||
3852 | /// Get an empty <see cref="Stream"/> that can be used for temporary output. | |||
3853 | /// </summary> | |||
3854 | /// <returns>Returns a temporary output <see cref="Stream"/></returns> | |||
3855 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3856 | Stream GetTemporaryOutput(); | |||
3857 | | |||
3858 | /// <summary> | |||
3859 | /// Convert a temporary output stream to a final stream. | |||
3860 | /// </summary> | |||
3861 | /// <returns>The resulting final <see cref="Stream"/></returns> | |||
3862 | /// <seealso cref="GetTemporaryOutput"/> | |||
3863 | Stream ConvertTemporaryToFinal(); | |||
3864 | | |||
3865 | /// <summary> | |||
3866 | /// Make a temporary copy of the original stream. | |||
3867 | /// </summary> | |||
3868 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
3869 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3870 | Stream MakeTemporaryCopy(Stream stream); | |||
3871 | | |||
3872 | /// <summary> | |||
3873 | /// Return a stream suitable for performing direct updates on the original source. | |||
3874 | /// </summary> | |||
3875 | /// <param name="stream">The current stream.</param> | |||
3876 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3877 | /// <remarks>This may be the current stream passed.</remarks> | |||
3878 | Stream OpenForDirectUpdate(Stream stream); | |||
3879 | | |||
3880 | /// <summary> | |||
3881 | /// Dispose of this instance. | |||
3882 | /// </summary> | |||
3883 | void Dispose(); | |||
3884 | } | |||
3885 | | |||
3886 | /// <summary> | |||
3887 | /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. | |||
3888 | /// </summary> | |||
3889 | abstract public class BaseArchiveStorage : IArchiveStorage | |||
3890 | { | |||
3891 | #region Constructors | |||
3892 | /// <summary> | |||
3893 | /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. | |||
3894 | /// </summary> | |||
3895 | /// <param name="updateMode">The update mode.</param> | |||
3896 | protected BaseArchiveStorage(FileUpdateMode updateMode) | |||
3897 | { | |||
3898 | updateMode_ = updateMode; | |||
3899 | } | |||
3900 | #endregion | |||
3901 | | |||
3902 | #region IArchiveStorage Members | |||
3903 | | |||
3904 | /// <summary> | |||
3905 | /// Gets a temporary output <see cref="Stream"/> | |||
3906 | /// </summary> | |||
3907 | /// <returns>Returns the temporary output stream.</returns> | |||
3908 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3909 | public abstract Stream GetTemporaryOutput(); | |||
3910 | | |||
3911 | /// <summary> | |||
3912 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
3913 | /// </summary> | |||
3914 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
3915 | /// the final storage for the archive.</returns> | |||
3916 | /// <seealso cref="GetTemporaryOutput"/> | |||
3917 | public abstract Stream ConvertTemporaryToFinal(); | |||
3918 | | |||
3919 | /// <summary> | |||
3920 | /// Make a temporary copy of a <see cref="Stream"/>. | |||
3921 | /// </summary> | |||
3922 | /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> | |||
3923 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3924 | public abstract Stream MakeTemporaryCopy(Stream stream); | |||
3925 | | |||
3926 | /// <summary> | |||
3927 | /// Return a stream suitable for performing direct updates on the original source. | |||
3928 | /// </summary> | |||
3929 | /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> | |||
3930 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3931 | public abstract Stream OpenForDirectUpdate(Stream stream); | |||
3932 | | |||
3933 | /// <summary> | |||
3934 | /// Disposes this instance. | |||
3935 | /// </summary> | |||
3936 | public abstract void Dispose(); | |||
3937 | | |||
3938 | /// <summary> | |||
3939 | /// Gets the update mode applicable. | |||
3940 | /// </summary> | |||
3941 | /// <value>The update mode.</value> | |||
3942 | public FileUpdateMode UpdateMode { | |||
3943 | get { | |||
3944 | return updateMode_; | |||
3945 | } | |||
3946 | } | |||
3947 | | |||
3948 | #endregion | |||
3949 | | |||
3950 | #region Instance Fields | |||
3951 | FileUpdateMode updateMode_; | |||
3952 | #endregion | |||
3953 | } | |||
3954 | | |||
3955 | /// <summary> | |||
3956 | /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. | |||
3957 | /// </summary> | |||
3958 | public class DiskArchiveStorage : BaseArchiveStorage | |||
3959 | { | |||
3960 | #region Constructors | |||
3961 | /// <summary> | |||
3962 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3963 | /// </summary> | |||
3964 | /// <param name="file">The file.</param> | |||
3965 | /// <param name="updateMode">The update mode.</param> | |||
3966 | public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) | |||
3967 | : base(updateMode) | |||
3968 | { | |||
3969 | if (file.Name == null) { | |||
3970 | throw new ZipException("Cant handle non file archives"); | |||
3971 | } | |||
3972 | | |||
3973 | fileName_ = file.Name; | |||
3974 | } | |||
3975 | | |||
3976 | /// <summary> | |||
3977 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3978 | /// </summary> | |||
3979 | /// <param name="file">The file.</param> | |||
3980 | public DiskArchiveStorage(ZipFile file) | |||
3981 | : this(file, FileUpdateMode.Safe) | |||
3982 | { | |||
3983 | } | |||
3984 | #endregion | |||
3985 | | |||
3986 | #region IArchiveStorage Members | |||
3987 | | |||
3988 | /// <summary> | |||
3989 | /// Gets a temporary output <see cref="Stream"/> for performing updates on. | |||
3990 | /// </summary> | |||
3991 | /// <returns>Returns the temporary output stream.</returns> | |||
3992 | public override Stream GetTemporaryOutput() | |||
3993 | { | |||
3994 | if (temporaryName_ != null) { | |||
3995 | temporaryName_ = GetTempFileName(temporaryName_, true); | |||
3996 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
3997 | } else { | |||
3998 | // Determine where to place files based on internal strategy. | |||
3999 | // Currently this is always done in system temp directory. | |||
4000 | temporaryName_ = Path.GetTempFileName(); | |||
4001 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
4002 | } | |||
4003 | | |||
4004 | return temporaryStream_; | |||
4005 | } | |||
4006 | | |||
4007 | /// <summary> | |||
4008 | /// Converts a temporary <see cref="Stream"/> to its final form. | |||
4009 | /// </summary> | |||
4010 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4011 | /// the final storage for the archive.</returns> | |||
4012 | public override Stream ConvertTemporaryToFinal() | |||
4013 | { | |||
4014 | if (temporaryStream_ == null) { | |||
4015 | throw new ZipException("No temporary stream has been created"); | |||
4016 | } | |||
4017 | | |||
4018 | Stream result = null; | |||
4019 | | |||
4020 | string moveTempName = GetTempFileName(fileName_, false); | |||
4021 | bool newFileCreated = false; | |||
4022 | | |||
4023 | try { | |||
4024 | temporaryStream_.Close(); | |||
4025 | File.Move(fileName_, moveTempName); | |||
4026 | File.Move(temporaryName_, fileName_); | |||
4027 | newFileCreated = true; | |||
4028 | File.Delete(moveTempName); | |||
4029 | | |||
4030 | result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
4031 | } catch (Exception) { | |||
4032 | result = null; | |||
4033 | | |||
4034 | // Try to roll back changes... | |||
4035 | if (!newFileCreated) { | |||
4036 | File.Move(moveTempName, fileName_); | |||
4037 | File.Delete(temporaryName_); | |||
4038 | } | |||
4039 | | |||
4040 | throw; | |||
4041 | } | |||
4042 | | |||
4043 | return result; | |||
4044 | } | |||
4045 | | |||
4046 | /// <summary> | |||
4047 | /// Make a temporary copy of a stream. | |||
4048 | /// </summary> | |||
4049 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4050 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4051 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4052 | { | |||
4053 | stream.Close(); | |||
4054 | | |||
4055 | temporaryName_ = GetTempFileName(fileName_, true); | |||
4056 | File.Copy(fileName_, temporaryName_, true); | |||
4057 | | |||
4058 | temporaryStream_ = new FileStream(temporaryName_, | |||
4059 | FileMode.Open, | |||
4060 | FileAccess.ReadWrite); | |||
4061 | return temporaryStream_; | |||
4062 | } | |||
4063 | | |||
4064 | /// <summary> | |||
4065 | /// Return a stream suitable for performing direct updates on the original source. | |||
4066 | /// </summary> | |||
4067 | /// <param name="stream">The current stream.</param> | |||
4068 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4069 | /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks> | |||
4070 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4071 | { | |||
4072 | Stream result; | |||
4073 | if ((stream == null) || !stream.CanWrite) { | |||
4074 | if (stream != null) { | |||
4075 | stream.Close(); | |||
4076 | } | |||
4077 | | |||
4078 | result = new FileStream(fileName_, | |||
4079 | FileMode.Open, | |||
4080 | FileAccess.ReadWrite); | |||
4081 | } else { | |||
4082 | result = stream; | |||
4083 | } | |||
4084 | | |||
4085 | return result; | |||
4086 | } | |||
4087 | | |||
4088 | /// <summary> | |||
4089 | /// Disposes this instance. | |||
4090 | /// </summary> | |||
4091 | public override void Dispose() | |||
4092 | { | |||
4093 | if (temporaryStream_ != null) { | |||
4094 | temporaryStream_.Close(); | |||
4095 | } | |||
4096 | } | |||
4097 | | |||
4098 | #endregion | |||
4099 | | |||
4100 | #region Internal routines | |||
4101 | static string GetTempFileName(string original, bool makeTempFile) | |||
4102 | { | |||
4103 | string result = null; | |||
4104 | | |||
4105 | if (original == null) { | |||
4106 | result = Path.GetTempFileName(); | |||
4107 | } else { | |||
4108 | int counter = 0; | |||
4109 | int suffixSeed = DateTime.Now.Second; | |||
4110 | | |||
4111 | while (result == null) { | |||
4112 | counter += 1; | |||
4113 | string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); | |||
4114 | if (!File.Exists(newName)) { | |||
4115 | if (makeTempFile) { | |||
4116 | try { | |||
4117 | // Try and create the file. | |||
4118 | using (FileStream stream = File.Create(newName)) { | |||
4119 | } | |||
4120 | result = newName; | |||
4121 | } catch { | |||
4122 | suffixSeed = DateTime.Now.Second; | |||
4123 | } | |||
4124 | } else { | |||
4125 | result = newName; | |||
4126 | } | |||
4127 | } | |||
4128 | } | |||
4129 | } | |||
4130 | return result; | |||
4131 | } | |||
4132 | #endregion | |||
4133 | | |||
4134 | #region Instance Fields | |||
4135 | Stream temporaryStream_; | |||
4136 | string fileName_; | |||
4137 | string temporaryName_; | |||
4138 | #endregion | |||
4139 | } | |||
4140 | | |||
4141 | /// <summary> | |||
4142 | /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. | |||
4143 | /// </summary> | |||
4144 | public class MemoryArchiveStorage : BaseArchiveStorage | |||
4145 | { | |||
4146 | #region Constructors | |||
4147 | /// <summary> | |||
4148 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4149 | /// </summary> | |||
4150 | public MemoryArchiveStorage() | |||
4151 | : base(FileUpdateMode.Direct) | |||
4152 | { | |||
4153 | } | |||
4154 | | |||
4155 | /// <summary> | |||
4156 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4157 | /// </summary> | |||
4158 | /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> | |||
4159 | /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> | |||
4160 | public MemoryArchiveStorage(FileUpdateMode updateMode) | |||
4161 | : base(updateMode) | |||
4162 | { | |||
4163 | } | |||
4164 | | |||
4165 | #endregion | |||
4166 | | |||
4167 | #region Properties | |||
4168 | /// <summary> | |||
4169 | /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. | |||
4170 | /// </summary> | |||
4171 | public MemoryStream FinalStream { | |||
4172 | get { return finalStream_; } | |||
4173 | } | |||
4174 | | |||
4175 | #endregion | |||
4176 | | |||
4177 | #region IArchiveStorage Members | |||
4178 | | |||
4179 | /// <summary> | |||
4180 | /// Gets the temporary output <see cref="Stream"/> | |||
4181 | /// </summary> | |||
4182 | /// <returns>Returns the temporary output stream.</returns> | |||
4183 | public override Stream GetTemporaryOutput() | |||
4184 | { | |||
4185 | temporaryStream_ = new MemoryStream(); | |||
4186 | return temporaryStream_; | |||
4187 | } | |||
4188 | | |||
4189 | /// <summary> | |||
4190 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
4191 | /// </summary> | |||
4192 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4193 | /// the final storage for the archive.</returns> | |||
4194 | public override Stream ConvertTemporaryToFinal() | |||
4195 | { | |||
4196 | if (temporaryStream_ == null) { | |||
4197 | throw new ZipException("No temporary stream has been created"); | |||
4198 | } | |||
4199 | | |||
4200 | finalStream_ = new MemoryStream(temporaryStream_.ToArray()); | |||
4201 | return finalStream_; | |||
4202 | } | |||
4203 | | |||
4204 | /// <summary> | |||
4205 | /// Make a temporary copy of the original stream. | |||
4206 | /// </summary> | |||
4207 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4208 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4209 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4210 | { | |||
4211 | temporaryStream_ = new MemoryStream(); | |||
4212 | stream.Position = 0; | |||
4213 | StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); | |||
4214 | return temporaryStream_; | |||
4215 | } | |||
4216 | | |||
4217 | /// <summary> | |||
4218 | /// Return a stream suitable for performing direct updates on the original source. | |||
4219 | /// </summary> | |||
4220 | /// <param name="stream">The original source stream</param> | |||
4221 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4222 | /// <remarks>If the <paramref name="stream"/> passed is not null this is used; | |||
4223 | /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> | |||
4224 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4225 | { | |||
4226 | Stream result; | |||
4227 | if ((stream == null) || !stream.CanWrite) { | |||
4228 | | |||
4229 | result = new MemoryStream(); | |||
4230 | | |||
4231 | if (stream != null) { | |||
4232 | stream.Position = 0; | |||
4233 | StreamUtils.Copy(stream, result, new byte[4096]); | |||
4234 | | |||
4235 | stream.Close(); | |||
4236 | } | |||
4237 | } else { | |||
4238 | result = stream; | |||
4239 | } | |||
4240 | | |||
4241 | return result; | |||
4242 | } | |||
4243 | | |||
4244 | /// <summary> | |||
4245 | /// Disposes this instance. | |||
4246 | /// </summary> | |||
4247 | public override void Dispose() | |||
4248 | { | |||
4249 | if (temporaryStream_ != null) { | |||
4250 | temporaryStream_.Close(); | |||
4251 | } | |||
4252 | } | |||
4253 | | |||
4254 | #endregion | |||
4255 | | |||
4256 | #region Instance Fields | |||
4257 | MemoryStream temporaryStream_; | |||
4258 | MemoryStream finalStream_; | |||
4259 | #endregion | |||
4260 | } | |||
4261 | | |||
4262 | #endregion | |||
4263 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.WindowsNameTransform |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\WindowsNameTransform.cs |
| Covered lines: | 42 |
| Uncovered lines: | 35 |
| Coverable lines: | 77 |
| Total lines: | 226 |
| Line coverage: | 54.5% |
| Branch coverage: | 42.8% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 2 | 83.33 | 66.67 |
| .ctor() | 1 | 100 | 100 |
| TransformDirectory(...) | 3 | 71.43 | 60 |
| TransformFile(...) | 4 | 77.78 | 71.43 |
| IsValidName(...) | 3 | 0 | 0 |
| .cctor() | 1 | 100 | 100 |
| MakeValidName(...) | 11 | 50 | 52.38 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Text; | |||
4 | using ICSharpCode.SharpZipLib.Core; | |||
5 | | |||
6 | namespace ICSharpCode.SharpZipLib.Zip | |||
7 | { | |||
8 | /// <summary> | |||
9 | /// WindowsNameTransform transforms <see cref="ZipFile"/> names to windows compatible ones. | |||
10 | /// </summary> | |||
11 | public class WindowsNameTransform : INameTransform | |||
12 | { | |||
13 | /// <summary> | |||
14 | /// Initialises a new instance of <see cref="WindowsNameTransform"/> | |||
15 | /// </summary> | |||
16 | /// <param name="baseDirectory"></param> | |||
| 1 | 17 | public WindowsNameTransform(string baseDirectory) | ||
18 | { | |||
| 1 | 19 | if (baseDirectory == null) { | ||
| 0 | 20 | throw new ArgumentNullException(nameof(baseDirectory), "Directory name is invalid"); | ||
21 | } | |||
22 | | |||
| 1 | 23 | BaseDirectory = baseDirectory; | ||
| 1 | 24 | } | ||
25 | | |||
26 | /// <summary> | |||
27 | /// Initialise a default instance of <see cref="WindowsNameTransform"/> | |||
28 | /// </summary> | |||
| 2 | 29 | public WindowsNameTransform() | ||
30 | { | |||
31 | // Do nothing. | |||
| 2 | 32 | } | ||
33 | | |||
34 | /// <summary> | |||
35 | /// Gets or sets a value containing the target directory to prefix values with. | |||
36 | /// </summary> | |||
37 | public string BaseDirectory { | |||
| 0 | 38 | get { return _baseDirectory; } | ||
39 | set { | |||
| 1 | 40 | if (value == null) { | ||
| 0 | 41 | throw new ArgumentNullException(nameof(value)); | ||
42 | } | |||
43 | | |||
| 1 | 44 | _baseDirectory = Path.GetFullPath(value); | ||
| 1 | 45 | } | ||
46 | } | |||
47 | | |||
48 | /// <summary> | |||
49 | /// Gets or sets a value indicating wether paths on incoming values should be removed. | |||
50 | /// </summary> | |||
51 | public bool TrimIncomingPaths { | |||
| 0 | 52 | get { return _trimIncomingPaths; } | ||
| 0 | 53 | set { _trimIncomingPaths = value; } | ||
54 | } | |||
55 | | |||
56 | /// <summary> | |||
57 | /// Transform a Zip directory name to a windows directory name. | |||
58 | /// </summary> | |||
59 | /// <param name="name">The directory name to transform.</param> | |||
60 | /// <returns>The transformed name.</returns> | |||
61 | public string TransformDirectory(string name) | |||
62 | { | |||
| 3 | 63 | name = TransformFile(name); | ||
| 2 | 64 | if (name.Length > 0) { | ||
| 2 | 65 | while (name.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { | ||
| 0 | 66 | name = name.Remove(name.Length - 1, 1); | ||
67 | } | |||
| 2 | 68 | } else { | ||
| 0 | 69 | throw new ZipException("Cannot have an empty directory name"); | ||
70 | } | |||
| 2 | 71 | return name; | ||
72 | } | |||
73 | | |||
74 | /// <summary> | |||
75 | /// Transform a Zip format file name to a windows style one. | |||
76 | /// </summary> | |||
77 | /// <param name="name">The file name to transform.</param> | |||
78 | /// <returns>The transformed name.</returns> | |||
79 | public string TransformFile(string name) | |||
80 | { | |||
| 3 | 81 | if (name != null) { | ||
| 3 | 82 | name = MakeValidName(name, _replacementChar); | ||
83 | | |||
| 2 | 84 | if (_trimIncomingPaths) { | ||
| 0 | 85 | name = Path.GetFileName(name); | ||
86 | } | |||
87 | | |||
88 | // This may exceed windows length restrictions. | |||
89 | // Combine will throw a PathTooLongException in that case. | |||
| 2 | 90 | if (_baseDirectory != null) { | ||
| 1 | 91 | name = Path.Combine(_baseDirectory, name); | ||
92 | } | |||
| 1 | 93 | } else { | ||
| 0 | 94 | name = string.Empty; | ||
95 | } | |||
| 2 | 96 | return name; | ||
97 | } | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Test a name to see if it is a valid name for a windows filename as extracted from a Zip archive. | |||
101 | /// </summary> | |||
102 | /// <param name="name">The name to test.</param> | |||
103 | /// <returns>Returns true if the name is a valid zip name; false otherwise.</returns> | |||
104 | /// <remarks>The filename isnt a true windows path in some fundamental ways like no absolute paths, no rooted paths | |||
105 | public static bool IsValidName(string name) | |||
106 | { | |||
| 0 | 107 | bool result = | ||
| 0 | 108 | (name != null) && | ||
| 0 | 109 | (name.Length <= MaxPath) && | ||
| 0 | 110 | (string.Compare(name, MakeValidName(name, '_'), StringComparison.Ordinal) == 0) | ||
| 0 | 111 | ; | ||
112 | | |||
| 0 | 113 | return result; | ||
114 | } | |||
115 | | |||
116 | /// <summary> | |||
117 | /// Initialise static class information. | |||
118 | /// </summary> | |||
119 | static WindowsNameTransform() | |||
120 | { | |||
121 | char[] invalidPathChars; | |||
122 | | |||
| 1 | 123 | invalidPathChars = Path.GetInvalidPathChars(); | ||
| 1 | 124 | int howMany = invalidPathChars.Length + 3; | ||
125 | | |||
| 1 | 126 | InvalidEntryChars = new char[howMany]; | ||
| 1 | 127 | Array.Copy(invalidPathChars, 0, InvalidEntryChars, 0, invalidPathChars.Length); | ||
| 1 | 128 | InvalidEntryChars[howMany - 1] = '*'; | ||
| 1 | 129 | InvalidEntryChars[howMany - 2] = '?'; | ||
| 1 | 130 | InvalidEntryChars[howMany - 3] = ':'; | ||
| 1 | 131 | } | ||
132 | | |||
133 | /// <summary> | |||
134 | /// Force a name to be valid by replacing invalid characters with a fixed value | |||
135 | /// </summary> | |||
136 | /// <param name="name">The name to make valid</param> | |||
137 | /// <param name="replacement">The replacement character to use for any invalid characters.</param> | |||
138 | /// <returns>Returns a valid name</returns> | |||
139 | public static string MakeValidName(string name, char replacement) | |||
140 | { | |||
| 3 | 141 | if (name == null) { | ||
| 0 | 142 | throw new ArgumentNullException(nameof(name)); | ||
143 | } | |||
144 | | |||
| 3 | 145 | name = WindowsPathUtils.DropPathRoot(name.Replace("/", Path.DirectorySeparatorChar.ToString())); | ||
146 | | |||
147 | // Drop any leading slashes. | |||
| 3 | 148 | while ((name.Length > 0) && (name[0] == Path.DirectorySeparatorChar)) { | ||
| 0 | 149 | name = name.Remove(0, 1); | ||
150 | } | |||
151 | | |||
152 | // Drop any trailing slashes. | |||
| 4 | 153 | while ((name.Length > 0) && (name[name.Length - 1] == Path.DirectorySeparatorChar)) { | ||
| 1 | 154 | name = name.Remove(name.Length - 1, 1); | ||
155 | } | |||
156 | | |||
157 | // Convert consecutive \\ characters to \ | |||
| 3 | 158 | int index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); | ||
| 3 | 159 | while (index >= 0) { | ||
| 0 | 160 | name = name.Remove(index, 1); | ||
| 0 | 161 | index = name.IndexOf(Path.DirectorySeparatorChar); | ||
162 | } | |||
163 | | |||
164 | // Convert any invalid characters using the replacement one. | |||
| 3 | 165 | index = name.IndexOfAny(InvalidEntryChars); | ||
| 3 | 166 | if (index >= 0) { | ||
| 0 | 167 | var builder = new StringBuilder(name); | ||
168 | | |||
| 0 | 169 | while (index >= 0) { | ||
| 0 | 170 | builder[index] = replacement; | ||
171 | | |||
| 0 | 172 | if (index >= name.Length) { | ||
| 0 | 173 | index = -1; | ||
| 0 | 174 | } else { | ||
| 0 | 175 | index = name.IndexOfAny(InvalidEntryChars, index + 1); | ||
176 | } | |||
177 | } | |||
| 0 | 178 | name = builder.ToString(); | ||
179 | } | |||
180 | | |||
181 | // Check for names greater than MaxPath characters. | |||
182 | // TODO: Were is CLR version of MaxPath defined? Can't find it in Environment. | |||
| 3 | 183 | if (name.Length > MaxPath) { | ||
| 1 | 184 | throw new PathTooLongException(); | ||
185 | } | |||
186 | | |||
| 2 | 187 | return name; | ||
188 | } | |||
189 | | |||
190 | /// <summary> | |||
191 | /// Gets or set the character to replace invalid characters during transformations. | |||
192 | /// </summary> | |||
193 | public char Replacement { | |||
| 0 | 194 | get { return _replacementChar; } | ||
195 | set { | |||
| 0 | 196 | for (int i = 0; i < InvalidEntryChars.Length; ++i) { | ||
| 0 | 197 | if (InvalidEntryChars[i] == value) { | ||
| 0 | 198 | throw new ArgumentException("invalid path character"); | ||
199 | } | |||
200 | } | |||
201 | | |||
| 0 | 202 | if ((value == Path.DirectorySeparatorChar) || (value == Path.AltDirectorySeparatorChar)) { | ||
| 0 | 203 | throw new ArgumentException("invalid replacement character"); | ||
204 | } | |||
205 | | |||
| 0 | 206 | _replacementChar = value; | ||
| 0 | 207 | } | ||
208 | } | |||
209 | | |||
210 | /// <summary> | |||
211 | /// The maximum windows path name permitted. | |||
212 | /// </summary> | |||
213 | /// <remarks>This may not valid for all windows systems - CE?, etc but I cant find the equivalent in the CLR.</remar | |||
214 | const int MaxPath = 260; | |||
215 | | |||
216 | #region Instance Fields | |||
217 | string _baseDirectory; | |||
218 | bool _trimIncomingPaths; | |||
| 3 | 219 | char _replacementChar = '_'; | ||
220 | #endregion | |||
221 | | |||
222 | #region Class Fields | |||
223 | static readonly char[] InvalidEntryChars; | |||
224 | #endregion | |||
225 | } | |||
226 | } |
| Class: | ICSharpCode.SharpZipLib.Core.WindowsPathUtils |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Core\WindowsPathUtils.cs |
| Covered lines: | 19 |
| Uncovered lines: | 3 |
| Coverable lines: | 22 |
| Total lines: | 57 |
| Line coverage: | 86.3% |
| Branch coverage: | 76.6% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 0 | 0 |
| DropPathRoot(...) | 17 | 90 | 77.42 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | namespace ICSharpCode.SharpZipLib.Core | |||
2 | { | |||
3 | /// <summary> | |||
4 | /// WindowsPathUtils provides simple utilities for handling windows paths. | |||
5 | /// </summary> | |||
6 | public abstract class WindowsPathUtils | |||
7 | { | |||
8 | /// <summary> | |||
9 | /// Initializes a new instance of the <see cref="WindowsPathUtils"/> class. | |||
10 | /// </summary> | |||
| 0 | 11 | internal WindowsPathUtils() | ||
12 | { | |||
| 0 | 13 | } | ||
14 | | |||
15 | /// <summary> | |||
16 | /// Remove any path root present in the path | |||
17 | /// </summary> | |||
18 | /// <param name="path">A <see cref="string"/> containing path information.</param> | |||
19 | /// <returns>The path with the root removed if it was present; path otherwise.</returns> | |||
20 | /// <remarks>Unlike the <see cref="System.IO.Path"/> class the path isnt otherwise checked for validity.</remarks> | |||
21 | public static string DropPathRoot(string path) | |||
22 | { | |||
| 65930 | 23 | string result = path; | ||
24 | | |||
| 65930 | 25 | if (!string.IsNullOrEmpty(path)) { | ||
| 65930 | 26 | if ((path[0] == '\\') || (path[0] == '/')) { | ||
27 | // UNC name ? | |||
| 6 | 28 | if ((path.Length > 1) && ((path[1] == '\\') || (path[1] == '/'))) { | ||
| 1 | 29 | int index = 2; | ||
| 1 | 30 | int elements = 2; | ||
31 | | |||
32 | // Scan for two separate elements \\machine\share\restofpath | |||
| 11 | 33 | while ((index <= path.Length) && | ||
| 11 | 34 | (((path[index] != '\\') && (path[index] != '/')) || (--elements > 0))) { | ||
| 10 | 35 | index++; | ||
36 | } | |||
37 | | |||
| 1 | 38 | index++; | ||
39 | | |||
| 1 | 40 | if (index < path.Length) { | ||
| 1 | 41 | result = path.Substring(index); | ||
| 1 | 42 | } else { | ||
| 0 | 43 | result = ""; | ||
44 | } | |||
45 | } | |||
| 65924 | 46 | } else if ((path.Length > 1) && (path[1] == ':')) { | ||
| 10 | 47 | int dropCount = 2; | ||
| 10 | 48 | if ((path.Length > 2) && ((path[2] == '\\') || (path[2] == '/'))) { | ||
| 9 | 49 | dropCount = 3; | ||
50 | } | |||
| 10 | 51 | result = result.Remove(0, dropCount); | ||
52 | } | |||
53 | } | |||
| 65930 | 54 | return result; | ||
55 | } | |||
56 | } | |||
57 | } |
| Class: | ICSharpCode.SharpZipLib.Encryption.ZipAESStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Encryption\ZipAESStream.cs |
| Covered lines: | 0 |
| Uncovered lines: | 50 |
| Coverable lines: | 50 |
| Total lines: | 134 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 2 | 0 | 0 |
| Read(...) | 9 | 0 | 0 |
| Write(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Security.Cryptography; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Encryption | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// Encrypts and decrypts AES ZIP | |||
9 | /// </summary> | |||
10 | /// <remarks> | |||
11 | /// Based on information from http://www.winzip.com/aes_info.htm | |||
12 | /// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/ | |||
13 | /// </remarks> | |||
14 | internal class ZipAESStream : CryptoStream | |||
15 | { | |||
16 | | |||
17 | /// <summary> | |||
18 | /// Constructor | |||
19 | /// </summary> | |||
20 | /// <param name="stream">The stream on which to perform the cryptographic transformation.</param> | |||
21 | /// <param name="transform">Instance of ZipAESTransform</param> | |||
22 | /// <param name="mode">Read or Write</param> | |||
23 | public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode) | |||
| 0 | 24 | : base(stream, transform, mode) | ||
25 | { | |||
26 | | |||
| 0 | 27 | _stream = stream; | ||
| 0 | 28 | _transform = transform; | ||
| 0 | 29 | _slideBuffer = new byte[1024]; | ||
30 | | |||
| 0 | 31 | _blockAndAuth = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH; | ||
32 | | |||
33 | // mode: | |||
34 | // CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method. | |||
35 | // Write bypasses this stream and uses the Transform directly. | |||
| 0 | 36 | if (mode != CryptoStreamMode.Read) { | ||
| 0 | 37 | throw new Exception("ZipAESStream only for read"); | ||
38 | } | |||
| 0 | 39 | } | ||
40 | | |||
41 | // The final n bytes of the AES stream contain the Auth Code. | |||
42 | private const int AUTH_CODE_LENGTH = 10; | |||
43 | | |||
44 | private Stream _stream; | |||
45 | private ZipAESTransform _transform; | |||
46 | private byte[] _slideBuffer; | |||
47 | private int _slideBufStartPos; | |||
48 | private int _slideBufFreePos; | |||
49 | // Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32. | |||
50 | private const int CRYPTO_BLOCK_SIZE = 16; | |||
51 | private int _blockAndAuth; | |||
52 | | |||
53 | /// <summary> | |||
54 | /// Reads a sequence of bytes from the current CryptoStream into buffer, | |||
55 | /// and advances the position within the stream by the number of bytes read. | |||
56 | /// </summary> | |||
57 | public override int Read(byte[] buffer, int offset, int count) | |||
58 | { | |||
| 0 | 59 | int nBytes = 0; | ||
| 0 | 60 | while (nBytes < count) { | ||
61 | // Calculate buffer quantities vs read-ahead size, and check for sufficient free space | |||
| 0 | 62 | int byteCount = _slideBufFreePos - _slideBufStartPos; | ||
63 | | |||
64 | // Need to handle final block and Auth Code specially, but don't know total data length. | |||
65 | // Maintain a read-ahead equal to the length of (crypto block + Auth Code). | |||
66 | // When that runs out we can detect these final sections. | |||
| 0 | 67 | int lengthToRead = _blockAndAuth - byteCount; | ||
| 0 | 68 | if (_slideBuffer.Length - _slideBufFreePos < lengthToRead) { | ||
69 | // Shift the data to the beginning of the buffer | |||
| 0 | 70 | int iTo = 0; | ||
| 0 | 71 | for (int iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++) { | ||
| 0 | 72 | _slideBuffer[iTo] = _slideBuffer[iFrom]; | ||
73 | } | |||
| 0 | 74 | _slideBufFreePos -= _slideBufStartPos; // Note the -= | ||
| 0 | 75 | _slideBufStartPos = 0; | ||
76 | } | |||
| 0 | 77 | int obtained = _stream.Read(_slideBuffer, _slideBufFreePos, lengthToRead); | ||
| 0 | 78 | _slideBufFreePos += obtained; | ||
79 | | |||
80 | // Recalculate how much data we now have | |||
| 0 | 81 | byteCount = _slideBufFreePos - _slideBufStartPos; | ||
| 0 | 82 | if (byteCount >= _blockAndAuth) { | ||
83 | // At least a 16 byte block and an auth code remains. | |||
| 0 | 84 | _transform.TransformBlock(_slideBuffer, | ||
| 0 | 85 | _slideBufStartPos, | ||
| 0 | 86 | CRYPTO_BLOCK_SIZE, | ||
| 0 | 87 | buffer, | ||
| 0 | 88 | offset); | ||
| 0 | 89 | nBytes += CRYPTO_BLOCK_SIZE; | ||
| 0 | 90 | offset += CRYPTO_BLOCK_SIZE; | ||
| 0 | 91 | _slideBufStartPos += CRYPTO_BLOCK_SIZE; | ||
| 0 | 92 | } else { | ||
93 | // Last round. | |||
| 0 | 94 | if (byteCount > AUTH_CODE_LENGTH) { | ||
95 | // At least one byte of data plus auth code | |||
| 0 | 96 | int finalBlock = byteCount - AUTH_CODE_LENGTH; | ||
| 0 | 97 | _transform.TransformBlock(_slideBuffer, | ||
| 0 | 98 | _slideBufStartPos, | ||
| 0 | 99 | finalBlock, | ||
| 0 | 100 | buffer, | ||
| 0 | 101 | offset); | ||
102 | | |||
| 0 | 103 | nBytes += finalBlock; | ||
| 0 | 104 | _slideBufStartPos += finalBlock; | ||
| 0 | 105 | } else if (byteCount < AUTH_CODE_LENGTH) | ||
| 0 | 106 | throw new Exception("Internal error missed auth code"); // Coding bug | ||
107 | // Final block done. Check Auth code. | |||
| 0 | 108 | byte[] calcAuthCode = _transform.GetAuthCode(); | ||
| 0 | 109 | for (int i = 0; i < AUTH_CODE_LENGTH; i++) { | ||
| 0 | 110 | if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i]) { | ||
| 0 | 111 | throw new Exception("AES Authentication Code does not match. This is a super-CRC check on the data in the | ||
| 0 | 112 | + "The file may be damaged."); | ||
113 | } | |||
114 | } | |||
115 | | |||
| 0 | 116 | break; // Reached the auth code | ||
117 | } | |||
118 | } | |||
| 0 | 119 | return nBytes; | ||
120 | } | |||
121 | | |||
122 | /// <summary> | |||
123 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the num | |||
124 | /// </summary> | |||
125 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream. </para | |||
126 | /// <param name="offset">The byte offset in buffer at which to begin copying bytes to the current stream. </param> | |||
127 | /// <param name="count">The number of bytes to be written to the current stream. </param> | |||
128 | public override void Write(byte[] buffer, int offset, int count) | |||
129 | { | |||
130 | // ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly. | |||
| 0 | 131 | throw new NotImplementedException(); | ||
132 | } | |||
133 | } | |||
134 | } |
| Class: | ICSharpCode.SharpZipLib.Encryption.ZipAESTransform |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Encryption\ZipAESTransform.cs |
| Covered lines: | 0 |
| Uncovered lines: | 47 |
| Coverable lines: | 47 |
| Total lines: | 183 |
| Line coverage: | 0% |
| Branch coverage: | 0% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 4 | 0 | 0 |
| TransformBlock(...) | 6 | 0 | 0 |
| GetAuthCode() | 2 | 0 | 0 |
| TransformFinalBlock(...) | 1 | 0 | 0 |
| Dispose() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Security.Cryptography; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Encryption | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// Transforms stream using AES in CTR mode | |||
8 | /// </summary> | |||
9 | internal class ZipAESTransform : ICryptoTransform | |||
10 | { | |||
11 | private const int PWD_VER_LENGTH = 2; | |||
12 | | |||
13 | // WinZip use iteration count of 1000 for PBKDF2 key generation | |||
14 | private const int KEY_ROUNDS = 1000; | |||
15 | | |||
16 | // For 128-bit AES (16 bytes) the encryption is implemented as expected. | |||
17 | // For 256-bit AES (32 bytes) WinZip do full 256 bit AES of the nonce to create the encryption | |||
18 | // block but use only the first 16 bytes of it, and discard the second half. | |||
19 | private const int ENCRYPT_BLOCK = 16; | |||
20 | | |||
21 | private int _blockSize; | |||
22 | private readonly ICryptoTransform _encryptor; | |||
23 | private readonly byte[] _counterNonce; | |||
24 | private byte[] _encryptBuffer; | |||
25 | private int _encrPos; | |||
26 | private byte[] _pwdVerifier; | |||
27 | private HMACSHA1 _hmacsha1; | |||
28 | private bool _finalised; | |||
29 | | |||
30 | private bool _writeMode; | |||
31 | | |||
32 | /// <summary> | |||
33 | /// Constructor. | |||
34 | /// </summary> | |||
35 | /// <param name="key">Password string</param> | |||
36 | /// <param name="saltBytes">Random bytes, length depends on encryption strength. | |||
37 | /// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.</param> | |||
38 | /// <param name="blockSize">The encryption strength, in bytes eg 16 for 128 bits.</param> | |||
39 | /// <param name="writeMode">True when creating a zip, false when reading. For the AuthCode.</param> | |||
40 | /// | |||
| 0 | 41 | public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode) | ||
42 | { | |||
43 | | |||
| 0 | 44 | if (blockSize != 16 && blockSize != 32) // 24 valid for AES but not supported by Winzip | ||
| 0 | 45 | throw new Exception("Invalid blocksize " + blockSize + ". Must be 16 or 32."); | ||
| 0 | 46 | if (saltBytes.Length != blockSize / 2) | ||
| 0 | 47 | throw new Exception("Invalid salt len. Must be " + blockSize / 2 + " for blocksize " + blockSize); | ||
48 | // initialise the encryption buffer and buffer pos | |||
| 0 | 49 | _blockSize = blockSize; | ||
| 0 | 50 | _encryptBuffer = new byte[_blockSize]; | ||
| 0 | 51 | _encrPos = ENCRYPT_BLOCK; | ||
52 | | |||
53 | // Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c | |||
| 0 | 54 | var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS); | ||
| 0 | 55 | var rm = new RijndaelManaged(); | ||
| 0 | 56 | rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode | ||
| 0 | 57 | _counterNonce = new byte[_blockSize]; | ||
| 0 | 58 | byte[] byteKey1 = pdb.GetBytes(_blockSize); | ||
| 0 | 59 | byte[] byteKey2 = pdb.GetBytes(_blockSize); | ||
| 0 | 60 | _encryptor = rm.CreateEncryptor(byteKey1, byteKey2); | ||
| 0 | 61 | _pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH); | ||
62 | // | |||
| 0 | 63 | _hmacsha1 = new HMACSHA1(byteKey2); | ||
| 0 | 64 | _writeMode = writeMode; | ||
| 0 | 65 | } | ||
66 | | |||
67 | /// <summary> | |||
68 | /// Implement the ICryptoTransform method. | |||
69 | /// </summary> | |||
70 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset | |||
71 | { | |||
72 | | |||
73 | // Pass the data stream to the hash algorithm for generating the Auth Code. | |||
74 | // This does not change the inputBuffer. Do this before decryption for read mode. | |||
| 0 | 75 | if (!_writeMode) { | ||
| 0 | 76 | _hmacsha1.TransformBlock(inputBuffer, inputOffset, inputCount, inputBuffer, inputOffset); | ||
77 | } | |||
78 | // Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this. | |||
| 0 | 79 | int ix = 0; | ||
| 0 | 80 | while (ix < inputCount) { | ||
| 0 | 81 | if (_encrPos == ENCRYPT_BLOCK) { | ||
82 | /* increment encryption nonce */ | |||
| 0 | 83 | int j = 0; | ||
| 0 | 84 | while (++_counterNonce[j] == 0) { | ||
| 0 | 85 | ++j; | ||
86 | } | |||
87 | /* encrypt the nonce to form next xor buffer */ | |||
| 0 | 88 | _encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0); | ||
| 0 | 89 | _encrPos = 0; | ||
90 | } | |||
| 0 | 91 | outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]); | ||
92 | // | |||
| 0 | 93 | ix++; | ||
94 | } | |||
| 0 | 95 | if (_writeMode) { | ||
96 | // This does not change the buffer. | |||
| 0 | 97 | _hmacsha1.TransformBlock(outputBuffer, outputOffset, inputCount, outputBuffer, outputOffset); | ||
98 | } | |||
| 0 | 99 | return inputCount; | ||
100 | } | |||
101 | | |||
102 | /// <summary> | |||
103 | /// Returns the 2 byte password verifier | |||
104 | /// </summary> | |||
105 | public byte[] PwdVerifier { | |||
106 | get { | |||
| 0 | 107 | return _pwdVerifier; | ||
108 | } | |||
109 | } | |||
110 | | |||
111 | /// <summary> | |||
112 | /// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream. | |||
113 | /// </summary> | |||
114 | public byte[] GetAuthCode() | |||
115 | { | |||
116 | // We usually don't get advance notice of final block. Hash requres a TransformFinal. | |||
| 0 | 117 | if (!_finalised) { | ||
| 0 | 118 | byte[] dummy = new byte[0]; | ||
| 0 | 119 | _hmacsha1.TransformFinalBlock(dummy, 0, 0); | ||
| 0 | 120 | _finalised = true; | ||
121 | } | |||
| 0 | 122 | return _hmacsha1.Hash; | ||
123 | } | |||
124 | | |||
125 | #region ICryptoTransform Members | |||
126 | | |||
127 | /// <summary> | |||
128 | /// Not implemented. | |||
129 | /// </summary> | |||
130 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) | |||
131 | { | |||
132 | | |||
| 0 | 133 | throw new NotImplementedException("ZipAESTransform.TransformFinalBlock"); | ||
134 | } | |||
135 | | |||
136 | /// <summary> | |||
137 | /// Gets the size of the input data blocks in bytes. | |||
138 | /// </summary> | |||
139 | public int InputBlockSize { | |||
140 | get { | |||
| 0 | 141 | return _blockSize; | ||
142 | } | |||
143 | } | |||
144 | | |||
145 | /// <summary> | |||
146 | /// Gets the size of the output data blocks in bytes. | |||
147 | /// </summary> | |||
148 | public int OutputBlockSize { | |||
149 | get { | |||
| 0 | 150 | return _blockSize; | ||
151 | } | |||
152 | } | |||
153 | | |||
154 | /// <summary> | |||
155 | /// Gets a value indicating whether multiple blocks can be transformed. | |||
156 | /// </summary> | |||
157 | public bool CanTransformMultipleBlocks { | |||
158 | get { | |||
| 0 | 159 | return true; | ||
160 | } | |||
161 | } | |||
162 | | |||
163 | /// <summary> | |||
164 | /// Gets a value indicating whether the current transform can be reused. | |||
165 | /// </summary> | |||
166 | public bool CanReuseTransform { | |||
167 | get { | |||
| 0 | 168 | return true; | ||
169 | } | |||
170 | } | |||
171 | | |||
172 | /// <summary> | |||
173 | /// Cleanup internal state. | |||
174 | /// </summary> | |||
175 | public void Dispose() | |||
176 | { | |||
| 0 | 177 | _encryptor.Dispose(); | ||
| 0 | 178 | } | ||
179 | | |||
180 | #endregion | |||
181 | | |||
182 | } | |||
183 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipConstants |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipConstants.cs |
| Covered lines: | 29 |
| Uncovered lines: | 8 |
| Coverable lines: | 37 |
| Total lines: | 591 |
| Line coverage: | 78.3% |
| Branch coverage: | 63.3% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| ConvertToString(...) | 2 | 66.67 | 66.67 |
| ConvertToString(...) | 2 | 66.67 | 66.67 |
| ConvertToStringExt(...) | 3 | 80 | 80 |
| ConvertToStringExt(...) | 3 | 80 | 80 |
| ConvertToArray(...) | 2 | 100 | 100 |
| ConvertToArray(...) | 3 | 80 | 80 |
| .ctor() | 1 | 0 | 0 |
| .cctor() | 5 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Text; | |||
3 | using System.Threading; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip | |||
6 | { | |||
7 | #region Enumerations | |||
8 | | |||
9 | /// <summary> | |||
10 | /// Determines how entries are tested to see if they should use Zip64 extensions or not. | |||
11 | /// </summary> | |||
12 | public enum UseZip64 | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Zip64 will not be forced on entries during processing. | |||
16 | /// </summary> | |||
17 | /// <remarks>An entry can have this overridden if required <see cref="ZipEntry.ForceZip64"></see></remarks> | |||
18 | Off, | |||
19 | /// <summary> | |||
20 | /// Zip64 should always be used. | |||
21 | /// </summary> | |||
22 | On, | |||
23 | /// <summary> | |||
24 | /// #ZipLib will determine use based on entry values when added to archive. | |||
25 | /// </summary> | |||
26 | Dynamic, | |||
27 | } | |||
28 | | |||
29 | /// <summary> | |||
30 | /// The kind of compression used for an entry in an archive | |||
31 | /// </summary> | |||
32 | public enum CompressionMethod | |||
33 | { | |||
34 | /// <summary> | |||
35 | /// A direct copy of the file contents is held in the archive | |||
36 | /// </summary> | |||
37 | Stored = 0, | |||
38 | | |||
39 | /// <summary> | |||
40 | /// Common Zip compression method using a sliding dictionary | |||
41 | /// of up to 32KB and secondary compression from Huffman/Shannon-Fano trees | |||
42 | /// </summary> | |||
43 | Deflated = 8, | |||
44 | | |||
45 | /// <summary> | |||
46 | /// An extension to deflate with a 64KB window. Not supported by #Zip currently | |||
47 | /// </summary> | |||
48 | Deflate64 = 9, | |||
49 | | |||
50 | /// <summary> | |||
51 | /// BZip2 compression. Not supported by #Zip. | |||
52 | /// </summary> | |||
53 | BZip2 = 11, | |||
54 | | |||
55 | /// <summary> | |||
56 | /// WinZip special for AES encryption, Now supported by #Zip. | |||
57 | /// </summary> | |||
58 | WinZipAES = 99, | |||
59 | | |||
60 | } | |||
61 | | |||
62 | /// <summary> | |||
63 | /// Identifies the encryption algorithm used for an entry | |||
64 | /// </summary> | |||
65 | public enum EncryptionAlgorithm | |||
66 | { | |||
67 | /// <summary> | |||
68 | /// No encryption has been used. | |||
69 | /// </summary> | |||
70 | None = 0, | |||
71 | /// <summary> | |||
72 | /// Encrypted using PKZIP 2.0 or 'classic' encryption. | |||
73 | /// </summary> | |||
74 | PkzipClassic = 1, | |||
75 | /// <summary> | |||
76 | /// DES encryption has been used. | |||
77 | /// </summary> | |||
78 | Des = 0x6601, | |||
79 | /// <summary> | |||
80 | /// RC2 encryption has been used for encryption. | |||
81 | /// </summary> | |||
82 | RC2 = 0x6602, | |||
83 | /// <summary> | |||
84 | /// Triple DES encryption with 168 bit keys has been used for this entry. | |||
85 | /// </summary> | |||
86 | TripleDes168 = 0x6603, | |||
87 | /// <summary> | |||
88 | /// Triple DES with 112 bit keys has been used for this entry. | |||
89 | /// </summary> | |||
90 | TripleDes112 = 0x6609, | |||
91 | /// <summary> | |||
92 | /// AES 128 has been used for encryption. | |||
93 | /// </summary> | |||
94 | Aes128 = 0x660e, | |||
95 | /// <summary> | |||
96 | /// AES 192 has been used for encryption. | |||
97 | /// </summary> | |||
98 | Aes192 = 0x660f, | |||
99 | /// <summary> | |||
100 | /// AES 256 has been used for encryption. | |||
101 | /// </summary> | |||
102 | Aes256 = 0x6610, | |||
103 | /// <summary> | |||
104 | /// RC2 corrected has been used for encryption. | |||
105 | /// </summary> | |||
106 | RC2Corrected = 0x6702, | |||
107 | /// <summary> | |||
108 | /// Blowfish has been used for encryption. | |||
109 | /// </summary> | |||
110 | Blowfish = 0x6720, | |||
111 | /// <summary> | |||
112 | /// Twofish has been used for encryption. | |||
113 | /// </summary> | |||
114 | Twofish = 0x6721, | |||
115 | /// <summary> | |||
116 | /// RC4 has been used for encryption. | |||
117 | /// </summary> | |||
118 | RC4 = 0x6801, | |||
119 | /// <summary> | |||
120 | /// An unknown algorithm has been used for encryption. | |||
121 | /// </summary> | |||
122 | Unknown = 0xffff | |||
123 | } | |||
124 | | |||
125 | /// <summary> | |||
126 | /// Defines the contents of the general bit flags field for an archive entry. | |||
127 | /// </summary> | |||
128 | [Flags] | |||
129 | public enum GeneralBitFlags | |||
130 | { | |||
131 | /// <summary> | |||
132 | /// Bit 0 if set indicates that the file is encrypted | |||
133 | /// </summary> | |||
134 | Encrypted = 0x0001, | |||
135 | /// <summary> | |||
136 | /// Bits 1 and 2 - Two bits defining the compression method (only for Method 6 Imploding and 8,9 Deflating) | |||
137 | /// </summary> | |||
138 | Method = 0x0006, | |||
139 | /// <summary> | |||
140 | /// Bit 3 if set indicates a trailing data desciptor is appended to the entry data | |||
141 | /// </summary> | |||
142 | Descriptor = 0x0008, | |||
143 | /// <summary> | |||
144 | /// Bit 4 is reserved for use with method 8 for enhanced deflation | |||
145 | /// </summary> | |||
146 | ReservedPKware4 = 0x0010, | |||
147 | /// <summary> | |||
148 | /// Bit 5 if set indicates the file contains Pkzip compressed patched data. | |||
149 | /// Requires version 2.7 or greater. | |||
150 | /// </summary> | |||
151 | Patched = 0x0020, | |||
152 | /// <summary> | |||
153 | /// Bit 6 if set indicates strong encryption has been used for this entry. | |||
154 | /// </summary> | |||
155 | StrongEncryption = 0x0040, | |||
156 | /// <summary> | |||
157 | /// Bit 7 is currently unused | |||
158 | /// </summary> | |||
159 | Unused7 = 0x0080, | |||
160 | /// <summary> | |||
161 | /// Bit 8 is currently unused | |||
162 | /// </summary> | |||
163 | Unused8 = 0x0100, | |||
164 | /// <summary> | |||
165 | /// Bit 9 is currently unused | |||
166 | /// </summary> | |||
167 | Unused9 = 0x0200, | |||
168 | /// <summary> | |||
169 | /// Bit 10 is currently unused | |||
170 | /// </summary> | |||
171 | Unused10 = 0x0400, | |||
172 | /// <summary> | |||
173 | /// Bit 11 if set indicates the filename and | |||
174 | /// comment fields for this file must be encoded using UTF-8. | |||
175 | /// </summary> | |||
176 | UnicodeText = 0x0800, | |||
177 | /// <summary> | |||
178 | /// Bit 12 is documented as being reserved by PKware for enhanced compression. | |||
179 | /// </summary> | |||
180 | EnhancedCompress = 0x1000, | |||
181 | /// <summary> | |||
182 | /// Bit 13 if set indicates that values in the local header are masked to hide | |||
183 | /// their actual values, and the central directory is encrypted. | |||
184 | /// </summary> | |||
185 | /// <remarks> | |||
186 | /// Used when encrypting the central directory contents. | |||
187 | /// </remarks> | |||
188 | HeaderMasked = 0x2000, | |||
189 | /// <summary> | |||
190 | /// Bit 14 is documented as being reserved for use by PKware | |||
191 | /// </summary> | |||
192 | ReservedPkware14 = 0x4000, | |||
193 | /// <summary> | |||
194 | /// Bit 15 is documented as being reserved for use by PKware | |||
195 | /// </summary> | |||
196 | ReservedPkware15 = 0x8000 | |||
197 | } | |||
198 | | |||
199 | #endregion | |||
200 | | |||
201 | /// <summary> | |||
202 | /// This class contains constants used for Zip format files | |||
203 | /// </summary> | |||
204 | public sealed class ZipConstants | |||
205 | { | |||
206 | #region Versions | |||
207 | /// <summary> | |||
208 | /// The version made by field for entries in the central header when created by this library | |||
209 | /// </summary> | |||
210 | /// <remarks> | |||
211 | /// This is also the Zip version for the library when comparing against the version required to extract | |||
212 | /// for an entry. See <see cref="ZipEntry.CanDecompress"/>. | |||
213 | /// </remarks> | |||
214 | public const int VersionMadeBy = 51; // was 45 before AES | |||
215 | | |||
216 | /// <summary> | |||
217 | /// The version made by field for entries in the central header when created by this library | |||
218 | /// </summary> | |||
219 | /// <remarks> | |||
220 | /// This is also the Zip version for the library when comparing against the version required to extract | |||
221 | /// for an entry. See <see cref="ZipInputStream.CanDecompressEntry">ZipInputStream.CanDecompressEntry</see>. | |||
222 | /// </remarks> | |||
223 | [Obsolete("Use VersionMadeBy instead")] | |||
224 | public const int VERSION_MADE_BY = 51; | |||
225 | | |||
226 | /// <summary> | |||
227 | /// The minimum version required to support strong encryption | |||
228 | /// </summary> | |||
229 | public const int VersionStrongEncryption = 50; | |||
230 | | |||
231 | /// <summary> | |||
232 | /// The minimum version required to support strong encryption | |||
233 | /// </summary> | |||
234 | [Obsolete("Use VersionStrongEncryption instead")] | |||
235 | public const int VERSION_STRONG_ENCRYPTION = 50; | |||
236 | | |||
237 | /// <summary> | |||
238 | /// Version indicating AES encryption | |||
239 | /// </summary> | |||
240 | public const int VERSION_AES = 51; | |||
241 | | |||
242 | /// <summary> | |||
243 | /// The version required for Zip64 extensions (4.5 or higher) | |||
244 | /// </summary> | |||
245 | public const int VersionZip64 = 45; | |||
246 | #endregion | |||
247 | | |||
248 | #region Header Sizes | |||
249 | /// <summary> | |||
250 | /// Size of local entry header (excluding variable length fields at end) | |||
251 | /// </summary> | |||
252 | public const int LocalHeaderBaseSize = 30; | |||
253 | | |||
254 | /// <summary> | |||
255 | /// Size of local entry header (excluding variable length fields at end) | |||
256 | /// </summary> | |||
257 | [Obsolete("Use LocalHeaderBaseSize instead")] | |||
258 | public const int LOCHDR = 30; | |||
259 | | |||
260 | /// <summary> | |||
261 | /// Size of Zip64 data descriptor | |||
262 | /// </summary> | |||
263 | public const int Zip64DataDescriptorSize = 24; | |||
264 | | |||
265 | /// <summary> | |||
266 | /// Size of data descriptor | |||
267 | /// </summary> | |||
268 | public const int DataDescriptorSize = 16; | |||
269 | | |||
270 | /// <summary> | |||
271 | /// Size of data descriptor | |||
272 | /// </summary> | |||
273 | [Obsolete("Use DataDescriptorSize instead")] | |||
274 | public const int EXTHDR = 16; | |||
275 | | |||
276 | /// <summary> | |||
277 | /// Size of central header entry (excluding variable fields) | |||
278 | /// </summary> | |||
279 | public const int CentralHeaderBaseSize = 46; | |||
280 | | |||
281 | /// <summary> | |||
282 | /// Size of central header entry | |||
283 | /// </summary> | |||
284 | [Obsolete("Use CentralHeaderBaseSize instead")] | |||
285 | public const int CENHDR = 46; | |||
286 | | |||
287 | /// <summary> | |||
288 | /// Size of end of central record (excluding variable fields) | |||
289 | /// </summary> | |||
290 | public const int EndOfCentralRecordBaseSize = 22; | |||
291 | | |||
292 | /// <summary> | |||
293 | /// Size of end of central record (excluding variable fields) | |||
294 | /// </summary> | |||
295 | [Obsolete("Use EndOfCentralRecordBaseSize instead")] | |||
296 | public const int ENDHDR = 22; | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Size of 'classic' cryptographic header stored before any entry data | |||
300 | /// </summary> | |||
301 | public const int CryptoHeaderSize = 12; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Size of cryptographic header stored before entry data | |||
305 | /// </summary> | |||
306 | [Obsolete("Use CryptoHeaderSize instead")] | |||
307 | public const int CRYPTO_HEADER_SIZE = 12; | |||
308 | #endregion | |||
309 | | |||
310 | #region Header Signatures | |||
311 | | |||
312 | /// <summary> | |||
313 | /// Signature for local entry header | |||
314 | /// </summary> | |||
315 | public const int LocalHeaderSignature = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); | |||
316 | | |||
317 | /// <summary> | |||
318 | /// Signature for local entry header | |||
319 | /// </summary> | |||
320 | [Obsolete("Use LocalHeaderSignature instead")] | |||
321 | public const int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); | |||
322 | | |||
323 | /// <summary> | |||
324 | /// Signature for spanning entry | |||
325 | /// </summary> | |||
326 | public const int SpanningSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); | |||
327 | | |||
328 | /// <summary> | |||
329 | /// Signature for spanning entry | |||
330 | /// </summary> | |||
331 | [Obsolete("Use SpanningSignature instead")] | |||
332 | public const int SPANNINGSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); | |||
333 | | |||
334 | /// <summary> | |||
335 | /// Signature for temporary spanning entry | |||
336 | /// </summary> | |||
337 | public const int SpanningTempSignature = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24); | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Signature for temporary spanning entry | |||
341 | /// </summary> | |||
342 | [Obsolete("Use SpanningTempSignature instead")] | |||
343 | public const int SPANTEMPSIG = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24); | |||
344 | | |||
345 | /// <summary> | |||
346 | /// Signature for data descriptor | |||
347 | /// </summary> | |||
348 | /// <remarks> | |||
349 | /// This is only used where the length, Crc, or compressed size isnt known when the | |||
350 | /// entry is created and the output stream doesnt support seeking. | |||
351 | /// The local entry cannot be 'patched' with the correct values in this case | |||
352 | /// so the values are recorded after the data prefixed by this header, as well as in the central directory. | |||
353 | /// </remarks> | |||
354 | public const int DataDescriptorSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); | |||
355 | | |||
356 | /// <summary> | |||
357 | /// Signature for data descriptor | |||
358 | /// </summary> | |||
359 | /// <remarks> | |||
360 | /// This is only used where the length, Crc, or compressed size isnt known when the | |||
361 | /// entry is created and the output stream doesnt support seeking. | |||
362 | /// The local entry cannot be 'patched' with the correct values in this case | |||
363 | /// so the values are recorded after the data prefixed by this header, as well as in the central directory. | |||
364 | /// </remarks> | |||
365 | [Obsolete("Use DataDescriptorSignature instead")] | |||
366 | public const int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); | |||
367 | | |||
368 | /// <summary> | |||
369 | /// Signature for central header | |||
370 | /// </summary> | |||
371 | [Obsolete("Use CentralHeaderSignature instead")] | |||
372 | public const int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); | |||
373 | | |||
374 | /// <summary> | |||
375 | /// Signature for central header | |||
376 | /// </summary> | |||
377 | public const int CentralHeaderSignature = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); | |||
378 | | |||
379 | /// <summary> | |||
380 | /// Signature for Zip64 central file header | |||
381 | /// </summary> | |||
382 | public const int Zip64CentralFileHeaderSignature = 'P' | ('K' << 8) | (6 << 16) | (6 << 24); | |||
383 | | |||
384 | /// <summary> | |||
385 | /// Signature for Zip64 central file header | |||
386 | /// </summary> | |||
387 | [Obsolete("Use Zip64CentralFileHeaderSignature instead")] | |||
388 | public const int CENSIG64 = 'P' | ('K' << 8) | (6 << 16) | (6 << 24); | |||
389 | | |||
390 | /// <summary> | |||
391 | /// Signature for Zip64 central directory locator | |||
392 | /// </summary> | |||
393 | public const int Zip64CentralDirLocatorSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24); | |||
394 | | |||
395 | /// <summary> | |||
396 | /// Signature for archive extra data signature (were headers are encrypted). | |||
397 | /// </summary> | |||
398 | public const int ArchiveExtraDataSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24); | |||
399 | | |||
400 | /// <summary> | |||
401 | /// Central header digitial signature | |||
402 | /// </summary> | |||
403 | public const int CentralHeaderDigitalSignature = 'P' | ('K' << 8) | (5 << 16) | (5 << 24); | |||
404 | | |||
405 | /// <summary> | |||
406 | /// Central header digitial signature | |||
407 | /// </summary> | |||
408 | [Obsolete("Use CentralHeaderDigitalSignaure instead")] | |||
409 | public const int CENDIGITALSIG = 'P' | ('K' << 8) | (5 << 16) | (5 << 24); | |||
410 | | |||
411 | /// <summary> | |||
412 | /// End of central directory record signature | |||
413 | /// </summary> | |||
414 | public const int EndOfCentralDirectorySignature = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); | |||
415 | | |||
416 | /// <summary> | |||
417 | /// End of central directory record signature | |||
418 | /// </summary> | |||
419 | [Obsolete("Use EndOfCentralDirectorySignature instead")] | |||
420 | public const int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); | |||
421 | #endregion | |||
422 | | |||
423 | /// <remarks> | |||
424 | /// Get OEM codepage from NetFX, which parses the NLP file with culture info table etc etc. | |||
425 | /// But sometimes it yields the special value of 1 which is nicknamed <c>CodePageNoOEM</c> in <see cref="Encoding"/> | |||
426 | /// This was observed on Ukranian and Hindu systems. | |||
427 | /// Given this value, <see cref="Encoding.GetEncoding(int)"/> throws an <see cref="ArgumentException"/>. | |||
428 | /// So replace it with some fallback, e.g. 437 which is the default cpcp in a console in a default Windows installat | |||
429 | /// </remarks> | |||
| 1 | 430 | static int defaultCodePage = | ||
| 1 | 431 | // these values cause ArgumentException in subsequent calls to Encoding::GetEncoding() | ||
| 1 | 432 | ((Thread.CurrentThread.CurrentCulture.TextInfo.OEMCodePage == 1) || (Thread.CurrentThread.CurrentCulture.TextInfo. | ||
| 1 | 433 | ? 437 // The default OEM encoding in a console in a default Windows installation, as a fallback. | ||
| 1 | 434 | : Thread.CurrentThread.CurrentCulture.TextInfo.OEMCodePage; | ||
435 | | |||
436 | /// <summary> | |||
437 | /// Default encoding used for string conversion. 0 gives the default system OEM code page. | |||
438 | /// Dont use unicode encodings if you want to be Zip compatible! | |||
439 | /// Using the default code page isnt the full solution neccessarily | |||
440 | /// there are many variable factors, codepage 850 is often a good choice for | |||
441 | /// European users, however be careful about compatability. | |||
442 | /// </summary> | |||
443 | public static int DefaultCodePage { | |||
444 | get { | |||
| 263830 | 445 | return defaultCodePage; | ||
446 | } | |||
447 | set { | |||
| 1 | 448 | if ((value < 0) || (value > 65535) || | ||
| 1 | 449 | (value == 1) || (value == 2) || (value == 3) || (value == 42)) { | ||
| 0 | 450 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
451 | } | |||
452 | | |||
| 1 | 453 | defaultCodePage = value; | ||
| 1 | 454 | } | ||
455 | } | |||
456 | | |||
457 | /// <summary> | |||
458 | /// Convert a portion of a byte array to a string. | |||
459 | /// </summary> | |||
460 | /// <param name="data"> | |||
461 | /// Data to convert to string | |||
462 | /// </param> | |||
463 | /// <param name="count"> | |||
464 | /// Number of bytes to convert starting from index 0 | |||
465 | /// </param> | |||
466 | /// <returns> | |||
467 | /// data[0]..data[count - 1] converted to a string | |||
468 | /// </returns> | |||
469 | public static string ConvertToString(byte[] data, int count) | |||
470 | { | |||
| 131956 | 471 | if (data == null) { | ||
| 0 | 472 | return string.Empty; | ||
473 | } | |||
474 | | |||
| 131956 | 475 | return Encoding.GetEncoding(DefaultCodePage).GetString(data, 0, count); | ||
476 | } | |||
477 | | |||
478 | /// <summary> | |||
479 | /// Convert a byte array to string | |||
480 | /// </summary> | |||
481 | /// <param name="data"> | |||
482 | /// Byte array to convert | |||
483 | /// </param> | |||
484 | /// <returns> | |||
485 | /// <paramref name="data">data</paramref>converted to a string | |||
486 | /// </returns> | |||
487 | public static string ConvertToString(byte[] data) | |||
488 | { | |||
| 12 | 489 | if (data == null) { | ||
| 0 | 490 | return string.Empty; | ||
491 | } | |||
| 12 | 492 | return ConvertToString(data, data.Length); | ||
493 | } | |||
494 | | |||
495 | /// <summary> | |||
496 | /// Convert a byte array to string | |||
497 | /// </summary> | |||
498 | /// <param name="flags">The applicable general purpose bits flags</param> | |||
499 | /// <param name="data"> | |||
500 | /// Byte array to convert | |||
501 | /// </param> | |||
502 | /// <param name="count">The number of bytes to convert.</param> | |||
503 | /// <returns> | |||
504 | /// <paramref name="data">data</paramref>converted to a string | |||
505 | /// </returns> | |||
506 | public static string ConvertToStringExt(int flags, byte[] data, int count) | |||
507 | { | |||
| 65956 | 508 | if (data == null) { | ||
| 0 | 509 | return string.Empty; | ||
510 | } | |||
511 | | |||
| 65956 | 512 | if ((flags & (int)GeneralBitFlags.UnicodeText) != 0) { | ||
| 5 | 513 | return Encoding.UTF8.GetString(data, 0, count); | ||
514 | } else { | |||
| 65951 | 515 | return ConvertToString(data, count); | ||
516 | } | |||
517 | } | |||
518 | | |||
519 | /// <summary> | |||
520 | /// Convert a byte array to string | |||
521 | /// </summary> | |||
522 | /// <param name="data"> | |||
523 | /// Byte array to convert | |||
524 | /// </param> | |||
525 | /// <param name="flags">The applicable general purpose bits flags</param> | |||
526 | /// <returns> | |||
527 | /// <paramref name="data">data</paramref>converted to a string | |||
528 | /// </returns> | |||
529 | public static string ConvertToStringExt(int flags, byte[] data) | |||
530 | { | |||
| 66000 | 531 | if (data == null) { | ||
| 0 | 532 | return string.Empty; | ||
533 | } | |||
534 | | |||
| 66000 | 535 | if ((flags & (int)GeneralBitFlags.UnicodeText) != 0) { | ||
| 7 | 536 | return Encoding.UTF8.GetString(data, 0, data.Length); | ||
537 | } else { | |||
| 65993 | 538 | return ConvertToString(data, data.Length); | ||
539 | } | |||
540 | } | |||
541 | | |||
542 | /// <summary> | |||
543 | /// Convert a string to a byte array | |||
544 | /// </summary> | |||
545 | /// <param name="str"> | |||
546 | /// String to convert to an array | |||
547 | /// </param> | |||
548 | /// <returns>Converted array</returns> | |||
549 | public static byte[] ConvertToArray(string str) | |||
550 | { | |||
| 131891 | 551 | if (str == null) { | ||
| 17 | 552 | return new byte[0]; | ||
553 | } | |||
554 | | |||
| 131874 | 555 | return Encoding.GetEncoding(DefaultCodePage).GetBytes(str); | ||
556 | } | |||
557 | | |||
558 | /// <summary> | |||
559 | /// Convert a string to a byte array | |||
560 | /// </summary> | |||
561 | /// <param name="flags">The applicable <see cref="GeneralBitFlags">general purpose bits flags</see></param> | |||
562 | /// <param name="str"> | |||
563 | /// String to convert to an array | |||
564 | /// </param> | |||
565 | /// <returns>Converted array</returns> | |||
566 | public static byte[] ConvertToArray(int flags, string str) | |||
567 | { | |||
| 131777 | 568 | if (str == null) { | ||
| 0 | 569 | return new byte[0]; | ||
570 | } | |||
571 | | |||
| 131777 | 572 | if ((flags & (int)GeneralBitFlags.UnicodeText) != 0) { | ||
| 12 | 573 | return Encoding.UTF8.GetBytes(str); | ||
574 | } else { | |||
| 131765 | 575 | return ConvertToArray(str); | ||
576 | } | |||
577 | } | |||
578 | | |||
579 | | |||
580 | /// <summary> | |||
581 | /// Initialise default instance of <see cref="ZipConstants">ZipConstants</see> | |||
582 | /// </summary> | |||
583 | /// <remarks> | |||
584 | /// Private to prevent instances being created. | |||
585 | /// </remarks> | |||
| 0 | 586 | ZipConstants() | ||
587 | { | |||
588 | // Do nothing | |||
| 0 | 589 | } | ||
590 | } | |||
591 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipEntry |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipEntry.cs |
| Covered lines: | 219 |
| Uncovered lines: | 69 |
| Coverable lines: | 288 |
| Total lines: | 1175 |
| Line coverage: | 76% |
| Branch coverage: | 59.5% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 5 | 87.5 | 77.78 |
| .ctor(...) | 3 | 96.15 | 60 |
| HasDosAttributes(...) | 4 | 100 | 100 |
| ForceZip64() | 1 | 100 | 100 |
| IsZip64Forced() | 1 | 100 | 100 |
| ProcessExtraData(...) | 11 | 72.22 | 66.67 |
| GetDateTime(...) | 5 | 90 | 33.33 |
| ProcessAESExtraData(...) | 3 | 0 | 0 |
| IsCompressionMethodSupported() | 1 | 100 | 100 |
| Clone() | 2 | 100 | 100 |
| ToString() | 1 | 0 | 0 |
| IsCompressionMethodSupported(...) | 2 | 100 | 100 |
| CleanName(...) | 5 | 62.5 | 55.56 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// Defines known values for the <see cref="HostSystemID"/> property. | |||
8 | /// </summary> | |||
9 | public enum HostSystemID | |||
10 | { | |||
11 | /// <summary> | |||
12 | /// Host system = MSDOS | |||
13 | /// </summary> | |||
14 | Msdos = 0, | |||
15 | /// <summary> | |||
16 | /// Host system = Amiga | |||
17 | /// </summary> | |||
18 | Amiga = 1, | |||
19 | /// <summary> | |||
20 | /// Host system = Open VMS | |||
21 | /// </summary> | |||
22 | OpenVms = 2, | |||
23 | /// <summary> | |||
24 | /// Host system = Unix | |||
25 | /// </summary> | |||
26 | Unix = 3, | |||
27 | /// <summary> | |||
28 | /// Host system = VMCms | |||
29 | /// </summary> | |||
30 | VMCms = 4, | |||
31 | /// <summary> | |||
32 | /// Host system = Atari ST | |||
33 | /// </summary> | |||
34 | AtariST = 5, | |||
35 | /// <summary> | |||
36 | /// Host system = OS2 | |||
37 | /// </summary> | |||
38 | OS2 = 6, | |||
39 | /// <summary> | |||
40 | /// Host system = Macintosh | |||
41 | /// </summary> | |||
42 | Macintosh = 7, | |||
43 | /// <summary> | |||
44 | /// Host system = ZSystem | |||
45 | /// </summary> | |||
46 | ZSystem = 8, | |||
47 | /// <summary> | |||
48 | /// Host system = Cpm | |||
49 | /// </summary> | |||
50 | Cpm = 9, | |||
51 | /// <summary> | |||
52 | /// Host system = Windows NT | |||
53 | /// </summary> | |||
54 | WindowsNT = 10, | |||
55 | /// <summary> | |||
56 | /// Host system = MVS | |||
57 | /// </summary> | |||
58 | MVS = 11, | |||
59 | /// <summary> | |||
60 | /// Host system = VSE | |||
61 | /// </summary> | |||
62 | Vse = 12, | |||
63 | /// <summary> | |||
64 | /// Host system = Acorn RISC | |||
65 | /// </summary> | |||
66 | AcornRisc = 13, | |||
67 | /// <summary> | |||
68 | /// Host system = VFAT | |||
69 | /// </summary> | |||
70 | Vfat = 14, | |||
71 | /// <summary> | |||
72 | /// Host system = Alternate MVS | |||
73 | /// </summary> | |||
74 | AlternateMvs = 15, | |||
75 | /// <summary> | |||
76 | /// Host system = BEOS | |||
77 | /// </summary> | |||
78 | BeOS = 16, | |||
79 | /// <summary> | |||
80 | /// Host system = Tandem | |||
81 | /// </summary> | |||
82 | Tandem = 17, | |||
83 | /// <summary> | |||
84 | /// Host system = OS400 | |||
85 | /// </summary> | |||
86 | OS400 = 18, | |||
87 | /// <summary> | |||
88 | /// Host system = OSX | |||
89 | /// </summary> | |||
90 | OSX = 19, | |||
91 | /// <summary> | |||
92 | /// Host system = WinZIP AES | |||
93 | /// </summary> | |||
94 | WinZipAES = 99, | |||
95 | } | |||
96 | | |||
97 | /// <summary> | |||
98 | /// This class represents an entry in a zip archive. This can be a file | |||
99 | /// or a directory | |||
100 | /// ZipFile and ZipInputStream will give you instances of this class as | |||
101 | /// information about the members in an archive. ZipOutputStream | |||
102 | /// uses an instance of this class when creating an entry in a Zip file. | |||
103 | /// <br/> | |||
104 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
105 | /// </summary> | |||
106 | public class ZipEntry : ICloneable | |||
107 | { | |||
108 | [Flags] | |||
109 | enum Known : byte | |||
110 | { | |||
111 | None = 0, | |||
112 | Size = 0x01, | |||
113 | CompressedSize = 0x02, | |||
114 | Crc = 0x04, | |||
115 | Time = 0x08, | |||
116 | ExternalAttributes = 0x10, | |||
117 | } | |||
118 | | |||
119 | #region Constructors | |||
120 | /// <summary> | |||
121 | /// Creates a zip entry with the given name. | |||
122 | /// </summary> | |||
123 | /// <param name="name"> | |||
124 | /// The name for this entry. Can include directory components. | |||
125 | /// The convention for names is 'unix' style paths with relative names only. | |||
126 | /// There are with no device names and path elements are separated by '/' characters. | |||
127 | /// </param> | |||
128 | /// <exception cref="ArgumentNullException"> | |||
129 | /// The name passed is null | |||
130 | /// </exception> | |||
131 | public ZipEntry(string name) | |||
| 65841 | 132 | : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated) | ||
133 | { | |||
| 65840 | 134 | } | ||
135 | | |||
136 | /// <summary> | |||
137 | /// Creates a zip entry with the given name and version required to extract | |||
138 | /// </summary> | |||
139 | /// <param name="name"> | |||
140 | /// The name for this entry. Can include directory components. | |||
141 | /// The convention for names is 'unix' style paths with no device names and | |||
142 | /// path elements separated by '/' characters. This is not enforced see <see cref="CleanName(string)">CleanName</se | |||
143 | /// on how to ensure names are valid if this is desired. | |||
144 | /// </param> | |||
145 | /// <param name="versionRequiredToExtract"> | |||
146 | /// The minimum 'feature version' required this entry | |||
147 | /// </param> | |||
148 | /// <exception cref="ArgumentNullException"> | |||
149 | /// The name passed is null | |||
150 | /// </exception> | |||
151 | internal ZipEntry(string name, int versionRequiredToExtract) | |||
| 81 | 152 | : this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, | ||
| 81 | 153 | CompressionMethod.Deflated) | ||
154 | { | |||
| 81 | 155 | } | ||
156 | | |||
157 | /// <summary> | |||
158 | /// Initializes an entry with the given name and made by information | |||
159 | /// </summary> | |||
160 | /// <param name="name">Name for this entry</param> | |||
161 | /// <param name="madeByInfo">Version and HostSystem Information</param> | |||
162 | /// <param name="versionRequiredToExtract">Minimum required zip feature version required to extract this entry</para | |||
163 | /// <param name="method">Compression method for this entry.</param> | |||
164 | /// <exception cref="ArgumentNullException"> | |||
165 | /// The name passed is null | |||
166 | /// </exception> | |||
167 | /// <exception cref="ArgumentOutOfRangeException"> | |||
168 | /// versionRequiredToExtract should be 0 (auto-calculate) or > 10 | |||
169 | /// </exception> | |||
170 | /// <remarks> | |||
171 | /// This constructor is used by the ZipFile class when reading from the central header | |||
172 | /// It is not generally useful, use the constructor specifying the name only. | |||
173 | /// </remarks> | |||
| 131878 | 174 | internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo, | ||
| 131878 | 175 | CompressionMethod method) | ||
176 | { | |||
| 131878 | 177 | if (name == null) { | ||
| 1 | 178 | throw new ArgumentNullException(nameof(name)); | ||
179 | } | |||
180 | | |||
| 131877 | 181 | if (name.Length > 0xffff) { | ||
| 0 | 182 | throw new ArgumentException("Name is too long", nameof(name)); | ||
183 | } | |||
184 | | |||
| 131877 | 185 | if ((versionRequiredToExtract != 0) && (versionRequiredToExtract < 10)) { | ||
| 0 | 186 | throw new ArgumentOutOfRangeException(nameof(versionRequiredToExtract)); | ||
187 | } | |||
188 | | |||
| 131877 | 189 | this.DateTime = DateTime.Now; | ||
| 131877 | 190 | this.name = CleanName(name); | ||
| 131877 | 191 | this.versionMadeBy = (ushort)madeByInfo; | ||
| 131877 | 192 | this.versionToExtract = (ushort)versionRequiredToExtract; | ||
| 131877 | 193 | this.method = method; | ||
| 131877 | 194 | } | ||
195 | | |||
196 | /// <summary> | |||
197 | /// Creates a deep copy of the given zip entry. | |||
198 | /// </summary> | |||
199 | /// <param name="entry"> | |||
200 | /// The entry to copy. | |||
201 | /// </param> | |||
202 | [Obsolete("Use Clone instead")] | |||
| 1 | 203 | public ZipEntry(ZipEntry entry) | ||
204 | { | |||
| 1 | 205 | if (entry == null) { | ||
| 0 | 206 | throw new ArgumentNullException(nameof(entry)); | ||
207 | } | |||
208 | | |||
| 1 | 209 | known = entry.known; | ||
| 1 | 210 | name = entry.name; | ||
| 1 | 211 | size = entry.size; | ||
| 1 | 212 | compressedSize = entry.compressedSize; | ||
| 1 | 213 | crc = entry.crc; | ||
| 1 | 214 | dosTime = entry.dosTime; | ||
| 1 | 215 | dateTime = entry.dateTime; | ||
| 1 | 216 | method = entry.method; | ||
| 1 | 217 | comment = entry.comment; | ||
| 1 | 218 | versionToExtract = entry.versionToExtract; | ||
| 1 | 219 | versionMadeBy = entry.versionMadeBy; | ||
| 1 | 220 | externalFileAttributes = entry.externalFileAttributes; | ||
| 1 | 221 | flags = entry.flags; | ||
222 | | |||
| 1 | 223 | zipFileIndex = entry.zipFileIndex; | ||
| 1 | 224 | offset = entry.offset; | ||
225 | | |||
| 1 | 226 | forceZip64_ = entry.forceZip64_; | ||
227 | | |||
| 1 | 228 | if (entry.extra != null) { | ||
| 1 | 229 | extra = new byte[entry.extra.Length]; | ||
| 1 | 230 | Array.Copy(entry.extra, 0, extra, 0, entry.extra.Length); | ||
231 | } | |||
| 1 | 232 | } | ||
233 | | |||
234 | #endregion | |||
235 | | |||
236 | /// <summary> | |||
237 | /// Get a value indicating wether the entry has a CRC value available. | |||
238 | /// </summary> | |||
239 | public bool HasCrc { | |||
240 | get { | |||
| 65754 | 241 | return (known & Known.Crc) != 0; | ||
242 | } | |||
243 | } | |||
244 | | |||
245 | /// <summary> | |||
246 | /// Get/Set flag indicating if entry is encrypted. | |||
247 | /// A simple helper routine to aid interpretation of <see cref="Flags">flags</see> | |||
248 | /// </summary> | |||
249 | /// <remarks>This is an assistant that interprets the <see cref="Flags">flags</see> property.</remarks> | |||
250 | public bool IsCrypted { | |||
251 | get { | |||
| 592001 | 252 | return (flags & 1) != 0; | ||
253 | } | |||
254 | set { | |||
| 65738 | 255 | if (value) { | ||
| 38 | 256 | flags |= 1; | ||
| 38 | 257 | } else { | ||
| 65700 | 258 | flags &= ~1; | ||
259 | } | |||
| 65700 | 260 | } | ||
261 | } | |||
262 | | |||
263 | /// <summary> | |||
264 | /// Get / set a flag indicating wether entry name and comment text are | |||
265 | /// encoded in <a href="http://www.unicode.org">unicode UTF8</a>. | |||
266 | /// </summary> | |||
267 | /// <remarks>This is an assistant that interprets the <see cref="Flags">flags</see> property.</remarks> | |||
268 | public bool IsUnicodeText { | |||
269 | get { | |||
| 4 | 270 | return (flags & (int)GeneralBitFlags.UnicodeText) != 0; | ||
271 | } | |||
272 | set { | |||
| 177 | 273 | if (value) { | ||
| 8 | 274 | flags |= (int)GeneralBitFlags.UnicodeText; | ||
| 8 | 275 | } else { | ||
| 169 | 276 | flags &= ~(int)GeneralBitFlags.UnicodeText; | ||
277 | } | |||
| 169 | 278 | } | ||
279 | } | |||
280 | | |||
281 | /// <summary> | |||
282 | /// Value used during password checking for PKZIP 2.0 / 'classic' encryption. | |||
283 | /// </summary> | |||
284 | internal byte CryptoCheckValue { | |||
285 | get { | |||
| 34 | 286 | return cryptoCheckValue_; | ||
287 | } | |||
288 | | |||
289 | set { | |||
| 66037 | 290 | cryptoCheckValue_ = value; | ||
| 66037 | 291 | } | ||
292 | } | |||
293 | | |||
294 | /// <summary> | |||
295 | /// Get/Set general purpose bit flag for entry | |||
296 | /// </summary> | |||
297 | /// <remarks> | |||
298 | /// General purpose bit flag<br/> | |||
299 | /// <br/> | |||
300 | /// Bit 0: If set, indicates the file is encrypted<br/> | |||
301 | /// Bit 1-2 Only used for compression type 6 Imploding, and 8, 9 deflating<br/> | |||
302 | /// Imploding:<br/> | |||
303 | /// Bit 1 if set indicates an 8K sliding dictionary was used. If clear a 4k dictionary was used<br/> | |||
304 | /// Bit 2 if set indicates 3 Shannon-Fanno trees were used to encode the sliding dictionary, 2 otherwise<br/> | |||
305 | /// <br/> | |||
306 | /// Deflating:<br/> | |||
307 | /// Bit 2 Bit 1<br/> | |||
308 | /// 0 0 Normal compression was used<br/> | |||
309 | /// 0 1 Maximum compression was used<br/> | |||
310 | /// 1 0 Fast compression was used<br/> | |||
311 | /// 1 1 Super fast compression was used<br/> | |||
312 | /// <br/> | |||
313 | /// Bit 3: If set, the fields crc-32, compressed size | |||
314 | /// and uncompressed size are were not able to be written during zip file creation | |||
315 | /// The correct values are held in a data descriptor immediately following the compressed data. <br/> | |||
316 | /// Bit 4: Reserved for use by PKZIP for enhanced deflating<br/> | |||
317 | /// Bit 5: If set indicates the file contains compressed patch data<br/> | |||
318 | /// Bit 6: If set indicates strong encryption was used.<br/> | |||
319 | /// Bit 7-10: Unused or reserved<br/> | |||
320 | /// Bit 11: If set the name and comments for this entry are in <a href="http://www.unicode.org">unicode</a>.<br/> | |||
321 | /// Bit 12-15: Unused or reserved<br/> | |||
322 | /// </remarks> | |||
323 | /// <seealso cref="IsUnicodeText"></seealso> | |||
324 | /// <seealso cref="IsCrypted"></seealso> | |||
325 | public int Flags { | |||
326 | get { | |||
| 395994 | 327 | return flags; | ||
328 | } | |||
329 | set { | |||
| 66249 | 330 | flags = value; | ||
| 66249 | 331 | } | ||
332 | } | |||
333 | | |||
334 | /// <summary> | |||
335 | /// Get/Set index of this entry in Zip file | |||
336 | /// </summary> | |||
337 | /// <remarks>This is only valid when the entry is part of a <see cref="ZipFile"></see></remarks> | |||
338 | public long ZipFileIndex { | |||
339 | get { | |||
| 65933 | 340 | return zipFileIndex; | ||
341 | } | |||
342 | set { | |||
| 65956 | 343 | zipFileIndex = value; | ||
| 65956 | 344 | } | ||
345 | } | |||
346 | | |||
347 | /// <summary> | |||
348 | /// Get/set offset for use in central header | |||
349 | /// </summary> | |||
350 | public long Offset { | |||
351 | get { | |||
| 396693 | 352 | return offset; | ||
353 | } | |||
354 | set { | |||
| 131833 | 355 | offset = value; | ||
| 131833 | 356 | } | ||
357 | } | |||
358 | | |||
359 | /// <summary> | |||
360 | /// Get/Set external file attributes as an integer. | |||
361 | /// The values of this are operating system dependant see | |||
362 | /// <see cref="HostSystem">HostSystem</see> for details | |||
363 | /// </summary> | |||
364 | public int ExternalFileAttributes { | |||
365 | get { | |||
| 527438 | 366 | if ((known & Known.ExternalAttributes) == 0) { | ||
| 65824 | 367 | return -1; | ||
368 | } else { | |||
| 461614 | 369 | return externalFileAttributes; | ||
370 | } | |||
371 | } | |||
372 | | |||
373 | set { | |||
| 65965 | 374 | externalFileAttributes = value; | ||
| 65965 | 375 | known |= Known.ExternalAttributes; | ||
| 65965 | 376 | } | ||
377 | } | |||
378 | | |||
379 | /// <summary> | |||
380 | /// Get the version made by for this entry or zero if unknown. | |||
381 | /// The value / 10 indicates the major version number, and | |||
382 | /// the value mod 10 is the minor version number | |||
383 | /// </summary> | |||
384 | public int VersionMadeBy { | |||
385 | get { | |||
| 0 | 386 | return (versionMadeBy & 0xff); | ||
387 | } | |||
388 | } | |||
389 | | |||
390 | /// <summary> | |||
391 | /// Get a value indicating this entry is for a DOS/Windows system. | |||
392 | /// </summary> | |||
393 | public bool IsDOSEntry { | |||
394 | get { | |||
| 2 | 395 | return ((HostSystem == (int)HostSystemID.Msdos) || | ||
| 2 | 396 | (HostSystem == (int)HostSystemID.WindowsNT)); | ||
397 | } | |||
398 | } | |||
399 | | |||
400 | /// <summary> | |||
401 | /// Test the external attributes for this <see cref="ZipEntry"/> to | |||
402 | /// see if the external attributes are Dos based (including WINNT and variants) | |||
403 | /// and match the values | |||
404 | /// </summary> | |||
405 | /// <param name="attributes">The attributes to test.</param> | |||
406 | /// <returns>Returns true if the external attributes are known to be DOS/Windows | |||
407 | /// based and have the same attributes set as the value passed.</returns> | |||
408 | bool HasDosAttributes(int attributes) | |||
409 | { | |||
| 920886 | 410 | bool result = false; | ||
| 920886 | 411 | if ((known & Known.ExternalAttributes) != 0) { | ||
| 461462 | 412 | result |= (((HostSystem == (int)HostSystemID.Msdos) || | ||
| 461462 | 413 | (HostSystem == (int)HostSystemID.WindowsNT)) && | ||
| 461462 | 414 | (ExternalFileAttributes & attributes) == attributes); | ||
415 | } | |||
| 920886 | 416 | return result; | ||
417 | } | |||
418 | | |||
419 | /// <summary> | |||
420 | /// Gets the compatability information for the <see cref="ExternalFileAttributes">external file attribute</see> | |||
421 | /// If the external file attributes are compatible with MS-DOS and can be read | |||
422 | /// by PKZIP for DOS version 2.04g then this value will be zero. Otherwise the value | |||
423 | /// will be non-zero and identify the host system on which the attributes are compatible. | |||
424 | /// </summary> | |||
425 | /// | |||
426 | /// <remarks> | |||
427 | /// The values for this as defined in the Zip File format and by others are shown below. The values are somewhat | |||
428 | /// misleading in some cases as they are not all used as shown. You should consult the relevant documentation | |||
429 | /// to obtain up to date and correct information. The modified appnote by the infozip group is | |||
430 | /// particularly helpful as it documents a lot of peculiarities. The document is however a little dated. | |||
431 | /// <list type="table"> | |||
432 | /// <item>0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)</item> | |||
433 | /// <item>1 - Amiga</item> | |||
434 | /// <item>2 - OpenVMS</item> | |||
435 | /// <item>3 - Unix</item> | |||
436 | /// <item>4 - VM/CMS</item> | |||
437 | /// <item>5 - Atari ST</item> | |||
438 | /// <item>6 - OS/2 HPFS</item> | |||
439 | /// <item>7 - Macintosh</item> | |||
440 | /// <item>8 - Z-System</item> | |||
441 | /// <item>9 - CP/M</item> | |||
442 | /// <item>10 - Windows NTFS</item> | |||
443 | /// <item>11 - MVS (OS/390 - Z/OS)</item> | |||
444 | /// <item>12 - VSE</item> | |||
445 | /// <item>13 - Acorn Risc</item> | |||
446 | /// <item>14 - VFAT</item> | |||
447 | /// <item>15 - Alternate MVS</item> | |||
448 | /// <item>16 - BeOS</item> | |||
449 | /// <item>17 - Tandem</item> | |||
450 | /// <item>18 - OS/400</item> | |||
451 | /// <item>19 - OS/X (Darwin)</item> | |||
452 | /// <item>99 - WinZip AES</item> | |||
453 | /// <item>remainder - unused</item> | |||
454 | /// </list> | |||
455 | /// </remarks> | |||
456 | public int HostSystem { | |||
457 | get { | |||
| 461464 | 458 | return (versionMadeBy >> 8) & 0xff; | ||
459 | } | |||
460 | | |||
461 | set { | |||
| 0 | 462 | versionMadeBy &= 0xff; | ||
| 0 | 463 | versionMadeBy |= (ushort)((value & 0xff) << 8); | ||
| 0 | 464 | } | ||
465 | } | |||
466 | | |||
467 | /// <summary> | |||
468 | /// Get minimum Zip feature version required to extract this entry | |||
469 | /// </summary> | |||
470 | /// <remarks> | |||
471 | /// Minimum features are defined as:<br/> | |||
472 | /// 1.0 - Default value<br/> | |||
473 | /// 1.1 - File is a volume label<br/> | |||
474 | /// 2.0 - File is a folder/directory<br/> | |||
475 | /// 2.0 - File is compressed using Deflate compression<br/> | |||
476 | /// 2.0 - File is encrypted using traditional encryption<br/> | |||
477 | /// 2.1 - File is compressed using Deflate64<br/> | |||
478 | /// 2.5 - File is compressed using PKWARE DCL Implode<br/> | |||
479 | /// 2.7 - File is a patch data set<br/> | |||
480 | /// 4.5 - File uses Zip64 format extensions<br/> | |||
481 | /// 4.6 - File is compressed using BZIP2 compression<br/> | |||
482 | /// 5.0 - File is encrypted using DES<br/> | |||
483 | /// 5.0 - File is encrypted using 3DES<br/> | |||
484 | /// 5.0 - File is encrypted using original RC2 encryption<br/> | |||
485 | /// 5.0 - File is encrypted using RC4 encryption<br/> | |||
486 | /// 5.1 - File is encrypted using AES encryption<br/> | |||
487 | /// 5.1 - File is encrypted using corrected RC2 encryption<br/> | |||
488 | /// 5.1 - File is encrypted using corrected RC2-64 encryption<br/> | |||
489 | /// 6.1 - File is encrypted using non-OAEP key wrapping<br/> | |||
490 | /// 6.2 - Central directory encryption (not confirmed yet)<br/> | |||
491 | /// 6.3 - File is compressed using LZMA<br/> | |||
492 | /// 6.3 - File is compressed using PPMD+<br/> | |||
493 | /// 6.3 - File is encrypted using Blowfish<br/> | |||
494 | /// 6.3 - File is encrypted using Twofish<br/> | |||
495 | /// </remarks> | |||
496 | /// <seealso cref="CanDecompress"></seealso> | |||
497 | public int Version { | |||
498 | get { | |||
499 | // Return recorded version if known. | |||
| 198066 | 500 | if (versionToExtract != 0) { | ||
| 66394 | 501 | return versionToExtract & 0x00ff; // Only lower order byte. High order is O/S file system. | ||
502 | } else { | |||
| 131672 | 503 | int result = 10; | ||
| 131672 | 504 | if (AESKeySize > 0) { | ||
| 0 | 505 | result = ZipConstants.VERSION_AES; // Ver 5.1 = AES | ||
| 131672 | 506 | } else if (CentralHeaderRequiresZip64) { | ||
| 247 | 507 | result = ZipConstants.VersionZip64; | ||
| 131672 | 508 | } else if (CompressionMethod.Deflated == method) { | ||
| 321 | 509 | result = 20; | ||
| 131425 | 510 | } else if (IsDirectory == true) { | ||
| 0 | 511 | result = 20; | ||
| 131104 | 512 | } else if (IsCrypted == true) { | ||
| 0 | 513 | result = 20; | ||
| 131104 | 514 | } else if (HasDosAttributes(0x08)) { | ||
| 0 | 515 | result = 11; | ||
516 | } | |||
| 131672 | 517 | return result; | ||
518 | } | |||
519 | } | |||
520 | } | |||
521 | | |||
522 | /// <summary> | |||
523 | /// Get a value indicating whether this entry can be decompressed by the library. | |||
524 | /// </summary> | |||
525 | /// <remarks>This is based on the <see cref="Version"></see> and | |||
526 | /// wether the <see cref="IsCompressionMethodSupported()">compression method</see> is supported.</remarks> | |||
527 | public bool CanDecompress { | |||
528 | get { | |||
| 73 | 529 | return (Version <= ZipConstants.VersionMadeBy) && | ||
| 73 | 530 | ((Version == 10) || | ||
| 73 | 531 | (Version == 11) || | ||
| 73 | 532 | (Version == 20) || | ||
| 73 | 533 | (Version == 45) || | ||
| 73 | 534 | (Version == 51)) && | ||
| 73 | 535 | IsCompressionMethodSupported(); | ||
536 | } | |||
537 | } | |||
538 | | |||
539 | /// <summary> | |||
540 | /// Force this entry to be recorded using Zip64 extensions. | |||
541 | /// </summary> | |||
542 | public void ForceZip64() | |||
543 | { | |||
| 124 | 544 | forceZip64_ = true; | ||
| 124 | 545 | } | ||
546 | | |||
547 | /// <summary> | |||
548 | /// Get a value indicating wether Zip64 extensions were forced. | |||
549 | /// </summary> | |||
550 | /// <returns>A <see cref="bool"/> value of true if Zip64 extensions have been forced on; false if not.</returns> | |||
551 | public bool IsZip64Forced() | |||
552 | { | |||
| 132042 | 553 | return forceZip64_; | ||
554 | } | |||
555 | | |||
556 | /// <summary> | |||
557 | /// Gets a value indicating if the entry requires Zip64 extensions | |||
558 | /// to store the full entry values. | |||
559 | /// </summary> | |||
560 | /// <value>A <see cref="bool"/> value of true if a local header requires Zip64 extensions; false if not.</value> | |||
561 | public bool LocalHeaderRequiresZip64 { | |||
562 | get { | |||
| 395536 | 563 | bool result = forceZip64_; | ||
564 | | |||
| 395536 | 565 | if (!result) { | ||
| 394651 | 566 | ulong trueCompressedSize = compressedSize; | ||
567 | | |||
| 394651 | 568 | if ((versionToExtract == 0) && IsCrypted) { | ||
| 68 | 569 | trueCompressedSize += ZipConstants.CryptoHeaderSize; | ||
570 | } | |||
571 | | |||
572 | // TODO: A better estimation of the true limit based on compression overhead should be used | |||
573 | // to determine when an entry should use Zip64. | |||
| 394651 | 574 | result = | ||
| 394651 | 575 | ((this.size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) && | ||
| 394651 | 576 | ((versionToExtract == 0) || (versionToExtract >= ZipConstants.VersionZip64)); | ||
577 | } | |||
578 | | |||
| 395536 | 579 | return result; | ||
580 | } | |||
581 | } | |||
582 | | |||
583 | /// <summary> | |||
584 | /// Get a value indicating wether the central directory entry requires Zip64 extensions to be stored. | |||
585 | /// </summary> | |||
586 | public bool CentralHeaderRequiresZip64 { | |||
587 | get { | |||
| 197572 | 588 | return LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); | ||
589 | } | |||
590 | } | |||
591 | | |||
592 | /// <summary> | |||
593 | /// Get/Set DosTime value. | |||
594 | /// </summary> | |||
595 | /// <remarks> | |||
596 | /// The MS-DOS date format can only represent dates between 1/1/1980 and 12/31/2107. | |||
597 | /// </remarks> | |||
598 | public long DosTime { | |||
599 | get { | |||
| 131812 | 600 | if ((known & Known.Time) == 0) { | ||
| 0 | 601 | return 0; | ||
602 | } else { | |||
| 131812 | 603 | return dosTime; | ||
604 | } | |||
605 | } | |||
606 | | |||
607 | set { | |||
608 | unchecked { | |||
| 197925 | 609 | dosTime = (uint)value; | ||
610 | } | |||
611 | | |||
| 197925 | 612 | known |= Known.Time; | ||
| 197925 | 613 | } | ||
614 | } | |||
615 | | |||
616 | /// <summary> | |||
617 | /// Gets/Sets the time of last modification of the entry. | |||
618 | /// </summary> | |||
619 | /// <remarks> | |||
620 | /// The <see cref="DosTime"></see> property is updated to match this as far as possible. | |||
621 | /// </remarks> | |||
622 | public DateTime DateTime | |||
623 | { | |||
| 2 | 624 | get { return dateTime; } | ||
625 | | |||
626 | set { | |||
| 131886 | 627 | var year = (uint)value.Year; | ||
| 131886 | 628 | var month = (uint)value.Month; | ||
| 131886 | 629 | var day = (uint)value.Day; | ||
| 131886 | 630 | var hour = (uint)value.Hour; | ||
| 131886 | 631 | var minute = (uint)value.Minute; | ||
| 131886 | 632 | var second = (uint)value.Second; | ||
633 | | |||
| 131886 | 634 | if (year < 1980) { | ||
| 0 | 635 | year = 1980; | ||
| 0 | 636 | month = 1; | ||
| 0 | 637 | day = 1; | ||
| 0 | 638 | hour = 0; | ||
| 0 | 639 | minute = 0; | ||
| 0 | 640 | second = 0; | ||
| 131886 | 641 | } else if (year > 2107) { | ||
| 0 | 642 | year = 2107; | ||
| 0 | 643 | month = 12; | ||
| 0 | 644 | day = 31; | ||
| 0 | 645 | hour = 23; | ||
| 0 | 646 | minute = 59; | ||
| 0 | 647 | second = 59; | ||
648 | } | |||
649 | | |||
| 131886 | 650 | DosTime = ((year - 1980) & 0x7f) << 25 | | ||
| 131886 | 651 | (month << 21) | | ||
| 131886 | 652 | (day << 16) | | ||
| 131886 | 653 | (hour << 11) | | ||
| 131886 | 654 | (minute << 5) | | ||
| 131886 | 655 | (second >> 1); | ||
| 131886 | 656 | } | ||
657 | } | |||
658 | | |||
659 | /// <summary> | |||
660 | /// Returns the entry name. | |||
661 | /// </summary> | |||
662 | /// <remarks> | |||
663 | /// The unix naming convention is followed. | |||
664 | /// Path components in the entry should always separated by forward slashes ('/'). | |||
665 | /// Dos device names like C: should also be removed. | |||
666 | /// See the <see cref="ZipNameTransform"/> class, or <see cref="CleanName(string)"/> | |||
667 | ///</remarks> | |||
668 | public string Name { | |||
669 | get { | |||
| 527086 | 670 | return name; | ||
671 | } | |||
672 | } | |||
673 | | |||
674 | /// <summary> | |||
675 | /// Gets/Sets the size of the uncompressed data. | |||
676 | /// </summary> | |||
677 | /// <returns> | |||
678 | /// The size or -1 if unknown. | |||
679 | /// </returns> | |||
680 | /// <remarks>Setting the size before adding an entry to an archive can help | |||
681 | /// avoid compatability problems with some archivers which dont understand Zip64 extensions.</remarks> | |||
682 | public long Size { | |||
683 | get { | |||
| 593205 | 684 | return (known & Known.Size) != 0 ? (long)size : -1L; | ||
685 | } | |||
686 | set { | |||
| 131877 | 687 | this.size = (ulong)value; | ||
| 131877 | 688 | this.known |= Known.Size; | ||
| 131877 | 689 | } | ||
690 | } | |||
691 | | |||
692 | /// <summary> | |||
693 | /// Gets/Sets the size of the compressed data. | |||
694 | /// </summary> | |||
695 | /// <returns> | |||
696 | /// The compressed entry size or -1 if unknown. | |||
697 | /// </returns> | |||
698 | public long CompressedSize { | |||
699 | get { | |||
| 462089 | 700 | return (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; | ||
701 | } | |||
702 | set { | |||
| 262984 | 703 | this.compressedSize = (ulong)value; | ||
| 262984 | 704 | this.known |= Known.CompressedSize; | ||
| 262984 | 705 | } | ||
706 | } | |||
707 | | |||
708 | /// <summary> | |||
709 | /// Gets/Sets the crc of the uncompressed data. | |||
710 | /// </summary> | |||
711 | /// <exception cref="System.ArgumentOutOfRangeException"> | |||
712 | /// Crc is not in the range 0..0xffffffffL | |||
713 | /// </exception> | |||
714 | /// <returns> | |||
715 | /// The crc value or -1 if unknown. | |||
716 | /// </returns> | |||
717 | public long Crc { | |||
718 | get { | |||
| 329635 | 719 | return (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; | ||
720 | } | |||
721 | set { | |||
| 131852 | 722 | if (((ulong)crc & 0xffffffff00000000L) != 0) { | ||
| 0 | 723 | throw new ArgumentOutOfRangeException(nameof(value)); | ||
724 | } | |||
| 131852 | 725 | this.crc = (uint)value; | ||
| 131852 | 726 | this.known |= Known.Crc; | ||
| 131852 | 727 | } | ||
728 | } | |||
729 | | |||
730 | /// <summary> | |||
731 | /// Gets/Sets the compression method. Only Deflated and Stored are supported. | |||
732 | /// </summary> | |||
733 | /// <returns> | |||
734 | /// The compression method for this entry | |||
735 | /// </returns> | |||
736 | /// <see cref="ICSharpCode.SharpZipLib.Zip.CompressionMethod.Deflated"/> | |||
737 | /// <see cref="ICSharpCode.SharpZipLib.Zip.CompressionMethod.Stored"/> | |||
738 | public CompressionMethod CompressionMethod { | |||
739 | get { | |||
| 461414 | 740 | return method; | ||
741 | } | |||
742 | | |||
743 | set { | |||
| 65837 | 744 | if (!IsCompressionMethodSupported(value)) { | ||
| 1 | 745 | throw new NotSupportedException("Compression method not supported"); | ||
746 | } | |||
| 65836 | 747 | this.method = value; | ||
| 65836 | 748 | } | ||
749 | } | |||
750 | | |||
751 | /// <summary> | |||
752 | /// Gets the compression method for outputting to the local or central header. | |||
753 | /// Returns same value as CompressionMethod except when AES encrypting, which | |||
754 | /// places 99 in the method and places the real method in the extra data. | |||
755 | /// </summary> | |||
756 | internal CompressionMethod CompressionMethodForHeader { | |||
757 | get { | |||
| 257 | 758 | return (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; | ||
759 | } | |||
760 | } | |||
761 | | |||
762 | /// <summary> | |||
763 | /// Gets/Sets the extra data. | |||
764 | /// </summary> | |||
765 | /// <exception cref="System.ArgumentOutOfRangeException"> | |||
766 | /// Extra data is longer than 64KB (0xffff) bytes. | |||
767 | /// </exception> | |||
768 | /// <returns> | |||
769 | /// Extra data or null if not set. | |||
770 | /// </returns> | |||
771 | public byte[] ExtraData { | |||
772 | | |||
773 | get { | |||
774 | // TODO: This is slightly safer but less efficient. Think about wether it should change. | |||
775 | // return (byte[]) extra.Clone(); | |||
| 263288 | 776 | return extra; | ||
777 | } | |||
778 | | |||
779 | set { | |||
| 65874 | 780 | if (value == null) { | ||
| 0 | 781 | extra = null; | ||
| 0 | 782 | } else { | ||
| 65874 | 783 | if (value.Length > 0xffff) { | ||
| 0 | 784 | throw new System.ArgumentOutOfRangeException(nameof(value)); | ||
785 | } | |||
786 | | |||
| 65874 | 787 | extra = new byte[value.Length]; | ||
| 65874 | 788 | Array.Copy(value, 0, extra, 0, value.Length); | ||
789 | } | |||
| 65874 | 790 | } | ||
791 | } | |||
792 | | |||
793 | | |||
794 | /// <summary> | |||
795 | /// For AES encrypted files returns or sets the number of bits of encryption (128, 192 or 256). | |||
796 | /// When setting, only 0 (off), 128 or 256 is supported. | |||
797 | /// </summary> | |||
798 | public int AESKeySize { | |||
799 | get { | |||
800 | // the strength (1 or 3) is in the entry header | |||
| 132508 | 801 | switch (_aesEncryptionStrength) { | ||
802 | case 0: | |||
| 132508 | 803 | return 0; // Not AES | ||
804 | case 1: | |||
| 0 | 805 | return 128; | ||
806 | case 2: | |||
| 0 | 807 | return 192; // Not used by WinZip | ||
808 | case 3: | |||
| 0 | 809 | return 256; | ||
810 | default: | |||
| 0 | 811 | throw new ZipException("Invalid AESEncryptionStrength " + _aesEncryptionStrength); | ||
812 | } | |||
813 | } | |||
814 | set { | |||
| 0 | 815 | switch (value) { | ||
816 | case 0: | |||
| 0 | 817 | _aesEncryptionStrength = 0; | ||
| 0 | 818 | break; | ||
819 | case 128: | |||
| 0 | 820 | _aesEncryptionStrength = 1; | ||
| 0 | 821 | break; | ||
822 | case 256: | |||
| 0 | 823 | _aesEncryptionStrength = 3; | ||
| 0 | 824 | break; | ||
825 | default: | |||
| 0 | 826 | throw new ZipException("AESKeySize must be 0, 128 or 256: " + value); | ||
827 | } | |||
828 | } | |||
829 | } | |||
830 | | |||
831 | /// <summary> | |||
832 | /// AES Encryption strength for storage in extra data in entry header. | |||
833 | /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit. | |||
834 | /// </summary> | |||
835 | internal byte AESEncryptionStrength { | |||
836 | get { | |||
| 0 | 837 | return (byte)_aesEncryptionStrength; | ||
838 | } | |||
839 | } | |||
840 | | |||
841 | /// <summary> | |||
842 | /// Returns the length of the salt, in bytes | |||
843 | /// </summary> | |||
844 | internal int AESSaltLen { | |||
845 | get { | |||
846 | // Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. | |||
| 0 | 847 | return AESKeySize / 16; | ||
848 | } | |||
849 | } | |||
850 | | |||
851 | /// <summary> | |||
852 | /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode) | |||
853 | /// </summary> | |||
854 | internal int AESOverheadSize { | |||
855 | get { | |||
856 | // File format: | |||
857 | // Bytes Content | |||
858 | // Variable Salt value | |||
859 | // 2 Password verification value | |||
860 | // Variable Encrypted file data | |||
861 | // 10 Authentication code | |||
| 0 | 862 | return 12 + AESSaltLen; | ||
863 | } | |||
864 | } | |||
865 | | |||
866 | /// <summary> | |||
867 | /// Process extra data fields updating the entry based on the contents. | |||
868 | /// </summary> | |||
869 | /// <param name="localHeader">True if the extra data fields should be handled | |||
870 | /// for a local header, rather than for a central header. | |||
871 | /// </param> | |||
872 | internal void ProcessExtraData(bool localHeader) | |||
873 | { | |||
| 66037 | 874 | var extraData = new ZipExtraData(this.extra); | ||
875 | | |||
| 66037 | 876 | if (extraData.Find(0x0001)) { | ||
877 | // Version required to extract is ignored here as some archivers dont set it correctly | |||
878 | // in theory it should be version 45 or higher | |||
879 | | |||
880 | // The recorded size will change but remember that this is zip64. | |||
| 123 | 881 | forceZip64_ = true; | ||
882 | | |||
| 123 | 883 | if (extraData.ValueLength < 4) { | ||
| 0 | 884 | throw new ZipException("Extra data extended Zip64 information length is invalid"); | ||
885 | } | |||
886 | | |||
887 | // (localHeader ||) was deleted, because actually there is no specific difference with reading sizes between loc | |||
888 | // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT | |||
889 | // ... | |||
890 | // 4.4 Explanation of fields | |||
891 | // ... | |||
892 | // 4.4.8 compressed size: (4 bytes) | |||
893 | // 4.4.9 uncompressed size: (4 bytes) | |||
894 | // | |||
895 | // The size of the file compressed (4.4.8) and uncompressed, | |||
896 | // (4.4.9) respectively. When a decryption header is present it | |||
897 | // will be placed in front of the file data and the value of the | |||
898 | // compressed file size will include the bytes of the decryption | |||
899 | // header. If bit 3 of the general purpose bit flag is set, | |||
900 | // these fields are set to zero in the local header and the | |||
901 | // correct values are put in the data descriptor and | |||
902 | // in the central directory. If an archive is in ZIP64 format | |||
903 | // and the value in this field is 0xFFFFFFFF, the size will be | |||
904 | // in the corresponding 8 byte ZIP64 extended information | |||
905 | // extra field. When encrypting the central directory, if the | |||
906 | // local header is not in ZIP64 format and general purpose bit | |||
907 | // flag 13 is set indicating masking, the value stored for the | |||
908 | // uncompressed size in the Local Header will be zero. | |||
909 | // | |||
910 | // Othewise there is problem with minizip implementation | |||
| 123 | 911 | if (size == uint.MaxValue) { | ||
| 123 | 912 | size = (ulong)extraData.ReadLong(); | ||
913 | } | |||
914 | | |||
| 123 | 915 | if (compressedSize == uint.MaxValue) { | ||
| 123 | 916 | compressedSize = (ulong)extraData.ReadLong(); | ||
917 | } | |||
918 | | |||
| 123 | 919 | if (!localHeader && (offset == uint.MaxValue)) { | ||
| 0 | 920 | offset = extraData.ReadLong(); | ||
921 | } | |||
922 | | |||
923 | // Disk number on which file starts is ignored | |||
| 0 | 924 | } else { | ||
| 65914 | 925 | if ( | ||
| 65914 | 926 | ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) && | ||
| 65914 | 927 | ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) | ||
| 65914 | 928 | ) { | ||
| 0 | 929 | throw new ZipException("Zip64 Extended information required but is missing."); | ||
930 | } | |||
931 | } | |||
932 | | |||
| 66037 | 933 | dateTime = GetDateTime(extraData); | ||
| 66037 | 934 | if (method == CompressionMethod.WinZipAES) { | ||
| 0 | 935 | ProcessAESExtraData(extraData); | ||
936 | } | |||
| 66037 | 937 | } | ||
938 | | |||
939 | private DateTime GetDateTime(ZipExtraData extraData) { | |||
940 | // Check for NT timestamp | |||
941 | // NOTE: Disable by default to match behavior of InfoZIP | |||
942 | #if RESPECT_NT_TIMESTAMP | |||
943 | NTTaggedData ntData = extraData.GetData<NTTaggedData>(); | |||
944 | if (ntData != null) | |||
945 | return ntData.LastModificationTime; | |||
946 | #endif | |||
947 | | |||
948 | // Check for Unix timestamp | |||
| 66037 | 949 | ExtendedUnixData unixData = extraData.GetData<ExtendedUnixData>(); | ||
| 66037 | 950 | if (unixData != null && | ||
| 66037 | 951 | // Only apply modification time, but require all other values to be present | ||
| 66037 | 952 | // This is done to match InfoZIP's behaviour | ||
| 66037 | 953 | ((unixData.Include & ExtendedUnixData.Flags.ModificationTime) != 0) && | ||
| 66037 | 954 | ((unixData.Include & ExtendedUnixData.Flags.AccessTime) != 0) && | ||
| 66037 | 955 | ((unixData.Include & ExtendedUnixData.Flags.CreateTime) != 0)) | ||
| 0 | 956 | return unixData.ModificationTime; | ||
957 | | |||
958 | // Fall back to DOS time | |||
| 66037 | 959 | uint sec = Math.Min(59, 2 * (dosTime & 0x1f)); | ||
| 66037 | 960 | uint min = Math.Min(59, (dosTime >> 5) & 0x3f); | ||
| 66037 | 961 | uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f); | ||
| 66037 | 962 | uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf))); | ||
| 66037 | 963 | uint year = ((dosTime >> 25) & 0x7f) + 1980; | ||
| 66037 | 964 | int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f))); | ||
| 66037 | 965 | return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Utc); | ||
966 | } | |||
967 | | |||
968 | // For AES the method in the entry is 99, and the real compression method is in the extradata | |||
969 | // | |||
970 | private void ProcessAESExtraData(ZipExtraData extraData) | |||
971 | { | |||
972 | | |||
| 0 | 973 | if (extraData.Find(0x9901)) { | ||
974 | // Set version and flag for Zipfile.CreateAndInitDecryptionStream | |||
| 0 | 975 | versionToExtract = ZipConstants.VERSION_AES; // Ver 5.1 = AES see "Version" getter | ||
976 | // Set StrongEncryption flag for ZipFile.CreateAndInitDecryptionStream | |||
| 0 | 977 | Flags = Flags | (int)GeneralBitFlags.StrongEncryption; | ||
978 | // | |||
979 | // Unpack AES extra data field see http://www.winzip.com/aes_info.htm | |||
| 0 | 980 | int length = extraData.ValueLength; // Data size currently 7 | ||
| 0 | 981 | if (length < 7) | ||
| 0 | 982 | throw new ZipException("AES Extra Data Length " + length + " invalid."); | ||
| 0 | 983 | int ver = extraData.ReadShort(); // Version number (1=AE-1 2=AE-2) | ||
| 0 | 984 | int vendorId = extraData.ReadShort(); // 2-character vendor ID 0x4541 = "AE" | ||
| 0 | 985 | int encrStrength = extraData.ReadByte(); // encryption strength 1 = 128 2 = 192 3 = 256 | ||
| 0 | 986 | int actualCompress = extraData.ReadShort(); // The actual compression method used to compress the file | ||
| 0 | 987 | _aesVer = ver; | ||
| 0 | 988 | _aesEncryptionStrength = encrStrength; | ||
| 0 | 989 | method = (CompressionMethod)actualCompress; | ||
| 0 | 990 | } else | ||
| 0 | 991 | throw new ZipException("AES Extra Data missing"); | ||
992 | } | |||
993 | | |||
994 | /// <summary> | |||
995 | /// Gets/Sets the entry comment. | |||
996 | /// </summary> | |||
997 | /// <exception cref="System.ArgumentOutOfRangeException"> | |||
998 | /// If comment is longer than 0xffff. | |||
999 | /// </exception> | |||
1000 | /// <returns> | |||
1001 | /// The comment or null if not set. | |||
1002 | /// </returns> | |||
1003 | /// <remarks> | |||
1004 | /// A comment is only available for entries when read via the <see cref="ZipFile"/> class. | |||
1005 | /// The <see cref="ZipInputStream"/> class doesnt have the comment data available. | |||
1006 | /// </remarks> | |||
1007 | public string Comment { | |||
1008 | get { | |||
| 131673 | 1009 | return comment; | ||
1010 | } | |||
1011 | set { | |||
1012 | // This test is strictly incorrect as the length is in characters | |||
1013 | // while the storage limit is in bytes. | |||
1014 | // While the test is partially correct in that a comment of this length or greater | |||
1015 | // is definitely invalid, shorter comments may also have an invalid length | |||
1016 | // where there are multi-byte characters | |||
1017 | // The full test is not possible here however as the code page to apply conversions with | |||
1018 | // isnt available. | |||
| 3 | 1019 | if ((value != null) && (value.Length > 0xffff)) { | ||
| 0 | 1020 | throw new ArgumentOutOfRangeException(nameof(value), "cannot exceed 65535"); | ||
1021 | } | |||
1022 | | |||
| 3 | 1023 | comment = value; | ||
| 3 | 1024 | } | ||
1025 | } | |||
1026 | | |||
1027 | /// <summary> | |||
1028 | /// Gets a value indicating if the entry is a directory. | |||
1029 | /// however. | |||
1030 | /// </summary> | |||
1031 | /// <remarks> | |||
1032 | /// A directory is determined by an entry name with a trailing slash '/'. | |||
1033 | /// The external file attributes can also indicate an entry is for a directory. | |||
1034 | /// Currently only dos/windows attributes are tested in this manner. | |||
1035 | /// The trailing slash convention should always be followed. | |||
1036 | /// </remarks> | |||
1037 | public bool IsDirectory { | |||
1038 | get { | |||
| 526334 | 1039 | int nameLength = name.Length; | ||
| 526334 | 1040 | bool result = | ||
| 526334 | 1041 | ((nameLength > 0) && | ||
| 526334 | 1042 | ((name[nameLength - 1] == '/') || (name[nameLength - 1] == '\\'))) || | ||
| 526334 | 1043 | HasDosAttributes(16) | ||
| 526334 | 1044 | ; | ||
| 27 | 1045 | return result; | ||
1046 | } | |||
1047 | } | |||
1048 | | |||
1049 | /// <summary> | |||
1050 | /// Get a value of true if the entry appears to be a file; false otherwise | |||
1051 | /// </summary> | |||
1052 | /// <remarks> | |||
1053 | /// This only takes account of DOS/Windows attributes. Other operating systems are ignored. | |||
1054 | /// For linux and others the result may be incorrect. | |||
1055 | /// </remarks> | |||
1056 | public bool IsFile { | |||
1057 | get { | |||
| 263486 | 1058 | return !IsDirectory && !HasDosAttributes(8); | ||
1059 | } | |||
1060 | } | |||
1061 | | |||
1062 | /// <summary> | |||
1063 | /// Test entry to see if data can be extracted. | |||
1064 | /// </summary> | |||
1065 | /// <returns>Returns true if data can be extracted for this entry; false otherwise.</returns> | |||
1066 | public bool IsCompressionMethodSupported() | |||
1067 | { | |||
| 132012 | 1068 | return IsCompressionMethodSupported(CompressionMethod); | ||
1069 | } | |||
1070 | | |||
1071 | #region ICloneable Members | |||
1072 | /// <summary> | |||
1073 | /// Creates a copy of this zip entry. | |||
1074 | /// </summary> | |||
1075 | /// <returns>An <see cref="Object"/> that is a copy of the current instance.</returns> | |||
1076 | public object Clone() | |||
1077 | { | |||
| 461093 | 1078 | var result = (ZipEntry)this.MemberwiseClone(); | ||
1079 | | |||
1080 | // Ensure extra data is unique if it exists. | |||
| 461093 | 1081 | if (extra != null) { | ||
| 72 | 1082 | result.extra = new byte[extra.Length]; | ||
| 72 | 1083 | Array.Copy(extra, 0, result.extra, 0, extra.Length); | ||
1084 | } | |||
1085 | | |||
| 461093 | 1086 | return result; | ||
1087 | } | |||
1088 | | |||
1089 | #endregion | |||
1090 | | |||
1091 | /// <summary> | |||
1092 | /// Gets a string representation of this ZipEntry. | |||
1093 | /// </summary> | |||
1094 | /// <returns>A readable textual representation of this <see cref="ZipEntry"/></returns> | |||
1095 | public override string ToString() | |||
1096 | { | |||
| 0 | 1097 | return name; | ||
1098 | } | |||
1099 | | |||
1100 | /// <summary> | |||
1101 | /// Test a <see cref="CompressionMethod">compression method</see> to see if this library | |||
1102 | /// supports extracting data compressed with that method | |||
1103 | /// </summary> | |||
1104 | /// <param name="method">The compression method to test.</param> | |||
1105 | /// <returns>Returns true if the compression method is supported; false otherwise</returns> | |||
1106 | public static bool IsCompressionMethodSupported(CompressionMethod method) | |||
1107 | { | |||
| 197849 | 1108 | return | ||
| 197849 | 1109 | (method == CompressionMethod.Deflated) || | ||
| 197849 | 1110 | (method == CompressionMethod.Stored); | ||
1111 | } | |||
1112 | | |||
1113 | /// <summary> | |||
1114 | /// Cleans a name making it conform to Zip file conventions. | |||
1115 | /// Devices names ('c:\') and UNC share names ('\\server\share') are removed | |||
1116 | /// and forward slashes ('\') are converted to back slashes ('/'). | |||
1117 | /// Names are made relative by trimming leading slashes which is compatible | |||
1118 | /// with the ZIP naming convention. | |||
1119 | /// </summary> | |||
1120 | /// <param name="name">The name to clean</param> | |||
1121 | /// <returns>The 'cleaned' name.</returns> | |||
1122 | /// <remarks> | |||
1123 | /// The <seealso cref="ZipNameTransform">Zip name transform</seealso> class is more flexible. | |||
1124 | /// </remarks> | |||
1125 | public static string CleanName(string name) | |||
1126 | { | |||
| 131877 | 1127 | if (name == null) { | ||
| 0 | 1128 | return string.Empty; | ||
1129 | } | |||
1130 | | |||
| 131877 | 1131 | if (Path.IsPathRooted(name)) { | ||
1132 | // NOTE: | |||
1133 | // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt | |||
| 0 | 1134 | name = name.Substring(Path.GetPathRoot(name).Length); | ||
1135 | } | |||
1136 | | |||
| 131877 | 1137 | name = name.Replace(@"\", "/"); | ||
1138 | | |||
| 131877 | 1139 | while ((name.Length > 0) && (name[0] == '/')) { | ||
| 0 | 1140 | name = name.Remove(0, 1); | ||
1141 | } | |||
| 131877 | 1142 | return name; | ||
1143 | } | |||
1144 | | |||
1145 | #region Instance Fields | |||
1146 | Known known; | |||
| 131879 | 1147 | int externalFileAttributes = -1; // contains external attributes (O/S dependant) | ||
1148 | | |||
1149 | ushort versionMadeBy; // Contains host system and version information | |||
1150 | // only relevant for central header entries | |||
1151 | | |||
1152 | string name; | |||
1153 | ulong size; | |||
1154 | ulong compressedSize; | |||
1155 | ushort versionToExtract; // Version required to extract (library handles <= 2.0) | |||
1156 | uint crc; | |||
1157 | uint dosTime; | |||
1158 | DateTime dateTime; | |||
1159 | | |||
| 131879 | 1160 | CompressionMethod method = CompressionMethod.Deflated; | ||
1161 | byte[] extra; | |||
1162 | string comment; | |||
1163 | | |||
1164 | int flags; // general purpose bit flags | |||
1165 | | |||
| 131879 | 1166 | long zipFileIndex = -1; // used by ZipFile | ||
1167 | long offset; // used by ZipFile and ZipOutputStream | |||
1168 | | |||
1169 | bool forceZip64_; | |||
1170 | byte cryptoCheckValue_; | |||
1171 | int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used. | |||
1172 | int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256 | |||
1173 | #endregion | |||
1174 | } | |||
1175 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipEntryFactory |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipEntryFactory.cs |
| Covered lines: | 51 |
| Uncovered lines: | 50 |
| Coverable lines: | 101 |
| Total lines: | 341 |
| Line coverage: | 50.4% |
| Branch coverage: | 35% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| MakeFileEntry(...) | 1 | 100 | 100 |
| MakeFileEntry(...) | 1 | 100 | 100 |
| MakeFileEntry(...) | 16 | 61.76 | 61.90 |
| MakeDirectoryEntry(...) | 1 | 0 | 0 |
| MakeDirectoryEntry(...) | 13 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using ICSharpCode.SharpZipLib.Core; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// Basic implementation of <see cref="IEntryFactory"></see> | |||
9 | /// </summary> | |||
10 | public class ZipEntryFactory : IEntryFactory | |||
11 | { | |||
12 | #region Enumerations | |||
13 | /// <summary> | |||
14 | /// Defines the possible values to be used for the <see cref="ZipEntry.DateTime"/>. | |||
15 | /// </summary> | |||
16 | public enum TimeSetting | |||
17 | { | |||
18 | /// <summary> | |||
19 | /// Use the recorded LastWriteTime value for the file. | |||
20 | /// </summary> | |||
21 | LastWriteTime, | |||
22 | /// <summary> | |||
23 | /// Use the recorded LastWriteTimeUtc value for the file | |||
24 | /// </summary> | |||
25 | LastWriteTimeUtc, | |||
26 | /// <summary> | |||
27 | /// Use the recorded CreateTime value for the file. | |||
28 | /// </summary> | |||
29 | CreateTime, | |||
30 | /// <summary> | |||
31 | /// Use the recorded CreateTimeUtc value for the file. | |||
32 | /// </summary> | |||
33 | CreateTimeUtc, | |||
34 | /// <summary> | |||
35 | /// Use the recorded LastAccessTime value for the file. | |||
36 | /// </summary> | |||
37 | LastAccessTime, | |||
38 | /// <summary> | |||
39 | /// Use the recorded LastAccessTimeUtc value for the file. | |||
40 | /// </summary> | |||
41 | LastAccessTimeUtc, | |||
42 | /// <summary> | |||
43 | /// Use a fixed value. | |||
44 | /// </summary> | |||
45 | /// <remarks>The actual <see cref="DateTime"/> value used can be | |||
46 | /// specified via the <see cref="ZipEntryFactory(DateTime)"/> constructor or | |||
47 | /// using the <see cref="ZipEntryFactory(TimeSetting)"/> with the setting set | |||
48 | /// to <see cref="TimeSetting.Fixed"/> which will use the <see cref="DateTime"/> when this class was constructed. | |||
49 | /// The <see cref="FixedDateTime"/> property can also be used to set this value.</remarks> | |||
50 | Fixed, | |||
51 | } | |||
52 | #endregion | |||
53 | | |||
54 | #region Constructors | |||
55 | /// <summary> | |||
56 | /// Initialise a new instance of the <see cref="ZipEntryFactory"/> class. | |||
57 | /// </summary> | |||
58 | /// <remarks>A default <see cref="INameTransform"/>, and the LastWriteTime for files is used.</remarks> | |||
| 95 | 59 | public ZipEntryFactory() | ||
60 | { | |||
| 95 | 61 | nameTransform_ = new ZipNameTransform(); | ||
| 95 | 62 | } | ||
63 | | |||
64 | /// <summary> | |||
65 | /// Initialise a new instance of <see cref="ZipEntryFactory"/> using the specified <see cref="TimeSetting"/> | |||
66 | /// </summary> | |||
67 | /// <param name="timeSetting">The <see cref="TimeSetting">time setting</see> to use when creating <see cref="ZipEntr | |||
| 1 | 68 | public ZipEntryFactory(TimeSetting timeSetting) | ||
69 | { | |||
| 1 | 70 | timeSetting_ = timeSetting; | ||
| 1 | 71 | nameTransform_ = new ZipNameTransform(); | ||
| 1 | 72 | } | ||
73 | | |||
74 | /// <summary> | |||
75 | /// Initialise a new instance of <see cref="ZipEntryFactory"/> using the specified <see cref="DateTime"/> | |||
76 | /// </summary> | |||
77 | /// <param name="time">The time to set all <see cref="ZipEntry.DateTime"/> values to.</param> | |||
| 1 | 78 | public ZipEntryFactory(DateTime time) | ||
79 | { | |||
| 1 | 80 | timeSetting_ = TimeSetting.Fixed; | ||
| 1 | 81 | FixedDateTime = time; | ||
| 1 | 82 | nameTransform_ = new ZipNameTransform(); | ||
| 1 | 83 | } | ||
84 | | |||
85 | #endregion | |||
86 | | |||
87 | #region Properties | |||
88 | /// <summary> | |||
89 | /// Get / set the <see cref="INameTransform"/> to be used when creating new <see cref="ZipEntry"/> values. | |||
90 | /// </summary> | |||
91 | /// <remarks> | |||
92 | /// Setting this property to null will cause a default <see cref="ZipNameTransform">name transform</see> to be used. | |||
93 | /// </remarks> | |||
94 | public INameTransform NameTransform { | |||
| 65743 | 95 | get { return nameTransform_; } | ||
96 | set { | |||
| 4 | 97 | if (value == null) { | ||
| 0 | 98 | nameTransform_ = new ZipNameTransform(); | ||
| 0 | 99 | } else { | ||
| 4 | 100 | nameTransform_ = value; | ||
101 | } | |||
| 4 | 102 | } | ||
103 | } | |||
104 | | |||
105 | /// <summary> | |||
106 | /// Get / set the <see cref="TimeSetting"/> in use. | |||
107 | /// </summary> | |||
108 | public TimeSetting Setting { | |||
| 3 | 109 | get { return timeSetting_; } | ||
| 2 | 110 | set { timeSetting_ = value; } | ||
111 | } | |||
112 | | |||
113 | /// <summary> | |||
114 | /// Get / set the <see cref="DateTime"/> value to use when <see cref="Setting"/> is set to <see cref="TimeSetting.Fi | |||
115 | /// </summary> | |||
116 | public DateTime FixedDateTime { | |||
| 5 | 117 | get { return fixedDateTime_; } | ||
118 | set { | |||
| 2 | 119 | if (value.Year < 1970) { | ||
| 0 | 120 | throw new ArgumentException("Value is too old to be valid", nameof(value)); | ||
121 | } | |||
| 2 | 122 | fixedDateTime_ = value; | ||
| 2 | 123 | } | ||
124 | } | |||
125 | | |||
126 | /// <summary> | |||
127 | /// A bitmask defining the attributes to be retrieved from the actual file. | |||
128 | /// </summary> | |||
129 | /// <remarks>The default is to get all possible attributes from the actual file.</remarks> | |||
130 | public int GetAttributes { | |||
| 3 | 131 | get { return getAttributes_; } | ||
| 0 | 132 | set { getAttributes_ = value; } | ||
133 | } | |||
134 | | |||
135 | /// <summary> | |||
136 | /// A bitmask defining which attributes are to be set on. | |||
137 | /// </summary> | |||
138 | /// <remarks>By default no attributes are set on.</remarks> | |||
139 | public int SetAttributes { | |||
| 3 | 140 | get { return setAttributes_; } | ||
| 2 | 141 | set { setAttributes_ = value; } | ||
142 | } | |||
143 | | |||
144 | /// <summary> | |||
145 | /// Get set a value indicating wether unidoce text should be set on. | |||
146 | /// </summary> | |||
147 | public bool IsUnicodeText { | |||
| 0 | 148 | get { return isUnicodeText_; } | ||
| 4 | 149 | set { isUnicodeText_ = value; } | ||
150 | } | |||
151 | | |||
152 | #endregion | |||
153 | | |||
154 | #region IEntryFactory Members | |||
155 | | |||
156 | /// <summary> | |||
157 | /// Make a new <see cref="ZipEntry"/> for a file. | |||
158 | /// </summary> | |||
159 | /// <param name="fileName">The name of the file to create a new entry for.</param> | |||
160 | /// <returns>Returns a new <see cref="ZipEntry"/> based on the <paramref name="fileName"/>.</returns> | |||
161 | public ZipEntry MakeFileEntry(string fileName) | |||
162 | { | |||
| 7 | 163 | return MakeFileEntry(fileName, null, true); | ||
164 | } | |||
165 | | |||
166 | /// <summary> | |||
167 | /// Make a new <see cref="ZipEntry"/> for a file. | |||
168 | /// </summary> | |||
169 | /// <param name="fileName">The name of the file to create a new entry for.</param> | |||
170 | /// <param name="useFileSystem">If true entry detail is retrieved from the file system if the file exists.</param> | |||
171 | /// <returns>Returns a new <see cref="ZipEntry"/> based on the <paramref name="fileName"/>.</returns> | |||
172 | public ZipEntry MakeFileEntry(string fileName, bool useFileSystem) | |||
173 | { | |||
| 165 | 174 | return MakeFileEntry(fileName, null, useFileSystem); | ||
175 | } | |||
176 | | |||
177 | /// <summary> | |||
178 | /// Make a new <see cref="ZipEntry"/> from a name. | |||
179 | /// </summary> | |||
180 | /// <param name="fileName">The name of the file to create a new entry for.</param> | |||
181 | /// <param name="entryName">An alternative name to be used for the new entry. Null if not applicable.</param> | |||
182 | /// <param name="useFileSystem">If true entry detail is retrieved from the file system if the file exists.</param> | |||
183 | /// <returns>Returns a new <see cref="ZipEntry"/> based on the <paramref name="fileName"/>.</returns> | |||
184 | public ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem) | |||
185 | { | |||
| 172 | 186 | var result = new ZipEntry(nameTransform_.TransformFile(!string.IsNullOrEmpty(entryName) ? entryName : fileName)); | ||
| 172 | 187 | result.IsUnicodeText = isUnicodeText_; | ||
188 | | |||
| 172 | 189 | int externalAttributes = 0; | ||
| 172 | 190 | bool useAttributes = (setAttributes_ != 0); | ||
191 | | |||
| 172 | 192 | FileInfo fi = null; | ||
| 172 | 193 | if (useFileSystem) { | ||
| 7 | 194 | fi = new FileInfo(fileName); | ||
195 | } | |||
196 | | |||
| 172 | 197 | if ((fi != null) && fi.Exists) { | ||
| 7 | 198 | switch (timeSetting_) { | ||
199 | case TimeSetting.CreateTime: | |||
| 0 | 200 | result.DateTime = fi.CreationTime; | ||
| 0 | 201 | break; | ||
202 | | |||
203 | case TimeSetting.CreateTimeUtc: | |||
| 0 | 204 | result.DateTime = fi.CreationTimeUtc; | ||
| 0 | 205 | break; | ||
206 | | |||
207 | case TimeSetting.LastAccessTime: | |||
| 0 | 208 | result.DateTime = fi.LastAccessTime; | ||
| 0 | 209 | break; | ||
210 | | |||
211 | case TimeSetting.LastAccessTimeUtc: | |||
| 0 | 212 | result.DateTime = fi.LastAccessTimeUtc; | ||
| 0 | 213 | break; | ||
214 | | |||
215 | case TimeSetting.LastWriteTime: | |||
| 7 | 216 | result.DateTime = fi.LastWriteTime; | ||
| 7 | 217 | break; | ||
218 | | |||
219 | case TimeSetting.LastWriteTimeUtc: | |||
| 0 | 220 | result.DateTime = fi.LastWriteTimeUtc; | ||
| 0 | 221 | break; | ||
222 | | |||
223 | case TimeSetting.Fixed: | |||
| 0 | 224 | result.DateTime = fixedDateTime_; | ||
| 0 | 225 | break; | ||
226 | | |||
227 | default: | |||
| 0 | 228 | throw new ZipException("Unhandled time setting in MakeFileEntry"); | ||
229 | } | |||
230 | | |||
| 7 | 231 | result.Size = fi.Length; | ||
232 | | |||
| 7 | 233 | useAttributes = true; | ||
| 7 | 234 | externalAttributes = ((int)fi.Attributes & getAttributes_); | ||
| 7 | 235 | } else { | ||
| 165 | 236 | if (timeSetting_ == TimeSetting.Fixed) { | ||
| 2 | 237 | result.DateTime = fixedDateTime_; | ||
238 | } | |||
239 | } | |||
240 | | |||
| 172 | 241 | if (useAttributes) { | ||
| 9 | 242 | externalAttributes |= setAttributes_; | ||
| 9 | 243 | result.ExternalFileAttributes = externalAttributes; | ||
244 | } | |||
245 | | |||
| 172 | 246 | return result; | ||
247 | } | |||
248 | | |||
249 | /// <summary> | |||
250 | /// Make a new <see cref="ZipEntry"></see> for a directory. | |||
251 | /// </summary> | |||
252 | /// <param name="directoryName">The raw untransformed name for the new directory</param> | |||
253 | /// <returns>Returns a new <see cref="ZipEntry"></see> representing a directory.</returns> | |||
254 | public ZipEntry MakeDirectoryEntry(string directoryName) | |||
255 | { | |||
| 0 | 256 | return MakeDirectoryEntry(directoryName, true); | ||
257 | } | |||
258 | | |||
259 | /// <summary> | |||
260 | /// Make a new <see cref="ZipEntry"></see> for a directory. | |||
261 | /// </summary> | |||
262 | /// <param name="directoryName">The raw untransformed name for the new directory</param> | |||
263 | /// <param name="useFileSystem">If true entry detail is retrieved from the file system if the file exists.</param> | |||
264 | /// <returns>Returns a new <see cref="ZipEntry"></see> representing a directory.</returns> | |||
265 | public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem) | |||
266 | { | |||
267 | | |||
| 0 | 268 | var result = new ZipEntry(nameTransform_.TransformDirectory(directoryName)); | ||
| 0 | 269 | result.IsUnicodeText = isUnicodeText_; | ||
| 0 | 270 | result.Size = 0; | ||
271 | | |||
| 0 | 272 | int externalAttributes = 0; | ||
273 | | |||
| 0 | 274 | DirectoryInfo di = null; | ||
275 | | |||
| 0 | 276 | if (useFileSystem) { | ||
| 0 | 277 | di = new DirectoryInfo(directoryName); | ||
278 | } | |||
279 | | |||
280 | | |||
| 0 | 281 | if ((di != null) && di.Exists) { | ||
| 0 | 282 | switch (timeSetting_) { | ||
283 | case TimeSetting.CreateTime: | |||
| 0 | 284 | result.DateTime = di.CreationTime; | ||
| 0 | 285 | break; | ||
286 | | |||
287 | case TimeSetting.CreateTimeUtc: | |||
| 0 | 288 | result.DateTime = di.CreationTimeUtc; | ||
| 0 | 289 | break; | ||
290 | | |||
291 | case TimeSetting.LastAccessTime: | |||
| 0 | 292 | result.DateTime = di.LastAccessTime; | ||
| 0 | 293 | break; | ||
294 | | |||
295 | case TimeSetting.LastAccessTimeUtc: | |||
| 0 | 296 | result.DateTime = di.LastAccessTimeUtc; | ||
| 0 | 297 | break; | ||
298 | | |||
299 | case TimeSetting.LastWriteTime: | |||
| 0 | 300 | result.DateTime = di.LastWriteTime; | ||
| 0 | 301 | break; | ||
302 | | |||
303 | case TimeSetting.LastWriteTimeUtc: | |||
| 0 | 304 | result.DateTime = di.LastWriteTimeUtc; | ||
| 0 | 305 | break; | ||
306 | | |||
307 | case TimeSetting.Fixed: | |||
| 0 | 308 | result.DateTime = fixedDateTime_; | ||
| 0 | 309 | break; | ||
310 | | |||
311 | default: | |||
| 0 | 312 | throw new ZipException("Unhandled time setting in MakeDirectoryEntry"); | ||
313 | } | |||
314 | | |||
| 0 | 315 | externalAttributes = ((int)di.Attributes & getAttributes_); | ||
| 0 | 316 | } else { | ||
| 0 | 317 | if (timeSetting_ == TimeSetting.Fixed) { | ||
| 0 | 318 | result.DateTime = fixedDateTime_; | ||
319 | } | |||
320 | } | |||
321 | | |||
322 | // Always set directory attribute on. | |||
| 0 | 323 | externalAttributes |= (setAttributes_ | 16); | ||
| 0 | 324 | result.ExternalFileAttributes = externalAttributes; | ||
325 | | |||
| 0 | 326 | return result; | ||
327 | } | |||
328 | | |||
329 | #endregion | |||
330 | | |||
331 | #region Instance Fields | |||
332 | INameTransform nameTransform_; | |||
| 97 | 333 | DateTime fixedDateTime_ = DateTime.Now; | ||
334 | TimeSetting timeSetting_; | |||
335 | bool isUnicodeText_; | |||
336 | | |||
| 97 | 337 | int getAttributes_ = -1; | ||
338 | int setAttributes_; | |||
339 | #endregion | |||
340 | } | |||
341 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipException |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipException.cs |
| Covered lines: | 2 |
| Uncovered lines: | 6 |
| Coverable lines: | 8 |
| Total lines: | 48 |
| Line coverage: | 25% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor() | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Runtime.Serialization; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip | |||
5 | { | |||
6 | /// <summary> | |||
7 | /// ZipException represents exceptions specific to Zip classes and code. | |||
8 | /// </summary> | |||
9 | [Serializable] | |||
10 | public class ZipException : SharpZipBaseException | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Deserialization constructor | |||
14 | /// </summary> | |||
15 | /// <param name="info"><see cref="SerializationInfo"/> for this constructor</param> | |||
16 | /// <param name="context"><see cref="StreamingContext"/> for this constructor</param> | |||
17 | protected ZipException(SerializationInfo info, StreamingContext context) | |||
| 0 | 18 | : base(info, context) | ||
19 | { | |||
| 0 | 20 | } | ||
21 | | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="ZipException" />. | |||
24 | /// </summary> | |||
| 0 | 25 | public ZipException() | ||
26 | { | |||
| 0 | 27 | } | ||
28 | | |||
29 | /// <summary> | |||
30 | /// Initialise a new instance of <see cref="ZipException" /> with its message string. | |||
31 | /// </summary> | |||
32 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
33 | public ZipException(string message) | |||
| 14 | 34 | : base(message) | ||
35 | { | |||
| 14 | 36 | } | ||
37 | | |||
38 | /// <summary> | |||
39 | /// Initialise a new instance of <see cref="ZipException" />. | |||
40 | /// </summary> | |||
41 | /// <param name="message">A <see cref="string"/> that describes the error.</param> | |||
42 | /// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param> | |||
43 | public ZipException(string message, Exception innerException) | |||
| 0 | 44 | : base(message, innerException) | ||
45 | { | |||
| 0 | 46 | } | ||
47 | } | |||
48 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipExtraData |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipExtraData.cs |
| Covered lines: | 121 |
| Uncovered lines: | 19 |
| Coverable lines: | 140 |
| Total lines: | 896 |
| Line coverage: | 86.4% |
| Branch coverage: | 67.2% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| .ctor(...) | 2 | 100 | 100 |
| GetEntryData() | 2 | 66.67 | 66.67 |
| Clear() | 3 | 100 | 60 |
| GetStreamForTag(...) | 2 | 100 | 66.67 |
| GetData() | 2 | 60 | 66.67 |
| Find(...) | 6 | 100 | 100 |
| AddEntry(...) | 2 | 0 | 0 |
| AddEntry(...) | 8 | 90 | 76.92 |
| StartNewEntry() | 1 | 100 | 100 |
| AddNewEntry(...) | 1 | 100 | 100 |
| AddData(...) | 1 | 100 | 100 |
| AddData(...) | 2 | 0 | 0 |
| AddLeShort(...) | 1 | 100 | 100 |
| AddLeInt(...) | 1 | 100 | 100 |
| AddLeLong(...) | 1 | 100 | 100 |
| Delete(...) | 2 | 100 | 100 |
| ReadLong() | 1 | 100 | 100 |
| ReadInt() | 1 | 100 | 100 |
| ReadShort() | 1 | 100 | 100 |
| ReadByte() | 3 | 100 | 100 |
| Skip(...) | 1 | 100 | 100 |
| ReadCheck(...) | 5 | 85.71 | 77.78 |
| ReadShortInternal() | 2 | 80 | 66.67 |
| SetShort(...) | 1 | 100 | 100 |
| Dispose() | 2 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | | |||
4 | namespace ICSharpCode.SharpZipLib.Zip | |||
5 | { | |||
6 | // TODO: Sort out wether tagged data is useful and what a good implementation might look like. | |||
7 | // Its just a sketch of an idea at the moment. | |||
8 | | |||
9 | /// <summary> | |||
10 | /// ExtraData tagged value interface. | |||
11 | /// </summary> | |||
12 | public interface ITaggedData | |||
13 | { | |||
14 | /// <summary> | |||
15 | /// Get the ID for this tagged data value. | |||
16 | /// </summary> | |||
17 | short TagID { get; } | |||
18 | | |||
19 | /// <summary> | |||
20 | /// Set the contents of this instance from the data passed. | |||
21 | /// </summary> | |||
22 | /// <param name="data">The data to extract contents from.</param> | |||
23 | /// <param name="offset">The offset to begin extracting data from.</param> | |||
24 | /// <param name="count">The number of bytes to extract.</param> | |||
25 | void SetData(byte[] data, int offset, int count); | |||
26 | | |||
27 | /// <summary> | |||
28 | /// Get the data representing this instance. | |||
29 | /// </summary> | |||
30 | /// <returns>Returns the data for this instance.</returns> | |||
31 | byte[] GetData(); | |||
32 | } | |||
33 | | |||
34 | /// <summary> | |||
35 | /// A raw binary tagged value | |||
36 | /// </summary> | |||
37 | public class RawTaggedData : ITaggedData | |||
38 | { | |||
39 | /// <summary> | |||
40 | /// Initialise a new instance. | |||
41 | /// </summary> | |||
42 | /// <param name="tag">The tag ID.</param> | |||
43 | public RawTaggedData(short tag) | |||
44 | { | |||
45 | _tag = tag; | |||
46 | } | |||
47 | | |||
48 | #region ITaggedData Members | |||
49 | | |||
50 | /// <summary> | |||
51 | /// Get the ID for this tagged data value. | |||
52 | /// </summary> | |||
53 | public short TagID { | |||
54 | get { return _tag; } | |||
55 | set { _tag = value; } | |||
56 | } | |||
57 | | |||
58 | /// <summary> | |||
59 | /// Set the data from the raw values provided. | |||
60 | /// </summary> | |||
61 | /// <param name="data">The raw data to extract values from.</param> | |||
62 | /// <param name="offset">The index to start extracting values from.</param> | |||
63 | /// <param name="count">The number of bytes available.</param> | |||
64 | public void SetData(byte[] data, int offset, int count) | |||
65 | { | |||
66 | if (data == null) { | |||
67 | throw new ArgumentNullException(nameof(data)); | |||
68 | } | |||
69 | | |||
70 | _data = new byte[count]; | |||
71 | Array.Copy(data, offset, _data, 0, count); | |||
72 | } | |||
73 | | |||
74 | /// <summary> | |||
75 | /// Get the binary data representing this instance. | |||
76 | /// </summary> | |||
77 | /// <returns>The raw binary data representing this instance.</returns> | |||
78 | public byte[] GetData() | |||
79 | { | |||
80 | return _data; | |||
81 | } | |||
82 | | |||
83 | #endregion | |||
84 | | |||
85 | /// <summary> | |||
86 | /// Get /set the binary data representing this instance. | |||
87 | /// </summary> | |||
88 | /// <returns>The raw binary data representing this instance.</returns> | |||
89 | public byte[] Data { | |||
90 | get { return _data; } | |||
91 | set { _data = value; } | |||
92 | } | |||
93 | | |||
94 | #region Instance Fields | |||
95 | /// <summary> | |||
96 | /// The tag ID for this instance. | |||
97 | /// </summary> | |||
98 | short _tag; | |||
99 | | |||
100 | byte[] _data; | |||
101 | #endregion | |||
102 | } | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Class representing extended unix date time values. | |||
106 | /// </summary> | |||
107 | public class ExtendedUnixData : ITaggedData | |||
108 | { | |||
109 | /// <summary> | |||
110 | /// Flags indicate which values are included in this instance. | |||
111 | /// </summary> | |||
112 | [Flags] | |||
113 | public enum Flags : byte | |||
114 | { | |||
115 | /// <summary> | |||
116 | /// The modification time is included | |||
117 | /// </summary> | |||
118 | ModificationTime = 0x01, | |||
119 | | |||
120 | /// <summary> | |||
121 | /// The access time is included | |||
122 | /// </summary> | |||
123 | AccessTime = 0x02, | |||
124 | | |||
125 | /// <summary> | |||
126 | /// The create time is included. | |||
127 | /// </summary> | |||
128 | CreateTime = 0x04, | |||
129 | } | |||
130 | | |||
131 | #region ITaggedData Members | |||
132 | | |||
133 | /// <summary> | |||
134 | /// Get the ID | |||
135 | /// </summary> | |||
136 | public short TagID { | |||
137 | get { return 0x5455; } | |||
138 | } | |||
139 | | |||
140 | /// <summary> | |||
141 | /// Set the data from the raw values provided. | |||
142 | /// </summary> | |||
143 | /// <param name="data">The raw data to extract values from.</param> | |||
144 | /// <param name="index">The index to start extracting values from.</param> | |||
145 | /// <param name="count">The number of bytes available.</param> | |||
146 | public void SetData(byte[] data, int index, int count) | |||
147 | { | |||
148 | using (MemoryStream ms = new MemoryStream(data, index, count, false)) | |||
149 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
150 | // bit 0 if set, modification time is present | |||
151 | // bit 1 if set, access time is present | |||
152 | // bit 2 if set, creation time is present | |||
153 | | |||
154 | _flags = (Flags)helperStream.ReadByte(); | |||
155 | if (((_flags & Flags.ModificationTime) != 0)) | |||
156 | { | |||
157 | int iTime = helperStream.ReadLEInt(); | |||
158 | | |||
159 | _modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
160 | new TimeSpan(0, 0, 0, iTime, 0); | |||
161 | | |||
162 | // Central-header version is truncated after modification time | |||
163 | if (count <= 5) return; | |||
164 | } | |||
165 | | |||
166 | if ((_flags & Flags.AccessTime) != 0) { | |||
167 | int iTime = helperStream.ReadLEInt(); | |||
168 | | |||
169 | _lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
170 | new TimeSpan(0, 0, 0, iTime, 0); | |||
171 | } | |||
172 | | |||
173 | if ((_flags & Flags.CreateTime) != 0) { | |||
174 | int iTime = helperStream.ReadLEInt(); | |||
175 | | |||
176 | _createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + | |||
177 | new TimeSpan(0, 0, 0, iTime, 0); | |||
178 | } | |||
179 | } | |||
180 | } | |||
181 | | |||
182 | /// <summary> | |||
183 | /// Get the binary data representing this instance. | |||
184 | /// </summary> | |||
185 | /// <returns>The raw binary data representing this instance.</returns> | |||
186 | public byte[] GetData() | |||
187 | { | |||
188 | using (MemoryStream ms = new MemoryStream()) | |||
189 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
190 | helperStream.IsStreamOwner = false; | |||
191 | helperStream.WriteByte((byte)_flags); // Flags | |||
192 | if ((_flags & Flags.ModificationTime) != 0) { | |||
193 | TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
194 | var seconds = (int)span.TotalSeconds; | |||
195 | helperStream.WriteLEInt(seconds); | |||
196 | } | |||
197 | if ((_flags & Flags.AccessTime) != 0) { | |||
198 | TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
199 | var seconds = (int)span.TotalSeconds; | |||
200 | helperStream.WriteLEInt(seconds); | |||
201 | } | |||
202 | if ((_flags & Flags.CreateTime) != 0) { | |||
203 | TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | |||
204 | var seconds = (int)span.TotalSeconds; | |||
205 | helperStream.WriteLEInt(seconds); | |||
206 | } | |||
207 | return ms.ToArray(); | |||
208 | } | |||
209 | } | |||
210 | | |||
211 | #endregion | |||
212 | | |||
213 | /// <summary> | |||
214 | /// Test a <see cref="DateTime"> value to see if is valid and can be represented here.</see> | |||
215 | /// </summary> | |||
216 | /// <param name="value">The <see cref="DateTime">value</see> to test.</param> | |||
217 | /// <returns>Returns true if the value is valid and can be represented; false if not.</returns> | |||
218 | /// <remarks>The standard Unix time is a signed integer data type, directly encoding the Unix time number, | |||
219 | /// which is the number of seconds since 1970-01-01. | |||
220 | /// Being 32 bits means the values here cover a range of about 136 years. | |||
221 | /// The minimum representable time is 1901-12-13 20:45:52, | |||
222 | /// and the maximum representable time is 2038-01-19 03:14:07. | |||
223 | /// </remarks> | |||
224 | public static bool IsValidValue(DateTime value) | |||
225 | { | |||
226 | return ((value >= new DateTime(1901, 12, 13, 20, 45, 52)) || | |||
227 | (value <= new DateTime(2038, 1, 19, 03, 14, 07))); | |||
228 | } | |||
229 | | |||
230 | /// <summary> | |||
231 | /// Get /set the Modification Time | |||
232 | /// </summary> | |||
233 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
234 | /// <seealso cref="IsValidValue"></seealso> | |||
235 | public DateTime ModificationTime { | |||
236 | get { return _modificationTime; } | |||
237 | set { | |||
238 | if (!IsValidValue(value)) { | |||
239 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
240 | } | |||
241 | | |||
242 | _flags |= Flags.ModificationTime; | |||
243 | _modificationTime = value; | |||
244 | } | |||
245 | } | |||
246 | | |||
247 | /// <summary> | |||
248 | /// Get / set the Access Time | |||
249 | /// </summary> | |||
250 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
251 | /// <seealso cref="IsValidValue"></seealso> | |||
252 | public DateTime AccessTime { | |||
253 | get { return _lastAccessTime; } | |||
254 | set { | |||
255 | if (!IsValidValue(value)) { | |||
256 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
257 | } | |||
258 | | |||
259 | _flags |= Flags.AccessTime; | |||
260 | _lastAccessTime = value; | |||
261 | } | |||
262 | } | |||
263 | | |||
264 | /// <summary> | |||
265 | /// Get / Set the Create Time | |||
266 | /// </summary> | |||
267 | /// <exception cref="ArgumentOutOfRangeException"></exception> | |||
268 | /// <seealso cref="IsValidValue"></seealso> | |||
269 | public DateTime CreateTime { | |||
270 | get { return _createTime; } | |||
271 | set { | |||
272 | if (!IsValidValue(value)) { | |||
273 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
274 | } | |||
275 | | |||
276 | _flags |= Flags.CreateTime; | |||
277 | _createTime = value; | |||
278 | } | |||
279 | } | |||
280 | | |||
281 | /// <summary> | |||
282 | /// Get/set the <see cref="Flags">values</see> to include. | |||
283 | /// </summary> | |||
284 | public Flags Include | |||
285 | { | |||
286 | get { return _flags; } | |||
287 | set { _flags = value; } | |||
288 | } | |||
289 | | |||
290 | #region Instance Fields | |||
291 | Flags _flags; | |||
292 | DateTime _modificationTime = new DateTime(1970, 1, 1); | |||
293 | DateTime _lastAccessTime = new DateTime(1970, 1, 1); | |||
294 | DateTime _createTime = new DateTime(1970, 1, 1); | |||
295 | #endregion | |||
296 | } | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Class handling NT date time values. | |||
300 | /// </summary> | |||
301 | public class NTTaggedData : ITaggedData | |||
302 | { | |||
303 | /// <summary> | |||
304 | /// Get the ID for this tagged data value. | |||
305 | /// </summary> | |||
306 | public short TagID { | |||
307 | get { return 10; } | |||
308 | } | |||
309 | | |||
310 | /// <summary> | |||
311 | /// Set the data from the raw values provided. | |||
312 | /// </summary> | |||
313 | /// <param name="data">The raw data to extract values from.</param> | |||
314 | /// <param name="index">The index to start extracting values from.</param> | |||
315 | /// <param name="count">The number of bytes available.</param> | |||
316 | public void SetData(byte[] data, int index, int count) | |||
317 | { | |||
318 | using (MemoryStream ms = new MemoryStream(data, index, count, false)) | |||
319 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
320 | helperStream.ReadLEInt(); // Reserved | |||
321 | while (helperStream.Position < helperStream.Length) { | |||
322 | int ntfsTag = helperStream.ReadLEShort(); | |||
323 | int ntfsLength = helperStream.ReadLEShort(); | |||
324 | if (ntfsTag == 1) { | |||
325 | if (ntfsLength >= 24) { | |||
326 | long lastModificationTicks = helperStream.ReadLELong(); | |||
327 | _lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks); | |||
328 | | |||
329 | long lastAccessTicks = helperStream.ReadLELong(); | |||
330 | _lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks); | |||
331 | | |||
332 | long createTimeTicks = helperStream.ReadLELong(); | |||
333 | _createTime = DateTime.FromFileTimeUtc(createTimeTicks); | |||
334 | } | |||
335 | break; | |||
336 | } else { | |||
337 | // An unknown NTFS tag so simply skip it. | |||
338 | helperStream.Seek(ntfsLength, SeekOrigin.Current); | |||
339 | } | |||
340 | } | |||
341 | } | |||
342 | } | |||
343 | | |||
344 | /// <summary> | |||
345 | /// Get the binary data representing this instance. | |||
346 | /// </summary> | |||
347 | /// <returns>The raw binary data representing this instance.</returns> | |||
348 | public byte[] GetData() | |||
349 | { | |||
350 | using (MemoryStream ms = new MemoryStream()) | |||
351 | using (ZipHelperStream helperStream = new ZipHelperStream(ms)) { | |||
352 | helperStream.IsStreamOwner = false; | |||
353 | helperStream.WriteLEInt(0); // Reserved | |||
354 | helperStream.WriteLEShort(1); // Tag | |||
355 | helperStream.WriteLEShort(24); // Length = 3 x 8. | |||
356 | helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc()); | |||
357 | helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc()); | |||
358 | helperStream.WriteLELong(_createTime.ToFileTimeUtc()); | |||
359 | return ms.ToArray(); | |||
360 | } | |||
361 | } | |||
362 | | |||
363 | /// <summary> | |||
364 | /// Test a <see cref="DateTime"> valuie to see if is valid and can be represented here.</see> | |||
365 | /// </summary> | |||
366 | /// <param name="value">The <see cref="DateTime">value</see> to test.</param> | |||
367 | /// <returns>Returns true if the value is valid and can be represented; false if not.</returns> | |||
368 | /// <remarks> | |||
369 | /// NTFS filetimes are 64-bit unsigned integers, stored in Intel | |||
370 | /// (least significant byte first) byte order. They determine the | |||
371 | /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch", | |||
372 | /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit | |||
373 | /// </remarks> | |||
374 | public static bool IsValidValue(DateTime value) | |||
375 | { | |||
376 | bool result = true; | |||
377 | try { | |||
378 | value.ToFileTimeUtc(); | |||
379 | } catch { | |||
380 | result = false; | |||
381 | } | |||
382 | return result; | |||
383 | } | |||
384 | | |||
385 | /// <summary> | |||
386 | /// Get/set the <see cref="DateTime">last modification time</see>. | |||
387 | /// </summary> | |||
388 | public DateTime LastModificationTime { | |||
389 | get { return _lastModificationTime; } | |||
390 | set { | |||
391 | if (!IsValidValue(value)) { | |||
392 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
393 | } | |||
394 | _lastModificationTime = value; | |||
395 | } | |||
396 | } | |||
397 | | |||
398 | /// <summary> | |||
399 | /// Get /set the <see cref="DateTime">create time</see> | |||
400 | /// </summary> | |||
401 | public DateTime CreateTime { | |||
402 | get { return _createTime; } | |||
403 | set { | |||
404 | if (!IsValidValue(value)) { | |||
405 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
406 | } | |||
407 | _createTime = value; | |||
408 | } | |||
409 | } | |||
410 | | |||
411 | /// <summary> | |||
412 | /// Get /set the <see cref="DateTime">last access time</see>. | |||
413 | /// </summary> | |||
414 | public DateTime LastAccessTime { | |||
415 | get { return _lastAccessTime; } | |||
416 | set { | |||
417 | if (!IsValidValue(value)) { | |||
418 | throw new ArgumentOutOfRangeException(nameof(value)); | |||
419 | } | |||
420 | _lastAccessTime = value; | |||
421 | } | |||
422 | } | |||
423 | | |||
424 | #region Instance Fields | |||
425 | DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0); | |||
426 | DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0); | |||
427 | DateTime _createTime = DateTime.FromFileTimeUtc(0); | |||
428 | #endregion | |||
429 | } | |||
430 | | |||
431 | /// <summary> | |||
432 | /// A factory that creates <see cref="ITaggedData">tagged data</see> instances. | |||
433 | /// </summary> | |||
434 | interface ITaggedDataFactory | |||
435 | { | |||
436 | /// <summary> | |||
437 | /// Get data for a specific tag value. | |||
438 | /// </summary> | |||
439 | /// <param name="tag">The tag ID to find.</param> | |||
440 | /// <param name="data">The data to search.</param> | |||
441 | /// <param name="offset">The offset to begin extracting data from.</param> | |||
442 | /// <param name="count">The number of bytes to extract.</param> | |||
443 | /// <returns>The located <see cref="ITaggedData">value found</see>, or null if not found.</returns> | |||
444 | ITaggedData Create(short tag, byte[] data, int offset, int count); | |||
445 | } | |||
446 | | |||
447 | /// | |||
448 | /// <summary> | |||
449 | /// A class to handle the extra data field for Zip entries | |||
450 | /// </summary> | |||
451 | /// <remarks> | |||
452 | /// Extra data contains 0 or more values each prefixed by a header tag and length. | |||
453 | /// They contain zero or more bytes of actual data. | |||
454 | /// The data is held internally using a copy on write strategy. This is more efficient but | |||
455 | /// means that for extra data created by passing in data can have the values modified by the caller | |||
456 | /// in some circumstances. | |||
457 | /// </remarks> | |||
458 | sealed public class ZipExtraData : IDisposable | |||
459 | { | |||
460 | #region Constructors | |||
461 | /// <summary> | |||
462 | /// Initialise a default instance. | |||
463 | /// </summary> | |||
| 5 | 464 | public ZipExtraData() | ||
465 | { | |||
| 5 | 466 | Clear(); | ||
| 5 | 467 | } | ||
468 | | |||
469 | /// <summary> | |||
470 | /// Initialise with known extra data. | |||
471 | /// </summary> | |||
472 | /// <param name="data">The extra data.</param> | |||
| 329692 | 473 | public ZipExtraData(byte[] data) | ||
474 | { | |||
| 329692 | 475 | if (data == null) { | ||
| 131944 | 476 | _data = new byte[0]; | ||
| 131944 | 477 | } else { | ||
| 197748 | 478 | _data = data; | ||
479 | } | |||
| 197748 | 480 | } | ||
481 | #endregion | |||
482 | | |||
483 | /// <summary> | |||
484 | /// Get the raw extra data value | |||
485 | /// </summary> | |||
486 | /// <returns>Returns the raw byte[] extra data this instance represents.</returns> | |||
487 | public byte[] GetEntryData() | |||
488 | { | |||
| 131788 | 489 | if (Length > ushort.MaxValue) { | ||
| 0 | 490 | throw new ZipException("Data exceeds maximum length"); | ||
491 | } | |||
492 | | |||
| 131788 | 493 | return (byte[])_data.Clone(); | ||
494 | } | |||
495 | | |||
496 | /// <summary> | |||
497 | /// Clear the stored data. | |||
498 | /// </summary> | |||
499 | public void Clear() | |||
500 | { | |||
| 5 | 501 | if ((_data == null) || (_data.Length != 0)) { | ||
| 5 | 502 | _data = new byte[0]; | ||
503 | } | |||
| 5 | 504 | } | ||
505 | | |||
506 | /// <summary> | |||
507 | /// Gets the current extra data length. | |||
508 | /// </summary> | |||
509 | public int Length { | |||
| 131811 | 510 | get { return _data.Length; } | ||
511 | } | |||
512 | | |||
513 | /// <summary> | |||
514 | /// Get a read-only <see cref="Stream"/> for the associated tag. | |||
515 | /// </summary> | |||
516 | /// <param name="tag">The tag to locate data for.</param> | |||
517 | /// <returns>Returns a <see cref="Stream"/> containing tag data or null if no tag was found.</returns> | |||
518 | public Stream GetStreamForTag(int tag) | |||
519 | { | |||
| 4 | 520 | Stream result = null; | ||
| 4 | 521 | if (Find(tag)) { | ||
| 4 | 522 | result = new MemoryStream(_data, _index, _readValueLength, false); | ||
523 | } | |||
| 4 | 524 | return result; | ||
525 | } | |||
526 | | |||
527 | /// <summary> | |||
528 | /// Get the <see cref="ITaggedData">tagged data</see> for a tag. | |||
529 | /// </summary> | |||
530 | /// <typeparam name="T">The tag to search for.</typeparam> | |||
531 | /// <returns>Returns a <see cref="ITaggedData">tagged value</see> or null if none found.</returns> | |||
532 | public T GetData<T>() | |||
533 | where T : class, ITaggedData, new() | |||
534 | { | |||
| 66037 | 535 | T result = new T(); | ||
| 66037 | 536 | if (Find(result.TagID)) | ||
537 | { | |||
| 0 | 538 | result.SetData(_data, _readValueStart, _readValueLength); | ||
| 0 | 539 | return result; | ||
540 | } | |||
| 66037 | 541 | else return null; | ||
542 | } | |||
543 | | |||
544 | /// <summary> | |||
545 | /// Get the length of the last value found by <see cref="Find"/> | |||
546 | /// </summary> | |||
547 | /// <remarks>This is only valid if <see cref="Find"/> has previously returned true.</remarks> | |||
548 | public int ValueLength { | |||
| 146 | 549 | get { return _readValueLength; } | ||
550 | } | |||
551 | | |||
552 | /// <summary> | |||
553 | /// Get the index for the current read value. | |||
554 | /// </summary> | |||
555 | /// <remarks>This is only valid if <see cref="Find"/> has previously returned true. | |||
556 | /// Initially the result will be the index of the first byte of actual data. The value is updated after calls to | |||
557 | /// <see cref="ReadInt"/>, <see cref="ReadShort"/> and <see cref="ReadLong"/>. </remarks> | |||
558 | public int CurrentReadIndex { | |||
| 101 | 559 | get { return _index; } | ||
560 | } | |||
561 | | |||
562 | /// <summary> | |||
563 | /// Get the number of bytes remaining to be read for the current value; | |||
564 | /// </summary> | |||
565 | public int UnreadCount { | |||
566 | get { | |||
| 16 | 567 | if ((_readValueStart > _data.Length) || | ||
| 16 | 568 | (_readValueStart < 4)) { | ||
| 0 | 569 | throw new ZipException("Find must be called before calling a Read method"); | ||
570 | } | |||
571 | | |||
| 16 | 572 | return _readValueStart + _readValueLength - _index; | ||
573 | } | |||
574 | } | |||
575 | | |||
576 | /// <summary> | |||
577 | /// Find an extra data value | |||
578 | /// </summary> | |||
579 | /// <param name="headerID">The identifier for the value to find.</param> | |||
580 | /// <returns>Returns true if the value was found; false otherwise.</returns> | |||
581 | public bool Find(int headerID) | |||
582 | { | |||
| 396136 | 583 | _readValueStart = _data.Length; | ||
| 396136 | 584 | _readValueLength = 0; | ||
| 396136 | 585 | _index = 0; | ||
586 | | |||
| 396136 | 587 | int localLength = _readValueStart; | ||
| 396136 | 588 | int localTag = headerID - 1; | ||
589 | | |||
590 | // Trailing bytes that cant make up an entry (as there arent enough | |||
591 | // bytes for a tag and length) are ignored! | |||
| 396614 | 592 | while ((localTag != headerID) && (_index < _data.Length - 3)) { | ||
| 478 | 593 | localTag = ReadShortInternal(); | ||
| 478 | 594 | localLength = ReadShortInternal(); | ||
| 478 | 595 | if (localTag != headerID) { | ||
| 148 | 596 | _index += localLength; | ||
597 | } | |||
598 | } | |||
599 | | |||
| 396136 | 600 | bool result = (localTag == headerID) && ((_index + localLength) <= _data.Length); | ||
601 | | |||
| 396136 | 602 | if (result) { | ||
| 330 | 603 | _readValueStart = _index; | ||
| 330 | 604 | _readValueLength = localLength; | ||
605 | } | |||
606 | | |||
| 396136 | 607 | return result; | ||
608 | } | |||
609 | | |||
610 | /// <summary> | |||
611 | /// Add a new entry to extra data. | |||
612 | /// </summary> | |||
613 | /// <param name="taggedData">The <see cref="ITaggedData"/> value to add.</param> | |||
614 | public void AddEntry(ITaggedData taggedData) | |||
615 | { | |||
| 0 | 616 | if (taggedData == null) { | ||
| 0 | 617 | throw new ArgumentNullException(nameof(taggedData)); | ||
618 | } | |||
| 0 | 619 | AddEntry(taggedData.TagID, taggedData.GetData()); | ||
| 0 | 620 | } | ||
621 | | |||
622 | /// <summary> | |||
623 | /// Add a new entry to extra data | |||
624 | /// </summary> | |||
625 | /// <param name="headerID">The ID for this entry.</param> | |||
626 | /// <param name="fieldData">The data to add.</param> | |||
627 | /// <remarks>If the ID already exists its contents are replaced.</remarks> | |||
628 | public void AddEntry(int headerID, byte[] fieldData) | |||
629 | { | |||
| 261 | 630 | if ((headerID > ushort.MaxValue) || (headerID < 0)) { | ||
| 0 | 631 | throw new ArgumentOutOfRangeException(nameof(headerID)); | ||
632 | } | |||
633 | | |||
| 261 | 634 | int addLength = (fieldData == null) ? 0 : fieldData.Length; | ||
635 | | |||
| 261 | 636 | if (addLength > ushort.MaxValue) { | ||
| 0 | 637 | throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length"); | ||
638 | } | |||
639 | | |||
640 | // Test for new length before adjusting data. | |||
| 261 | 641 | int newLength = _data.Length + addLength + 4; | ||
642 | | |||
| 261 | 643 | if (Find(headerID)) { | ||
| 4 | 644 | newLength -= (ValueLength + 4); | ||
645 | } | |||
646 | | |||
| 261 | 647 | if (newLength > ushort.MaxValue) { | ||
| 2 | 648 | throw new ZipException("Data exceeds maximum length"); | ||
649 | } | |||
650 | | |||
| 259 | 651 | Delete(headerID); | ||
652 | | |||
| 259 | 653 | byte[] newData = new byte[newLength]; | ||
| 259 | 654 | _data.CopyTo(newData, 0); | ||
| 259 | 655 | int index = _data.Length; | ||
| 259 | 656 | _data = newData; | ||
| 259 | 657 | SetShort(ref index, headerID); | ||
| 259 | 658 | SetShort(ref index, addLength); | ||
| 259 | 659 | if (fieldData != null) { | ||
| 257 | 660 | fieldData.CopyTo(newData, index); | ||
661 | } | |||
| 259 | 662 | } | ||
663 | | |||
664 | /// <summary> | |||
665 | /// Start adding a new entry. | |||
666 | /// </summary> | |||
667 | /// <remarks>Add data using <see cref="AddData(byte[])"/>, <see cref="AddLeShort"/>, <see cref="AddLeInt"/>, or <see | |||
668 | /// The new entry is completed and actually added by calling <see cref="AddNewEntry"/></remarks> | |||
669 | /// <seealso cref="AddEntry(ITaggedData)"/> | |||
670 | public void StartNewEntry() | |||
671 | { | |||
| 249 | 672 | _newEntry = new MemoryStream(); | ||
| 249 | 673 | } | ||
674 | | |||
675 | /// <summary> | |||
676 | /// Add entry data added since <see cref="StartNewEntry"/> using the ID passed. | |||
677 | /// </summary> | |||
678 | /// <param name="headerID">The identifier to use for this entry.</param> | |||
679 | public void AddNewEntry(int headerID) | |||
680 | { | |||
| 249 | 681 | byte[] newData = _newEntry.ToArray(); | ||
| 249 | 682 | _newEntry = null; | ||
| 249 | 683 | AddEntry(headerID, newData); | ||
| 249 | 684 | } | ||
685 | | |||
686 | /// <summary> | |||
687 | /// Add a byte of data to the pending new entry. | |||
688 | /// </summary> | |||
689 | /// <param name="data">The byte to add.</param> | |||
690 | /// <seealso cref="StartNewEntry"/> | |||
691 | public void AddData(byte data) | |||
692 | { | |||
| 1 | 693 | _newEntry.WriteByte(data); | ||
| 1 | 694 | } | ||
695 | | |||
696 | /// <summary> | |||
697 | /// Add data to a pending new entry. | |||
698 | /// </summary> | |||
699 | /// <param name="data">The data to add.</param> | |||
700 | /// <seealso cref="StartNewEntry"/> | |||
701 | public void AddData(byte[] data) | |||
702 | { | |||
| 0 | 703 | if (data == null) { | ||
| 0 | 704 | throw new ArgumentNullException(nameof(data)); | ||
705 | } | |||
706 | | |||
| 0 | 707 | _newEntry.Write(data, 0, data.Length); | ||
| 0 | 708 | } | ||
709 | | |||
710 | /// <summary> | |||
711 | /// Add a short value in little endian order to the pending new entry. | |||
712 | /// </summary> | |||
713 | /// <param name="toAdd">The data to add.</param> | |||
714 | /// <seealso cref="StartNewEntry"/> | |||
715 | public void AddLeShort(int toAdd) | |||
716 | { | |||
717 | unchecked { | |||
| 2004 | 718 | _newEntry.WriteByte((byte)toAdd); | ||
| 2004 | 719 | _newEntry.WriteByte((byte)(toAdd >> 8)); | ||
720 | } | |||
| 2004 | 721 | } | ||
722 | | |||
723 | /// <summary> | |||
724 | /// Add an integer value in little endian order to the pending new entry. | |||
725 | /// </summary> | |||
726 | /// <param name="toAdd">The data to add.</param> | |||
727 | /// <seealso cref="StartNewEntry"/> | |||
728 | public void AddLeInt(int toAdd) | |||
729 | { | |||
730 | unchecked { | |||
| 1002 | 731 | AddLeShort((short)toAdd); | ||
| 1002 | 732 | AddLeShort((short)(toAdd >> 16)); | ||
733 | } | |||
| 1002 | 734 | } | ||
735 | | |||
736 | /// <summary> | |||
737 | /// Add a long value in little endian order to the pending new entry. | |||
738 | /// </summary> | |||
739 | /// <param name="toAdd">The data to add.</param> | |||
740 | /// <seealso cref="StartNewEntry"/> | |||
741 | public void AddLeLong(long toAdd) | |||
742 | { | |||
743 | unchecked { | |||
| 501 | 744 | AddLeInt((int)(toAdd & 0xffffffff)); | ||
| 501 | 745 | AddLeInt((int)(toAdd >> 32)); | ||
746 | } | |||
| 501 | 747 | } | ||
748 | | |||
749 | /// <summary> | |||
750 | /// Delete an extra data field. | |||
751 | /// </summary> | |||
752 | /// <param name="headerID">The identifier of the field to delete.</param> | |||
753 | /// <returns>Returns true if the field was found and deleted.</returns> | |||
754 | public bool Delete(int headerID) | |||
755 | { | |||
| 131791 | 756 | bool result = false; | ||
757 | | |||
| 131791 | 758 | if (Find(headerID)) { | ||
| 6 | 759 | result = true; | ||
| 6 | 760 | int trueStart = _readValueStart - 4; | ||
761 | | |||
| 6 | 762 | byte[] newData = new byte[_data.Length - (ValueLength + 4)]; | ||
| 6 | 763 | Array.Copy(_data, 0, newData, 0, trueStart); | ||
764 | | |||
| 6 | 765 | int trueEnd = trueStart + ValueLength + 4; | ||
| 6 | 766 | Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd); | ||
| 6 | 767 | _data = newData; | ||
768 | } | |||
| 131791 | 769 | return result; | ||
770 | } | |||
771 | | |||
772 | #region Reading Support | |||
773 | /// <summary> | |||
774 | /// Read a long in little endian form from the last <see cref="Find">found</see> data value | |||
775 | /// </summary> | |||
776 | /// <returns>Returns the long value read.</returns> | |||
777 | public long ReadLong() | |||
778 | { | |||
| 357 | 779 | ReadCheck(8); | ||
| 354 | 780 | return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32); | ||
781 | } | |||
782 | | |||
783 | /// <summary> | |||
784 | /// Read an integer in little endian form from the last <see cref="Find">found</see> data value. | |||
785 | /// </summary> | |||
786 | /// <returns>Returns the integer read.</returns> | |||
787 | public int ReadInt() | |||
788 | { | |||
| 712 | 789 | ReadCheck(4); | ||
790 | | |||
| 709 | 791 | int result = _data[_index] + (_data[_index + 1] << 8) + | ||
| 709 | 792 | (_data[_index + 2] << 16) + (_data[_index + 3] << 24); | ||
| 709 | 793 | _index += 4; | ||
| 709 | 794 | return result; | ||
795 | } | |||
796 | | |||
797 | /// <summary> | |||
798 | /// Read a short value in little endian form from the last <see cref="Find">found</see> data value. | |||
799 | /// </summary> | |||
800 | /// <returns>Returns the short value read.</returns> | |||
801 | public int ReadShort() | |||
802 | { | |||
| 4 | 803 | ReadCheck(2); | ||
| 1 | 804 | int result = _data[_index] + (_data[_index + 1] << 8); | ||
| 1 | 805 | _index += 2; | ||
| 1 | 806 | return result; | ||
807 | } | |||
808 | | |||
809 | /// <summary> | |||
810 | /// Read a byte from an extra data | |||
811 | /// </summary> | |||
812 | /// <returns>The byte value read or -1 if the end of data has been reached.</returns> | |||
813 | public int ReadByte() | |||
814 | { | |||
| 26 | 815 | int result = -1; | ||
| 26 | 816 | if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index)) { | ||
| 19 | 817 | result = _data[_index]; | ||
| 19 | 818 | _index += 1; | ||
819 | } | |||
| 26 | 820 | return result; | ||
821 | } | |||
822 | | |||
823 | /// <summary> | |||
824 | /// Skip data during reading. | |||
825 | /// </summary> | |||
826 | /// <param name="amount">The number of bytes to skip.</param> | |||
827 | public void Skip(int amount) | |||
828 | { | |||
| 6 | 829 | ReadCheck(amount); | ||
| 4 | 830 | _index += amount; | ||
| 4 | 831 | } | ||
832 | | |||
833 | void ReadCheck(int length) | |||
834 | { | |||
| 1079 | 835 | if ((_readValueStart > _data.Length) || | ||
| 1079 | 836 | (_readValueStart < 4)) { | ||
| 0 | 837 | throw new ZipException("Find must be called before calling a Read method"); | ||
838 | } | |||
839 | | |||
| 1079 | 840 | if (_index > _readValueStart + _readValueLength - length) { | ||
| 10 | 841 | throw new ZipException("End of extra data"); | ||
842 | } | |||
843 | | |||
| 1069 | 844 | if (_index + length < 4) { | ||
| 1 | 845 | throw new ZipException("Cannot read before start of tag"); | ||
846 | } | |||
| 1068 | 847 | } | ||
848 | | |||
849 | /// <summary> | |||
850 | /// Internal form of <see cref="ReadShort"/> that reads data at any location. | |||
851 | /// </summary> | |||
852 | /// <returns>Returns the short value read.</returns> | |||
853 | int ReadShortInternal() | |||
854 | { | |||
| 956 | 855 | if (_index > _data.Length - 2) { | ||
| 0 | 856 | throw new ZipException("End of extra data"); | ||
857 | } | |||
858 | | |||
| 956 | 859 | int result = _data[_index] + (_data[_index + 1] << 8); | ||
| 956 | 860 | _index += 2; | ||
| 956 | 861 | return result; | ||
862 | } | |||
863 | | |||
864 | void SetShort(ref int index, int source) | |||
865 | { | |||
| 518 | 866 | _data[index] = (byte)source; | ||
| 518 | 867 | _data[index + 1] = (byte)(source >> 8); | ||
| 518 | 868 | index += 2; | ||
| 518 | 869 | } | ||
870 | | |||
871 | #endregion | |||
872 | | |||
873 | #region IDisposable Members | |||
874 | | |||
875 | /// <summary> | |||
876 | /// Dispose of this instance. | |||
877 | /// </summary> | |||
878 | public void Dispose() | |||
879 | { | |||
| 0 | 880 | if (_newEntry != null) { | ||
| 0 | 881 | _newEntry.Close(); | ||
882 | } | |||
| 0 | 883 | } | ||
884 | | |||
885 | #endregion | |||
886 | | |||
887 | #region Instance Fields | |||
888 | int _index; | |||
889 | int _readValueStart; | |||
890 | int _readValueLength; | |||
891 | | |||
892 | MemoryStream _newEntry; | |||
893 | byte[] _data; | |||
894 | #endregion | |||
895 | } | |||
896 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipFile |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs |
| Covered lines: | 953 |
| Uncovered lines: | 356 |
| Coverable lines: | 1309 |
| Total lines: | 4263 |
| Line coverage: | 72.8% |
| Branch coverage: | 58.6% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| OnKeysRequired(...) | 2 | 40 | 66.67 |
| .ctor(...) | 2 | 93.33 | 66.67 |
| .ctor(...) | 3 | 35.29 | 40 |
| .ctor(...) | 4 | 73.68 | 71.43 |
| .ctor() | 1 | 100 | 100 |
| Finalize() | 1 | 100 | 100 |
| Close() | 1 | 100 | 100 |
| Create(...) | 2 | 87.5 | 66.67 |
| Create(...) | 4 | 66.67 | 57.14 |
| GetEnumerator() | 2 | 66.67 | 66.67 |
| FindEntry(...) | 4 | 87.5 | 85.71 |
| GetEntry(...) | 3 | 0 | 0 |
| GetInputStream(...) | 7 | 50 | 46.15 |
| GetInputStream(...) | 6 | 78.57 | 72.73 |
| TestArchive(...) | 1 | 100 | 100 |
| TestArchive(...) | 25 | 53.62 | 60 |
| TestLocalHeader(...) | 74 | 65.96 | 55.24 |
| BeginUpdate(...) | 9 | 87.10 | 63.64 |
| BeginUpdate(...) | 1 | 100 | 100 |
| BeginUpdate() | 2 | 100 | 100 |
| CommitUpdate() | 7 | 90 | 77.78 |
| AbortUpdate() | 1 | 0 | 0 |
| SetComment(...) | 3 | 66.67 | 60 |
| AddUpdate(...) | 3 | 63.64 | 40 |
| Add(...) | 4 | 0 | 0 |
| Add(...) | 3 | 0 | 0 |
| Add(...) | 2 | 80 | 66.67 |
| Add(...) | 3 | 0 | 0 |
| Add(...) | 3 | 71.43 | 60 |
| Add(...) | 3 | 77.78 | 60 |
| Add(...) | 3 | 80 | 60 |
| Add(...) | 4 | 71.43 | 57.14 |
| AddDirectory(...) | 2 | 0 | 0 |
| Delete(...) | 4 | 84.62 | 57.14 |
| Delete(...) | 3 | 80 | 60 |
| WriteLEShort(...) | 1 | 100 | 100 |
| WriteLEUshort(...) | 1 | 100 | 100 |
| WriteLEInt(...) | 1 | 100 | 100 |
| WriteLEUint(...) | 1 | 100 | 100 |
| WriteLeLong(...) | 1 | 100 | 100 |
| WriteLEUlong(...) | 1 | 0 | 0 |
| WriteLocalEntryHeader(...) | 20 | 93.65 | 82.05 |
| WriteCentralDirectoryHeader(...) | 24 | 83.61 | 66.67 |
| PostUpdateCleanup() | 2 | 100 | 100 |
| GetTransformedFileName(...) | 2 | 100 | 66.67 |
| GetTransformedDirectoryName(...) | 2 | 0 | 0 |
| GetBuffer() | 2 | 100 | 100 |
| CopyDescriptorBytes(...) | 4 | 25 | 28.57 |
| CopyBytes(...) | 9 | 90.91 | 64.71 |
| GetDescriptorSize(...) | 3 | 50 | 40 |
| CopyDescriptorBytesDirect(...) | 3 | 20 | 40 |
| CopyEntryDataDirect(...) | 8 | 88 | 53.33 |
| FindExistingUpdate(...) | 2 | 100 | 66.67 |
| FindExistingUpdate(...) | 2 | 100 | 100 |
| GetOutputStream(...) | 4 | 91.67 | 85.71 |
| AddEntry(...) | 9 | 96.30 | 84.62 |
| ModifyEntry(...) | 5 | 0 | 0 |
| CopyEntryDirect(...) | 5 | 100 | 88.89 |
| CopyEntry(...) | 2 | 100 | 66.67 |
| Reopen(...) | 2 | 83.33 | 66.67 |
| Reopen() | 2 | 0 | 0 |
| UpdateCommentOnly() | 6 | 87.10 | 77.78 |
| RunUpdates() | 29 | 91.14 | 84.62 |
| CheckUpdating() | 2 | 66.67 | 66.67 |
| System.IDisposable.Dispose() | 1 | 100 | 100 |
| DisposeInternal(...) | 5 | 100 | 100 |
| Dispose(...) | 1 | 100 | 100 |
| ReadLEUshort() | 3 | 71.43 | 60 |
| ReadLEUint() | 1 | 100 | 100 |
| ReadLEUlong() | 1 | 100 | 100 |
| LocateBlockWithSignature(...) | 2 | 100 | 100 |
| ReadEntries() | 20 | 92.39 | 74.36 |
| LocateEntry(...) | 1 | 100 | 100 |
| CreateAndInitDecryptionStream(...) | 9 | 31.03 | 23.53 |
| CreateAndInitEncryptionStream(...) | 6 | 83.33 | 54.55 |
| CheckClassicPassword(...) | 2 | 80 | 66.67 |
| WriteEncryptionHeader(...) | 1 | 100 | 100 |
| Compare(...) | 13 | 100 | 88.24 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| GetSource() | 2 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| Reset() | 2 | 0 | 0 |
| MakeTextAvailable() | 2 | 0 | 0 |
| MakeBytesAvailable() | 2 | 100 | 100 |
| op_Implicit(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| Reset() | 1 | 0 | 0 |
| MoveNext() | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| Close() | 1 | 100 | 100 |
| Flush() | 1 | 0 | 0 |
| Read(...) | 1 | 0 | 0 |
| Seek(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| Write(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| ReadByte() | 3 | 83.33 | 66.67 |
| Close() | 1 | 100 | 100 |
| Read(...) | 6 | 91.67 | 77.78 |
| Write(...) | 1 | 0 | 0 |
| SetLength(...) | 1 | 0 | 0 |
| Seek(...) | 6 | 61.54 | 44.44 |
| Flush() | 1 | 0 | 0 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.Collections; | |||
3 | using System.IO; | |||
4 | using System.Text; | |||
5 | using System.Globalization; | |||
6 | using System.Security.Cryptography; | |||
7 | using ICSharpCode.SharpZipLib.Encryption; | |||
8 | using ICSharpCode.SharpZipLib.Core; | |||
9 | using ICSharpCode.SharpZipLib.Checksum; | |||
10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
11 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
12 | | |||
13 | namespace ICSharpCode.SharpZipLib.Zip | |||
14 | { | |||
15 | #region Keys Required Event Args | |||
16 | /// <summary> | |||
17 | /// Arguments used with KeysRequiredEvent | |||
18 | /// </summary> | |||
19 | public class KeysRequiredEventArgs : EventArgs | |||
20 | { | |||
21 | #region Constructors | |||
22 | /// <summary> | |||
23 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
24 | /// </summary> | |||
25 | /// <param name="name">The name of the file for which keys are required.</param> | |||
26 | public KeysRequiredEventArgs(string name) | |||
27 | { | |||
28 | fileName = name; | |||
29 | } | |||
30 | | |||
31 | /// <summary> | |||
32 | /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> | |||
33 | /// </summary> | |||
34 | /// <param name="name">The name of the file for which keys are required.</param> | |||
35 | /// <param name="keyValue">The current key value.</param> | |||
36 | public KeysRequiredEventArgs(string name, byte[] keyValue) | |||
37 | { | |||
38 | fileName = name; | |||
39 | key = keyValue; | |||
40 | } | |||
41 | | |||
42 | #endregion | |||
43 | #region Properties | |||
44 | /// <summary> | |||
45 | /// Gets the name of the file for which keys are required. | |||
46 | /// </summary> | |||
47 | public string FileName { | |||
48 | get { return fileName; } | |||
49 | } | |||
50 | | |||
51 | /// <summary> | |||
52 | /// Gets or sets the key value | |||
53 | /// </summary> | |||
54 | public byte[] Key { | |||
55 | get { return key; } | |||
56 | set { key = value; } | |||
57 | } | |||
58 | #endregion | |||
59 | | |||
60 | #region Instance Fields | |||
61 | string fileName; | |||
62 | byte[] key; | |||
63 | #endregion | |||
64 | } | |||
65 | #endregion | |||
66 | | |||
67 | #region Test Definitions | |||
68 | /// <summary> | |||
69 | /// The strategy to apply to testing. | |||
70 | /// </summary> | |||
71 | public enum TestStrategy | |||
72 | { | |||
73 | /// <summary> | |||
74 | /// Find the first error only. | |||
75 | /// </summary> | |||
76 | FindFirstError, | |||
77 | /// <summary> | |||
78 | /// Find all possible errors. | |||
79 | /// </summary> | |||
80 | FindAllErrors, | |||
81 | } | |||
82 | | |||
83 | /// <summary> | |||
84 | /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. | |||
85 | /// </summary> | |||
86 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
87 | public enum TestOperation | |||
88 | { | |||
89 | /// <summary> | |||
90 | /// Setting up testing. | |||
91 | /// </summary> | |||
92 | Initialising, | |||
93 | | |||
94 | /// <summary> | |||
95 | /// Testing an individual entries header | |||
96 | /// </summary> | |||
97 | EntryHeader, | |||
98 | | |||
99 | /// <summary> | |||
100 | /// Testing an individual entries data | |||
101 | /// </summary> | |||
102 | EntryData, | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Testing an individual entry has completed. | |||
106 | /// </summary> | |||
107 | EntryComplete, | |||
108 | | |||
109 | /// <summary> | |||
110 | /// Running miscellaneous tests | |||
111 | /// </summary> | |||
112 | MiscellaneousTests, | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Testing is complete | |||
116 | /// </summary> | |||
117 | Complete, | |||
118 | } | |||
119 | | |||
120 | /// <summary> | |||
121 | /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. | |||
122 | /// </summary> | |||
123 | /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> | |||
124 | public class TestStatus | |||
125 | { | |||
126 | #region Constructors | |||
127 | /// <summary> | |||
128 | /// Initialise a new instance of <see cref="TestStatus"/> | |||
129 | /// </summary> | |||
130 | /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> | |||
131 | public TestStatus(ZipFile file) | |||
132 | { | |||
133 | file_ = file; | |||
134 | } | |||
135 | #endregion | |||
136 | | |||
137 | #region Properties | |||
138 | | |||
139 | /// <summary> | |||
140 | /// Get the current <see cref="TestOperation"/> in progress. | |||
141 | /// </summary> | |||
142 | public TestOperation Operation { | |||
143 | get { return operation_; } | |||
144 | } | |||
145 | | |||
146 | /// <summary> | |||
147 | /// Get the <see cref="ZipFile"/> this status is applicable to. | |||
148 | /// </summary> | |||
149 | public ZipFile File { | |||
150 | get { return file_; } | |||
151 | } | |||
152 | | |||
153 | /// <summary> | |||
154 | /// Get the current/last entry tested. | |||
155 | /// </summary> | |||
156 | public ZipEntry Entry { | |||
157 | get { return entry_; } | |||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Get the number of errors detected so far. | |||
162 | /// </summary> | |||
163 | public int ErrorCount { | |||
164 | get { return errorCount_; } | |||
165 | } | |||
166 | | |||
167 | /// <summary> | |||
168 | /// Get the number of bytes tested so far for the current entry. | |||
169 | /// </summary> | |||
170 | public long BytesTested { | |||
171 | get { return bytesTested_; } | |||
172 | } | |||
173 | | |||
174 | /// <summary> | |||
175 | /// Get a value indicating wether the last entry test was valid. | |||
176 | /// </summary> | |||
177 | public bool EntryValid { | |||
178 | get { return entryValid_; } | |||
179 | } | |||
180 | #endregion | |||
181 | | |||
182 | #region Internal API | |||
183 | internal void AddError() | |||
184 | { | |||
185 | errorCount_++; | |||
186 | entryValid_ = false; | |||
187 | } | |||
188 | | |||
189 | internal void SetOperation(TestOperation operation) | |||
190 | { | |||
191 | operation_ = operation; | |||
192 | } | |||
193 | | |||
194 | internal void SetEntry(ZipEntry entry) | |||
195 | { | |||
196 | entry_ = entry; | |||
197 | entryValid_ = true; | |||
198 | bytesTested_ = 0; | |||
199 | } | |||
200 | | |||
201 | internal void SetBytesTested(long value) | |||
202 | { | |||
203 | bytesTested_ = value; | |||
204 | } | |||
205 | #endregion | |||
206 | | |||
207 | #region Instance Fields | |||
208 | ZipFile file_; | |||
209 | ZipEntry entry_; | |||
210 | bool entryValid_; | |||
211 | int errorCount_; | |||
212 | long bytesTested_; | |||
213 | TestOperation operation_; | |||
214 | #endregion | |||
215 | } | |||
216 | | |||
217 | /// <summary> | |||
218 | /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if | |||
219 | /// </summary> | |||
220 | /// <remarks>If the message is non-null an error has occured. If the message is null | |||
221 | /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> | |||
222 | public delegate void ZipTestResultHandler(TestStatus status, string message); | |||
223 | #endregion | |||
224 | | |||
225 | #region Update Definitions | |||
226 | /// <summary> | |||
227 | /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. | |||
228 | /// </summary> | |||
229 | public enum FileUpdateMode | |||
230 | { | |||
231 | /// <summary> | |||
232 | /// Perform all updates on temporary files ensuring that the original file is saved. | |||
233 | /// </summary> | |||
234 | Safe, | |||
235 | /// <summary> | |||
236 | /// Update the archive directly, which is faster but less safe. | |||
237 | /// </summary> | |||
238 | Direct, | |||
239 | } | |||
240 | #endregion | |||
241 | | |||
242 | #region ZipFile Class | |||
243 | /// <summary> | |||
244 | /// This class represents a Zip archive. You can ask for the contained | |||
245 | /// entries, or get an input stream for a file entry. The entry is | |||
246 | /// automatically decompressed. | |||
247 | /// | |||
248 | /// You can also update the archive adding or deleting entries. | |||
249 | /// | |||
250 | /// This class is thread safe for input: You can open input streams for arbitrary | |||
251 | /// entries in different threads. | |||
252 | /// <br/> | |||
253 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
254 | /// </summary> | |||
255 | /// <example> | |||
256 | /// <code> | |||
257 | /// using System; | |||
258 | /// using System.Text; | |||
259 | /// using System.Collections; | |||
260 | /// using System.IO; | |||
261 | /// | |||
262 | /// using ICSharpCode.SharpZipLib.Zip; | |||
263 | /// | |||
264 | /// class MainClass | |||
265 | /// { | |||
266 | /// static public void Main(string[] args) | |||
267 | /// { | |||
268 | /// using (ZipFile zFile = new ZipFile(args[0])) { | |||
269 | /// Console.WriteLine("Listing of : " + zFile.Name); | |||
270 | /// Console.WriteLine(""); | |||
271 | /// Console.WriteLine("Raw Size Size Date Time Name"); | |||
272 | /// Console.WriteLine("-------- -------- -------- ------ ---------"); | |||
273 | /// foreach (ZipEntry e in zFile) { | |||
274 | /// if ( e.IsFile ) { | |||
275 | /// DateTime d = e.DateTime; | |||
276 | /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, | |||
277 | /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), | |||
278 | /// e.Name); | |||
279 | /// } | |||
280 | /// } | |||
281 | /// } | |||
282 | /// } | |||
283 | /// } | |||
284 | /// </code> | |||
285 | /// </example> | |||
286 | public class ZipFile : IEnumerable, IDisposable | |||
287 | { | |||
288 | #region KeyHandling | |||
289 | | |||
290 | /// <summary> | |||
291 | /// Delegate for handling keys/password setting during compresion/decompression. | |||
292 | /// </summary> | |||
293 | public delegate void KeysRequiredEventHandler( | |||
294 | object sender, | |||
295 | KeysRequiredEventArgs e | |||
296 | ); | |||
297 | | |||
298 | /// <summary> | |||
299 | /// Event handler for handling encryption keys. | |||
300 | /// </summary> | |||
301 | public KeysRequiredEventHandler KeysRequired; | |||
302 | | |||
303 | /// <summary> | |||
304 | /// Handles getting of encryption keys when required. | |||
305 | /// </summary> | |||
306 | /// <param name="fileName">The file for which encryption keys are required.</param> | |||
307 | void OnKeysRequired(string fileName) | |||
308 | { | |||
| 15 | 309 | if (KeysRequired != null) { | ||
| 0 | 310 | var krea = new KeysRequiredEventArgs(fileName, key); | ||
| 0 | 311 | KeysRequired(this, krea); | ||
| 0 | 312 | key = krea.Key; | ||
313 | } | |||
| 15 | 314 | } | ||
315 | | |||
316 | /// <summary> | |||
317 | /// Get/set the encryption key value. | |||
318 | /// </summary> | |||
319 | byte[] Key { | |||
| 0 | 320 | get { return key; } | ||
| 0 | 321 | set { key = value; } | ||
322 | } | |||
323 | | |||
324 | /// <summary> | |||
325 | /// Password to be used for encrypting/decrypting files. | |||
326 | /// </summary> | |||
327 | /// <remarks>Set to null if no password is required.</remarks> | |||
328 | public string Password { | |||
329 | set { | |||
| 16 | 330 | if (string.IsNullOrEmpty(value)) { | ||
| 5 | 331 | key = null; | ||
| 5 | 332 | } else { | ||
| 11 | 333 | rawPassword_ = value; | ||
| 11 | 334 | key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); | ||
335 | } | |||
| 11 | 336 | } | ||
337 | } | |||
338 | | |||
339 | /// <summary> | |||
340 | /// Get a value indicating wether encryption keys are currently available. | |||
341 | /// </summary> | |||
342 | bool HaveKeys { | |||
| 65720 | 343 | get { return key != null; } | ||
344 | } | |||
345 | #endregion | |||
346 | | |||
347 | #region Constructors | |||
348 | /// <summary> | |||
349 | /// Opens a Zip file with the given name for reading. | |||
350 | /// </summary> | |||
351 | /// <param name="name">The name of the file to open.</param> | |||
352 | /// <exception cref="ArgumentNullException">The argument supplied is null.</exception> | |||
353 | /// <exception cref="IOException"> | |||
354 | /// An i/o error occurs | |||
355 | /// </exception> | |||
356 | /// <exception cref="ZipException"> | |||
357 | /// The file doesn't contain a valid zip archive. | |||
358 | /// </exception> | |||
| 15 | 359 | public ZipFile(string name) | ||
360 | { | |||
| 15 | 361 | if (name == null) { | ||
| 0 | 362 | throw new ArgumentNullException(nameof(name)); | ||
363 | } | |||
364 | | |||
| 15 | 365 | name_ = name; | ||
366 | | |||
| 15 | 367 | baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
| 15 | 368 | isStreamOwner = true; | ||
369 | | |||
370 | try { | |||
| 15 | 371 | ReadEntries(); | ||
| 15 | 372 | } catch { | ||
| 1 | 373 | DisposeInternal(true); | ||
| 1 | 374 | throw; | ||
375 | } | |||
| 14 | 376 | } | ||
377 | | |||
378 | /// <summary> | |||
379 | /// Opens a Zip file reading the given <see cref="FileStream"/>. | |||
380 | /// </summary> | |||
381 | /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> | |||
382 | /// <exception cref="ArgumentNullException">The supplied argument is null.</exception> | |||
383 | /// <exception cref="IOException"> | |||
384 | /// An i/o error occurs. | |||
385 | /// </exception> | |||
386 | /// <exception cref="ZipException"> | |||
387 | /// The file doesn't contain a valid zip archive. | |||
388 | /// </exception> | |||
| 1 | 389 | public ZipFile(FileStream file) | ||
390 | { | |||
| 1 | 391 | if (file == null) { | ||
| 1 | 392 | throw new ArgumentNullException(nameof(file)); | ||
393 | } | |||
394 | | |||
| 0 | 395 | if (!file.CanSeek) { | ||
| 0 | 396 | throw new ArgumentException("Stream is not seekable", nameof(file)); | ||
397 | } | |||
398 | | |||
| 0 | 399 | baseStream_ = file; | ||
| 0 | 400 | name_ = file.Name; | ||
| 0 | 401 | isStreamOwner = true; | ||
402 | | |||
403 | try { | |||
| 0 | 404 | ReadEntries(); | ||
| 0 | 405 | } catch { | ||
| 0 | 406 | DisposeInternal(true); | ||
| 0 | 407 | throw; | ||
408 | } | |||
| 0 | 409 | } | ||
410 | | |||
411 | /// <summary> | |||
412 | /// Opens a Zip file reading the given <see cref="Stream"/>. | |||
413 | /// </summary> | |||
414 | /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> | |||
415 | /// <exception cref="IOException"> | |||
416 | /// An i/o error occurs | |||
417 | /// </exception> | |||
418 | /// <exception cref="ZipException"> | |||
419 | /// The stream doesn't contain a valid zip archive.<br/> | |||
420 | /// </exception> | |||
421 | /// <exception cref="ArgumentException"> | |||
422 | /// The <see cref="Stream">stream</see> doesnt support seeking. | |||
423 | /// </exception> | |||
424 | /// <exception cref="ArgumentNullException"> | |||
425 | /// The <see cref="Stream">stream</see> argument is null. | |||
426 | /// </exception> | |||
| 62 | 427 | public ZipFile(Stream stream) | ||
428 | { | |||
| 62 | 429 | if (stream == null) { | ||
| 0 | 430 | throw new ArgumentNullException(nameof(stream)); | ||
431 | } | |||
432 | | |||
| 62 | 433 | if (!stream.CanSeek) { | ||
| 0 | 434 | throw new ArgumentException("Stream is not seekable", nameof(stream)); | ||
435 | } | |||
436 | | |||
| 62 | 437 | baseStream_ = stream; | ||
| 62 | 438 | isStreamOwner = true; | ||
439 | | |||
| 62 | 440 | if (baseStream_.Length > 0) { | ||
441 | try { | |||
| 52 | 442 | ReadEntries(); | ||
| 52 | 443 | } catch { | ||
| 0 | 444 | DisposeInternal(true); | ||
| 0 | 445 | throw; | ||
446 | } | |||
447 | } else { | |||
| 10 | 448 | entries_ = new ZipEntry[0]; | ||
| 10 | 449 | isNewArchive_ = true; | ||
450 | } | |||
| 62 | 451 | } | ||
452 | | |||
453 | /// <summary> | |||
454 | /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. | |||
455 | /// </summary> | |||
| 10 | 456 | internal ZipFile() | ||
457 | { | |||
| 10 | 458 | entries_ = new ZipEntry[0]; | ||
| 10 | 459 | isNewArchive_ = true; | ||
| 10 | 460 | } | ||
461 | | |||
462 | #endregion | |||
463 | | |||
464 | #region Destructors and Closing | |||
465 | /// <summary> | |||
466 | /// Finalize this instance. | |||
467 | /// </summary> | |||
468 | ~ZipFile() | |||
469 | { | |||
| 6 | 470 | Dispose(false); | ||
| 12 | 471 | } | ||
472 | | |||
473 | /// <summary> | |||
474 | /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying | |||
475 | /// Once closed, no further instance methods should be called. | |||
476 | /// </summary> | |||
477 | /// <exception cref="System.IO.IOException"> | |||
478 | /// An i/o error occurs. | |||
479 | /// </exception> | |||
480 | public void Close() | |||
481 | { | |||
| 91 | 482 | DisposeInternal(true); | ||
| 91 | 483 | GC.SuppressFinalize(this); | ||
| 91 | 484 | } | ||
485 | | |||
486 | #endregion | |||
487 | | |||
488 | #region Creators | |||
489 | /// <summary> | |||
490 | /// Create a new <see cref="ZipFile"/> whose data will be stored in a file. | |||
491 | /// </summary> | |||
492 | /// <param name="fileName">The name of the archive to create.</param> | |||
493 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
494 | /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception> | |||
495 | public static ZipFile Create(string fileName) | |||
496 | { | |||
| 7 | 497 | if (fileName == null) { | ||
| 0 | 498 | throw new ArgumentNullException(nameof(fileName)); | ||
499 | } | |||
500 | | |||
| 7 | 501 | FileStream fs = File.Create(fileName); | ||
502 | | |||
| 7 | 503 | var result = new ZipFile(); | ||
| 7 | 504 | result.name_ = fileName; | ||
| 7 | 505 | result.baseStream_ = fs; | ||
| 7 | 506 | result.isStreamOwner = true; | ||
| 7 | 507 | return result; | ||
508 | } | |||
509 | | |||
510 | /// <summary> | |||
511 | /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. | |||
512 | /// </summary> | |||
513 | /// <param name="outStream">The stream providing data storage.</param> | |||
514 | /// <returns>Returns the newly created <see cref="ZipFile"/></returns> | |||
515 | /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception> | |||
516 | /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception> | |||
517 | public static ZipFile Create(Stream outStream) | |||
518 | { | |||
| 3 | 519 | if (outStream == null) { | ||
| 0 | 520 | throw new ArgumentNullException(nameof(outStream)); | ||
521 | } | |||
522 | | |||
| 3 | 523 | if (!outStream.CanWrite) { | ||
| 0 | 524 | throw new ArgumentException("Stream is not writeable", nameof(outStream)); | ||
525 | } | |||
526 | | |||
| 3 | 527 | if (!outStream.CanSeek) { | ||
| 0 | 528 | throw new ArgumentException("Stream is not seekable", nameof(outStream)); | ||
529 | } | |||
530 | | |||
| 3 | 531 | var result = new ZipFile(); | ||
| 3 | 532 | result.baseStream_ = outStream; | ||
| 3 | 533 | return result; | ||
534 | } | |||
535 | | |||
536 | #endregion | |||
537 | | |||
538 | #region Properties | |||
539 | /// <summary> | |||
540 | /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. | |||
541 | /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. | |||
542 | /// </summary> | |||
543 | /// <remarks> | |||
544 | /// The default value is true in all cases. | |||
545 | /// </remarks> | |||
546 | public bool IsStreamOwner { | |||
| 88 | 547 | get { return isStreamOwner; } | ||
| 74 | 548 | set { isStreamOwner = value; } | ||
549 | } | |||
550 | | |||
551 | /// <summary> | |||
552 | /// Get a value indicating wether | |||
553 | /// this archive is embedded in another file or not. | |||
554 | /// </summary> | |||
555 | public bool IsEmbeddedArchive { | |||
556 | // Not strictly correct in all circumstances currently | |||
| 48 | 557 | get { return offsetOfFirstEntry > 0; } | ||
558 | } | |||
559 | | |||
560 | /// <summary> | |||
561 | /// Get a value indicating that this archive is a new one. | |||
562 | /// </summary> | |||
563 | public bool IsNewArchive { | |||
| 65793 | 564 | get { return isNewArchive_; } | ||
565 | } | |||
566 | | |||
567 | /// <summary> | |||
568 | /// Gets the comment for the zip file. | |||
569 | /// </summary> | |||
570 | public string ZipFileComment { | |||
| 6 | 571 | get { return comment_; } | ||
572 | } | |||
573 | | |||
574 | /// <summary> | |||
575 | /// Gets the name of this zip file. | |||
576 | /// </summary> | |||
577 | public string Name { | |||
| 38 | 578 | get { return name_; } | ||
579 | } | |||
580 | | |||
581 | /// <summary> | |||
582 | /// Gets the number of entries in this zip file. | |||
583 | /// </summary> | |||
584 | /// <exception cref="InvalidOperationException"> | |||
585 | /// The Zip file has been closed. | |||
586 | /// </exception> | |||
587 | [Obsolete("Use the Count property instead")] | |||
588 | public int Size { | |||
589 | get { | |||
| 0 | 590 | return entries_.Length; | ||
591 | } | |||
592 | } | |||
593 | | |||
594 | /// <summary> | |||
595 | /// Get the number of entries contained in this <see cref="ZipFile"/>. | |||
596 | /// </summary> | |||
597 | public long Count { | |||
598 | get { | |||
| 66100 | 599 | return entries_.Length; | ||
600 | } | |||
601 | } | |||
602 | | |||
603 | /// <summary> | |||
604 | /// Indexer property for ZipEntries | |||
605 | /// </summary> | |||
606 | [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] | |||
607 | public ZipEntry this[int index] { | |||
608 | get { | |||
| 329670 | 609 | return (ZipEntry)entries_[index].Clone(); | ||
610 | } | |||
611 | } | |||
612 | | |||
613 | #endregion | |||
614 | | |||
615 | #region Input Handling | |||
616 | /// <summary> | |||
617 | /// Gets an enumerator for the Zip entries in this Zip file. | |||
618 | /// </summary> | |||
619 | /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> | |||
620 | /// <exception cref="ObjectDisposedException"> | |||
621 | /// The Zip file has been closed. | |||
622 | /// </exception> | |||
623 | public IEnumerator GetEnumerator() | |||
624 | { | |||
| 4 | 625 | if (isDisposed_) { | ||
| 0 | 626 | throw new ObjectDisposedException("ZipFile"); | ||
627 | } | |||
628 | | |||
| 4 | 629 | return new ZipEntryEnumerator(entries_); | ||
630 | } | |||
631 | | |||
632 | /// <summary> | |||
633 | /// Return the index of the entry with a matching name | |||
634 | /// </summary> | |||
635 | /// <param name="name">Entry name to find</param> | |||
636 | /// <param name="ignoreCase">If true the comparison is case insensitive</param> | |||
637 | /// <returns>The index position of the matching entry or -1 if not found</returns> | |||
638 | /// <exception cref="ObjectDisposedException"> | |||
639 | /// The Zip file has been closed. | |||
640 | /// </exception> | |||
641 | public int FindEntry(string name, bool ignoreCase) | |||
642 | { | |||
| 12 | 643 | if (isDisposed_) { | ||
| 0 | 644 | throw new ObjectDisposedException("ZipFile"); | ||
645 | } | |||
646 | | |||
647 | // TODO: This will be slow as the next ice age for huge archives! | |||
| 62 | 648 | for (int i = 0; i < entries_.Length; i++) { | ||
| 28 | 649 | if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) { | ||
| 9 | 650 | return i; | ||
651 | } | |||
652 | } | |||
| 3 | 653 | return -1; | ||
654 | } | |||
655 | | |||
656 | /// <summary> | |||
657 | /// Searches for a zip entry in this archive with the given name. | |||
658 | /// String comparisons are case insensitive | |||
659 | /// </summary> | |||
660 | /// <param name="name"> | |||
661 | /// The name to find. May contain directory components separated by slashes ('/'). | |||
662 | /// </param> | |||
663 | /// <returns> | |||
664 | /// A clone of the zip entry, or null if no entry with that name exists. | |||
665 | /// </returns> | |||
666 | /// <exception cref="ObjectDisposedException"> | |||
667 | /// The Zip file has been closed. | |||
668 | /// </exception> | |||
669 | public ZipEntry GetEntry(string name) | |||
670 | { | |||
| 0 | 671 | if (isDisposed_) { | ||
| 0 | 672 | throw new ObjectDisposedException("ZipFile"); | ||
673 | } | |||
674 | | |||
| 0 | 675 | int index = FindEntry(name, true); | ||
| 0 | 676 | return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; | ||
677 | } | |||
678 | | |||
679 | /// <summary> | |||
680 | /// Gets an input stream for reading the given zip entry data in an uncompressed form. | |||
681 | /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). | |||
682 | /// </summary> | |||
683 | /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> | |||
684 | /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> | |||
685 | /// <exception cref="ObjectDisposedException"> | |||
686 | /// The ZipFile has already been closed | |||
687 | /// </exception> | |||
688 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
689 | /// The compression method for the entry is unknown | |||
690 | /// </exception> | |||
691 | /// <exception cref="IndexOutOfRangeException"> | |||
692 | /// The entry is not found in the ZipFile | |||
693 | /// </exception> | |||
694 | public Stream GetInputStream(ZipEntry entry) | |||
695 | { | |||
| 65933 | 696 | if (entry == null) { | ||
| 0 | 697 | throw new ArgumentNullException(nameof(entry)); | ||
698 | } | |||
699 | | |||
| 65933 | 700 | if (isDisposed_) { | ||
| 0 | 701 | throw new ObjectDisposedException("ZipFile"); | ||
702 | } | |||
703 | | |||
| 65933 | 704 | long index = entry.ZipFileIndex; | ||
| 65933 | 705 | if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { | ||
| 0 | 706 | index = FindEntry(entry.Name, true); | ||
| 0 | 707 | if (index < 0) { | ||
| 0 | 708 | throw new ZipException("Entry cannot be found"); | ||
709 | } | |||
710 | } | |||
| 65933 | 711 | return GetInputStream(index); | ||
712 | } | |||
713 | | |||
714 | /// <summary> | |||
715 | /// Creates an input stream reading a zip entry | |||
716 | /// </summary> | |||
717 | /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> | |||
718 | /// <returns> | |||
719 | /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> | |||
720 | /// </returns> | |||
721 | /// <exception cref="ObjectDisposedException"> | |||
722 | /// The ZipFile has already been closed | |||
723 | /// </exception> | |||
724 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
725 | /// The compression method for the entry is unknown | |||
726 | /// </exception> | |||
727 | /// <exception cref="IndexOutOfRangeException"> | |||
728 | /// The entry is not found in the ZipFile | |||
729 | /// </exception> | |||
730 | public Stream GetInputStream(long entryIndex) | |||
731 | { | |||
| 65946 | 732 | if (isDisposed_) { | ||
| 0 | 733 | throw new ObjectDisposedException("ZipFile"); | ||
734 | } | |||
735 | | |||
| 65946 | 736 | long start = LocateEntry(entries_[entryIndex]); | ||
| 65946 | 737 | CompressionMethod method = entries_[entryIndex].CompressionMethod; | ||
| 65946 | 738 | Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); | ||
739 | | |||
| 65946 | 740 | if (entries_[entryIndex].IsCrypted == true) { | ||
| 10 | 741 | result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); | ||
| 10 | 742 | if (result == null) { | ||
| 0 | 743 | throw new ZipException("Unable to decrypt this entry"); | ||
744 | } | |||
745 | } | |||
746 | | |||
| 65946 | 747 | switch (method) { | ||
748 | case CompressionMethod.Stored: | |||
749 | // read as is. | |||
750 | break; | |||
751 | | |||
752 | case CompressionMethod.Deflated: | |||
753 | // No need to worry about ownership and closing as underlying stream close does nothing. | |||
| 353 | 754 | result = new InflaterInputStream(result, new Inflater(true)); | ||
| 353 | 755 | break; | ||
756 | | |||
757 | default: | |||
| 0 | 758 | throw new ZipException("Unsupported compression method " + method); | ||
759 | } | |||
760 | | |||
| 65946 | 761 | return result; | ||
762 | } | |||
763 | | |||
764 | #endregion | |||
765 | | |||
766 | #region Archive Testing | |||
767 | /// <summary> | |||
768 | /// Test an archive for integrity/validity | |||
769 | /// </summary> | |||
770 | /// <param name="testData">Perform low level data Crc check</param> | |||
771 | /// <returns>true if all tests pass, false otherwise</returns> | |||
772 | /// <remarks>Testing will terminate on the first error found.</remarks> | |||
773 | public bool TestArchive(bool testData) | |||
774 | { | |||
| 96 | 775 | return TestArchive(testData, TestStrategy.FindFirstError, null); | ||
776 | } | |||
777 | | |||
778 | /// <summary> | |||
779 | /// Test an archive for integrity/validity | |||
780 | /// </summary> | |||
781 | /// <param name="testData">Perform low level data Crc check</param> | |||
782 | /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> | |||
783 | /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> | |||
784 | /// <returns>true if all tests pass, false otherwise</returns> | |||
785 | /// <exception cref="ObjectDisposedException">The object has already been closed.</exception> | |||
786 | public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) | |||
787 | { | |||
| 96 | 788 | if (isDisposed_) { | ||
| 0 | 789 | throw new ObjectDisposedException("ZipFile"); | ||
790 | } | |||
791 | | |||
| 96 | 792 | var status = new TestStatus(this); | ||
793 | | |||
| 96 | 794 | if (resultHandler != null) { | ||
| 0 | 795 | resultHandler(status, null); | ||
796 | } | |||
797 | | |||
| 96 | 798 | HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; | ||
799 | | |||
| 96 | 800 | bool testing = true; | ||
801 | | |||
802 | try { | |||
| 96 | 803 | int entryIndex = 0; | ||
804 | | |||
| 66012 | 805 | while (testing && (entryIndex < Count)) { | ||
| 65916 | 806 | if (resultHandler != null) { | ||
| 0 | 807 | status.SetEntry(this[entryIndex]); | ||
| 0 | 808 | status.SetOperation(TestOperation.EntryHeader); | ||
| 0 | 809 | resultHandler(status, null); | ||
810 | } | |||
811 | | |||
812 | try { | |||
| 65916 | 813 | TestLocalHeader(this[entryIndex], test); | ||
| 65916 | 814 | } catch (ZipException ex) { | ||
| 0 | 815 | status.AddError(); | ||
816 | | |||
| 0 | 817 | if (resultHandler != null) { | ||
| 0 | 818 | resultHandler(status, | ||
| 0 | 819 | string.Format("Exception during test - '{0}'", ex.Message)); | ||
820 | } | |||
821 | | |||
| 0 | 822 | testing &= strategy != TestStrategy.FindFirstError; | ||
| 0 | 823 | } | ||
824 | | |||
| 65916 | 825 | if (testing && testData && this[entryIndex].IsFile) { | ||
| 65912 | 826 | if (resultHandler != null) { | ||
| 0 | 827 | status.SetOperation(TestOperation.EntryData); | ||
| 0 | 828 | resultHandler(status, null); | ||
829 | } | |||
830 | | |||
| 65912 | 831 | var crc = new Crc32(); | ||
832 | | |||
| 65912 | 833 | using (Stream entryStream = this.GetInputStream(this[entryIndex])) { | ||
834 | | |||
| 65912 | 835 | byte[] buffer = new byte[4096]; | ||
| 65912 | 836 | long totalBytes = 0; | ||
837 | int bytesRead; | |||
| 66297 | 838 | while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { | ||
| 385 | 839 | crc.Update(buffer, 0, bytesRead); | ||
840 | | |||
| 385 | 841 | if (resultHandler != null) { | ||
| 0 | 842 | totalBytes += bytesRead; | ||
| 0 | 843 | status.SetBytesTested(totalBytes); | ||
| 0 | 844 | resultHandler(status, null); | ||
845 | } | |||
846 | } | |||
| 65912 | 847 | } | ||
848 | | |||
| 65912 | 849 | if (this[entryIndex].Crc != crc.Value) { | ||
| 1 | 850 | status.AddError(); | ||
851 | | |||
| 1 | 852 | if (resultHandler != null) { | ||
| 0 | 853 | resultHandler(status, "CRC mismatch"); | ||
854 | } | |||
855 | | |||
| 1 | 856 | testing &= strategy != TestStrategy.FindFirstError; | ||
857 | } | |||
858 | | |||
| 65912 | 859 | if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) { | ||
| 13 | 860 | var helper = new ZipHelperStream(baseStream_); | ||
| 13 | 861 | var data = new DescriptorData(); | ||
| 13 | 862 | helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); | ||
| 13 | 863 | if (this[entryIndex].Crc != data.Crc) { | ||
| 0 | 864 | status.AddError(); | ||
865 | } | |||
866 | | |||
| 13 | 867 | if (this[entryIndex].CompressedSize != data.CompressedSize) { | ||
| 0 | 868 | status.AddError(); | ||
869 | } | |||
870 | | |||
| 13 | 871 | if (this[entryIndex].Size != data.Size) { | ||
| 0 | 872 | status.AddError(); | ||
873 | } | |||
874 | } | |||
875 | } | |||
876 | | |||
| 65916 | 877 | if (resultHandler != null) { | ||
| 0 | 878 | status.SetOperation(TestOperation.EntryComplete); | ||
| 0 | 879 | resultHandler(status, null); | ||
880 | } | |||
881 | | |||
| 65916 | 882 | entryIndex += 1; | ||
883 | } | |||
884 | | |||
| 96 | 885 | if (resultHandler != null) { | ||
| 0 | 886 | status.SetOperation(TestOperation.MiscellaneousTests); | ||
| 0 | 887 | resultHandler(status, null); | ||
888 | } | |||
889 | | |||
890 | // TODO: the 'Corrina Johns' test where local headers are missing from | |||
891 | // the central directory. They are therefore invisible to many archivers. | |||
| 96 | 892 | } catch (Exception ex) { | ||
| 0 | 893 | status.AddError(); | ||
894 | | |||
| 0 | 895 | if (resultHandler != null) { | ||
| 0 | 896 | resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); | ||
897 | } | |||
| 0 | 898 | } | ||
899 | | |||
| 96 | 900 | if (resultHandler != null) { | ||
| 0 | 901 | status.SetOperation(TestOperation.Complete); | ||
| 0 | 902 | status.SetEntry(null); | ||
| 0 | 903 | resultHandler(status, null); | ||
904 | } | |||
905 | | |||
| 96 | 906 | return (status.ErrorCount == 0); | ||
907 | } | |||
908 | | |||
909 | [Flags] | |||
910 | enum HeaderTest | |||
911 | { | |||
912 | Extract = 0x01, // Check that this header represents an entry whose data can be extracted | |||
913 | Header = 0x02, // Check that this header contents are valid | |||
914 | } | |||
915 | | |||
916 | /// <summary> | |||
917 | /// Test a local header against that provided from the central directory | |||
918 | /// </summary> | |||
919 | /// <param name="entry"> | |||
920 | /// The entry to test against | |||
921 | /// </param> | |||
922 | /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> | |||
923 | /// <returns>The offset of the entries data in the file</returns> | |||
924 | long TestLocalHeader(ZipEntry entry, HeaderTest tests) | |||
925 | { | |||
| 131862 | 926 | lock (baseStream_) { | ||
| 131862 | 927 | bool testHeader = (tests & HeaderTest.Header) != 0; | ||
| 131862 | 928 | bool testData = (tests & HeaderTest.Extract) != 0; | ||
929 | | |||
| 131862 | 930 | baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); | ||
| 131862 | 931 | if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { | ||
| 0 | 932 | throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset) | ||
933 | } | |||
934 | | |||
| 131862 | 935 | var extractVersion = (short)(ReadLEUshort() & 0x00ff); | ||
| 131862 | 936 | var localFlags = (short)ReadLEUshort(); | ||
| 131862 | 937 | var compressionMethod = (short)ReadLEUshort(); | ||
| 131862 | 938 | var fileTime = (short)ReadLEUshort(); | ||
| 131862 | 939 | var fileDate = (short)ReadLEUshort(); | ||
| 131862 | 940 | uint crcValue = ReadLEUint(); | ||
| 131862 | 941 | long compressedSize = ReadLEUint(); | ||
| 131862 | 942 | long size = ReadLEUint(); | ||
| 131862 | 943 | int storedNameLength = ReadLEUshort(); | ||
| 131862 | 944 | int extraDataLength = ReadLEUshort(); | ||
945 | | |||
| 131862 | 946 | byte[] nameData = new byte[storedNameLength]; | ||
| 131862 | 947 | StreamUtils.ReadFully(baseStream_, nameData); | ||
948 | | |||
| 131862 | 949 | byte[] extraData = new byte[extraDataLength]; | ||
| 131862 | 950 | StreamUtils.ReadFully(baseStream_, extraData); | ||
951 | | |||
| 131862 | 952 | var localExtraData = new ZipExtraData(extraData); | ||
953 | | |||
954 | // Extra data / zip64 checks | |||
| 131862 | 955 | if (localExtraData.Find(1)) { | ||
956 | // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 | |||
957 | // and size or compressedSize = MaxValue, due to rogue creators. | |||
958 | | |||
| 50 | 959 | size = localExtraData.ReadLong(); | ||
| 50 | 960 | compressedSize = localExtraData.ReadLong(); | ||
961 | | |||
| 50 | 962 | if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) { | ||
963 | // These may be valid if patched later | |||
| 9 | 964 | if ((size != -1) && (size != entry.Size)) { | ||
| 0 | 965 | throw new ZipException("Size invalid for descriptor"); | ||
966 | } | |||
967 | | |||
| 9 | 968 | if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) { | ||
| 0 | 969 | throw new ZipException("Compressed size invalid for descriptor"); | ||
970 | } | |||
971 | } | |||
972 | } else { | |||
973 | // No zip64 extra data but entry requires it. | |||
| 131812 | 974 | if ((extractVersion >= ZipConstants.VersionZip64) && | ||
| 131812 | 975 | (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) { | ||
| 0 | 976 | throw new ZipException("Required Zip64 extended information missing"); | ||
977 | } | |||
978 | } | |||
979 | | |||
| 131862 | 980 | if (testData) { | ||
| 131862 | 981 | if (entry.IsFile) { | ||
| 131858 | 982 | if (!entry.IsCompressionMethodSupported()) { | ||
| 0 | 983 | throw new ZipException("Compression method not supported"); | ||
984 | } | |||
985 | | |||
| 131858 | 986 | if ((extractVersion > ZipConstants.VersionMadeBy) | ||
| 131858 | 987 | || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { | ||
| 0 | 988 | throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract | ||
989 | } | |||
990 | | |||
| 131858 | 991 | if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance | ||
| 0 | 992 | throw new ZipException("The library does not support the zip version required to extract this entry"); | ||
993 | } | |||
994 | } | |||
995 | } | |||
996 | | |||
| 131862 | 997 | if (testHeader) { | ||
| 65916 | 998 | if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. | ||
| 65916 | 999 | (extractVersion != 10) && | ||
| 65916 | 1000 | (extractVersion != 11) && | ||
| 65916 | 1001 | (extractVersion != 20) && | ||
| 65916 | 1002 | (extractVersion != 21) && | ||
| 65916 | 1003 | (extractVersion != 25) && | ||
| 65916 | 1004 | (extractVersion != 27) && | ||
| 65916 | 1005 | (extractVersion != 45) && | ||
| 65916 | 1006 | (extractVersion != 46) && | ||
| 65916 | 1007 | (extractVersion != 50) && | ||
| 65916 | 1008 | (extractVersion != 51) && | ||
| 65916 | 1009 | (extractVersion != 52) && | ||
| 65916 | 1010 | (extractVersion != 61) && | ||
| 65916 | 1011 | (extractVersion != 62) && | ||
| 65916 | 1012 | (extractVersion != 63) | ||
| 65916 | 1013 | ) { | ||
| 0 | 1014 | throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersi | ||
1015 | } | |||
1016 | | |||
1017 | // Local entry flags dont have reserved bit set on. | |||
| 65916 | 1018 | if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R | ||
| 0 | 1019 | throw new ZipException("Reserved bit flags cannot be set."); | ||
1020 | } | |||
1021 | | |||
1022 | // Encryption requires extract version >= 20 | |||
| 65916 | 1023 | if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { | ||
| 0 | 1024 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0}) | ||
1025 | } | |||
1026 | | |||
1027 | // Strong encryption requires encryption flag to be set and extract version >= 50. | |||
| 65916 | 1028 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | ||
| 0 | 1029 | if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) { | ||
| 0 | 1030 | throw new ZipException("Strong encryption flag set but encryption flag is not set"); | ||
1031 | } | |||
1032 | | |||
| 0 | 1033 | if (extractVersion < 50) { | ||
| 0 | 1034 | throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0 | ||
1035 | } | |||
1036 | } | |||
1037 | | |||
1038 | // Patched entries require extract version >= 27 | |||
| 65916 | 1039 | if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { | ||
| 0 | 1040 | throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); | ||
1041 | } | |||
1042 | | |||
1043 | // Central header flags match local entry flags. | |||
| 65916 | 1044 | if (localFlags != entry.Flags) { | ||
| 0 | 1045 | throw new ZipException("Central header/local header flags mismatch"); | ||
1046 | } | |||
1047 | | |||
1048 | // Central header compression method matches local entry | |||
| 65916 | 1049 | if (entry.CompressionMethod != (CompressionMethod)compressionMethod) { | ||
| 0 | 1050 | throw new ZipException("Central header/local header compression method mismatch"); | ||
1051 | } | |||
1052 | | |||
| 65916 | 1053 | if (entry.Version != extractVersion) { | ||
| 0 | 1054 | throw new ZipException("Extract version mismatch"); | ||
1055 | } | |||
1056 | | |||
1057 | // Strong encryption and extract version match | |||
| 65916 | 1058 | if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) { | ||
| 0 | 1059 | if (extractVersion < 62) { | ||
| 0 | 1060 | throw new ZipException("Strong encryption flag set but version not high enough"); | ||
1061 | } | |||
1062 | } | |||
1063 | | |||
| 65916 | 1064 | if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) { | ||
| 0 | 1065 | if ((fileTime != 0) || (fileDate != 0)) { | ||
| 0 | 1066 | throw new ZipException("Header masked set but date/time values non-zero"); | ||
1067 | } | |||
1068 | } | |||
1069 | | |||
| 65916 | 1070 | if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) { | ||
| 65900 | 1071 | if (crcValue != (uint)entry.Crc) { | ||
| 0 | 1072 | throw new ZipException("Central header/local header crc mismatch"); | ||
1073 | } | |||
1074 | } | |||
1075 | | |||
1076 | // Crc valid for empty entry. | |||
1077 | // This will also apply to streamed entries where size isnt known and the header cant be patched | |||
| 65916 | 1078 | if ((size == 0) && (compressedSize == 0)) { | ||
| 65541 | 1079 | if (crcValue != 0) { | ||
| 0 | 1080 | throw new ZipException("Invalid CRC for empty entry"); | ||
1081 | } | |||
1082 | } | |||
1083 | | |||
1084 | // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS str | |||
1085 | // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably | |||
| 65916 | 1086 | if (entry.Name.Length > storedNameLength) { | ||
| 0 | 1087 | throw new ZipException("File name length mismatch"); | ||
1088 | } | |||
1089 | | |||
1090 | // Name data has already been read convert it and compare. | |||
| 65916 | 1091 | string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); | ||
1092 | | |||
1093 | // Central directory and local entry name match | |||
| 65916 | 1094 | if (localName != entry.Name) { | ||
| 0 | 1095 | throw new ZipException("Central header and local header file name mismatch"); | ||
1096 | } | |||
1097 | | |||
1098 | // Directories have zero actual size but can have compressed size | |||
| 65916 | 1099 | if (entry.IsDirectory) { | ||
| 4 | 1100 | if (size > 0) { | ||
| 0 | 1101 | throw new ZipException("Directory cannot have size"); | ||
1102 | } | |||
1103 | | |||
1104 | // There may be other cases where the compressed size can be greater than this? | |||
1105 | // If so until details are known we will be strict. | |||
| 4 | 1106 | if (entry.IsCrypted) { | ||
| 2 | 1107 | if (compressedSize > ZipConstants.CryptoHeaderSize + 2) { | ||
| 0 | 1108 | throw new ZipException("Directory compressed size invalid"); | ||
1109 | } | |||
| 2 | 1110 | } else if (compressedSize > 2) { | ||
1111 | // When not compressed the directory size can validly be 2 bytes | |||
1112 | // if the true size wasnt known when data was originally being written. | |||
1113 | // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes | |||
| 0 | 1114 | throw new ZipException("Directory compressed size invalid"); | ||
1115 | } | |||
1116 | } | |||
1117 | | |||
| 65916 | 1118 | if (!ZipNameTransform.IsValidName(localName, true)) { | ||
| 0 | 1119 | throw new ZipException("Name is invalid"); | ||
1120 | } | |||
1121 | } | |||
1122 | | |||
1123 | // Tests that apply to both data and header. | |||
1124 | | |||
1125 | // Size can be verified only if it is known in the local header. | |||
1126 | // it will always be known in the central header. | |||
| 131862 | 1127 | if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || | ||
| 131862 | 1128 | ((size > 0 || compressedSize > 0) && entry.Size > 0)) { | ||
1129 | | |||
| 131847 | 1130 | if ((size != 0) | ||
| 131847 | 1131 | && (size != entry.Size)) { | ||
| 0 | 1132 | throw new ZipException( | ||
| 0 | 1133 | string.Format("Size mismatch between central header({0}) and local header({1})", | ||
| 0 | 1134 | entry.Size, size)); | ||
1135 | } | |||
1136 | | |||
| 131847 | 1137 | if ((compressedSize != 0) | ||
| 131847 | 1138 | && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) { | ||
| 0 | 1139 | throw new ZipException( | ||
| 0 | 1140 | string.Format("Compressed size mismatch between central header({0}) and local header({1})", | ||
| 0 | 1141 | entry.CompressedSize, compressedSize)); | ||
1142 | } | |||
1143 | } | |||
1144 | | |||
| 131862 | 1145 | int extraLength = storedNameLength + extraDataLength; | ||
| 131862 | 1146 | return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; | ||
1147 | } | |||
| 131862 | 1148 | } | ||
1149 | | |||
1150 | #endregion | |||
1151 | | |||
1152 | #region Updating | |||
1153 | | |||
1154 | const int DefaultBufferSize = 4096; | |||
1155 | | |||
1156 | /// <summary> | |||
1157 | /// The kind of update to apply. | |||
1158 | /// </summary> | |||
1159 | enum UpdateCommand | |||
1160 | { | |||
1161 | Copy, // Copy original file contents. | |||
1162 | Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. | |||
1163 | Add, // Add a new file to the archive. | |||
1164 | } | |||
1165 | | |||
1166 | #region Properties | |||
1167 | /// <summary> | |||
1168 | /// Get / set the <see cref="INameTransform"/> to apply to names when updating. | |||
1169 | /// </summary> | |||
1170 | public INameTransform NameTransform { | |||
1171 | get { | |||
| 65740 | 1172 | return updateEntryFactory_.NameTransform; | ||
1173 | } | |||
1174 | | |||
1175 | set { | |||
| 0 | 1176 | updateEntryFactory_.NameTransform = value; | ||
| 0 | 1177 | } | ||
1178 | } | |||
1179 | | |||
1180 | /// <summary> | |||
1181 | /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values | |||
1182 | /// during updates. | |||
1183 | /// </summary> | |||
1184 | public IEntryFactory EntryFactory { | |||
1185 | get { | |||
| 172 | 1186 | return updateEntryFactory_; | ||
1187 | } | |||
1188 | | |||
1189 | set { | |||
| 0 | 1190 | if (value == null) { | ||
| 0 | 1191 | updateEntryFactory_ = new ZipEntryFactory(); | ||
| 0 | 1192 | } else { | ||
| 0 | 1193 | updateEntryFactory_ = value; | ||
1194 | } | |||
| 0 | 1195 | } | ||
1196 | } | |||
1197 | | |||
1198 | /// <summary> | |||
1199 | /// Get /set the buffer size to be used when updating this zip file. | |||
1200 | /// </summary> | |||
1201 | public int BufferSize { | |||
| 0 | 1202 | get { return bufferSize_; } | ||
1203 | set { | |||
| 0 | 1204 | if (value < 1024) { | ||
| 0 | 1205 | throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); | ||
1206 | } | |||
1207 | | |||
| 0 | 1208 | if (bufferSize_ != value) { | ||
| 0 | 1209 | bufferSize_ = value; | ||
| 0 | 1210 | copyBuffer_ = null; | ||
1211 | } | |||
| 0 | 1212 | } | ||
1213 | } | |||
1214 | | |||
1215 | /// <summary> | |||
1216 | /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. | |||
1217 | /// </summary> | |||
1218 | public bool IsUpdating { | |||
| 0 | 1219 | get { return updates_ != null; } | ||
1220 | } | |||
1221 | | |||
1222 | /// <summary> | |||
1223 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
1224 | /// </summary> | |||
1225 | public UseZip64 UseZip64 { | |||
| 3 | 1226 | get { return useZip64_; } | ||
| 12 | 1227 | set { useZip64_ = value; } | ||
1228 | } | |||
1229 | | |||
1230 | #endregion | |||
1231 | | |||
1232 | #region Immediate updating | |||
1233 | // TBD: Direct form of updating | |||
1234 | // | |||
1235 | // public void Update(IEntryMatcher deleteMatcher) | |||
1236 | // { | |||
1237 | // } | |||
1238 | // | |||
1239 | // public void Update(IScanner addScanner) | |||
1240 | // { | |||
1241 | // } | |||
1242 | #endregion | |||
1243 | | |||
1244 | #region Deferred Updating | |||
1245 | /// <summary> | |||
1246 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1247 | /// </summary> | |||
1248 | /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</p | |||
1249 | /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param | |||
1250 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1251 | /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception> | |||
1252 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1253 | public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) | |||
1254 | { | |||
| 48 | 1255 | if (archiveStorage == null) { | ||
| 0 | 1256 | throw new ArgumentNullException(nameof(archiveStorage)); | ||
1257 | } | |||
1258 | | |||
| 48 | 1259 | if (dataSource == null) { | ||
| 0 | 1260 | throw new ArgumentNullException(nameof(dataSource)); | ||
1261 | } | |||
1262 | | |||
| 48 | 1263 | if (isDisposed_) { | ||
| 0 | 1264 | throw new ObjectDisposedException("ZipFile"); | ||
1265 | } | |||
1266 | | |||
| 48 | 1267 | if (IsEmbeddedArchive) { | ||
| 0 | 1268 | throw new ZipException("Cannot update embedded/SFX archives"); | ||
1269 | } | |||
1270 | | |||
| 48 | 1271 | archiveStorage_ = archiveStorage; | ||
| 48 | 1272 | updateDataSource_ = dataSource; | ||
1273 | | |||
1274 | // NOTE: the baseStream_ may not currently support writing or seeking. | |||
1275 | | |||
| 48 | 1276 | updateIndex_ = new Hashtable(); | ||
1277 | | |||
| 48 | 1278 | updates_ = new ArrayList(entries_.Length); | ||
| 318 | 1279 | foreach (ZipEntry entry in entries_) { | ||
| 111 | 1280 | int index = updates_.Add(new ZipUpdate(entry)); | ||
| 111 | 1281 | updateIndex_.Add(entry.Name, index); | ||
1282 | } | |||
1283 | | |||
1284 | // We must sort by offset before using offset's calculated sizes | |||
| 48 | 1285 | updates_.Sort(new UpdateComparer()); | ||
1286 | | |||
| 48 | 1287 | int idx = 0; | ||
| 287 | 1288 | foreach (ZipUpdate update in updates_) { | ||
1289 | //If last entry, there is no next entry offset to use | |||
| 111 | 1290 | if (idx == updates_.Count - 1) | ||
| 31 | 1291 | break; | ||
1292 | | |||
| 80 | 1293 | update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; | ||
| 80 | 1294 | idx++; | ||
1295 | } | |||
| 48 | 1296 | updateCount_ = updates_.Count; | ||
1297 | | |||
| 48 | 1298 | contentsEdited_ = false; | ||
| 48 | 1299 | commentEdited_ = false; | ||
| 48 | 1300 | newComment_ = null; | ||
| 48 | 1301 | } | ||
1302 | | |||
1303 | /// <summary> | |||
1304 | /// Begin updating to this <see cref="ZipFile"/> archive. | |||
1305 | /// </summary> | |||
1306 | /// <param name="archiveStorage">The storage to use during the update.</param> | |||
1307 | public void BeginUpdate(IArchiveStorage archiveStorage) | |||
1308 | { | |||
| 32 | 1309 | BeginUpdate(archiveStorage, new DynamicDiskDataSource()); | ||
| 32 | 1310 | } | ||
1311 | | |||
1312 | /// <summary> | |||
1313 | /// Begin updating this <see cref="ZipFile"/> archive. | |||
1314 | /// </summary> | |||
1315 | /// <seealso cref="BeginUpdate(IArchiveStorage)"/> | |||
1316 | /// <seealso cref="CommitUpdate"></seealso> | |||
1317 | /// <seealso cref="AbortUpdate"></seealso> | |||
1318 | public void BeginUpdate() | |||
1319 | { | |||
| 16 | 1320 | if (Name == null) { | ||
| 6 | 1321 | BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); | ||
| 6 | 1322 | } else { | ||
| 10 | 1323 | BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); | ||
1324 | } | |||
| 10 | 1325 | } | ||
1326 | | |||
1327 | /// <summary> | |||
1328 | /// Commit current updates, updating this archive. | |||
1329 | /// </summary> | |||
1330 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1331 | /// <seealso cref="AbortUpdate"></seealso> | |||
1332 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1333 | public void CommitUpdate() | |||
1334 | { | |||
| 48 | 1335 | if (isDisposed_) { | ||
| 0 | 1336 | throw new ObjectDisposedException("ZipFile"); | ||
1337 | } | |||
1338 | | |||
| 48 | 1339 | CheckUpdating(); | ||
1340 | | |||
1341 | try { | |||
| 48 | 1342 | updateIndex_.Clear(); | ||
| 48 | 1343 | updateIndex_ = null; | ||
1344 | | |||
| 48 | 1345 | if (contentsEdited_) { | ||
| 44 | 1346 | RunUpdates(); | ||
| 48 | 1347 | } else if (commentEdited_) { | ||
| 3 | 1348 | UpdateCommentOnly(); | ||
| 3 | 1349 | } else { | ||
1350 | // Create an empty archive if none existed originally. | |||
| 1 | 1351 | if (entries_.Length == 0) { | ||
| 1 | 1352 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | ||
| 1 | 1353 | using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) { | ||
| 1 | 1354 | zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); | ||
| 1 | 1355 | } | ||
1356 | } | |||
1357 | } | |||
1358 | | |||
| 0 | 1359 | } finally { | ||
| 48 | 1360 | PostUpdateCleanup(); | ||
| 48 | 1361 | } | ||
| 48 | 1362 | } | ||
1363 | | |||
1364 | /// <summary> | |||
1365 | /// Abort updating leaving the archive unchanged. | |||
1366 | /// </summary> | |||
1367 | /// <seealso cref="BeginUpdate()"></seealso> | |||
1368 | /// <seealso cref="CommitUpdate"></seealso> | |||
1369 | public void AbortUpdate() | |||
1370 | { | |||
| 0 | 1371 | PostUpdateCleanup(); | ||
| 0 | 1372 | } | ||
1373 | | |||
1374 | /// <summary> | |||
1375 | /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. | |||
1376 | /// </summary> | |||
1377 | /// <param name="comment">The comment to record.</param> | |||
1378 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1379 | public void SetComment(string comment) | |||
1380 | { | |||
| 3 | 1381 | if (isDisposed_) { | ||
| 0 | 1382 | throw new ObjectDisposedException("ZipFile"); | ||
1383 | } | |||
1384 | | |||
| 3 | 1385 | CheckUpdating(); | ||
1386 | | |||
| 3 | 1387 | newComment_ = new ZipString(comment); | ||
1388 | | |||
| 3 | 1389 | if (newComment_.RawLength > 0xffff) { | ||
| 0 | 1390 | newComment_ = null; | ||
| 0 | 1391 | throw new ZipException("Comment length exceeds maximum - 65535"); | ||
1392 | } | |||
1393 | | |||
1394 | // We dont take account of the original and current comment appearing to be the same | |||
1395 | // as encoding may be different. | |||
| 3 | 1396 | commentEdited_ = true; | ||
| 3 | 1397 | } | ||
1398 | | |||
1399 | #endregion | |||
1400 | | |||
1401 | #region Adding Entries | |||
1402 | | |||
1403 | void AddUpdate(ZipUpdate update) | |||
1404 | { | |||
| 65705 | 1405 | contentsEdited_ = true; | ||
1406 | | |||
| 65705 | 1407 | int index = FindExistingUpdate(update.Entry.Name); | ||
1408 | | |||
| 65705 | 1409 | if (index >= 0) { | ||
| 0 | 1410 | if (updates_[index] == null) { | ||
| 0 | 1411 | updateCount_ += 1; | ||
1412 | } | |||
1413 | | |||
1414 | // Direct replacement is faster than delete and add. | |||
| 0 | 1415 | updates_[index] = update; | ||
| 0 | 1416 | } else { | ||
| 65705 | 1417 | index = updates_.Add(update); | ||
| 65705 | 1418 | updateCount_ += 1; | ||
| 65705 | 1419 | updateIndex_.Add(update.Entry.Name, index); | ||
1420 | } | |||
| 65705 | 1421 | } | ||
1422 | | |||
1423 | /// <summary> | |||
1424 | /// Add a new entry to the archive. | |||
1425 | /// </summary> | |||
1426 | /// <param name="fileName">The name of the file to add.</param> | |||
1427 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1428 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> | |||
1429 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1430 | /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception> | |||
1431 | /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception> | |||
1432 | public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) | |||
1433 | { | |||
| 0 | 1434 | if (fileName == null) { | ||
| 0 | 1435 | throw new ArgumentNullException(nameof(fileName)); | ||
1436 | } | |||
1437 | | |||
| 0 | 1438 | if (isDisposed_) { | ||
| 0 | 1439 | throw new ObjectDisposedException("ZipFile"); | ||
1440 | } | |||
1441 | | |||
| 0 | 1442 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | ||
| 0 | 1443 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | ||
1444 | } | |||
1445 | | |||
| 0 | 1446 | CheckUpdating(); | ||
| 0 | 1447 | contentsEdited_ = true; | ||
1448 | | |||
| 0 | 1449 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | ||
| 0 | 1450 | entry.IsUnicodeText = useUnicodeText; | ||
| 0 | 1451 | entry.CompressionMethod = compressionMethod; | ||
1452 | | |||
| 0 | 1453 | AddUpdate(new ZipUpdate(fileName, entry)); | ||
| 0 | 1454 | } | ||
1455 | | |||
1456 | /// <summary> | |||
1457 | /// Add a new entry to the archive. | |||
1458 | /// </summary> | |||
1459 | /// <param name="fileName">The name of the file to add.</param> | |||
1460 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1461 | /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception> | |||
1462 | /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception> | |||
1463 | public void Add(string fileName, CompressionMethod compressionMethod) | |||
1464 | { | |||
| 0 | 1465 | if (fileName == null) { | ||
| 0 | 1466 | throw new ArgumentNullException(nameof(fileName)); | ||
1467 | } | |||
1468 | | |||
| 0 | 1469 | if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { | ||
| 0 | 1470 | throw new ArgumentOutOfRangeException(nameof(compressionMethod)); | ||
1471 | } | |||
1472 | | |||
| 0 | 1473 | CheckUpdating(); | ||
| 0 | 1474 | contentsEdited_ = true; | ||
1475 | | |||
| 0 | 1476 | ZipEntry entry = EntryFactory.MakeFileEntry(fileName); | ||
| 0 | 1477 | entry.CompressionMethod = compressionMethod; | ||
| 0 | 1478 | AddUpdate(new ZipUpdate(fileName, entry)); | ||
| 0 | 1479 | } | ||
1480 | | |||
1481 | /// <summary> | |||
1482 | /// Add a file to the archive. | |||
1483 | /// </summary> | |||
1484 | /// <param name="fileName">The name of the file to add.</param> | |||
1485 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1486 | public void Add(string fileName) | |||
1487 | { | |||
| 3 | 1488 | if (fileName == null) { | ||
| 0 | 1489 | throw new ArgumentNullException(nameof(fileName)); | ||
1490 | } | |||
1491 | | |||
| 3 | 1492 | CheckUpdating(); | ||
| 3 | 1493 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); | ||
| 3 | 1494 | } | ||
1495 | | |||
1496 | /// <summary> | |||
1497 | /// Add a file to the archive. | |||
1498 | /// </summary> | |||
1499 | /// <param name="fileName">The name of the file to add.</param> | |||
1500 | /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param> | |||
1501 | /// <exception cref="ArgumentNullException">Argument supplied is null.</exception> | |||
1502 | public void Add(string fileName, string entryName) | |||
1503 | { | |||
| 0 | 1504 | if (fileName == null) { | ||
| 0 | 1505 | throw new ArgumentNullException(nameof(fileName)); | ||
1506 | } | |||
1507 | | |||
| 0 | 1508 | if (entryName == null) { | ||
| 0 | 1509 | throw new ArgumentNullException(nameof(entryName)); | ||
1510 | } | |||
1511 | | |||
| 0 | 1512 | CheckUpdating(); | ||
| 0 | 1513 | AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); | ||
| 0 | 1514 | } | ||
1515 | | |||
1516 | | |||
1517 | /// <summary> | |||
1518 | /// Add a file entry with data. | |||
1519 | /// </summary> | |||
1520 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1521 | /// <param name="entryName">The name to give to the entry.</param> | |||
1522 | public void Add(IStaticDataSource dataSource, string entryName) | |||
1523 | { | |||
| 146 | 1524 | if (dataSource == null) { | ||
| 0 | 1525 | throw new ArgumentNullException(nameof(dataSource)); | ||
1526 | } | |||
1527 | | |||
| 146 | 1528 | if (entryName == null) { | ||
| 0 | 1529 | throw new ArgumentNullException(nameof(entryName)); | ||
1530 | } | |||
1531 | | |||
| 146 | 1532 | CheckUpdating(); | ||
| 146 | 1533 | AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); | ||
| 146 | 1534 | } | ||
1535 | | |||
1536 | /// <summary> | |||
1537 | /// Add a file entry with data. | |||
1538 | /// </summary> | |||
1539 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1540 | /// <param name="entryName">The name to give to the entry.</param> | |||
1541 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1542 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | |||
1543 | { | |||
| 15 | 1544 | if (dataSource == null) { | ||
| 0 | 1545 | throw new ArgumentNullException(nameof(dataSource)); | ||
1546 | } | |||
1547 | | |||
| 15 | 1548 | if (entryName == null) { | ||
| 0 | 1549 | throw new ArgumentNullException(nameof(entryName)); | ||
1550 | } | |||
1551 | | |||
| 15 | 1552 | CheckUpdating(); | ||
1553 | | |||
| 15 | 1554 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | ||
| 15 | 1555 | entry.CompressionMethod = compressionMethod; | ||
1556 | | |||
| 15 | 1557 | AddUpdate(new ZipUpdate(dataSource, entry)); | ||
| 15 | 1558 | } | ||
1559 | | |||
1560 | /// <summary> | |||
1561 | /// Add a file entry with data. | |||
1562 | /// </summary> | |||
1563 | /// <param name="dataSource">The source of the data for this entry.</param> | |||
1564 | /// <param name="entryName">The name to give to the entry.</param> | |||
1565 | /// <param name="compressionMethod">The compression method to use.</param> | |||
1566 | /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> | |||
1567 | public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicode | |||
1568 | { | |||
| 4 | 1569 | if (dataSource == null) { | ||
| 0 | 1570 | throw new ArgumentNullException(nameof(dataSource)); | ||
1571 | } | |||
1572 | | |||
| 4 | 1573 | if (entryName == null) { | ||
| 0 | 1574 | throw new ArgumentNullException(nameof(entryName)); | ||
1575 | } | |||
1576 | | |||
| 4 | 1577 | CheckUpdating(); | ||
1578 | | |||
| 4 | 1579 | ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); | ||
| 4 | 1580 | entry.IsUnicodeText = useUnicodeText; | ||
| 4 | 1581 | entry.CompressionMethod = compressionMethod; | ||
1582 | | |||
| 4 | 1583 | AddUpdate(new ZipUpdate(dataSource, entry)); | ||
| 4 | 1584 | } | ||
1585 | | |||
1586 | /// <summary> | |||
1587 | /// Add a <see cref="ZipEntry"/> that contains no data. | |||
1588 | /// </summary> | |||
1589 | /// <param name="entry">The entry to add.</param> | |||
1590 | /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> | |||
1591 | public void Add(ZipEntry entry) | |||
1592 | { | |||
| 65537 | 1593 | if (entry == null) { | ||
| 0 | 1594 | throw new ArgumentNullException(nameof(entry)); | ||
1595 | } | |||
1596 | | |||
| 65537 | 1597 | CheckUpdating(); | ||
1598 | | |||
| 65537 | 1599 | if ((entry.Size != 0) || (entry.CompressedSize != 0)) { | ||
| 0 | 1600 | throw new ZipException("Entry cannot have any data"); | ||
1601 | } | |||
1602 | | |||
| 65537 | 1603 | AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); | ||
| 65537 | 1604 | } | ||
1605 | | |||
1606 | /// <summary> | |||
1607 | /// Add a directory entry to the archive. | |||
1608 | /// </summary> | |||
1609 | /// <param name="directoryName">The directory to add.</param> | |||
1610 | public void AddDirectory(string directoryName) | |||
1611 | { | |||
| 0 | 1612 | if (directoryName == null) { | ||
| 0 | 1613 | throw new ArgumentNullException(nameof(directoryName)); | ||
1614 | } | |||
1615 | | |||
| 0 | 1616 | CheckUpdating(); | ||
1617 | | |||
| 0 | 1618 | ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); | ||
| 0 | 1619 | AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); | ||
| 0 | 1620 | } | ||
1621 | | |||
1622 | #endregion | |||
1623 | | |||
1624 | #region Modifying Entries | |||
1625 | /* Modify not yet ready for public consumption. | |||
1626 | Direct modification of an entry should not overwrite original data before its read. | |||
1627 | Safe mode is trivial in this sense. | |||
1628 | public void Modify(ZipEntry original, ZipEntry updated) | |||
1629 | { | |||
1630 | if ( original == null ) { | |||
1631 | throw new ArgumentNullException("original"); | |||
1632 | } | |||
1633 | | |||
1634 | if ( updated == null ) { | |||
1635 | throw new ArgumentNullException("updated"); | |||
1636 | } | |||
1637 | | |||
1638 | CheckUpdating(); | |||
1639 | contentsEdited_ = true; | |||
1640 | updates_.Add(new ZipUpdate(original, updated)); | |||
1641 | } | |||
1642 | */ | |||
1643 | #endregion | |||
1644 | | |||
1645 | #region Deleting Entries | |||
1646 | /// <summary> | |||
1647 | /// Delete an entry by name | |||
1648 | /// </summary> | |||
1649 | /// <param name="fileName">The filename to delete</param> | |||
1650 | /// <returns>True if the entry was found and deleted; false otherwise.</returns> | |||
1651 | public bool Delete(string fileName) | |||
1652 | { | |||
| 3 | 1653 | if (fileName == null) { | ||
| 0 | 1654 | throw new ArgumentNullException(nameof(fileName)); | ||
1655 | } | |||
1656 | | |||
| 3 | 1657 | CheckUpdating(); | ||
1658 | | |||
| 3 | 1659 | bool result = false; | ||
| 3 | 1660 | int index = FindExistingUpdate(fileName); | ||
| 3 | 1661 | if ((index >= 0) && (updates_[index] != null)) { | ||
| 3 | 1662 | result = true; | ||
| 3 | 1663 | contentsEdited_ = true; | ||
| 3 | 1664 | updates_[index] = null; | ||
| 3 | 1665 | updateCount_ -= 1; | ||
| 3 | 1666 | } else { | ||
| 0 | 1667 | throw new ZipException("Cannot find entry to delete"); | ||
1668 | } | |||
| 3 | 1669 | return result; | ||
1670 | } | |||
1671 | | |||
1672 | /// <summary> | |||
1673 | /// Delete a <see cref="ZipEntry"/> from the archive. | |||
1674 | /// </summary> | |||
1675 | /// <param name="entry">The entry to delete.</param> | |||
1676 | public void Delete(ZipEntry entry) | |||
1677 | { | |||
| 32 | 1678 | if (entry == null) { | ||
| 0 | 1679 | throw new ArgumentNullException(nameof(entry)); | ||
1680 | } | |||
1681 | | |||
| 32 | 1682 | CheckUpdating(); | ||
1683 | | |||
| 32 | 1684 | int index = FindExistingUpdate(entry); | ||
| 32 | 1685 | if (index >= 0) { | ||
| 32 | 1686 | contentsEdited_ = true; | ||
| 32 | 1687 | updates_[index] = null; | ||
| 32 | 1688 | updateCount_ -= 1; | ||
| 32 | 1689 | } else { | ||
| 0 | 1690 | throw new ZipException("Cannot find entry to delete"); | ||
1691 | } | |||
1692 | } | |||
1693 | | |||
1694 | #endregion | |||
1695 | | |||
1696 | #region Update Support | |||
1697 | | |||
1698 | #region Writing Values/Headers | |||
1699 | void WriteLEShort(int value) | |||
1700 | { | |||
| 2237048 | 1701 | baseStream_.WriteByte((byte)(value & 0xff)); | ||
| 2237048 | 1702 | baseStream_.WriteByte((byte)((value >> 8) & 0xff)); | ||
| 2237048 | 1703 | } | ||
1704 | | |||
1705 | /// <summary> | |||
1706 | /// Write an unsigned short in little endian byte order. | |||
1707 | /// </summary> | |||
1708 | void WriteLEUshort(ushort value) | |||
1709 | { | |||
| 262944 | 1710 | baseStream_.WriteByte((byte)(value & 0xff)); | ||
| 262944 | 1711 | baseStream_.WriteByte((byte)(value >> 8)); | ||
| 262944 | 1712 | } | ||
1713 | | |||
1714 | /// <summary> | |||
1715 | /// Write an int in little endian byte order. | |||
1716 | /// </summary> | |||
1717 | void WriteLEInt(int value) | |||
1718 | { | |||
| 658180 | 1719 | WriteLEShort(value & 0xffff); | ||
| 658180 | 1720 | WriteLEShort(value >> 16); | ||
| 658180 | 1721 | } | ||
1722 | | |||
1723 | /// <summary> | |||
1724 | /// Write an unsigned int in little endian byte order. | |||
1725 | /// </summary> | |||
1726 | void WriteLEUint(uint value) | |||
1727 | { | |||
| 131472 | 1728 | WriteLEUshort((ushort)(value & 0xffff)); | ||
| 131472 | 1729 | WriteLEUshort((ushort)(value >> 16)); | ||
| 131472 | 1730 | } | ||
1731 | | |||
1732 | /// <summary> | |||
1733 | /// Write a long in little endian byte order. | |||
1734 | /// </summary> | |||
1735 | void WriteLeLong(long value) | |||
1736 | { | |||
| 4 | 1737 | WriteLEInt((int)(value & 0xffffffff)); | ||
| 4 | 1738 | WriteLEInt((int)(value >> 32)); | ||
| 4 | 1739 | } | ||
1740 | | |||
1741 | void WriteLEUlong(ulong value) | |||
1742 | { | |||
| 0 | 1743 | WriteLEUint((uint)(value & 0xffffffff)); | ||
| 0 | 1744 | WriteLEUint((uint)(value >> 32)); | ||
| 0 | 1745 | } | ||
1746 | | |||
1747 | void WriteLocalEntryHeader(ZipUpdate update) | |||
1748 | { | |||
| 65748 | 1749 | ZipEntry entry = update.OutEntry; | ||
1750 | | |||
1751 | // TODO: Local offset will require adjusting for multi-disk zip files. | |||
| 65748 | 1752 | entry.Offset = baseStream_.Position; | ||
1753 | | |||
1754 | // TODO: Need to clear any entry flags that dont make sense or throw an exception here. | |||
| 65748 | 1755 | if (update.Command != UpdateCommand.Copy) { | ||
| 65705 | 1756 | if (entry.CompressionMethod == CompressionMethod.Deflated) { | ||
| 65690 | 1757 | if (entry.Size == 0) { | ||
1758 | // No need to compress - no data. | |||
| 65537 | 1759 | entry.CompressedSize = entry.Size; | ||
| 65537 | 1760 | entry.Crc = 0; | ||
| 65537 | 1761 | entry.CompressionMethod = CompressionMethod.Stored; | ||
1762 | } | |||
| 65552 | 1763 | } else if (entry.CompressionMethod == CompressionMethod.Stored) { | ||
| 15 | 1764 | entry.Flags &= ~(int)GeneralBitFlags.Descriptor; | ||
1765 | } | |||
1766 | | |||
| 65705 | 1767 | if (HaveKeys) { | ||
| 5 | 1768 | entry.IsCrypted = true; | ||
| 5 | 1769 | if (entry.Crc < 0) { | ||
| 5 | 1770 | entry.Flags |= (int)GeneralBitFlags.Descriptor; | ||
1771 | } | |||
| 5 | 1772 | } else { | ||
| 65700 | 1773 | entry.IsCrypted = false; | ||
1774 | } | |||
1775 | | |||
| 65705 | 1776 | switch (useZip64_) { | ||
1777 | case UseZip64.Dynamic: | |||
| 65701 | 1778 | if (entry.Size < 0) { | ||
| 0 | 1779 | entry.ForceZip64(); | ||
1780 | } | |||
| 0 | 1781 | break; | ||
1782 | | |||
1783 | case UseZip64.On: | |||
| 2 | 1784 | entry.ForceZip64(); | ||
1785 | break; | |||
1786 | | |||
1787 | case UseZip64.Off: | |||
1788 | // Do nothing. The entry itself may be using Zip64 independantly. | |||
1789 | break; | |||
1790 | } | |||
1791 | } | |||
1792 | | |||
1793 | // Write the local file header | |||
| 65748 | 1794 | WriteLEInt(ZipConstants.LocalHeaderSignature); | ||
1795 | | |||
| 65748 | 1796 | WriteLEShort(entry.Version); | ||
| 65748 | 1797 | WriteLEShort(entry.Flags); | ||
1798 | | |||
| 65748 | 1799 | WriteLEShort((byte)entry.CompressionMethod); | ||
| 65748 | 1800 | WriteLEInt((int)entry.DosTime); | ||
1801 | | |||
| 65748 | 1802 | if (!entry.HasCrc) { | ||
1803 | // Note patch address for updating CRC later. | |||
| 168 | 1804 | update.CrcPatchOffset = baseStream_.Position; | ||
| 168 | 1805 | WriteLEInt((int)0); | ||
| 168 | 1806 | } else { | ||
| 65580 | 1807 | WriteLEInt(unchecked((int)entry.Crc)); | ||
1808 | } | |||
1809 | | |||
| 65748 | 1810 | if (entry.LocalHeaderRequiresZip64) { | ||
| 2 | 1811 | WriteLEInt(-1); | ||
| 2 | 1812 | WriteLEInt(-1); | ||
| 2 | 1813 | } else { | ||
| 65746 | 1814 | if ((entry.CompressedSize < 0) || (entry.Size < 0)) { | ||
| 166 | 1815 | update.SizePatchOffset = baseStream_.Position; | ||
1816 | } | |||
1817 | | |||
| 65746 | 1818 | WriteLEInt((int)entry.CompressedSize); | ||
| 65746 | 1819 | WriteLEInt((int)entry.Size); | ||
1820 | } | |||
1821 | | |||
| 65748 | 1822 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | ||
1823 | | |||
| 65748 | 1824 | if (name.Length > 0xFFFF) { | ||
| 0 | 1825 | throw new ZipException("Entry name too long."); | ||
1826 | } | |||
1827 | | |||
| 65748 | 1828 | var ed = new ZipExtraData(entry.ExtraData); | ||
1829 | | |||
| 65748 | 1830 | if (entry.LocalHeaderRequiresZip64) { | ||
| 2 | 1831 | ed.StartNewEntry(); | ||
1832 | | |||
1833 | // Local entry header always includes size and compressed size. | |||
1834 | // NOTE the order of these fields is reversed when compared to the normal headers! | |||
| 2 | 1835 | ed.AddLeLong(entry.Size); | ||
| 2 | 1836 | ed.AddLeLong(entry.CompressedSize); | ||
| 2 | 1837 | ed.AddNewEntry(1); | ||
| 2 | 1838 | } else { | ||
| 65746 | 1839 | ed.Delete(1); | ||
1840 | } | |||
1841 | | |||
| 65748 | 1842 | entry.ExtraData = ed.GetEntryData(); | ||
1843 | | |||
| 65748 | 1844 | WriteLEShort(name.Length); | ||
| 65748 | 1845 | WriteLEShort(entry.ExtraData.Length); | ||
1846 | | |||
| 65748 | 1847 | if (name.Length > 0) { | ||
| 65748 | 1848 | baseStream_.Write(name, 0, name.Length); | ||
1849 | } | |||
1850 | | |||
| 65748 | 1851 | if (entry.LocalHeaderRequiresZip64) { | ||
| 2 | 1852 | if (!ed.Find(1)) { | ||
| 0 | 1853 | throw new ZipException("Internal error cannot find extra data"); | ||
1854 | } | |||
1855 | | |||
| 2 | 1856 | update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; | ||
1857 | } | |||
1858 | | |||
| 65748 | 1859 | if (entry.ExtraData.Length > 0) { | ||
| 2 | 1860 | baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); | ||
1861 | } | |||
| 65748 | 1862 | } | ||
1863 | | |||
1864 | int WriteCentralDirectoryHeader(ZipEntry entry) | |||
1865 | { | |||
| 65772 | 1866 | if (entry.CompressedSize < 0) { | ||
| 0 | 1867 | throw new ZipException("Attempt to write central directory entry with unknown csize"); | ||
1868 | } | |||
1869 | | |||
| 65772 | 1870 | if (entry.Size < 0) { | ||
| 0 | 1871 | throw new ZipException("Attempt to write central directory entry with unknown size"); | ||
1872 | } | |||
1873 | | |||
| 65772 | 1874 | if (entry.Crc < 0) { | ||
| 0 | 1875 | throw new ZipException("Attempt to write central directory entry with unknown crc"); | ||
1876 | } | |||
1877 | | |||
1878 | // Write the central file header | |||
| 65772 | 1879 | WriteLEInt(ZipConstants.CentralHeaderSignature); | ||
1880 | | |||
1881 | // Version made by | |||
| 65772 | 1882 | WriteLEShort(ZipConstants.VersionMadeBy); | ||
1883 | | |||
1884 | // Version required to extract | |||
| 65772 | 1885 | WriteLEShort(entry.Version); | ||
1886 | | |||
| 65772 | 1887 | WriteLEShort(entry.Flags); | ||
1888 | | |||
1889 | unchecked { | |||
| 65772 | 1890 | WriteLEShort((byte)entry.CompressionMethod); | ||
| 65772 | 1891 | WriteLEInt((int)entry.DosTime); | ||
| 65772 | 1892 | WriteLEInt((int)entry.Crc); | ||
1893 | } | |||
1894 | | |||
| 65772 | 1895 | if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { | ||
| 2 | 1896 | WriteLEInt(-1); | ||
| 2 | 1897 | } else { | ||
| 65770 | 1898 | WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); | ||
1899 | } | |||
1900 | | |||
| 65772 | 1901 | if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { | ||
| 2 | 1902 | WriteLEInt(-1); | ||
| 2 | 1903 | } else { | ||
| 65770 | 1904 | WriteLEInt((int)entry.Size); | ||
1905 | } | |||
1906 | | |||
| 65772 | 1907 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | ||
1908 | | |||
| 65772 | 1909 | if (name.Length > 0xFFFF) { | ||
| 0 | 1910 | throw new ZipException("Entry name is too long."); | ||
1911 | } | |||
1912 | | |||
| 65772 | 1913 | WriteLEShort(name.Length); | ||
1914 | | |||
1915 | // Central header extra data is different to local header version so regenerate. | |||
| 65772 | 1916 | var ed = new ZipExtraData(entry.ExtraData); | ||
1917 | | |||
| 65772 | 1918 | if (entry.CentralHeaderRequiresZip64) { | ||
| 2 | 1919 | ed.StartNewEntry(); | ||
1920 | | |||
| 2 | 1921 | if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | ||
| 2 | 1922 | ed.AddLeLong(entry.Size); | ||
1923 | } | |||
1924 | | |||
| 2 | 1925 | if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { | ||
| 2 | 1926 | ed.AddLeLong(entry.CompressedSize); | ||
1927 | } | |||
1928 | | |||
| 2 | 1929 | if (entry.Offset >= 0xffffffff) { | ||
| 0 | 1930 | ed.AddLeLong(entry.Offset); | ||
1931 | } | |||
1932 | | |||
1933 | // Number of disk on which this file starts isnt supported and is never written here. | |||
| 2 | 1934 | ed.AddNewEntry(1); | ||
| 2 | 1935 | } else { | ||
1936 | // Should have already be done when local header was added. | |||
| 65770 | 1937 | ed.Delete(1); | ||
1938 | } | |||
1939 | | |||
| 65772 | 1940 | byte[] centralExtraData = ed.GetEntryData(); | ||
1941 | | |||
| 65772 | 1942 | WriteLEShort(centralExtraData.Length); | ||
| 65772 | 1943 | WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); | ||
1944 | | |||
| 65772 | 1945 | WriteLEShort(0); // disk number | ||
| 65772 | 1946 | WriteLEShort(0); // internal file attributes | ||
1947 | | |||
1948 | // External file attributes... | |||
| 65772 | 1949 | if (entry.ExternalFileAttributes != -1) { | ||
| 72 | 1950 | WriteLEInt(entry.ExternalFileAttributes); | ||
| 72 | 1951 | } else { | ||
| 65700 | 1952 | if (entry.IsDirectory) { | ||
| 0 | 1953 | WriteLEUint(16); | ||
| 0 | 1954 | } else { | ||
| 65700 | 1955 | WriteLEUint(0); | ||
1956 | } | |||
1957 | } | |||
1958 | | |||
| 65772 | 1959 | if (entry.Offset >= 0xffffffff) { | ||
| 0 | 1960 | WriteLEUint(0xffffffff); | ||
| 0 | 1961 | } else { | ||
| 65772 | 1962 | WriteLEUint((uint)(int)entry.Offset); | ||
1963 | } | |||
1964 | | |||
| 65772 | 1965 | if (name.Length > 0) { | ||
| 65772 | 1966 | baseStream_.Write(name, 0, name.Length); | ||
1967 | } | |||
1968 | | |||
| 65772 | 1969 | if (centralExtraData.Length > 0) { | ||
| 2 | 1970 | baseStream_.Write(centralExtraData, 0, centralExtraData.Length); | ||
1971 | } | |||
1972 | | |||
| 65772 | 1973 | byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; | ||
1974 | | |||
| 65772 | 1975 | if (rawComment.Length > 0) { | ||
| 0 | 1976 | baseStream_.Write(rawComment, 0, rawComment.Length); | ||
1977 | } | |||
1978 | | |||
| 65772 | 1979 | return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; | ||
1980 | } | |||
1981 | #endregion | |||
1982 | | |||
1983 | void PostUpdateCleanup() | |||
1984 | { | |||
| 136 | 1985 | updateDataSource_ = null; | ||
| 136 | 1986 | updates_ = null; | ||
| 136 | 1987 | updateIndex_ = null; | ||
1988 | | |||
| 136 | 1989 | if (archiveStorage_ != null) { | ||
| 48 | 1990 | archiveStorage_.Dispose(); | ||
| 48 | 1991 | archiveStorage_ = null; | ||
1992 | } | |||
| 136 | 1993 | } | ||
1994 | | |||
1995 | string GetTransformedFileName(string name) | |||
1996 | { | |||
| 65740 | 1997 | INameTransform transform = NameTransform; | ||
| 65740 | 1998 | return (transform != null) ? | ||
| 65740 | 1999 | transform.TransformFile(name) : | ||
| 65740 | 2000 | name; | ||
2001 | } | |||
2002 | | |||
2003 | string GetTransformedDirectoryName(string name) | |||
2004 | { | |||
| 0 | 2005 | INameTransform transform = NameTransform; | ||
| 0 | 2006 | return (transform != null) ? | ||
| 0 | 2007 | transform.TransformDirectory(name) : | ||
| 0 | 2008 | name; | ||
2009 | } | |||
2010 | | |||
2011 | /// <summary> | |||
2012 | /// Get a raw memory buffer. | |||
2013 | /// </summary> | |||
2014 | /// <returns>Returns a raw memory buffer.</returns> | |||
2015 | byte[] GetBuffer() | |||
2016 | { | |||
| 211 | 2017 | if (copyBuffer_ == null) { | ||
| 41 | 2018 | copyBuffer_ = new byte[bufferSize_]; | ||
2019 | } | |||
| 211 | 2020 | return copyBuffer_; | ||
2021 | } | |||
2022 | | |||
2023 | void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) | |||
2024 | { | |||
| 4 | 2025 | int bytesToCopy = GetDescriptorSize(update); | ||
2026 | | |||
| 4 | 2027 | if (bytesToCopy > 0) { | ||
| 0 | 2028 | byte[] buffer = GetBuffer(); | ||
2029 | | |||
| 0 | 2030 | while (bytesToCopy > 0) { | ||
| 0 | 2031 | int readSize = Math.Min(buffer.Length, bytesToCopy); | ||
2032 | | |||
| 0 | 2033 | int bytesRead = source.Read(buffer, 0, readSize); | ||
| 0 | 2034 | if (bytesRead > 0) { | ||
| 0 | 2035 | dest.Write(buffer, 0, bytesRead); | ||
| 0 | 2036 | bytesToCopy -= bytesRead; | ||
| 0 | 2037 | } else { | ||
| 0 | 2038 | throw new ZipException("Unxpected end of stream"); | ||
2039 | } | |||
2040 | } | |||
2041 | } | |||
| 4 | 2042 | } | ||
2043 | | |||
2044 | void CopyBytes(ZipUpdate update, Stream destination, Stream source, | |||
2045 | long bytesToCopy, bool updateCrc) | |||
2046 | { | |||
| 172 | 2047 | if (destination == source) { | ||
| 0 | 2048 | throw new InvalidOperationException("Destination and source are the same"); | ||
2049 | } | |||
2050 | | |||
2051 | // NOTE: Compressed size is updated elsewhere. | |||
| 172 | 2052 | var crc = new Crc32(); | ||
| 172 | 2053 | byte[] buffer = GetBuffer(); | ||
2054 | | |||
| 172 | 2055 | long targetBytes = bytesToCopy; | ||
| 172 | 2056 | long totalBytesRead = 0; | ||
2057 | | |||
2058 | int bytesRead; | |||
2059 | do { | |||
| 172 | 2060 | int readSize = buffer.Length; | ||
2061 | | |||
| 172 | 2062 | if (bytesToCopy < readSize) { | ||
| 172 | 2063 | readSize = (int)bytesToCopy; | ||
2064 | } | |||
2065 | | |||
| 172 | 2066 | bytesRead = source.Read(buffer, 0, readSize); | ||
| 172 | 2067 | if (bytesRead > 0) { | ||
| 172 | 2068 | if (updateCrc) { | ||
| 168 | 2069 | crc.Update(buffer, 0, bytesRead); | ||
2070 | } | |||
| 172 | 2071 | destination.Write(buffer, 0, bytesRead); | ||
| 172 | 2072 | bytesToCopy -= bytesRead; | ||
| 172 | 2073 | totalBytesRead += bytesRead; | ||
2074 | } | |||
2075 | } | |||
| 172 | 2076 | while ((bytesRead > 0) && (bytesToCopy > 0)); | ||
2077 | | |||
| 172 | 2078 | if (totalBytesRead != targetBytes) { | ||
| 0 | 2079 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | ||
2080 | } | |||
2081 | | |||
| 172 | 2082 | if (updateCrc) { | ||
| 168 | 2083 | update.OutEntry.Crc = crc.Value; | ||
2084 | } | |||
| 172 | 2085 | } | ||
2086 | | |||
2087 | /// <summary> | |||
2088 | /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. | |||
2089 | /// </summary> | |||
2090 | /// <param name="update">The update to get the size for.</param> | |||
2091 | /// <returns>The descriptor size, zero if there isnt one.</returns> | |||
2092 | int GetDescriptorSize(ZipUpdate update) | |||
2093 | { | |||
| 45 | 2094 | int result = 0; | ||
| 45 | 2095 | if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | ||
| 0 | 2096 | result = ZipConstants.DataDescriptorSize - 4; | ||
| 0 | 2097 | if (update.Entry.LocalHeaderRequiresZip64) { | ||
| 0 | 2098 | result = ZipConstants.Zip64DataDescriptorSize - 4; | ||
2099 | } | |||
2100 | } | |||
| 45 | 2101 | return result; | ||
2102 | } | |||
2103 | | |||
2104 | void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) | |||
2105 | { | |||
| 39 | 2106 | int bytesToCopy = GetDescriptorSize(update); | ||
2107 | | |||
| 39 | 2108 | while (bytesToCopy > 0) { | ||
| 0 | 2109 | var readSize = (int)bytesToCopy; | ||
| 0 | 2110 | byte[] buffer = GetBuffer(); | ||
2111 | | |||
| 0 | 2112 | stream.Position = sourcePosition; | ||
| 0 | 2113 | int bytesRead = stream.Read(buffer, 0, readSize); | ||
| 0 | 2114 | if (bytesRead > 0) { | ||
| 0 | 2115 | stream.Position = destinationPosition; | ||
| 0 | 2116 | stream.Write(buffer, 0, bytesRead); | ||
| 0 | 2117 | bytesToCopy -= bytesRead; | ||
| 0 | 2118 | destinationPosition += bytesRead; | ||
| 0 | 2119 | sourcePosition += bytesRead; | ||
| 0 | 2120 | } else { | ||
| 0 | 2121 | throw new ZipException("Unxpected end of stream"); | ||
2122 | } | |||
2123 | } | |||
| 39 | 2124 | } | ||
2125 | | |||
2126 | void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou | |||
2127 | { | |||
| 39 | 2128 | long bytesToCopy = update.Entry.CompressedSize; | ||
2129 | | |||
2130 | // NOTE: Compressed size is updated elsewhere. | |||
| 39 | 2131 | var crc = new Crc32(); | ||
| 39 | 2132 | byte[] buffer = GetBuffer(); | ||
2133 | | |||
| 39 | 2134 | long targetBytes = bytesToCopy; | ||
| 39 | 2135 | long totalBytesRead = 0; | ||
2136 | | |||
2137 | int bytesRead; | |||
2138 | do { | |||
| 39 | 2139 | int readSize = buffer.Length; | ||
2140 | | |||
| 39 | 2141 | if (bytesToCopy < readSize) { | ||
| 39 | 2142 | readSize = (int)bytesToCopy; | ||
2143 | } | |||
2144 | | |||
| 39 | 2145 | stream.Position = sourcePosition; | ||
| 39 | 2146 | bytesRead = stream.Read(buffer, 0, readSize); | ||
| 39 | 2147 | if (bytesRead > 0) { | ||
| 39 | 2148 | if (updateCrc) { | ||
| 0 | 2149 | crc.Update(buffer, 0, bytesRead); | ||
2150 | } | |||
| 39 | 2151 | stream.Position = destinationPosition; | ||
| 39 | 2152 | stream.Write(buffer, 0, bytesRead); | ||
2153 | | |||
| 39 | 2154 | destinationPosition += bytesRead; | ||
| 39 | 2155 | sourcePosition += bytesRead; | ||
| 39 | 2156 | bytesToCopy -= bytesRead; | ||
| 39 | 2157 | totalBytesRead += bytesRead; | ||
2158 | } | |||
2159 | } | |||
| 39 | 2160 | while ((bytesRead > 0) && (bytesToCopy > 0)); | ||
2161 | | |||
| 39 | 2162 | if (totalBytesRead != targetBytes) { | ||
| 0 | 2163 | throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)) | ||
2164 | } | |||
2165 | | |||
| 39 | 2166 | if (updateCrc) { | ||
| 0 | 2167 | update.OutEntry.Crc = crc.Value; | ||
2168 | } | |||
| 39 | 2169 | } | ||
2170 | | |||
2171 | int FindExistingUpdate(ZipEntry entry) | |||
2172 | { | |||
| 32 | 2173 | int result = -1; | ||
| 32 | 2174 | string convertedName = GetTransformedFileName(entry.Name); | ||
2175 | | |||
| 32 | 2176 | if (updateIndex_.ContainsKey(convertedName)) { | ||
| 32 | 2177 | result = (int)updateIndex_[convertedName]; | ||
2178 | } | |||
2179 | /* | |||
2180 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2181 | // for CF? | |||
2182 | for (int index = 0; index < updates_.Count; ++index) | |||
2183 | { | |||
2184 | ZipUpdate zu = ( ZipUpdate )updates_[index]; | |||
2185 | if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && | |||
2186 | (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { | |||
2187 | result = index; | |||
2188 | break; | |||
2189 | } | |||
2190 | } | |||
2191 | */ | |||
| 32 | 2192 | return result; | ||
2193 | } | |||
2194 | | |||
2195 | int FindExistingUpdate(string fileName) | |||
2196 | { | |||
| 65708 | 2197 | int result = -1; | ||
2198 | | |||
| 65708 | 2199 | string convertedName = GetTransformedFileName(fileName); | ||
2200 | | |||
| 65708 | 2201 | if (updateIndex_.ContainsKey(convertedName)) { | ||
| 3 | 2202 | result = (int)updateIndex_[convertedName]; | ||
2203 | } | |||
2204 | | |||
2205 | /* | |||
2206 | // This is slow like the coming of the next ice age but takes less storage and may be useful | |||
2207 | // for CF? | |||
2208 | for ( int index = 0; index < updates_.Count; ++index ) { | |||
2209 | if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, | |||
2210 | true, CultureInfo.InvariantCulture) == 0 ) { | |||
2211 | result = index; | |||
2212 | break; | |||
2213 | } | |||
2214 | } | |||
2215 | */ | |||
2216 | | |||
| 65708 | 2217 | return result; | ||
2218 | } | |||
2219 | | |||
2220 | /// <summary> | |||
2221 | /// Get an output stream for the specified <see cref="ZipEntry"/> | |||
2222 | /// </summary> | |||
2223 | /// <param name="entry">The entry to get an output stream for.</param> | |||
2224 | /// <returns>The output stream obtained for the entry.</returns> | |||
2225 | Stream GetOutputStream(ZipEntry entry) | |||
2226 | { | |||
| 168 | 2227 | Stream result = baseStream_; | ||
2228 | | |||
| 168 | 2229 | if (entry.IsCrypted == true) { | ||
| 5 | 2230 | result = CreateAndInitEncryptionStream(result, entry); | ||
2231 | } | |||
2232 | | |||
| 168 | 2233 | switch (entry.CompressionMethod) { | ||
2234 | case CompressionMethod.Stored: | |||
| 15 | 2235 | result = new UncompressedStream(result); | ||
| 15 | 2236 | break; | ||
2237 | | |||
2238 | case CompressionMethod.Deflated: | |||
| 153 | 2239 | var dos = new DeflaterOutputStream(result, new Deflater(9, true)); | ||
| 153 | 2240 | dos.IsStreamOwner = false; | ||
| 153 | 2241 | result = dos; | ||
| 153 | 2242 | break; | ||
2243 | | |||
2244 | default: | |||
| 0 | 2245 | throw new ZipException("Unknown compression method " + entry.CompressionMethod); | ||
2246 | } | |||
| 168 | 2247 | return result; | ||
2248 | } | |||
2249 | | |||
2250 | void AddEntry(ZipFile workFile, ZipUpdate update) | |||
2251 | { | |||
| 65705 | 2252 | Stream source = null; | ||
2253 | | |||
| 65705 | 2254 | if (update.Entry.IsFile) { | ||
| 65705 | 2255 | source = update.GetSource(); | ||
2256 | | |||
| 65705 | 2257 | if (source == null) { | ||
| 65540 | 2258 | source = updateDataSource_.GetSource(update.Entry, update.Filename); | ||
2259 | } | |||
2260 | } | |||
2261 | | |||
| 65705 | 2262 | if (source != null) { | ||
| 168 | 2263 | using (source) { | ||
| 168 | 2264 | long sourceStreamLength = source.Length; | ||
| 168 | 2265 | if (update.OutEntry.Size < 0) { | ||
| 165 | 2266 | update.OutEntry.Size = sourceStreamLength; | ||
| 165 | 2267 | } else { | ||
2268 | // Check for errant entries. | |||
| 3 | 2269 | if (update.OutEntry.Size != sourceStreamLength) { | ||
| 0 | 2270 | throw new ZipException("Entry size/stream size mismatch"); | ||
2271 | } | |||
2272 | } | |||
2273 | | |||
| 168 | 2274 | workFile.WriteLocalEntryHeader(update); | ||
2275 | | |||
| 168 | 2276 | long dataStart = workFile.baseStream_.Position; | ||
2277 | | |||
| 168 | 2278 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | ||
| 168 | 2279 | CopyBytes(update, output, source, sourceStreamLength, true); | ||
| 168 | 2280 | } | ||
2281 | | |||
| 168 | 2282 | long dataEnd = workFile.baseStream_.Position; | ||
| 168 | 2283 | update.OutEntry.CompressedSize = dataEnd - dataStart; | ||
2284 | | |||
| 168 | 2285 | if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) { | ||
| 5 | 2286 | var helper = new ZipHelperStream(workFile.baseStream_); | ||
| 5 | 2287 | helper.WriteDataDescriptor(update.OutEntry); | ||
2288 | } | |||
| 168 | 2289 | } | ||
2290 | } else { | |||
| 65537 | 2291 | workFile.WriteLocalEntryHeader(update); | ||
| 65537 | 2292 | update.OutEntry.CompressedSize = 0; | ||
2293 | } | |||
2294 | | |||
| 65705 | 2295 | } | ||
2296 | | |||
2297 | void ModifyEntry(ZipFile workFile, ZipUpdate update) | |||
2298 | { | |||
| 0 | 2299 | workFile.WriteLocalEntryHeader(update); | ||
| 0 | 2300 | long dataStart = workFile.baseStream_.Position; | ||
2301 | | |||
2302 | // TODO: This is slow if the changes don't effect the data!! | |||
| 0 | 2303 | if (update.Entry.IsFile && (update.Filename != null)) { | ||
| 0 | 2304 | using (Stream output = workFile.GetOutputStream(update.OutEntry)) { | ||
| 0 | 2305 | using (Stream source = this.GetInputStream(update.Entry)) { | ||
| 0 | 2306 | CopyBytes(update, output, source, source.Length, true); | ||
| 0 | 2307 | } | ||
2308 | } | |||
2309 | } | |||
2310 | | |||
| 0 | 2311 | long dataEnd = workFile.baseStream_.Position; | ||
| 0 | 2312 | update.Entry.CompressedSize = dataEnd - dataStart; | ||
| 0 | 2313 | } | ||
2314 | | |||
2315 | void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) | |||
2316 | { | |||
| 63 | 2317 | bool skipOver = false || update.Entry.Offset == destinationPosition; | ||
2318 | | |||
| 63 | 2319 | if (!skipOver) { | ||
| 39 | 2320 | baseStream_.Position = destinationPosition; | ||
| 39 | 2321 | workFile.WriteLocalEntryHeader(update); | ||
| 39 | 2322 | destinationPosition = baseStream_.Position; | ||
2323 | } | |||
2324 | | |||
| 63 | 2325 | long sourcePosition = 0; | ||
2326 | | |||
2327 | const int NameLengthOffset = 26; | |||
2328 | | |||
2329 | // TODO: Add base for SFX friendly handling | |||
| 63 | 2330 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | ||
2331 | | |||
| 63 | 2332 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | ||
2333 | | |||
2334 | // Clumsy way of handling retrieving the original name and extra data length for now. | |||
2335 | // TODO: Stop re-reading name and data length in CopyEntryDirect. | |||
| 63 | 2336 | uint nameLength = ReadLEUshort(); | ||
| 63 | 2337 | uint extraLength = ReadLEUshort(); | ||
2338 | | |||
| 63 | 2339 | sourcePosition = baseStream_.Position + nameLength + extraLength; | ||
2340 | | |||
| 63 | 2341 | if (skipOver) { | ||
| 24 | 2342 | if (update.OffsetBasedSize != -1) | ||
| 22 | 2343 | destinationPosition += update.OffsetBasedSize; | ||
2344 | else | |||
2345 | // TODO: Find out why this calculation comes up 4 bytes short on some entries in ODT (Office Document Text) ar | |||
2346 | // WinZip produces a warning on these entries: | |||
2347 | // "caution: value of lrec.csize (compressed size) changed from ..." | |||
| 2 | 2348 | destinationPosition += | ||
| 2 | 2349 | (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size | ||
| 2 | 2350 | update.Entry.CompressedSize + GetDescriptorSize(update); | ||
| 2 | 2351 | } else { | ||
| 39 | 2352 | if (update.Entry.CompressedSize > 0) { | ||
| 39 | 2353 | CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); | ||
2354 | } | |||
| 39 | 2355 | CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); | ||
2356 | } | |||
| 39 | 2357 | } | ||
2358 | | |||
2359 | void CopyEntry(ZipFile workFile, ZipUpdate update) | |||
2360 | { | |||
| 4 | 2361 | workFile.WriteLocalEntryHeader(update); | ||
2362 | | |||
| 4 | 2363 | if (update.Entry.CompressedSize > 0) { | ||
2364 | const int NameLengthOffset = 26; | |||
2365 | | |||
| 4 | 2366 | long entryDataOffset = update.Entry.Offset + NameLengthOffset; | ||
2367 | | |||
2368 | // TODO: This wont work for SFX files! | |||
| 4 | 2369 | baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); | ||
2370 | | |||
| 4 | 2371 | uint nameLength = ReadLEUshort(); | ||
| 4 | 2372 | uint extraLength = ReadLEUshort(); | ||
2373 | | |||
| 4 | 2374 | baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); | ||
2375 | | |||
| 4 | 2376 | CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); | ||
2377 | } | |||
| 4 | 2378 | CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); | ||
| 4 | 2379 | } | ||
2380 | | |||
2381 | void Reopen(Stream source) | |||
2382 | { | |||
| 4 | 2383 | if (source == null) { | ||
| 0 | 2384 | throw new ZipException("Failed to reopen archive - no source"); | ||
2385 | } | |||
2386 | | |||
| 4 | 2387 | isNewArchive_ = false; | ||
| 4 | 2388 | baseStream_ = source; | ||
| 4 | 2389 | ReadEntries(); | ||
| 4 | 2390 | } | ||
2391 | | |||
2392 | void Reopen() | |||
2393 | { | |||
| 0 | 2394 | if (Name == null) { | ||
| 0 | 2395 | throw new InvalidOperationException("Name is not known cannot Reopen"); | ||
2396 | } | |||
2397 | | |||
| 0 | 2398 | Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); | ||
| 0 | 2399 | } | ||
2400 | | |||
2401 | void UpdateCommentOnly() | |||
2402 | { | |||
| 3 | 2403 | long baseLength = baseStream_.Length; | ||
2404 | | |||
| 3 | 2405 | ZipHelperStream updateFile = null; | ||
2406 | | |||
| 3 | 2407 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | ||
| 1 | 2408 | Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); | ||
| 1 | 2409 | updateFile = new ZipHelperStream(copyStream); | ||
| 1 | 2410 | updateFile.IsStreamOwner = true; | ||
2411 | | |||
| 1 | 2412 | baseStream_.Close(); | ||
| 1 | 2413 | baseStream_ = null; | ||
| 1 | 2414 | } else { | ||
| 2 | 2415 | if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | ||
2416 | // TODO: archiveStorage wasnt originally intended for this use. | |||
2417 | // Need to revisit this to tidy up handling as archive storage currently doesnt | |||
2418 | // handle the original stream well. | |||
2419 | // The problem is when using an existing zip archive with an in memory archive storage. | |||
2420 | // The open stream wont support writing but the memory storage should open the same file not an in memory one. | |||
2421 | | |||
2422 | // Need to tidy up the archive storage interface and contract basically. | |||
| 2 | 2423 | baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); | ||
| 2 | 2424 | updateFile = new ZipHelperStream(baseStream_); | ||
| 2 | 2425 | } else { | ||
| 0 | 2426 | baseStream_.Close(); | ||
| 0 | 2427 | baseStream_ = null; | ||
| 0 | 2428 | updateFile = new ZipHelperStream(Name); | ||
2429 | } | |||
2430 | } | |||
2431 | | |||
| 3 | 2432 | using (updateFile) { | ||
| 3 | 2433 | long locatedCentralDirOffset = | ||
| 3 | 2434 | updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | ||
| 3 | 2435 | baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | ||
| 3 | 2436 | if (locatedCentralDirOffset < 0) { | ||
| 0 | 2437 | throw new ZipException("Cannot find central directory"); | ||
2438 | } | |||
2439 | | |||
2440 | const int CentralHeaderCommentSizeOffset = 16; | |||
| 3 | 2441 | updateFile.Position += CentralHeaderCommentSizeOffset; | ||
2442 | | |||
| 3 | 2443 | byte[] rawComment = newComment_.RawComment; | ||
2444 | | |||
| 3 | 2445 | updateFile.WriteLEShort(rawComment.Length); | ||
| 3 | 2446 | updateFile.Write(rawComment, 0, rawComment.Length); | ||
| 3 | 2447 | updateFile.SetLength(updateFile.Position); | ||
| 3 | 2448 | } | ||
2449 | | |||
| 3 | 2450 | if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) { | ||
| 1 | 2451 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | ||
| 1 | 2452 | } else { | ||
| 2 | 2453 | ReadEntries(); | ||
2454 | } | |||
| 2 | 2455 | } | ||
2456 | | |||
2457 | /// <summary> | |||
2458 | /// Class used to sort updates. | |||
2459 | /// </summary> | |||
2460 | class UpdateComparer : IComparer | |||
2461 | { | |||
2462 | /// <summary> | |||
2463 | /// Compares two objects and returns a value indicating whether one is | |||
2464 | /// less than, equal to or greater than the other. | |||
2465 | /// </summary> | |||
2466 | /// <param name="x">First object to compare</param> | |||
2467 | /// <param name="y">Second object to compare.</param> | |||
2468 | /// <returns>Compare result.</returns> | |||
2469 | public int Compare( | |||
2470 | object x, | |||
2471 | object y) | |||
2472 | { | |||
| 471 | 2473 | var zx = x as ZipUpdate; | ||
| 471 | 2474 | var zy = y as ZipUpdate; | ||
2475 | | |||
2476 | int result; | |||
2477 | | |||
| 471 | 2478 | if (zx == null) { | ||
| 50 | 2479 | if (zy == null) { | ||
| 10 | 2480 | result = 0; | ||
| 10 | 2481 | } else { | ||
| 40 | 2482 | result = -1; | ||
2483 | } | |||
| 461 | 2484 | } else if (zy == null) { | ||
| 11 | 2485 | result = 1; | ||
| 11 | 2486 | } else { | ||
| 410 | 2487 | int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; | ||
| 410 | 2488 | int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; | ||
2489 | | |||
| 410 | 2490 | result = xCmdValue - yCmdValue; | ||
| 410 | 2491 | if (result == 0) { | ||
| 378 | 2492 | long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; | ||
| 378 | 2493 | if (offsetDiff < 0) { | ||
| 16 | 2494 | result = -1; | ||
| 378 | 2495 | } else if (offsetDiff == 0) { | ||
| 255 | 2496 | result = 0; | ||
| 255 | 2497 | } else { | ||
| 107 | 2498 | result = 1; | ||
2499 | } | |||
2500 | } | |||
2501 | } | |||
| 471 | 2502 | return result; | ||
2503 | } | |||
2504 | } | |||
2505 | | |||
2506 | void RunUpdates() | |||
2507 | { | |||
| 44 | 2508 | long sizeEntries = 0; | ||
| 44 | 2509 | long endOfStream = 0; | ||
| 44 | 2510 | bool directUpdate = false; | ||
| 44 | 2511 | long destinationPosition = 0; // NOT SFX friendly | ||
2512 | | |||
2513 | ZipFile workFile; | |||
2514 | | |||
| 44 | 2515 | if (IsNewArchive) { | ||
| 16 | 2516 | workFile = this; | ||
| 16 | 2517 | workFile.baseStream_.Position = 0; | ||
| 16 | 2518 | directUpdate = true; | ||
| 44 | 2519 | } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { | ||
| 25 | 2520 | workFile = this; | ||
| 25 | 2521 | workFile.baseStream_.Position = 0; | ||
| 25 | 2522 | directUpdate = true; | ||
2523 | | |||
2524 | // Sort the updates by offset within copies/modifies, then adds. | |||
2525 | // This ensures that data required by copies will not be overwritten. | |||
| 25 | 2526 | updates_.Sort(new UpdateComparer()); | ||
| 25 | 2527 | } else { | ||
| 3 | 2528 | workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); | ||
| 3 | 2529 | workFile.UseZip64 = UseZip64; | ||
2530 | | |||
| 3 | 2531 | if (key != null) { | ||
| 1 | 2532 | workFile.key = (byte[])key.Clone(); | ||
2533 | } | |||
2534 | } | |||
2535 | | |||
2536 | try { | |||
| 131702 | 2537 | foreach (ZipUpdate update in updates_) { | ||
| 65807 | 2538 | if (update != null) { | ||
| 65772 | 2539 | switch (update.Command) { | ||
2540 | case UpdateCommand.Copy: | |||
| 67 | 2541 | if (directUpdate) { | ||
| 63 | 2542 | CopyEntryDirect(workFile, update, ref destinationPosition); | ||
| 63 | 2543 | } else { | ||
| 4 | 2544 | CopyEntry(workFile, update); | ||
2545 | } | |||
| 4 | 2546 | break; | ||
2547 | | |||
2548 | case UpdateCommand.Modify: | |||
2549 | // TODO: Direct modifying of an entry will take some legwork. | |||
| 0 | 2550 | ModifyEntry(workFile, update); | ||
| 0 | 2551 | break; | ||
2552 | | |||
2553 | case UpdateCommand.Add: | |||
| 65705 | 2554 | if (!IsNewArchive && directUpdate) { | ||
| 134 | 2555 | workFile.baseStream_.Position = destinationPosition; | ||
2556 | } | |||
2557 | | |||
| 65705 | 2558 | AddEntry(workFile, update); | ||
2559 | | |||
| 65705 | 2560 | if (directUpdate) { | ||
| 65704 | 2561 | destinationPosition = workFile.baseStream_.Position; | ||
2562 | } | |||
2563 | break; | |||
2564 | } | |||
2565 | } | |||
2566 | } | |||
2567 | | |||
| 44 | 2568 | if (!IsNewArchive && directUpdate) { | ||
| 25 | 2569 | workFile.baseStream_.Position = destinationPosition; | ||
2570 | } | |||
2571 | | |||
| 44 | 2572 | long centralDirOffset = workFile.baseStream_.Position; | ||
2573 | | |||
| 131702 | 2574 | foreach (ZipUpdate update in updates_) { | ||
| 65807 | 2575 | if (update != null) { | ||
| 65772 | 2576 | sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); | ||
2577 | } | |||
2578 | } | |||
2579 | | |||
| 44 | 2580 | byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); | ||
| 44 | 2581 | using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) { | ||
| 44 | 2582 | zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); | ||
| 44 | 2583 | } | ||
2584 | | |||
| 44 | 2585 | endOfStream = workFile.baseStream_.Position; | ||
2586 | | |||
2587 | // And now patch entries... | |||
| 131702 | 2588 | foreach (ZipUpdate update in updates_) { | ||
| 65807 | 2589 | if (update != null) { | ||
2590 | // If the size of the entry is zero leave the crc as 0 as well. | |||
2591 | // The calculated crc will be all bits on... | |||
| 65772 | 2592 | if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { | ||
| 168 | 2593 | workFile.baseStream_.Position = update.CrcPatchOffset; | ||
| 168 | 2594 | workFile.WriteLEInt((int)update.OutEntry.Crc); | ||
2595 | } | |||
2596 | | |||
| 65772 | 2597 | if (update.SizePatchOffset > 0) { | ||
| 168 | 2598 | workFile.baseStream_.Position = update.SizePatchOffset; | ||
| 168 | 2599 | if (update.OutEntry.LocalHeaderRequiresZip64) { | ||
| 2 | 2600 | workFile.WriteLeLong(update.OutEntry.Size); | ||
| 2 | 2601 | workFile.WriteLeLong(update.OutEntry.CompressedSize); | ||
| 2 | 2602 | } else { | ||
| 166 | 2603 | workFile.WriteLEInt((int)update.OutEntry.CompressedSize); | ||
| 166 | 2604 | workFile.WriteLEInt((int)update.OutEntry.Size); | ||
2605 | } | |||
2606 | } | |||
2607 | } | |||
2608 | } | |||
| 44 | 2609 | } catch { | ||
| 0 | 2610 | workFile.Close(); | ||
| 0 | 2611 | if (!directUpdate && (workFile.Name != null)) { | ||
| 0 | 2612 | File.Delete(workFile.Name); | ||
2613 | } | |||
| 0 | 2614 | throw; | ||
2615 | } | |||
2616 | | |||
| 44 | 2617 | if (directUpdate) { | ||
| 41 | 2618 | workFile.baseStream_.SetLength(endOfStream); | ||
| 41 | 2619 | workFile.baseStream_.Flush(); | ||
| 41 | 2620 | isNewArchive_ = false; | ||
| 41 | 2621 | ReadEntries(); | ||
| 41 | 2622 | } else { | ||
| 3 | 2623 | baseStream_.Close(); | ||
| 3 | 2624 | Reopen(archiveStorage_.ConvertTemporaryToFinal()); | ||
2625 | } | |||
| 3 | 2626 | } | ||
2627 | | |||
2628 | void CheckUpdating() | |||
2629 | { | |||
| 65791 | 2630 | if (updates_ == null) { | ||
| 0 | 2631 | throw new InvalidOperationException("BeginUpdate has not been called"); | ||
2632 | } | |||
| 65791 | 2633 | } | ||
2634 | | |||
2635 | #endregion | |||
2636 | | |||
2637 | #region ZipUpdate class | |||
2638 | /// <summary> | |||
2639 | /// Represents a pending update to a Zip file. | |||
2640 | /// </summary> | |||
2641 | class ZipUpdate | |||
2642 | { | |||
2643 | #region Constructors | |||
| 3 | 2644 | public ZipUpdate(string fileName, ZipEntry entry) | ||
2645 | { | |||
| 3 | 2646 | command_ = UpdateCommand.Add; | ||
| 3 | 2647 | entry_ = entry; | ||
| 3 | 2648 | filename_ = fileName; | ||
| 3 | 2649 | } | ||
2650 | | |||
2651 | [Obsolete] | |||
| 0 | 2652 | public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) | ||
2653 | { | |||
| 0 | 2654 | command_ = UpdateCommand.Add; | ||
| 0 | 2655 | entry_ = new ZipEntry(entryName); | ||
| 0 | 2656 | entry_.CompressionMethod = compressionMethod; | ||
| 0 | 2657 | filename_ = fileName; | ||
| 0 | 2658 | } | ||
2659 | | |||
2660 | [Obsolete] | |||
2661 | public ZipUpdate(string fileName, string entryName) | |||
| 0 | 2662 | : this(fileName, entryName, CompressionMethod.Deflated) | ||
2663 | { | |||
2664 | // Do nothing. | |||
| 0 | 2665 | } | ||
2666 | | |||
2667 | [Obsolete] | |||
| 0 | 2668 | public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) | ||
2669 | { | |||
| 0 | 2670 | command_ = UpdateCommand.Add; | ||
| 0 | 2671 | entry_ = new ZipEntry(entryName); | ||
| 0 | 2672 | entry_.CompressionMethod = compressionMethod; | ||
| 0 | 2673 | dataSource_ = dataSource; | ||
| 0 | 2674 | } | ||
2675 | | |||
| 165 | 2676 | public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) | ||
2677 | { | |||
| 165 | 2678 | command_ = UpdateCommand.Add; | ||
| 165 | 2679 | entry_ = entry; | ||
| 165 | 2680 | dataSource_ = dataSource; | ||
| 165 | 2681 | } | ||
2682 | | |||
| 0 | 2683 | public ZipUpdate(ZipEntry original, ZipEntry updated) | ||
2684 | { | |||
| 0 | 2685 | throw new ZipException("Modify not currently supported"); | ||
2686 | /* | |||
2687 | command_ = UpdateCommand.Modify; | |||
2688 | entry_ = ( ZipEntry )original.Clone(); | |||
2689 | outEntry_ = ( ZipEntry )updated.Clone(); | |||
2690 | */ | |||
2691 | } | |||
2692 | | |||
| 65648 | 2693 | public ZipUpdate(UpdateCommand command, ZipEntry entry) | ||
2694 | { | |||
| 65648 | 2695 | command_ = command; | ||
| 65648 | 2696 | entry_ = (ZipEntry)entry.Clone(); | ||
| 65648 | 2697 | } | ||
2698 | | |||
2699 | | |||
2700 | /// <summary> | |||
2701 | /// Copy an existing entry. | |||
2702 | /// </summary> | |||
2703 | /// <param name="entry">The existing entry to copy.</param> | |||
2704 | public ZipUpdate(ZipEntry entry) | |||
| 111 | 2705 | : this(UpdateCommand.Copy, entry) | ||
2706 | { | |||
2707 | // Do nothing. | |||
| 111 | 2708 | } | ||
2709 | #endregion | |||
2710 | | |||
2711 | /// <summary> | |||
2712 | /// Get the <see cref="ZipEntry"/> for this update. | |||
2713 | /// </summary> | |||
2714 | /// <remarks>This is the source or original entry.</remarks> | |||
2715 | public ZipEntry Entry { | |||
| 263834 | 2716 | get { return entry_; } | ||
2717 | } | |||
2718 | | |||
2719 | /// <summary> | |||
2720 | /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. | |||
2721 | /// </summary> | |||
2722 | public ZipEntry OutEntry { | |||
2723 | get { | |||
| 198910 | 2724 | if (outEntry_ == null) { | ||
| 65772 | 2725 | outEntry_ = (ZipEntry)entry_.Clone(); | ||
2726 | } | |||
2727 | | |||
| 198910 | 2728 | return outEntry_; | ||
2729 | } | |||
2730 | } | |||
2731 | | |||
2732 | /// <summary> | |||
2733 | /// Get the command for this update. | |||
2734 | /// </summary> | |||
2735 | public UpdateCommand Command { | |||
| 132882 | 2736 | get { return command_; } | ||
2737 | } | |||
2738 | | |||
2739 | /// <summary> | |||
2740 | /// Get the filename if any for this update. Null if none exists. | |||
2741 | /// </summary> | |||
2742 | public string Filename { | |||
| 65540 | 2743 | get { return filename_; } | ||
2744 | } | |||
2745 | | |||
2746 | /// <summary> | |||
2747 | /// Get/set the location of the size patch for this update. | |||
2748 | /// </summary> | |||
2749 | public long SizePatchOffset { | |||
| 65940 | 2750 | get { return sizePatchOffset_; } | ||
| 336 | 2751 | set { sizePatchOffset_ = value; } | ||
2752 | } | |||
2753 | | |||
2754 | /// <summary> | |||
2755 | /// Get /set the location of the crc patch for this update. | |||
2756 | /// </summary> | |||
2757 | public long CrcPatchOffset { | |||
| 65940 | 2758 | get { return crcPatchOffset_; } | ||
| 336 | 2759 | set { crcPatchOffset_ = value; } | ||
2760 | } | |||
2761 | | |||
2762 | /// <summary> | |||
2763 | /// Get/set the size calculated by offset. | |||
2764 | /// Specifically, the difference between this and next entry's starting offset. | |||
2765 | /// </summary> | |||
2766 | public long OffsetBasedSize { | |||
| 46 | 2767 | get { return _offsetBasedSize; } | ||
| 160 | 2768 | set { _offsetBasedSize = value; } | ||
2769 | } | |||
2770 | | |||
2771 | public Stream GetSource() | |||
2772 | { | |||
| 65705 | 2773 | Stream result = null; | ||
| 65705 | 2774 | if (dataSource_ != null) { | ||
| 165 | 2775 | result = dataSource_.GetSource(); | ||
2776 | } | |||
2777 | | |||
| 65705 | 2778 | return result; | ||
2779 | } | |||
2780 | | |||
2781 | #region Instance Fields | |||
2782 | ZipEntry entry_; | |||
2783 | ZipEntry outEntry_; | |||
2784 | UpdateCommand command_; | |||
2785 | IStaticDataSource dataSource_; | |||
2786 | string filename_; | |||
| 65816 | 2787 | long sizePatchOffset_ = -1; | ||
| 65816 | 2788 | long crcPatchOffset_ = -1; | ||
| 65816 | 2789 | long _offsetBasedSize = -1; | ||
2790 | #endregion | |||
2791 | } | |||
2792 | | |||
2793 | #endregion | |||
2794 | #endregion | |||
2795 | | |||
2796 | #region Disposing | |||
2797 | | |||
2798 | #region IDisposable Members | |||
2799 | void IDisposable.Dispose() | |||
2800 | { | |||
| 82 | 2801 | Close(); | ||
| 82 | 2802 | } | ||
2803 | #endregion | |||
2804 | | |||
2805 | void DisposeInternal(bool disposing) | |||
2806 | { | |||
| 98 | 2807 | if (!isDisposed_) { | ||
| 88 | 2808 | isDisposed_ = true; | ||
| 88 | 2809 | entries_ = new ZipEntry[0]; | ||
2810 | | |||
| 88 | 2811 | if (IsStreamOwner && (baseStream_ != null)) { | ||
| 49 | 2812 | lock (baseStream_) { | ||
| 49 | 2813 | baseStream_.Close(); | ||
| 49 | 2814 | } | ||
2815 | } | |||
2816 | | |||
| 88 | 2817 | PostUpdateCleanup(); | ||
2818 | } | |||
| 98 | 2819 | } | ||
2820 | | |||
2821 | /// <summary> | |||
2822 | /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. | |||
2823 | /// </summary> | |||
2824 | /// <param name="disposing">true to release both managed and unmanaged resources; | |||
2825 | /// false to release only unmanaged resources.</param> | |||
2826 | protected virtual void Dispose(bool disposing) | |||
2827 | { | |||
| 6 | 2828 | DisposeInternal(disposing); | ||
| 6 | 2829 | } | ||
2830 | | |||
2831 | #endregion | |||
2832 | | |||
2833 | #region Internal routines | |||
2834 | #region Reading | |||
2835 | /// <summary> | |||
2836 | /// Read an unsigned short in little endian byte order. | |||
2837 | /// </summary> | |||
2838 | /// <returns>Returns the value read.</returns> | |||
2839 | /// <exception cref="EndOfStreamException"> | |||
2840 | /// The stream ends prematurely | |||
2841 | /// </exception> | |||
2842 | ushort ReadLEUshort() | |||
2843 | { | |||
| 3496105 | 2844 | int data1 = baseStream_.ReadByte(); | ||
2845 | | |||
| 3496105 | 2846 | if (data1 < 0) { | ||
| 0 | 2847 | throw new EndOfStreamException("End of stream"); | ||
2848 | } | |||
2849 | | |||
| 3496105 | 2850 | int data2 = baseStream_.ReadByte(); | ||
2851 | | |||
| 3496105 | 2852 | if (data2 < 0) { | ||
| 0 | 2853 | throw new EndOfStreamException("End of stream"); | ||
2854 | } | |||
2855 | | |||
2856 | | |||
| 3496105 | 2857 | return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); | ||
2858 | } | |||
2859 | | |||
2860 | /// <summary> | |||
2861 | /// Read a uint in little endian byte order. | |||
2862 | /// </summary> | |||
2863 | /// <returns>Returns the value read.</returns> | |||
2864 | /// <exception cref="IOException"> | |||
2865 | /// An i/o error occurs. | |||
2866 | /// </exception> | |||
2867 | /// <exception cref="System.IO.EndOfStreamException"> | |||
2868 | /// The file ends prematurely | |||
2869 | /// </exception> | |||
2870 | uint ReadLEUint() | |||
2871 | { | |||
| 989383 | 2872 | return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); | ||
2873 | } | |||
2874 | | |||
2875 | ulong ReadLEUlong() | |||
2876 | { | |||
| 6 | 2877 | return ReadLEUint() | ((ulong)ReadLEUint() << 32); | ||
2878 | } | |||
2879 | | |||
2880 | #endregion | |||
2881 | // NOTE this returns the offset of the first byte after the signature. | |||
2882 | long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
2883 | { | |||
| 115 | 2884 | using (ZipHelperStream les = new ZipHelperStream(baseStream_)) { | ||
| 115 | 2885 | return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); | ||
2886 | } | |||
| 115 | 2887 | } | ||
2888 | | |||
2889 | /// <summary> | |||
2890 | /// Search for and read the central directory of a zip file filling the entries array. | |||
2891 | /// </summary> | |||
2892 | /// <exception cref="System.IO.IOException"> | |||
2893 | /// An i/o error occurs. | |||
2894 | /// </exception> | |||
2895 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
2896 | /// The central directory is malformed or cannot be found | |||
2897 | /// </exception> | |||
2898 | void ReadEntries() | |||
2899 | { | |||
2900 | // Search for the End Of Central Directory. When a zip comment is | |||
2901 | // present the directory will start earlier | |||
2902 | // | |||
2903 | // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. | |||
2904 | // This should be compatible with both SFX and ZIP files but has only been tested for Zip files | |||
2905 | // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then | |||
2906 | // this could be invalid. | |||
2907 | // Could also speed this up by reading memory in larger blocks. | |||
2908 | | |||
| 114 | 2909 | if (baseStream_.CanSeek == false) { | ||
| 0 | 2910 | throw new ZipException("ZipFile stream must be seekable"); | ||
2911 | } | |||
2912 | | |||
| 114 | 2913 | long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, | ||
| 114 | 2914 | baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); | ||
2915 | | |||
| 114 | 2916 | if (locatedEndOfCentralDir < 0) { | ||
| 1 | 2917 | throw new ZipException("Cannot find central directory"); | ||
2918 | } | |||
2919 | | |||
2920 | // Read end of central directory record | |||
| 113 | 2921 | ushort thisDiskNumber = ReadLEUshort(); | ||
| 113 | 2922 | ushort startCentralDirDisk = ReadLEUshort(); | ||
| 113 | 2923 | ulong entriesForThisDisk = ReadLEUshort(); | ||
| 113 | 2924 | ulong entriesForWholeCentralDir = ReadLEUshort(); | ||
| 113 | 2925 | ulong centralDirSize = ReadLEUint(); | ||
| 113 | 2926 | long offsetOfCentralDir = ReadLEUint(); | ||
| 113 | 2927 | uint commentSize = ReadLEUshort(); | ||
2928 | | |||
| 113 | 2929 | if (commentSize > 0) { | ||
| 10 | 2930 | byte[] comment = new byte[commentSize]; | ||
2931 | | |||
| 10 | 2932 | StreamUtils.ReadFully(baseStream_, comment); | ||
| 10 | 2933 | comment_ = ZipConstants.ConvertToString(comment); | ||
| 10 | 2934 | } else { | ||
| 103 | 2935 | comment_ = string.Empty; | ||
2936 | } | |||
2937 | | |||
| 113 | 2938 | bool isZip64 = false; | ||
2939 | | |||
2940 | // Check if zip64 header information is required. | |||
| 113 | 2941 | if ((thisDiskNumber == 0xffff) || | ||
| 113 | 2942 | (startCentralDirDisk == 0xffff) || | ||
| 113 | 2943 | (entriesForThisDisk == 0xffff) || | ||
| 113 | 2944 | (entriesForWholeCentralDir == 0xffff) || | ||
| 113 | 2945 | (centralDirSize == 0xffffffff) || | ||
| 113 | 2946 | (offsetOfCentralDir == 0xffffffff)) { | ||
| 1 | 2947 | isZip64 = true; | ||
2948 | | |||
| 1 | 2949 | long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, | ||
| 1 | 2950 | if (offset < 0) { | ||
| 0 | 2951 | throw new ZipException("Cannot find Zip64 locator"); | ||
2952 | } | |||
2953 | | |||
2954 | // number of the disk with the start of the zip64 end of central directory 4 bytes | |||
2955 | // relative offset of the zip64 end of central directory record 8 bytes | |||
2956 | // total number of disks 4 bytes | |||
| 1 | 2957 | ReadLEUint(); // startDisk64 is not currently used | ||
| 1 | 2958 | ulong offset64 = ReadLEUlong(); | ||
| 1 | 2959 | uint totalDisks = ReadLEUint(); | ||
2960 | | |||
| 1 | 2961 | baseStream_.Position = (long)offset64; | ||
| 1 | 2962 | long sig64 = ReadLEUint(); | ||
2963 | | |||
| 1 | 2964 | if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { | ||
| 0 | 2965 | throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); | ||
2966 | } | |||
2967 | | |||
2968 | // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. | |||
| 1 | 2969 | ulong recordSize = ReadLEUlong(); | ||
| 1 | 2970 | int versionMadeBy = ReadLEUshort(); | ||
| 1 | 2971 | int versionToExtract = ReadLEUshort(); | ||
| 1 | 2972 | uint thisDisk = ReadLEUint(); | ||
| 1 | 2973 | uint centralDirDisk = ReadLEUint(); | ||
| 1 | 2974 | entriesForThisDisk = ReadLEUlong(); | ||
| 1 | 2975 | entriesForWholeCentralDir = ReadLEUlong(); | ||
| 1 | 2976 | centralDirSize = ReadLEUlong(); | ||
| 1 | 2977 | offsetOfCentralDir = (long)ReadLEUlong(); | ||
2978 | | |||
2979 | // NOTE: zip64 extensible data sector (variable size) is ignored. | |||
2980 | } | |||
2981 | | |||
| 113 | 2982 | entries_ = new ZipEntry[entriesForThisDisk]; | ||
2983 | | |||
2984 | // SFX/embedded support, find the offset of the first entry vis the start of the stream | |||
2985 | // This applies to Zip files that are appended to the end of an SFX stub. | |||
2986 | // Or are appended as a resource to an executable. | |||
2987 | // Zip files created by some archivers have the offsets altered to reflect the true offsets | |||
2988 | // and so dont require any adjustment here... | |||
2989 | // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? | |||
| 113 | 2990 | if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) { | ||
| 2 | 2991 | offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); | ||
| 2 | 2992 | if (offsetOfFirstEntry <= 0) { | ||
| 0 | 2993 | throw new ZipException("Invalid embedded zip archive"); | ||
2994 | } | |||
2995 | } | |||
2996 | | |||
| 113 | 2997 | baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); | ||
2998 | | |||
| 132138 | 2999 | for (ulong i = 0; i < entriesForThisDisk; i++) { | ||
| 65956 | 3000 | if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { | ||
| 0 | 3001 | throw new ZipException("Wrong Central Directory signature"); | ||
3002 | } | |||
3003 | | |||
| 65956 | 3004 | int versionMadeBy = ReadLEUshort(); | ||
| 65956 | 3005 | int versionToExtract = ReadLEUshort(); | ||
| 65956 | 3006 | int bitFlags = ReadLEUshort(); | ||
| 65956 | 3007 | int method = ReadLEUshort(); | ||
| 65956 | 3008 | uint dostime = ReadLEUint(); | ||
| 65956 | 3009 | uint crc = ReadLEUint(); | ||
| 65956 | 3010 | var csize = (long)ReadLEUint(); | ||
| 65956 | 3011 | var size = (long)ReadLEUint(); | ||
| 65956 | 3012 | int nameLen = ReadLEUshort(); | ||
| 65956 | 3013 | int extraLen = ReadLEUshort(); | ||
| 65956 | 3014 | int commentLen = ReadLEUshort(); | ||
3015 | | |||
| 65956 | 3016 | int diskStartNo = ReadLEUshort(); // Not currently used | ||
| 65956 | 3017 | int internalAttributes = ReadLEUshort(); // Not currently used | ||
3018 | | |||
| 65956 | 3019 | uint externalAttributes = ReadLEUint(); | ||
| 65956 | 3020 | long offset = ReadLEUint(); | ||
3021 | | |||
| 65956 | 3022 | byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; | ||
3023 | | |||
| 65956 | 3024 | StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); | ||
| 65956 | 3025 | string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); | ||
3026 | | |||
| 65956 | 3027 | var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); | ||
| 65956 | 3028 | entry.Crc = crc & 0xffffffffL; | ||
| 65956 | 3029 | entry.Size = size & 0xffffffffL; | ||
| 65956 | 3030 | entry.CompressedSize = csize & 0xffffffffL; | ||
| 65956 | 3031 | entry.Flags = bitFlags; | ||
| 65956 | 3032 | entry.DosTime = (uint)dostime; | ||
| 65956 | 3033 | entry.ZipFileIndex = (long)i; | ||
| 65956 | 3034 | entry.Offset = offset; | ||
| 65956 | 3035 | entry.ExternalFileAttributes = (int)externalAttributes; | ||
3036 | | |||
| 65956 | 3037 | if ((bitFlags & 8) == 0) { | ||
| 65937 | 3038 | entry.CryptoCheckValue = (byte)(crc >> 24); | ||
| 65937 | 3039 | } else { | ||
| 19 | 3040 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | ||
3041 | } | |||
3042 | | |||
| 65956 | 3043 | if (extraLen > 0) { | ||
| 45 | 3044 | byte[] extra = new byte[extraLen]; | ||
| 45 | 3045 | StreamUtils.ReadFully(baseStream_, extra); | ||
| 45 | 3046 | entry.ExtraData = extra; | ||
3047 | } | |||
3048 | | |||
| 65956 | 3049 | entry.ProcessExtraData(false); | ||
3050 | | |||
| 65956 | 3051 | if (commentLen > 0) { | ||
| 0 | 3052 | StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); | ||
| 0 | 3053 | entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); | ||
3054 | } | |||
3055 | | |||
| 65956 | 3056 | entries_[i] = entry; | ||
3057 | } | |||
| 113 | 3058 | } | ||
3059 | | |||
3060 | /// <summary> | |||
3061 | /// Locate the data for a given entry. | |||
3062 | /// </summary> | |||
3063 | /// <returns> | |||
3064 | /// The start offset of the data. | |||
3065 | /// </returns> | |||
3066 | /// <exception cref="System.IO.EndOfStreamException"> | |||
3067 | /// The stream ends prematurely | |||
3068 | /// </exception> | |||
3069 | /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException"> | |||
3070 | /// The local header signature is invalid, the entry and central header file name lengths are different | |||
3071 | /// or the local and entry compression methods dont match | |||
3072 | /// </exception> | |||
3073 | long LocateEntry(ZipEntry entry) | |||
3074 | { | |||
| 65946 | 3075 | return TestLocalHeader(entry, HeaderTest.Extract); | ||
3076 | } | |||
3077 | | |||
3078 | Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) | |||
3079 | { | |||
| 10 | 3080 | CryptoStream result = null; | ||
3081 | | |||
| 10 | 3082 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | ||
| 10 | 3083 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | ||
| 10 | 3084 | var classicManaged = new PkzipClassicManaged(); | ||
3085 | | |||
| 10 | 3086 | OnKeysRequired(entry.Name); | ||
| 10 | 3087 | if (HaveKeys == false) { | ||
| 0 | 3088 | throw new ZipException("No password available for encrypted stream"); | ||
3089 | } | |||
3090 | | |||
| 10 | 3091 | result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); | ||
| 10 | 3092 | CheckClassicPassword(result, entry); | ||
| 10 | 3093 | } else { | ||
| 0 | 3094 | if (entry.Version == ZipConstants.VERSION_AES) { | ||
3095 | // | |||
| 0 | 3096 | OnKeysRequired(entry.Name); | ||
| 0 | 3097 | if (HaveKeys == false) { | ||
| 0 | 3098 | throw new ZipException("No password available for AES encrypted stream"); | ||
3099 | } | |||
| 0 | 3100 | int saltLen = entry.AESSaltLen; | ||
| 0 | 3101 | byte[] saltBytes = new byte[saltLen]; | ||
| 0 | 3102 | int saltIn = baseStream.Read(saltBytes, 0, saltLen); | ||
| 0 | 3103 | if (saltIn != saltLen) | ||
| 0 | 3104 | throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); | ||
3105 | // | |||
| 0 | 3106 | byte[] pwdVerifyRead = new byte[2]; | ||
| 0 | 3107 | baseStream.Read(pwdVerifyRead, 0, 2); | ||
| 0 | 3108 | int blockSize = entry.AESKeySize / 8; // bits to bytes | ||
3109 | | |||
| 0 | 3110 | var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); | ||
| 0 | 3111 | byte[] pwdVerifyCalc = decryptor.PwdVerifier; | ||
| 0 | 3112 | if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) | ||
| 0 | 3113 | throw new ZipException("Invalid password for AES"); | ||
| 0 | 3114 | result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); | ||
| 0 | 3115 | } else { | ||
| 0 | 3116 | throw new ZipException("Decryption method not supported"); | ||
3117 | } | |||
3118 | } | |||
3119 | | |||
| 10 | 3120 | return result; | ||
3121 | } | |||
3122 | | |||
3123 | Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) | |||
3124 | { | |||
| 5 | 3125 | CryptoStream result = null; | ||
| 5 | 3126 | if ((entry.Version < ZipConstants.VersionStrongEncryption) | ||
| 5 | 3127 | || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { | ||
| 5 | 3128 | var classicManaged = new PkzipClassicManaged(); | ||
3129 | | |||
| 5 | 3130 | OnKeysRequired(entry.Name); | ||
| 5 | 3131 | if (HaveKeys == false) { | ||
| 0 | 3132 | throw new ZipException("No password available for encrypted stream"); | ||
3133 | } | |||
3134 | | |||
3135 | // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream | |||
3136 | // which doesnt do this. | |||
| 5 | 3137 | result = new CryptoStream(new UncompressedStream(baseStream), | ||
| 5 | 3138 | classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); | ||
3139 | | |||
| 5 | 3140 | if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { | ||
| 5 | 3141 | WriteEncryptionHeader(result, entry.DosTime << 16); | ||
| 5 | 3142 | } else { | ||
| 0 | 3143 | WriteEncryptionHeader(result, entry.Crc); | ||
3144 | } | |||
3145 | } | |||
| 5 | 3146 | return result; | ||
3147 | } | |||
3148 | | |||
3149 | static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) | |||
3150 | { | |||
| 10 | 3151 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | ||
| 10 | 3152 | StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); | ||
| 10 | 3153 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | ||
| 0 | 3154 | throw new ZipException("Invalid password"); | ||
3155 | } | |||
| 10 | 3156 | } | ||
3157 | | |||
3158 | static void WriteEncryptionHeader(Stream stream, long crcValue) | |||
3159 | { | |||
| 5 | 3160 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | ||
| 5 | 3161 | var rnd = new Random(); | ||
| 5 | 3162 | rnd.NextBytes(cryptBuffer); | ||
| 5 | 3163 | cryptBuffer[11] = (byte)(crcValue >> 24); | ||
| 5 | 3164 | stream.Write(cryptBuffer, 0, cryptBuffer.Length); | ||
| 5 | 3165 | } | ||
3166 | | |||
3167 | #endregion | |||
3168 | | |||
3169 | #region Instance Fields | |||
3170 | bool isDisposed_; | |||
3171 | string name_; | |||
3172 | string comment_; | |||
3173 | string rawPassword_; | |||
3174 | Stream baseStream_; | |||
3175 | bool isStreamOwner; | |||
3176 | long offsetOfFirstEntry; | |||
3177 | ZipEntry[] entries_; | |||
3178 | byte[] key; | |||
3179 | bool isNewArchive_; | |||
3180 | | |||
3181 | // Default is dynamic which is not backwards compatible and can cause problems | |||
3182 | // with XP's built in compression which cant read Zip64 archives. | |||
3183 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
3184 | // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. | |||
| 88 | 3185 | UseZip64 useZip64_ = UseZip64.Dynamic; | ||
3186 | | |||
3187 | #region Zip Update Instance Fields | |||
3188 | ArrayList updates_; | |||
3189 | long updateCount_; // Count is managed manually as updates_ can contain nulls! | |||
3190 | Hashtable updateIndex_; | |||
3191 | IArchiveStorage archiveStorage_; | |||
3192 | IDynamicDataSource updateDataSource_; | |||
3193 | bool contentsEdited_; | |||
| 88 | 3194 | int bufferSize_ = DefaultBufferSize; | ||
3195 | byte[] copyBuffer_; | |||
3196 | ZipString newComment_; | |||
3197 | bool commentEdited_; | |||
| 88 | 3198 | IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); | ||
3199 | #endregion | |||
3200 | #endregion | |||
3201 | | |||
3202 | #region Support Classes | |||
3203 | /// <summary> | |||
3204 | /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. | |||
3205 | /// </summary> | |||
3206 | class ZipString | |||
3207 | { | |||
3208 | #region Constructors | |||
3209 | /// <summary> | |||
3210 | /// Initialise a <see cref="ZipString"/> with a string. | |||
3211 | /// </summary> | |||
3212 | /// <param name="comment">The textual string form.</param> | |||
| 3 | 3213 | public ZipString(string comment) | ||
3214 | { | |||
| 3 | 3215 | comment_ = comment; | ||
| 3 | 3216 | isSourceString_ = true; | ||
| 3 | 3217 | } | ||
3218 | | |||
3219 | /// <summary> | |||
3220 | /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. | |||
3221 | /// </summary> | |||
3222 | /// <param name="rawString"></param> | |||
| 0 | 3223 | public ZipString(byte[] rawString) | ||
3224 | { | |||
| 0 | 3225 | rawComment_ = rawString; | ||
| 0 | 3226 | } | ||
3227 | #endregion | |||
3228 | | |||
3229 | /// <summary> | |||
3230 | /// Get a value indicating the original source of data for this instance. | |||
3231 | /// True if the source was a string; false if the source was binary data. | |||
3232 | /// </summary> | |||
3233 | public bool IsSourceString { | |||
| 0 | 3234 | get { return isSourceString_; } | ||
3235 | } | |||
3236 | | |||
3237 | /// <summary> | |||
3238 | /// Get the length of the comment when represented as raw bytes. | |||
3239 | /// </summary> | |||
3240 | public int RawLength { | |||
3241 | get { | |||
| 3 | 3242 | MakeBytesAvailable(); | ||
| 3 | 3243 | return rawComment_.Length; | ||
3244 | } | |||
3245 | } | |||
3246 | | |||
3247 | /// <summary> | |||
3248 | /// Get the comment in its 'raw' form as plain bytes. | |||
3249 | /// </summary> | |||
3250 | public byte[] RawComment { | |||
3251 | get { | |||
| 3 | 3252 | MakeBytesAvailable(); | ||
| 3 | 3253 | return (byte[])rawComment_.Clone(); | ||
3254 | } | |||
3255 | } | |||
3256 | | |||
3257 | /// <summary> | |||
3258 | /// Reset the comment to its initial state. | |||
3259 | /// </summary> | |||
3260 | public void Reset() | |||
3261 | { | |||
| 0 | 3262 | if (isSourceString_) { | ||
| 0 | 3263 | rawComment_ = null; | ||
| 0 | 3264 | } else { | ||
| 0 | 3265 | comment_ = null; | ||
3266 | } | |||
| 0 | 3267 | } | ||
3268 | | |||
3269 | void MakeTextAvailable() | |||
3270 | { | |||
| 0 | 3271 | if (comment_ == null) { | ||
| 0 | 3272 | comment_ = ZipConstants.ConvertToString(rawComment_); | ||
3273 | } | |||
| 0 | 3274 | } | ||
3275 | | |||
3276 | void MakeBytesAvailable() | |||
3277 | { | |||
| 6 | 3278 | if (rawComment_ == null) { | ||
| 3 | 3279 | rawComment_ = ZipConstants.ConvertToArray(comment_); | ||
3280 | } | |||
| 6 | 3281 | } | ||
3282 | | |||
3283 | /// <summary> | |||
3284 | /// Implicit conversion of comment to a string. | |||
3285 | /// </summary> | |||
3286 | /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> | |||
3287 | /// <returns>The textual equivalent for the input value.</returns> | |||
3288 | static public implicit operator string(ZipString zipString) | |||
3289 | { | |||
| 0 | 3290 | zipString.MakeTextAvailable(); | ||
| 0 | 3291 | return zipString.comment_; | ||
3292 | } | |||
3293 | | |||
3294 | #region Instance Fields | |||
3295 | string comment_; | |||
3296 | byte[] rawComment_; | |||
3297 | bool isSourceString_; | |||
3298 | #endregion | |||
3299 | } | |||
3300 | | |||
3301 | /// <summary> | |||
3302 | /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> | |||
3303 | /// </summary> | |||
3304 | class ZipEntryEnumerator : IEnumerator | |||
3305 | { | |||
3306 | #region Constructors | |||
| 4 | 3307 | public ZipEntryEnumerator(ZipEntry[] entries) | ||
3308 | { | |||
| 4 | 3309 | array = entries; | ||
| 4 | 3310 | } | ||
3311 | | |||
3312 | #endregion | |||
3313 | #region IEnumerator Members | |||
3314 | public object Current { | |||
3315 | get { | |||
| 22 | 3316 | return array[index]; | ||
3317 | } | |||
3318 | } | |||
3319 | | |||
3320 | public void Reset() | |||
3321 | { | |||
| 0 | 3322 | index = -1; | ||
| 0 | 3323 | } | ||
3324 | | |||
3325 | public bool MoveNext() | |||
3326 | { | |||
| 26 | 3327 | return (++index < array.Length); | ||
3328 | } | |||
3329 | #endregion | |||
3330 | #region Instance Fields | |||
3331 | ZipEntry[] array; | |||
| 4 | 3332 | int index = -1; | ||
3333 | #endregion | |||
3334 | } | |||
3335 | | |||
3336 | /// <summary> | |||
3337 | /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data | |||
3338 | /// to and flush, but cannot read, seek or do anything else to. | |||
3339 | /// </summary> | |||
3340 | class UncompressedStream : Stream | |||
3341 | { | |||
3342 | #region Constructors | |||
| 20 | 3343 | public UncompressedStream(Stream baseStream) | ||
3344 | { | |||
| 20 | 3345 | baseStream_ = baseStream; | ||
| 20 | 3346 | } | ||
3347 | | |||
3348 | #endregion | |||
3349 | | |||
3350 | /// <summary> | |||
3351 | /// Close this stream instance. | |||
3352 | /// </summary> | |||
3353 | public override void Close() | |||
3354 | { | |||
3355 | // Do nothing | |||
| 15 | 3356 | } | ||
3357 | | |||
3358 | /// <summary> | |||
3359 | /// Gets a value indicating whether the current stream supports reading. | |||
3360 | /// </summary> | |||
3361 | public override bool CanRead { | |||
3362 | get { | |||
| 0 | 3363 | return false; | ||
3364 | } | |||
3365 | } | |||
3366 | | |||
3367 | /// <summary> | |||
3368 | /// Write any buffered data to underlying storage. | |||
3369 | /// </summary> | |||
3370 | public override void Flush() | |||
3371 | { | |||
| 0 | 3372 | baseStream_.Flush(); | ||
| 0 | 3373 | } | ||
3374 | | |||
3375 | /// <summary> | |||
3376 | /// Gets a value indicating whether the current stream supports writing. | |||
3377 | /// </summary> | |||
3378 | public override bool CanWrite { | |||
3379 | get { | |||
| 5 | 3380 | return baseStream_.CanWrite; | ||
3381 | } | |||
3382 | } | |||
3383 | | |||
3384 | /// <summary> | |||
3385 | /// Gets a value indicating whether the current stream supports seeking. | |||
3386 | /// </summary> | |||
3387 | public override bool CanSeek { | |||
3388 | get { | |||
| 0 | 3389 | return false; | ||
3390 | } | |||
3391 | } | |||
3392 | | |||
3393 | /// <summary> | |||
3394 | /// Get the length in bytes of the stream. | |||
3395 | /// </summary> | |||
3396 | public override long Length { | |||
3397 | get { | |||
| 0 | 3398 | return 0; | ||
3399 | } | |||
3400 | } | |||
3401 | | |||
3402 | /// <summary> | |||
3403 | /// Gets or sets the position within the current stream. | |||
3404 | /// </summary> | |||
3405 | public override long Position { | |||
3406 | get { | |||
| 0 | 3407 | return baseStream_.Position; | ||
3408 | } | |||
3409 | set { | |||
| 0 | 3410 | throw new NotImplementedException(); | ||
3411 | } | |||
3412 | } | |||
3413 | | |||
3414 | /// <summary> | |||
3415 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3416 | /// </summary> | |||
3417 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3418 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3419 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3420 | /// <returns> | |||
3421 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3422 | /// </returns> | |||
3423 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3424 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3425 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3426 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3427 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3428 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3429 | public override int Read(byte[] buffer, int offset, int count) | |||
3430 | { | |||
| 0 | 3431 | return 0; | ||
3432 | } | |||
3433 | | |||
3434 | /// <summary> | |||
3435 | /// Sets the position within the current stream. | |||
3436 | /// </summary> | |||
3437 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3438 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3439 | /// <returns> | |||
3440 | /// The new position within the current stream. | |||
3441 | /// </returns> | |||
3442 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3443 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3444 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3445 | public override long Seek(long offset, SeekOrigin origin) | |||
3446 | { | |||
| 0 | 3447 | return 0; | ||
3448 | } | |||
3449 | | |||
3450 | /// <summary> | |||
3451 | /// Sets the length of the current stream. | |||
3452 | /// </summary> | |||
3453 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3454 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3455 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3456 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3457 | public override void SetLength(long value) | |||
3458 | { | |||
| 0 | 3459 | } | ||
3460 | | |||
3461 | /// <summary> | |||
3462 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3463 | /// </summary> | |||
3464 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3465 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3466 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3467 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3468 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3469 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3470 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3471 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3472 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3473 | public override void Write(byte[] buffer, int offset, int count) | |||
3474 | { | |||
| 25 | 3475 | baseStream_.Write(buffer, offset, count); | ||
| 25 | 3476 | } | ||
3477 | | |||
3478 | readonly | |||
3479 | | |||
3480 | #region Instance Fields | |||
3481 | Stream baseStream_; | |||
3482 | #endregion | |||
3483 | } | |||
3484 | | |||
3485 | /// <summary> | |||
3486 | /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> | |||
3487 | /// whose data is only a part or subsection of a file. | |||
3488 | /// </summary> | |||
3489 | class PartialInputStream : Stream | |||
3490 | { | |||
3491 | #region Constructors | |||
3492 | /// <summary> | |||
3493 | /// Initialise a new instance of the <see cref="PartialInputStream"/> class. | |||
3494 | /// </summary> | |||
3495 | /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param> | |||
3496 | /// <param name="start">The start of the partial data.</param> | |||
3497 | /// <param name="length">The length of the partial data.</param> | |||
| 65946 | 3498 | public PartialInputStream(ZipFile zipFile, long start, long length) | ||
3499 | { | |||
| 65946 | 3500 | start_ = start; | ||
| 65946 | 3501 | length_ = length; | ||
3502 | | |||
3503 | // Although this is the only time the zipfile is used | |||
3504 | // keeping a reference here prevents premature closure of | |||
3505 | // this zip file and thus the baseStream_. | |||
3506 | | |||
3507 | // Code like this will cause apparently random failures depending | |||
3508 | // on the size of the files and when garbage is collected. | |||
3509 | // | |||
3510 | // ZipFile z = new ZipFile (stream); | |||
3511 | // Stream reader = z.GetInputStream(0); | |||
3512 | // uses reader here.... | |||
| 65946 | 3513 | zipFile_ = zipFile; | ||
| 65946 | 3514 | baseStream_ = zipFile_.baseStream_; | ||
| 65946 | 3515 | readPos_ = start; | ||
| 65946 | 3516 | end_ = start + length; | ||
| 65946 | 3517 | } | ||
3518 | #endregion | |||
3519 | | |||
3520 | /// <summary> | |||
3521 | /// Read a byte from this stream. | |||
3522 | /// </summary> | |||
3523 | /// <returns>Returns the byte read or -1 on end of stream.</returns> | |||
3524 | public override int ReadByte() | |||
3525 | { | |||
| 158 | 3526 | if (readPos_ >= end_) { | ||
3527 | // -1 is the correct value at end of stream. | |||
| 0 | 3528 | return -1; | ||
3529 | } | |||
3530 | | |||
| 158 | 3531 | lock (baseStream_) { | ||
| 158 | 3532 | baseStream_.Seek(readPos_++, SeekOrigin.Begin); | ||
| 158 | 3533 | return baseStream_.ReadByte(); | ||
3534 | } | |||
| 158 | 3535 | } | ||
3536 | | |||
3537 | /// <summary> | |||
3538 | /// Close this <see cref="PartialInputStream">partial input stream</see>. | |||
3539 | /// </summary> | |||
3540 | /// <remarks> | |||
3541 | /// The underlying stream is not closed. Close the parent ZipFile class to do that. | |||
3542 | /// </remarks> | |||
3543 | public override void Close() | |||
3544 | { | |||
3545 | // Do nothing at all! | |||
| 65918 | 3546 | } | ||
3547 | | |||
3548 | /// <summary> | |||
3549 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of | |||
3550 | /// </summary> | |||
3551 | /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array | |||
3552 | /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the cur | |||
3553 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | |||
3554 | /// <returns> | |||
3555 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that ma | |||
3556 | /// </returns> | |||
3557 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </e | |||
3558 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3559 | /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception> | |||
3560 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3561 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3562 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3563 | public override int Read(byte[] buffer, int offset, int count) | |||
3564 | { | |||
| 66379 | 3565 | lock (baseStream_) { | ||
| 66379 | 3566 | if (count > end_ - readPos_) { | ||
| 66348 | 3567 | count = (int)(end_ - readPos_); | ||
| 66348 | 3568 | if (count == 0) { | ||
| 65943 | 3569 | return 0; | ||
3570 | } | |||
3571 | } | |||
3572 | // Protect against Stream implementations that throw away their buffer on every Seek | |||
3573 | // (for example, Mono FileStream) | |||
| 436 | 3574 | if (baseStream_.Position != readPos_) { | ||
| 0 | 3575 | baseStream_.Seek(readPos_, SeekOrigin.Begin); | ||
3576 | } | |||
| 436 | 3577 | int readCount = baseStream_.Read(buffer, offset, count); | ||
| 436 | 3578 | if (readCount > 0) { | ||
| 436 | 3579 | readPos_ += readCount; | ||
3580 | } | |||
| 436 | 3581 | return readCount; | ||
3582 | } | |||
| 66379 | 3583 | } | ||
3584 | | |||
3585 | /// <summary> | |||
3586 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the n | |||
3587 | /// </summary> | |||
3588 | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</par | |||
3589 | /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current strea | |||
3590 | /// <param name="count">The number of bytes to be written to the current stream.</param> | |||
3591 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3592 | /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception> | |||
3593 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3594 | /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception> | |||
3595 | /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </ | |||
3596 | /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception> | |||
3597 | public override void Write(byte[] buffer, int offset, int count) | |||
3598 | { | |||
| 0 | 3599 | throw new NotSupportedException(); | ||
3600 | } | |||
3601 | | |||
3602 | /// <summary> | |||
3603 | /// When overridden in a derived class, sets the length of the current stream. | |||
3604 | /// </summary> | |||
3605 | /// <param name="value">The desired length of the current stream in bytes.</param> | |||
3606 | /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as | |||
3607 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3608 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3609 | public override void SetLength(long value) | |||
3610 | { | |||
| 0 | 3611 | throw new NotSupportedException(); | ||
3612 | } | |||
3613 | | |||
3614 | /// <summary> | |||
3615 | /// When overridden in a derived class, sets the position within the current stream. | |||
3616 | /// </summary> | |||
3617 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | |||
3618 | /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point | |||
3619 | /// <returns> | |||
3620 | /// The new position within the current stream. | |||
3621 | /// </returns> | |||
3622 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3623 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is | |||
3624 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3625 | public override long Seek(long offset, SeekOrigin origin) | |||
3626 | { | |||
| 5 | 3627 | long newPos = readPos_; | ||
3628 | | |||
| 5 | 3629 | switch (origin) { | ||
3630 | case SeekOrigin.Begin: | |||
| 5 | 3631 | newPos = start_ + offset; | ||
| 5 | 3632 | break; | ||
3633 | | |||
3634 | case SeekOrigin.Current: | |||
| 0 | 3635 | newPos = readPos_ + offset; | ||
| 0 | 3636 | break; | ||
3637 | | |||
3638 | case SeekOrigin.End: | |||
| 0 | 3639 | newPos = end_ + offset; | ||
3640 | break; | |||
3641 | } | |||
3642 | | |||
| 5 | 3643 | if (newPos < start_) { | ||
| 0 | 3644 | throw new ArgumentException("Negative position is invalid"); | ||
3645 | } | |||
3646 | | |||
| 5 | 3647 | if (newPos >= end_) { | ||
| 0 | 3648 | throw new IOException("Cannot seek past end"); | ||
3649 | } | |||
| 5 | 3650 | readPos_ = newPos; | ||
| 5 | 3651 | return readPos_; | ||
3652 | } | |||
3653 | | |||
3654 | /// <summary> | |||
3655 | /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | |||
3656 | /// </summary> | |||
3657 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3658 | public override void Flush() | |||
3659 | { | |||
3660 | // Nothing to do. | |||
| 0 | 3661 | } | ||
3662 | | |||
3663 | /// <summary> | |||
3664 | /// Gets or sets the position within the current stream. | |||
3665 | /// </summary> | |||
3666 | /// <value></value> | |||
3667 | /// <returns>The current position within the stream.</returns> | |||
3668 | /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception> | |||
3669 | /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception> | |||
3670 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3671 | public override long Position { | |||
| 3 | 3672 | get { return readPos_ - start_; } | ||
3673 | set { | |||
| 0 | 3674 | long newPos = start_ + value; | ||
3675 | | |||
| 0 | 3676 | if (newPos < start_) { | ||
| 0 | 3677 | throw new ArgumentException("Negative position is invalid"); | ||
3678 | } | |||
3679 | | |||
| 0 | 3680 | if (newPos >= end_) { | ||
| 0 | 3681 | throw new InvalidOperationException("Cannot seek past end"); | ||
3682 | } | |||
| 0 | 3683 | readPos_ = newPos; | ||
| 0 | 3684 | } | ||
3685 | } | |||
3686 | | |||
3687 | /// <summary> | |||
3688 | /// Gets the length in bytes of the stream. | |||
3689 | /// </summary> | |||
3690 | /// <value></value> | |||
3691 | /// <returns>A long value representing the length of the stream in bytes.</returns> | |||
3692 | /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </excep | |||
3693 | /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exceptio | |||
3694 | public override long Length { | |||
| 2 | 3695 | get { return length_; } | ||
3696 | } | |||
3697 | | |||
3698 | /// <summary> | |||
3699 | /// Gets a value indicating whether the current stream supports writing. | |||
3700 | /// </summary> | |||
3701 | /// <value>false</value> | |||
3702 | /// <returns>true if the stream supports writing; otherwise, false.</returns> | |||
3703 | public override bool CanWrite { | |||
| 0 | 3704 | get { return false; } | ||
3705 | } | |||
3706 | | |||
3707 | /// <summary> | |||
3708 | /// Gets a value indicating whether the current stream supports seeking. | |||
3709 | /// </summary> | |||
3710 | /// <value>true</value> | |||
3711 | /// <returns>true if the stream supports seeking; otherwise, false.</returns> | |||
3712 | public override bool CanSeek { | |||
| 2 | 3713 | get { return true; } | ||
3714 | } | |||
3715 | | |||
3716 | /// <summary> | |||
3717 | /// Gets a value indicating whether the current stream supports reading. | |||
3718 | /// </summary> | |||
3719 | /// <value>true.</value> | |||
3720 | /// <returns>true if the stream supports reading; otherwise, false.</returns> | |||
3721 | public override bool CanRead { | |||
| 13 | 3722 | get { return true; } | ||
3723 | } | |||
3724 | | |||
3725 | /// <summary> | |||
3726 | /// Gets a value that determines whether the current stream can time out. | |||
3727 | /// </summary> | |||
3728 | /// <value></value> | |||
3729 | /// <returns>A value that determines whether the current stream can time out.</returns> | |||
3730 | public override bool CanTimeout { | |||
| 0 | 3731 | get { return baseStream_.CanTimeout; } | ||
3732 | } | |||
3733 | #region Instance Fields | |||
3734 | ZipFile zipFile_; | |||
3735 | Stream baseStream_; | |||
3736 | long start_; | |||
3737 | long length_; | |||
3738 | long readPos_; | |||
3739 | long end_; | |||
3740 | #endregion | |||
3741 | } | |||
3742 | #endregion | |||
3743 | } | |||
3744 | | |||
3745 | #endregion | |||
3746 | | |||
3747 | #region DataSources | |||
3748 | /// <summary> | |||
3749 | /// Provides a static way to obtain a source of data for an entry. | |||
3750 | /// </summary> | |||
3751 | public interface IStaticDataSource | |||
3752 | { | |||
3753 | /// <summary> | |||
3754 | /// Get a source of data by creating a new stream. | |||
3755 | /// </summary> | |||
3756 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3757 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3758 | Stream GetSource(); | |||
3759 | } | |||
3760 | | |||
3761 | /// <summary> | |||
3762 | /// Represents a source of data that can dynamically provide | |||
3763 | /// multiple <see cref="Stream">data sources</see> based on the parameters passed. | |||
3764 | /// </summary> | |||
3765 | public interface IDynamicDataSource | |||
3766 | { | |||
3767 | /// <summary> | |||
3768 | /// Get a data source. | |||
3769 | /// </summary> | |||
3770 | /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> | |||
3771 | /// <param name="name">The name for data if known.</param> | |||
3772 | /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> | |||
3773 | /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> | |||
3774 | Stream GetSource(ZipEntry entry, string name); | |||
3775 | } | |||
3776 | | |||
3777 | /// <summary> | |||
3778 | /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. | |||
3779 | /// </summary> | |||
3780 | public class StaticDiskDataSource : IStaticDataSource | |||
3781 | { | |||
3782 | /// <summary> | |||
3783 | /// Initialise a new instnace of <see cref="StaticDiskDataSource"/> | |||
3784 | /// </summary> | |||
3785 | /// <param name="fileName">The name of the file to obtain data from.</param> | |||
3786 | public StaticDiskDataSource(string fileName) | |||
3787 | { | |||
3788 | fileName_ = fileName; | |||
3789 | } | |||
3790 | | |||
3791 | #region IDataSource Members | |||
3792 | | |||
3793 | /// <summary> | |||
3794 | /// Get a <see cref="Stream"/> providing data. | |||
3795 | /// </summary> | |||
3796 | /// <returns>Returns a <see cref="Stream"/> provising data.</returns> | |||
3797 | public Stream GetSource() | |||
3798 | { | |||
3799 | return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3800 | } | |||
3801 | | |||
3802 | readonly | |||
3803 | | |||
3804 | #endregion | |||
3805 | #region Instance Fields | |||
3806 | string fileName_; | |||
3807 | #endregion | |||
3808 | } | |||
3809 | | |||
3810 | | |||
3811 | /// <summary> | |||
3812 | /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. | |||
3813 | /// </summary> | |||
3814 | public class DynamicDiskDataSource : IDynamicDataSource | |||
3815 | { | |||
3816 | | |||
3817 | #region IDataSource Members | |||
3818 | /// <summary> | |||
3819 | /// Get a <see cref="Stream"/> providing data for an entry. | |||
3820 | /// </summary> | |||
3821 | /// <param name="entry">The entry to provide data for.</param> | |||
3822 | /// <param name="name">The file name for data if known.</param> | |||
3823 | /// <returns>Returns a stream providing data; or null if not available</returns> | |||
3824 | public Stream GetSource(ZipEntry entry, string name) | |||
3825 | { | |||
3826 | Stream result = null; | |||
3827 | | |||
3828 | if (name != null) { | |||
3829 | result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
3830 | } | |||
3831 | | |||
3832 | return result; | |||
3833 | } | |||
3834 | | |||
3835 | #endregion | |||
3836 | } | |||
3837 | | |||
3838 | #endregion | |||
3839 | | |||
3840 | #region Archive Storage | |||
3841 | /// <summary> | |||
3842 | /// Defines facilities for data storage when updating Zip Archives. | |||
3843 | /// </summary> | |||
3844 | public interface IArchiveStorage | |||
3845 | { | |||
3846 | /// <summary> | |||
3847 | /// Get the <see cref="FileUpdateMode"/> to apply during updates. | |||
3848 | /// </summary> | |||
3849 | FileUpdateMode UpdateMode { get; } | |||
3850 | | |||
3851 | /// <summary> | |||
3852 | /// Get an empty <see cref="Stream"/> that can be used for temporary output. | |||
3853 | /// </summary> | |||
3854 | /// <returns>Returns a temporary output <see cref="Stream"/></returns> | |||
3855 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3856 | Stream GetTemporaryOutput(); | |||
3857 | | |||
3858 | /// <summary> | |||
3859 | /// Convert a temporary output stream to a final stream. | |||
3860 | /// </summary> | |||
3861 | /// <returns>The resulting final <see cref="Stream"/></returns> | |||
3862 | /// <seealso cref="GetTemporaryOutput"/> | |||
3863 | Stream ConvertTemporaryToFinal(); | |||
3864 | | |||
3865 | /// <summary> | |||
3866 | /// Make a temporary copy of the original stream. | |||
3867 | /// </summary> | |||
3868 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
3869 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3870 | Stream MakeTemporaryCopy(Stream stream); | |||
3871 | | |||
3872 | /// <summary> | |||
3873 | /// Return a stream suitable for performing direct updates on the original source. | |||
3874 | /// </summary> | |||
3875 | /// <param name="stream">The current stream.</param> | |||
3876 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3877 | /// <remarks>This may be the current stream passed.</remarks> | |||
3878 | Stream OpenForDirectUpdate(Stream stream); | |||
3879 | | |||
3880 | /// <summary> | |||
3881 | /// Dispose of this instance. | |||
3882 | /// </summary> | |||
3883 | void Dispose(); | |||
3884 | } | |||
3885 | | |||
3886 | /// <summary> | |||
3887 | /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. | |||
3888 | /// </summary> | |||
3889 | abstract public class BaseArchiveStorage : IArchiveStorage | |||
3890 | { | |||
3891 | #region Constructors | |||
3892 | /// <summary> | |||
3893 | /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. | |||
3894 | /// </summary> | |||
3895 | /// <param name="updateMode">The update mode.</param> | |||
3896 | protected BaseArchiveStorage(FileUpdateMode updateMode) | |||
3897 | { | |||
3898 | updateMode_ = updateMode; | |||
3899 | } | |||
3900 | #endregion | |||
3901 | | |||
3902 | #region IArchiveStorage Members | |||
3903 | | |||
3904 | /// <summary> | |||
3905 | /// Gets a temporary output <see cref="Stream"/> | |||
3906 | /// </summary> | |||
3907 | /// <returns>Returns the temporary output stream.</returns> | |||
3908 | /// <seealso cref="ConvertTemporaryToFinal"></seealso> | |||
3909 | public abstract Stream GetTemporaryOutput(); | |||
3910 | | |||
3911 | /// <summary> | |||
3912 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
3913 | /// </summary> | |||
3914 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
3915 | /// the final storage for the archive.</returns> | |||
3916 | /// <seealso cref="GetTemporaryOutput"/> | |||
3917 | public abstract Stream ConvertTemporaryToFinal(); | |||
3918 | | |||
3919 | /// <summary> | |||
3920 | /// Make a temporary copy of a <see cref="Stream"/>. | |||
3921 | /// </summary> | |||
3922 | /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> | |||
3923 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
3924 | public abstract Stream MakeTemporaryCopy(Stream stream); | |||
3925 | | |||
3926 | /// <summary> | |||
3927 | /// Return a stream suitable for performing direct updates on the original source. | |||
3928 | /// </summary> | |||
3929 | /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> | |||
3930 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
3931 | public abstract Stream OpenForDirectUpdate(Stream stream); | |||
3932 | | |||
3933 | /// <summary> | |||
3934 | /// Disposes this instance. | |||
3935 | /// </summary> | |||
3936 | public abstract void Dispose(); | |||
3937 | | |||
3938 | /// <summary> | |||
3939 | /// Gets the update mode applicable. | |||
3940 | /// </summary> | |||
3941 | /// <value>The update mode.</value> | |||
3942 | public FileUpdateMode UpdateMode { | |||
3943 | get { | |||
3944 | return updateMode_; | |||
3945 | } | |||
3946 | } | |||
3947 | | |||
3948 | #endregion | |||
3949 | | |||
3950 | #region Instance Fields | |||
3951 | FileUpdateMode updateMode_; | |||
3952 | #endregion | |||
3953 | } | |||
3954 | | |||
3955 | /// <summary> | |||
3956 | /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. | |||
3957 | /// </summary> | |||
3958 | public class DiskArchiveStorage : BaseArchiveStorage | |||
3959 | { | |||
3960 | #region Constructors | |||
3961 | /// <summary> | |||
3962 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3963 | /// </summary> | |||
3964 | /// <param name="file">The file.</param> | |||
3965 | /// <param name="updateMode">The update mode.</param> | |||
3966 | public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) | |||
3967 | : base(updateMode) | |||
3968 | { | |||
3969 | if (file.Name == null) { | |||
3970 | throw new ZipException("Cant handle non file archives"); | |||
3971 | } | |||
3972 | | |||
3973 | fileName_ = file.Name; | |||
3974 | } | |||
3975 | | |||
3976 | /// <summary> | |||
3977 | /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. | |||
3978 | /// </summary> | |||
3979 | /// <param name="file">The file.</param> | |||
3980 | public DiskArchiveStorage(ZipFile file) | |||
3981 | : this(file, FileUpdateMode.Safe) | |||
3982 | { | |||
3983 | } | |||
3984 | #endregion | |||
3985 | | |||
3986 | #region IArchiveStorage Members | |||
3987 | | |||
3988 | /// <summary> | |||
3989 | /// Gets a temporary output <see cref="Stream"/> for performing updates on. | |||
3990 | /// </summary> | |||
3991 | /// <returns>Returns the temporary output stream.</returns> | |||
3992 | public override Stream GetTemporaryOutput() | |||
3993 | { | |||
3994 | if (temporaryName_ != null) { | |||
3995 | temporaryName_ = GetTempFileName(temporaryName_, true); | |||
3996 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
3997 | } else { | |||
3998 | // Determine where to place files based on internal strategy. | |||
3999 | // Currently this is always done in system temp directory. | |||
4000 | temporaryName_ = Path.GetTempFileName(); | |||
4001 | temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); | |||
4002 | } | |||
4003 | | |||
4004 | return temporaryStream_; | |||
4005 | } | |||
4006 | | |||
4007 | /// <summary> | |||
4008 | /// Converts a temporary <see cref="Stream"/> to its final form. | |||
4009 | /// </summary> | |||
4010 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4011 | /// the final storage for the archive.</returns> | |||
4012 | public override Stream ConvertTemporaryToFinal() | |||
4013 | { | |||
4014 | if (temporaryStream_ == null) { | |||
4015 | throw new ZipException("No temporary stream has been created"); | |||
4016 | } | |||
4017 | | |||
4018 | Stream result = null; | |||
4019 | | |||
4020 | string moveTempName = GetTempFileName(fileName_, false); | |||
4021 | bool newFileCreated = false; | |||
4022 | | |||
4023 | try { | |||
4024 | temporaryStream_.Close(); | |||
4025 | File.Move(fileName_, moveTempName); | |||
4026 | File.Move(temporaryName_, fileName_); | |||
4027 | newFileCreated = true; | |||
4028 | File.Delete(moveTempName); | |||
4029 | | |||
4030 | result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
4031 | } catch (Exception) { | |||
4032 | result = null; | |||
4033 | | |||
4034 | // Try to roll back changes... | |||
4035 | if (!newFileCreated) { | |||
4036 | File.Move(moveTempName, fileName_); | |||
4037 | File.Delete(temporaryName_); | |||
4038 | } | |||
4039 | | |||
4040 | throw; | |||
4041 | } | |||
4042 | | |||
4043 | return result; | |||
4044 | } | |||
4045 | | |||
4046 | /// <summary> | |||
4047 | /// Make a temporary copy of a stream. | |||
4048 | /// </summary> | |||
4049 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4050 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4051 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4052 | { | |||
4053 | stream.Close(); | |||
4054 | | |||
4055 | temporaryName_ = GetTempFileName(fileName_, true); | |||
4056 | File.Copy(fileName_, temporaryName_, true); | |||
4057 | | |||
4058 | temporaryStream_ = new FileStream(temporaryName_, | |||
4059 | FileMode.Open, | |||
4060 | FileAccess.ReadWrite); | |||
4061 | return temporaryStream_; | |||
4062 | } | |||
4063 | | |||
4064 | /// <summary> | |||
4065 | /// Return a stream suitable for performing direct updates on the original source. | |||
4066 | /// </summary> | |||
4067 | /// <param name="stream">The current stream.</param> | |||
4068 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4069 | /// <remarks>If the <paramref name="stream"/> is not null this is used as is.</remarks> | |||
4070 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4071 | { | |||
4072 | Stream result; | |||
4073 | if ((stream == null) || !stream.CanWrite) { | |||
4074 | if (stream != null) { | |||
4075 | stream.Close(); | |||
4076 | } | |||
4077 | | |||
4078 | result = new FileStream(fileName_, | |||
4079 | FileMode.Open, | |||
4080 | FileAccess.ReadWrite); | |||
4081 | } else { | |||
4082 | result = stream; | |||
4083 | } | |||
4084 | | |||
4085 | return result; | |||
4086 | } | |||
4087 | | |||
4088 | /// <summary> | |||
4089 | /// Disposes this instance. | |||
4090 | /// </summary> | |||
4091 | public override void Dispose() | |||
4092 | { | |||
4093 | if (temporaryStream_ != null) { | |||
4094 | temporaryStream_.Close(); | |||
4095 | } | |||
4096 | } | |||
4097 | | |||
4098 | #endregion | |||
4099 | | |||
4100 | #region Internal routines | |||
4101 | static string GetTempFileName(string original, bool makeTempFile) | |||
4102 | { | |||
4103 | string result = null; | |||
4104 | | |||
4105 | if (original == null) { | |||
4106 | result = Path.GetTempFileName(); | |||
4107 | } else { | |||
4108 | int counter = 0; | |||
4109 | int suffixSeed = DateTime.Now.Second; | |||
4110 | | |||
4111 | while (result == null) { | |||
4112 | counter += 1; | |||
4113 | string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); | |||
4114 | if (!File.Exists(newName)) { | |||
4115 | if (makeTempFile) { | |||
4116 | try { | |||
4117 | // Try and create the file. | |||
4118 | using (FileStream stream = File.Create(newName)) { | |||
4119 | } | |||
4120 | result = newName; | |||
4121 | } catch { | |||
4122 | suffixSeed = DateTime.Now.Second; | |||
4123 | } | |||
4124 | } else { | |||
4125 | result = newName; | |||
4126 | } | |||
4127 | } | |||
4128 | } | |||
4129 | } | |||
4130 | return result; | |||
4131 | } | |||
4132 | #endregion | |||
4133 | | |||
4134 | #region Instance Fields | |||
4135 | Stream temporaryStream_; | |||
4136 | string fileName_; | |||
4137 | string temporaryName_; | |||
4138 | #endregion | |||
4139 | } | |||
4140 | | |||
4141 | /// <summary> | |||
4142 | /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. | |||
4143 | /// </summary> | |||
4144 | public class MemoryArchiveStorage : BaseArchiveStorage | |||
4145 | { | |||
4146 | #region Constructors | |||
4147 | /// <summary> | |||
4148 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4149 | /// </summary> | |||
4150 | public MemoryArchiveStorage() | |||
4151 | : base(FileUpdateMode.Direct) | |||
4152 | { | |||
4153 | } | |||
4154 | | |||
4155 | /// <summary> | |||
4156 | /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. | |||
4157 | /// </summary> | |||
4158 | /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> | |||
4159 | /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> | |||
4160 | public MemoryArchiveStorage(FileUpdateMode updateMode) | |||
4161 | : base(updateMode) | |||
4162 | { | |||
4163 | } | |||
4164 | | |||
4165 | #endregion | |||
4166 | | |||
4167 | #region Properties | |||
4168 | /// <summary> | |||
4169 | /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. | |||
4170 | /// </summary> | |||
4171 | public MemoryStream FinalStream { | |||
4172 | get { return finalStream_; } | |||
4173 | } | |||
4174 | | |||
4175 | #endregion | |||
4176 | | |||
4177 | #region IArchiveStorage Members | |||
4178 | | |||
4179 | /// <summary> | |||
4180 | /// Gets the temporary output <see cref="Stream"/> | |||
4181 | /// </summary> | |||
4182 | /// <returns>Returns the temporary output stream.</returns> | |||
4183 | public override Stream GetTemporaryOutput() | |||
4184 | { | |||
4185 | temporaryStream_ = new MemoryStream(); | |||
4186 | return temporaryStream_; | |||
4187 | } | |||
4188 | | |||
4189 | /// <summary> | |||
4190 | /// Converts the temporary <see cref="Stream"/> to its final form. | |||
4191 | /// </summary> | |||
4192 | /// <returns>Returns a <see cref="Stream"/> that can be used to read | |||
4193 | /// the final storage for the archive.</returns> | |||
4194 | public override Stream ConvertTemporaryToFinal() | |||
4195 | { | |||
4196 | if (temporaryStream_ == null) { | |||
4197 | throw new ZipException("No temporary stream has been created"); | |||
4198 | } | |||
4199 | | |||
4200 | finalStream_ = new MemoryStream(temporaryStream_.ToArray()); | |||
4201 | return finalStream_; | |||
4202 | } | |||
4203 | | |||
4204 | /// <summary> | |||
4205 | /// Make a temporary copy of the original stream. | |||
4206 | /// </summary> | |||
4207 | /// <param name="stream">The <see cref="Stream"/> to copy.</param> | |||
4208 | /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> | |||
4209 | public override Stream MakeTemporaryCopy(Stream stream) | |||
4210 | { | |||
4211 | temporaryStream_ = new MemoryStream(); | |||
4212 | stream.Position = 0; | |||
4213 | StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); | |||
4214 | return temporaryStream_; | |||
4215 | } | |||
4216 | | |||
4217 | /// <summary> | |||
4218 | /// Return a stream suitable for performing direct updates on the original source. | |||
4219 | /// </summary> | |||
4220 | /// <param name="stream">The original source stream</param> | |||
4221 | /// <returns>Returns a stream suitable for direct updating.</returns> | |||
4222 | /// <remarks>If the <paramref name="stream"/> passed is not null this is used; | |||
4223 | /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> | |||
4224 | public override Stream OpenForDirectUpdate(Stream stream) | |||
4225 | { | |||
4226 | Stream result; | |||
4227 | if ((stream == null) || !stream.CanWrite) { | |||
4228 | | |||
4229 | result = new MemoryStream(); | |||
4230 | | |||
4231 | if (stream != null) { | |||
4232 | stream.Position = 0; | |||
4233 | StreamUtils.Copy(stream, result, new byte[4096]); | |||
4234 | | |||
4235 | stream.Close(); | |||
4236 | } | |||
4237 | } else { | |||
4238 | result = stream; | |||
4239 | } | |||
4240 | | |||
4241 | return result; | |||
4242 | } | |||
4243 | | |||
4244 | /// <summary> | |||
4245 | /// Disposes this instance. | |||
4246 | /// </summary> | |||
4247 | public override void Dispose() | |||
4248 | { | |||
4249 | if (temporaryStream_ != null) { | |||
4250 | temporaryStream_.Close(); | |||
4251 | } | |||
4252 | } | |||
4253 | | |||
4254 | #endregion | |||
4255 | | |||
4256 | #region Instance Fields | |||
4257 | MemoryStream temporaryStream_; | |||
4258 | MemoryStream finalStream_; | |||
4259 | #endregion | |||
4260 | } | |||
4261 | | |||
4262 | #endregion | |||
4263 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipHelperStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipHelperStream.cs |
| Covered lines: | 106 |
| Uncovered lines: | 88 |
| Coverable lines: | 194 |
| Total lines: | 560 |
| Line coverage: | 54.6% |
| Branch coverage: | 42.8% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 0 | 0 |
| .ctor(...) | 1 | 100 | 100 |
| Flush() | 1 | 0 | 0 |
| Seek(...) | 1 | 100 | 100 |
| SetLength(...) | 1 | 100 | 100 |
| Read(...) | 1 | 100 | 100 |
| Write(...) | 1 | 100 | 100 |
| Close() | 3 | 100 | 100 |
| WriteLocalHeader(...) | 16 | 0 | 0 |
| LocateBlockWithSignature(...) | 4 | 88.89 | 85.71 |
| WriteZip64EndOfCentralDirectory(...) | 1 | 100 | 100 |
| WriteEndOfCentralDirectory(...) | 11 | 80.77 | 73.68 |
| ReadLEShort() | 3 | 71.43 | 60 |
| ReadLEInt() | 1 | 100 | 100 |
| ReadLELong() | 1 | 100 | 100 |
| WriteLEShort(...) | 1 | 100 | 100 |
| WriteLEUshort(...) | 1 | 100 | 100 |
| WriteLEInt(...) | 1 | 100 | 100 |
| WriteLEUint(...) | 1 | 0 | 0 |
| WriteLELong(...) | 1 | 100 | 100 |
| WriteLEUlong(...) | 1 | 0 | 0 |
| WriteDataDescriptor(...) | 4 | 68.75 | 57.14 |
| ReadDataDescriptor(...) | 3 | 90.91 | 80 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Text; | |||
4 | | |||
5 | namespace ICSharpCode.SharpZipLib.Zip | |||
6 | { | |||
7 | /// <summary> | |||
8 | /// Holds data pertinent to a data descriptor. | |||
9 | /// </summary> | |||
10 | public class DescriptorData | |||
11 | { | |||
12 | /// <summary> | |||
13 | /// Get /set the compressed size of data. | |||
14 | /// </summary> | |||
15 | public long CompressedSize { | |||
16 | get { return compressedSize; } | |||
17 | set { compressedSize = value; } | |||
18 | } | |||
19 | | |||
20 | /// <summary> | |||
21 | /// Get / set the uncompressed size of data | |||
22 | /// </summary> | |||
23 | public long Size { | |||
24 | get { return size; } | |||
25 | set { size = value; } | |||
26 | } | |||
27 | | |||
28 | /// <summary> | |||
29 | /// Get /set the crc value. | |||
30 | /// </summary> | |||
31 | public long Crc { | |||
32 | get { return crc; } | |||
33 | set { crc = (value & 0xffffffff); } | |||
34 | } | |||
35 | | |||
36 | #region Instance Fields | |||
37 | long size; | |||
38 | long compressedSize; | |||
39 | long crc; | |||
40 | #endregion | |||
41 | } | |||
42 | | |||
43 | class EntryPatchData | |||
44 | { | |||
45 | public long SizePatchOffset { | |||
46 | get { return sizePatchOffset_; } | |||
47 | set { sizePatchOffset_ = value; } | |||
48 | } | |||
49 | | |||
50 | public long CrcPatchOffset { | |||
51 | get { return crcPatchOffset_; } | |||
52 | set { crcPatchOffset_ = value; } | |||
53 | } | |||
54 | | |||
55 | #region Instance Fields | |||
56 | long sizePatchOffset_; | |||
57 | long crcPatchOffset_; | |||
58 | #endregion | |||
59 | } | |||
60 | | |||
61 | /// <summary> | |||
62 | /// This class assists with writing/reading from Zip files. | |||
63 | /// </summary> | |||
64 | internal class ZipHelperStream : Stream | |||
65 | { | |||
66 | #region Constructors | |||
67 | /// <summary> | |||
68 | /// Initialise an instance of this class. | |||
69 | /// </summary> | |||
70 | /// <param name="name">The name of the file to open.</param> | |||
| 0 | 71 | public ZipHelperStream(string name) | ||
72 | { | |||
| 0 | 73 | stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite); | ||
| 0 | 74 | isOwner_ = true; | ||
| 0 | 75 | } | ||
76 | | |||
77 | /// <summary> | |||
78 | /// Initialise a new instance of <see cref="ZipHelperStream"/>. | |||
79 | /// </summary> | |||
80 | /// <param name="stream">The stream to use.</param> | |||
| 272 | 81 | public ZipHelperStream(Stream stream) | ||
82 | { | |||
| 272 | 83 | stream_ = stream; | ||
| 272 | 84 | } | ||
85 | #endregion | |||
86 | | |||
87 | /// <summary> | |||
88 | /// Get / set a value indicating wether the the underlying stream is owned or not. | |||
89 | /// </summary> | |||
90 | /// <remarks>If the stream is owned it is closed when this instance is closed.</remarks> | |||
91 | public bool IsStreamOwner { | |||
| 0 | 92 | get { return isOwner_; } | ||
| 8 | 93 | set { isOwner_ = value; } | ||
94 | } | |||
95 | | |||
96 | #region Base Stream Methods | |||
97 | public override bool CanRead { | |||
| 0 | 98 | get { return stream_.CanRead; } | ||
99 | } | |||
100 | | |||
101 | public override bool CanSeek { | |||
| 0 | 102 | get { return stream_.CanSeek; } | ||
103 | } | |||
104 | | |||
105 | public override bool CanTimeout { | |||
| 0 | 106 | get { return stream_.CanTimeout; } | ||
107 | } | |||
108 | | |||
109 | public override long Length { | |||
| 1 | 110 | get { return stream_.Length; } | ||
111 | } | |||
112 | | |||
113 | public override long Position { | |||
| 124 | 114 | get { return stream_.Position; } | ||
| 6 | 115 | set { stream_.Position = value; } | ||
116 | } | |||
117 | | |||
118 | public override bool CanWrite { | |||
| 0 | 119 | get { return stream_.CanWrite; } | ||
120 | } | |||
121 | | |||
122 | public override void Flush() | |||
123 | { | |||
| 0 | 124 | stream_.Flush(); | ||
| 0 | 125 | } | ||
126 | | |||
127 | public override long Seek(long offset, SeekOrigin origin) | |||
128 | { | |||
| 131329 | 129 | return stream_.Seek(offset, origin); | ||
130 | } | |||
131 | | |||
132 | public override void SetLength(long value) | |||
133 | { | |||
| 3 | 134 | stream_.SetLength(value); | ||
| 3 | 135 | } | ||
136 | | |||
137 | public override int Read(byte[] buffer, int offset, int count) | |||
138 | { | |||
| 1 | 139 | return stream_.Read(buffer, offset, count); | ||
140 | } | |||
141 | | |||
142 | public override void Write(byte[] buffer, int offset, int count) | |||
143 | { | |||
| 9 | 144 | stream_.Write(buffer, offset, count); | ||
| 9 | 145 | } | ||
146 | | |||
147 | /// <summary> | |||
148 | /// Close the stream. | |||
149 | /// </summary> | |||
150 | /// <remarks> | |||
151 | /// The underlying stream is closed only if <see cref="IsStreamOwner"/> is true. | |||
152 | /// </remarks> | |||
153 | override public void Close() | |||
154 | { | |||
| 254 | 155 | Stream toClose = stream_; | ||
| 254 | 156 | stream_ = null; | ||
| 254 | 157 | if (isOwner_ && (toClose != null)) { | ||
| 1 | 158 | isOwner_ = false; | ||
| 1 | 159 | toClose.Close(); | ||
160 | } | |||
| 254 | 161 | } | ||
162 | #endregion | |||
163 | | |||
164 | // Write the local file header | |||
165 | // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage | |||
166 | void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData) | |||
167 | { | |||
| 0 | 168 | CompressionMethod method = entry.CompressionMethod; | ||
| 0 | 169 | bool headerInfoAvailable = true; // How to get this? | ||
| 0 | 170 | bool patchEntryHeader = false; | ||
171 | | |||
| 0 | 172 | WriteLEInt(ZipConstants.LocalHeaderSignature); | ||
173 | | |||
| 0 | 174 | WriteLEShort(entry.Version); | ||
| 0 | 175 | WriteLEShort(entry.Flags); | ||
| 0 | 176 | WriteLEShort((byte)method); | ||
| 0 | 177 | WriteLEInt((int)entry.DosTime); | ||
178 | | |||
| 0 | 179 | if (headerInfoAvailable == true) { | ||
| 0 | 180 | WriteLEInt((int)entry.Crc); | ||
| 0 | 181 | if (entry.LocalHeaderRequiresZip64) { | ||
| 0 | 182 | WriteLEInt(-1); | ||
| 0 | 183 | WriteLEInt(-1); | ||
| 0 | 184 | } else { | ||
| 0 | 185 | WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.Compressed | ||
| 0 | 186 | WriteLEInt((int)entry.Size); | ||
187 | } | |||
| 0 | 188 | } else { | ||
| 0 | 189 | if (patchData != null) { | ||
| 0 | 190 | patchData.CrcPatchOffset = stream_.Position; | ||
191 | } | |||
| 0 | 192 | WriteLEInt(0); // Crc | ||
193 | | |||
| 0 | 194 | if (patchData != null) { | ||
| 0 | 195 | patchData.SizePatchOffset = stream_.Position; | ||
196 | } | |||
197 | | |||
198 | // For local header both sizes appear in Zip64 Extended Information | |||
| 0 | 199 | if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { | ||
| 0 | 200 | WriteLEInt(-1); | ||
| 0 | 201 | WriteLEInt(-1); | ||
| 0 | 202 | } else { | ||
| 0 | 203 | WriteLEInt(0); // Compressed size | ||
| 0 | 204 | WriteLEInt(0); // Uncompressed size | ||
205 | } | |||
206 | } | |||
207 | | |||
| 0 | 208 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | ||
209 | | |||
| 0 | 210 | if (name.Length > 0xFFFF) { | ||
| 0 | 211 | throw new ZipException("Entry name too long."); | ||
212 | } | |||
213 | | |||
| 0 | 214 | var ed = new ZipExtraData(entry.ExtraData); | ||
215 | | |||
| 0 | 216 | if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) { | ||
| 0 | 217 | ed.StartNewEntry(); | ||
| 0 | 218 | if (headerInfoAvailable) { | ||
| 0 | 219 | ed.AddLeLong(entry.Size); | ||
| 0 | 220 | ed.AddLeLong(entry.CompressedSize); | ||
| 0 | 221 | } else { | ||
| 0 | 222 | ed.AddLeLong(-1); | ||
| 0 | 223 | ed.AddLeLong(-1); | ||
224 | } | |||
| 0 | 225 | ed.AddNewEntry(1); | ||
226 | | |||
| 0 | 227 | if (!ed.Find(1)) { | ||
| 0 | 228 | throw new ZipException("Internal error cant find extra data"); | ||
229 | } | |||
230 | | |||
| 0 | 231 | if (patchData != null) { | ||
| 0 | 232 | patchData.SizePatchOffset = ed.CurrentReadIndex; | ||
233 | } | |||
| 0 | 234 | } else { | ||
| 0 | 235 | ed.Delete(1); | ||
236 | } | |||
237 | | |||
| 0 | 238 | byte[] extra = ed.GetEntryData(); | ||
239 | | |||
| 0 | 240 | WriteLEShort(name.Length); | ||
| 0 | 241 | WriteLEShort(extra.Length); | ||
242 | | |||
| 0 | 243 | if (name.Length > 0) { | ||
| 0 | 244 | stream_.Write(name, 0, name.Length); | ||
245 | } | |||
246 | | |||
| 0 | 247 | if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { | ||
| 0 | 248 | patchData.SizePatchOffset += stream_.Position; | ||
249 | } | |||
250 | | |||
| 0 | 251 | if (extra.Length > 0) { | ||
| 0 | 252 | stream_.Write(extra, 0, extra.Length); | ||
253 | } | |||
| 0 | 254 | } | ||
255 | | |||
256 | /// <summary> | |||
257 | /// Locates a block with the desired <paramref name="signature"/>. | |||
258 | /// </summary> | |||
259 | /// <param name="signature">The signature to find.</param> | |||
260 | /// <param name="endLocation">Location, marking the end of block.</param> | |||
261 | /// <param name="minimumBlockSize">Minimum size of the block.</param> | |||
262 | /// <param name="maximumVariableData">The maximum variable data.</param> | |||
263 | /// <returns>Eeturns the offset of the first byte after the signature; -1 if not found</returns> | |||
264 | public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) | |||
265 | { | |||
| 118 | 266 | long pos = endLocation - minimumBlockSize; | ||
| 118 | 267 | if (pos < 0) { | ||
| 0 | 268 | return -1; | ||
269 | } | |||
270 | | |||
| 118 | 271 | long giveUpMarker = Math.Max(pos - maximumVariableData, 0); | ||
272 | | |||
273 | // TODO: This loop could be optimised for speed. | |||
274 | do { | |||
| 131330 | 275 | if (pos < giveUpMarker) { | ||
| 1 | 276 | return -1; | ||
277 | } | |||
| 131329 | 278 | Seek(pos--, SeekOrigin.Begin); | ||
| 131329 | 279 | } while (ReadLEInt() != signature); | ||
280 | | |||
| 117 | 281 | return Position; | ||
282 | } | |||
283 | | |||
284 | /// <summary> | |||
285 | /// Write Zip64 end of central directory records (File header and locator). | |||
286 | /// </summary> | |||
287 | /// <param name="noOfEntries">The number of entries in the central directory.</param> | |||
288 | /// <param name="sizeEntries">The size of entries in the central directory.</param> | |||
289 | /// <param name="centralDirOffset">The offset of the dentral directory.</param> | |||
290 | public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset) | |||
291 | { | |||
| 1 | 292 | long centralSignatureOffset = stream_.Position; | ||
| 1 | 293 | WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature); | ||
| 1 | 294 | WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12) | ||
| 1 | 295 | WriteLEShort(ZipConstants.VersionMadeBy); // Version made by | ||
| 1 | 296 | WriteLEShort(ZipConstants.VersionZip64); // Version to extract | ||
| 1 | 297 | WriteLEInt(0); // Number of this disk | ||
| 1 | 298 | WriteLEInt(0); // number of the disk with the start of the central directory | ||
| 1 | 299 | WriteLELong(noOfEntries); // No of entries on this disk | ||
| 1 | 300 | WriteLELong(noOfEntries); // Total No of entries in central directory | ||
| 1 | 301 | WriteLELong(sizeEntries); // Size of the central directory | ||
| 1 | 302 | WriteLELong(centralDirOffset); // offset of start of central directory | ||
303 | // zip64 extensible data sector not catered for here (variable size) | |||
304 | | |||
305 | // Write the Zip64 end of central directory locator | |||
| 1 | 306 | WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature); | ||
307 | | |||
308 | // no of the disk with the start of the zip64 end of central directory | |||
| 1 | 309 | WriteLEInt(0); | ||
310 | | |||
311 | // relative offset of the zip64 end of central directory record | |||
| 1 | 312 | WriteLELong(centralSignatureOffset); | ||
313 | | |||
314 | // total number of disks | |||
| 1 | 315 | WriteLEInt(1); | ||
| 1 | 316 | } | ||
317 | | |||
318 | /// <summary> | |||
319 | /// Write the required records to end the central directory. | |||
320 | /// </summary> | |||
321 | /// <param name="noOfEntries">The number of entries in the directory.</param> | |||
322 | /// <param name="sizeEntries">The size of the entries in the directory.</param> | |||
323 | /// <param name="startOfCentralDirectory">The start of the central directory.</param> | |||
324 | /// <param name="comment">The archive comment. (This can be null).</param> | |||
325 | public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries, | |||
326 | long startOfCentralDirectory, byte[] comment) | |||
327 | { | |||
328 | | |||
| 131 | 329 | if ((noOfEntries >= 0xffff) || | ||
| 131 | 330 | (startOfCentralDirectory >= 0xffffffff) || | ||
| 131 | 331 | (sizeEntries >= 0xffffffff)) { | ||
| 1 | 332 | WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory); | ||
333 | } | |||
334 | | |||
| 131 | 335 | WriteLEInt(ZipConstants.EndOfCentralDirectorySignature); | ||
336 | | |||
337 | // TODO: ZipFile Multi disk handling not done | |||
| 130 | 338 | WriteLEShort(0); // number of this disk | ||
| 130 | 339 | WriteLEShort(0); // no of disk with start of central dir | ||
340 | | |||
341 | | |||
342 | // Number of entries | |||
| 130 | 343 | if (noOfEntries >= 0xffff) { | ||
| 1 | 344 | WriteLEUshort(0xffff); // Zip64 marker | ||
| 1 | 345 | WriteLEUshort(0xffff); | ||
| 1 | 346 | } else { | ||
| 129 | 347 | WriteLEShort((short)noOfEntries); // entries in central dir for this disk | ||
| 129 | 348 | WriteLEShort((short)noOfEntries); // total entries in central directory | ||
349 | } | |||
350 | | |||
351 | // Size of the central directory | |||
| 130 | 352 | if (sizeEntries >= 0xffffffff) { | ||
| 0 | 353 | WriteLEUint(0xffffffff); // Zip64 marker | ||
| 0 | 354 | } else { | ||
| 130 | 355 | WriteLEInt((int)sizeEntries); | ||
356 | } | |||
357 | | |||
358 | | |||
359 | // offset of start of central directory | |||
| 130 | 360 | if (startOfCentralDirectory >= 0xffffffff) { | ||
| 0 | 361 | WriteLEUint(0xffffffff); // Zip64 marker | ||
| 0 | 362 | } else { | ||
| 130 | 363 | WriteLEInt((int)startOfCentralDirectory); | ||
364 | } | |||
365 | | |||
| 130 | 366 | int commentLength = (comment != null) ? comment.Length : 0; | ||
367 | | |||
| 130 | 368 | if (commentLength > 0xffff) { | ||
| 0 | 369 | throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength)); | ||
370 | } | |||
371 | | |||
| 130 | 372 | WriteLEShort(commentLength); | ||
373 | | |||
| 130 | 374 | if (commentLength > 0) { | ||
| 5 | 375 | Write(comment, 0, comment.Length); | ||
376 | } | |||
| 130 | 377 | } | ||
378 | | |||
379 | #region LE value reading/writing | |||
380 | /// <summary> | |||
381 | /// Read an unsigned short in little endian byte order. | |||
382 | /// </summary> | |||
383 | /// <returns>Returns the value read.</returns> | |||
384 | /// <exception cref="IOException"> | |||
385 | /// An i/o error occurs. | |||
386 | /// </exception> | |||
387 | /// <exception cref="EndOfStreamException"> | |||
388 | /// The file ends prematurely | |||
389 | /// </exception> | |||
390 | public int ReadLEShort() | |||
391 | { | |||
| 262792 | 392 | int byteValue1 = stream_.ReadByte(); | ||
393 | | |||
| 262792 | 394 | if (byteValue1 < 0) { | ||
| 0 | 395 | throw new EndOfStreamException(); | ||
396 | } | |||
397 | | |||
| 262792 | 398 | int byteValue2 = stream_.ReadByte(); | ||
| 262792 | 399 | if (byteValue2 < 0) { | ||
| 0 | 400 | throw new EndOfStreamException(); | ||
401 | } | |||
402 | | |||
| 262792 | 403 | return byteValue1 | (byteValue2 << 8); | ||
404 | } | |||
405 | | |||
406 | /// <summary> | |||
407 | /// Read an int in little endian byte order. | |||
408 | /// </summary> | |||
409 | /// <returns>Returns the value read.</returns> | |||
410 | /// <exception cref="IOException"> | |||
411 | /// An i/o error occurs. | |||
412 | /// </exception> | |||
413 | /// <exception cref="System.IO.EndOfStreamException"> | |||
414 | /// The file ends prematurely | |||
415 | /// </exception> | |||
416 | public int ReadLEInt() | |||
417 | { | |||
| 131395 | 418 | return ReadLEShort() | (ReadLEShort() << 16); | ||
419 | } | |||
420 | | |||
421 | /// <summary> | |||
422 | /// Read a long in little endian byte order. | |||
423 | /// </summary> | |||
424 | /// <returns>The value read.</returns> | |||
425 | public long ReadLELong() | |||
426 | { | |||
| 9 | 427 | return (uint)ReadLEInt() | ((long)ReadLEInt() << 32); | ||
428 | } | |||
429 | | |||
430 | /// <summary> | |||
431 | /// Write an unsigned short in little endian byte order. | |||
432 | /// </summary> | |||
433 | /// <param name="value">The value to write.</param> | |||
434 | public void WriteLEShort(int value) | |||
435 | { | |||
| 1545 | 436 | stream_.WriteByte((byte)(value & 0xff)); | ||
| 1544 | 437 | stream_.WriteByte((byte)((value >> 8) & 0xff)); | ||
| 1544 | 438 | } | ||
439 | | |||
440 | /// <summary> | |||
441 | /// Write a ushort in little endian byte order. | |||
442 | /// </summary> | |||
443 | /// <param name="value">The value to write.</param> | |||
444 | public void WriteLEUshort(ushort value) | |||
445 | { | |||
| 2 | 446 | stream_.WriteByte((byte)(value & 0xff)); | ||
| 2 | 447 | stream_.WriteByte((byte)(value >> 8)); | ||
| 2 | 448 | } | ||
449 | | |||
450 | /// <summary> | |||
451 | /// Write an int in little endian byte order. | |||
452 | /// </summary> | |||
453 | /// <param name="value">The value to write.</param> | |||
454 | public void WriteLEInt(int value) | |||
455 | { | |||
| 444 | 456 | WriteLEShort(value); | ||
| 444 | 457 | WriteLEShort(value >> 16); | ||
| 443 | 458 | } | ||
459 | | |||
460 | /// <summary> | |||
461 | /// Write a uint in little endian byte order. | |||
462 | /// </summary> | |||
463 | /// <param name="value">The value to write.</param> | |||
464 | public void WriteLEUint(uint value) | |||
465 | { | |||
| 0 | 466 | WriteLEUshort((ushort)(value & 0xffff)); | ||
| 0 | 467 | WriteLEUshort((ushort)(value >> 16)); | ||
| 0 | 468 | } | ||
469 | | |||
470 | /// <summary> | |||
471 | /// Write a long in little endian byte order. | |||
472 | /// </summary> | |||
473 | /// <param name="value">The value to write.</param> | |||
474 | public void WriteLELong(long value) | |||
475 | { | |||
| 12 | 476 | WriteLEInt((int)value); | ||
| 12 | 477 | WriteLEInt((int)(value >> 32)); | ||
| 12 | 478 | } | ||
479 | | |||
480 | /// <summary> | |||
481 | /// Write a ulong in little endian byte order. | |||
482 | /// </summary> | |||
483 | /// <param name="value">The value to write.</param> | |||
484 | public void WriteLEUlong(ulong value) | |||
485 | { | |||
| 0 | 486 | WriteLEUint((uint)(value & 0xffffffff)); | ||
| 0 | 487 | WriteLEUint((uint)(value >> 32)); | ||
| 0 | 488 | } | ||
489 | | |||
490 | #endregion | |||
491 | | |||
492 | /// <summary> | |||
493 | /// Write a data descriptor. | |||
494 | /// </summary> | |||
495 | /// <param name="entry">The entry to write a descriptor for.</param> | |||
496 | /// <returns>Returns the number of descriptor bytes written.</returns> | |||
497 | public int WriteDataDescriptor(ZipEntry entry) | |||
498 | { | |||
| 5 | 499 | if (entry == null) { | ||
| 0 | 500 | throw new ArgumentNullException(nameof(entry)); | ||
501 | } | |||
502 | | |||
| 5 | 503 | int result = 0; | ||
504 | | |||
505 | // Add data descriptor if flagged as required | |||
| 5 | 506 | if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { | ||
507 | // The signature is not PKZIP originally but is now described as optional | |||
508 | // in the PKZIP Appnote documenting trhe format. | |||
| 5 | 509 | WriteLEInt(ZipConstants.DataDescriptorSignature); | ||
| 5 | 510 | WriteLEInt(unchecked((int)(entry.Crc))); | ||
511 | | |||
| 5 | 512 | result += 8; | ||
513 | | |||
| 5 | 514 | if (entry.LocalHeaderRequiresZip64) { | ||
| 0 | 515 | WriteLELong(entry.CompressedSize); | ||
| 0 | 516 | WriteLELong(entry.Size); | ||
| 0 | 517 | result += 16; | ||
| 0 | 518 | } else { | ||
| 5 | 519 | WriteLEInt((int)entry.CompressedSize); | ||
| 5 | 520 | WriteLEInt((int)entry.Size); | ||
| 5 | 521 | result += 8; | ||
522 | } | |||
523 | } | |||
524 | | |||
| 5 | 525 | return result; | ||
526 | } | |||
527 | | |||
528 | /// <summary> | |||
529 | /// Read data descriptor at the end of compressed data. | |||
530 | /// </summary> | |||
531 | /// <param name="zip64">if set to <c>true</c> [zip64].</param> | |||
532 | /// <param name="data">The data to fill in.</param> | |||
533 | /// <returns>Returns the number of bytes read in the descriptor.</returns> | |||
534 | public void ReadDataDescriptor(bool zip64, DescriptorData data) | |||
535 | { | |||
| 13 | 536 | int intValue = ReadLEInt(); | ||
537 | | |||
538 | // In theory this may not be a descriptor according to PKZIP appnote. | |||
539 | // In practise its always there. | |||
| 13 | 540 | if (intValue != ZipConstants.DataDescriptorSignature) { | ||
| 0 | 541 | throw new ZipException("Data descriptor signature not found"); | ||
542 | } | |||
543 | | |||
| 13 | 544 | data.Crc = ReadLEInt(); | ||
545 | | |||
| 13 | 546 | if (zip64) { | ||
| 3 | 547 | data.CompressedSize = ReadLELong(); | ||
| 3 | 548 | data.Size = ReadLELong(); | ||
| 3 | 549 | } else { | ||
| 10 | 550 | data.CompressedSize = ReadLEInt(); | ||
| 10 | 551 | data.Size = ReadLEInt(); | ||
552 | } | |||
| 10 | 553 | } | ||
554 | | |||
555 | #region Instance Fields | |||
556 | bool isOwner_; | |||
557 | Stream stream_; | |||
558 | #endregion | |||
559 | } | |||
560 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipInputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipInputStream.cs |
| Covered lines: | 162 |
| Uncovered lines: | 44 |
| Coverable lines: | 206 |
| Total lines: | 610 |
| Line coverage: | 78.6% |
| Branch coverage: | 67.3% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| GetNextEntry() | 24 | 91.38 | 75.56 |
| ReadDataDescriptor() | 3 | 75 | 60 |
| CompleteCloseEntry(...) | 6 | 90.91 | 81.82 |
| CloseEntry() | 10 | 47.83 | 42.11 |
| ReadByte() | 2 | 75 | 66.67 |
| ReadingNotAvailable(...) | 1 | 0 | 0 |
| ReadingNotSupported(...) | 1 | 0 | 0 |
| InitialRead(...) | 11 | 84 | 80.95 |
| Read(...) | 5 | 100 | 100 |
| BodyRead(...) | 22 | 82.86 | 69.23 |
| Close() | 1 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using ICSharpCode.SharpZipLib.Checksum; | |||
4 | using ICSharpCode.SharpZipLib.Encryption; | |||
5 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
6 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
7 | | |||
8 | namespace ICSharpCode.SharpZipLib.Zip | |||
9 | { | |||
10 | /// <summary> | |||
11 | /// This is an InflaterInputStream that reads the files baseInputStream an zip archive | |||
12 | /// one after another. It has a special method to get the zip entry of | |||
13 | /// the next file. The zip entry contains information about the file name | |||
14 | /// size, compressed size, Crc, etc. | |||
15 | /// It includes support for Stored and Deflated entries. | |||
16 | /// <br/> | |||
17 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
18 | /// </summary> | |||
19 | /// | |||
20 | /// <example> This sample shows how to read a zip file | |||
21 | /// <code lang="C#"> | |||
22 | /// using System; | |||
23 | /// using System.Text; | |||
24 | /// using System.IO; | |||
25 | /// | |||
26 | /// using ICSharpCode.SharpZipLib.Zip; | |||
27 | /// | |||
28 | /// class MainClass | |||
29 | /// { | |||
30 | /// public static void Main(string[] args) | |||
31 | /// { | |||
32 | /// using ( ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]))) { | |||
33 | /// | |||
34 | /// ZipEntry theEntry; | |||
35 | /// const int size = 2048; | |||
36 | /// byte[] data = new byte[2048]; | |||
37 | /// | |||
38 | /// while ((theEntry = s.GetNextEntry()) != null) { | |||
39 | /// if ( entry.IsFile ) { | |||
40 | /// Console.Write("Show contents (y/n) ?"); | |||
41 | /// if (Console.ReadLine() == "y") { | |||
42 | /// while (true) { | |||
43 | /// size = s.Read(data, 0, data.Length); | |||
44 | /// if (size > 0) { | |||
45 | /// Console.Write(new ASCIIEncoding().GetString(data, 0, size)); | |||
46 | /// } else { | |||
47 | /// break; | |||
48 | /// } | |||
49 | /// } | |||
50 | /// } | |||
51 | /// } | |||
52 | /// } | |||
53 | /// } | |||
54 | /// } | |||
55 | /// } | |||
56 | /// </code> | |||
57 | /// </example> | |||
58 | public class ZipInputStream : InflaterInputStream | |||
59 | { | |||
60 | #region Instance Fields | |||
61 | | |||
62 | /// <summary> | |||
63 | /// Delegate for reading bytes from a stream. | |||
64 | /// </summary> | |||
65 | delegate int ReadDataHandler(byte[] b, int offset, int length); | |||
66 | | |||
67 | /// <summary> | |||
68 | /// The current reader this instance. | |||
69 | /// </summary> | |||
70 | ReadDataHandler internalReader; | |||
71 | | |||
| 57 | 72 | Crc32 crc = new Crc32(); | ||
73 | ZipEntry entry; | |||
74 | | |||
75 | long size; | |||
76 | int method; | |||
77 | int flags; | |||
78 | string password; | |||
79 | #endregion | |||
80 | | |||
81 | #region Constructors | |||
82 | /// <summary> | |||
83 | /// Creates a new Zip input stream, for reading a zip archive. | |||
84 | /// </summary> | |||
85 | /// <param name="baseInputStream">The underlying <see cref="Stream"/> providing data.</param> | |||
86 | public ZipInputStream(Stream baseInputStream) | |||
| 57 | 87 | : base(baseInputStream, new Inflater(true)) | ||
88 | { | |||
| 57 | 89 | internalReader = new ReadDataHandler(ReadingNotAvailable); | ||
| 57 | 90 | } | ||
91 | | |||
92 | /// <summary> | |||
93 | /// Creates a new Zip input stream, for reading a zip archive. | |||
94 | /// </summary> | |||
95 | /// <param name="baseInputStream">The underlying <see cref="Stream"/> providing data.</param> | |||
96 | /// <param name="bufferSize">Size of the buffer.</param> | |||
97 | public ZipInputStream(Stream baseInputStream, int bufferSize) | |||
| 0 | 98 | : base(baseInputStream, new Inflater(true), bufferSize) | ||
99 | { | |||
| 0 | 100 | internalReader = new ReadDataHandler(ReadingNotAvailable); | ||
| 0 | 101 | } | ||
102 | #endregion | |||
103 | | |||
104 | /// <summary> | |||
105 | /// Optional password used for encryption when non-null | |||
106 | /// </summary> | |||
107 | /// <value>A password for all encrypted <see cref="ZipEntry">entries </see> in this <see cref="ZipInputStream"/></va | |||
108 | public string Password { | |||
109 | get { | |||
| 0 | 110 | return password; | ||
111 | } | |||
112 | set { | |||
| 23 | 113 | password = value; | ||
| 23 | 114 | } | ||
115 | } | |||
116 | | |||
117 | | |||
118 | /// <summary> | |||
119 | /// Gets a value indicating if there is a current entry and it can be decompressed | |||
120 | /// </summary> | |||
121 | /// <remarks> | |||
122 | /// The entry can only be decompressed if the library supports the zip features required to extract it. | |||
123 | /// See the <see cref="ZipEntry.Version">ZipEntry Version</see> property for more details. | |||
124 | /// </remarks> | |||
125 | public bool CanDecompressEntry { | |||
126 | get { | |||
| 70 | 127 | return (entry != null) && entry.CanDecompress; | ||
128 | } | |||
129 | } | |||
130 | | |||
131 | /// <summary> | |||
132 | /// Advances to the next entry in the archive | |||
133 | /// </summary> | |||
134 | /// <returns> | |||
135 | /// The next <see cref="ZipEntry">entry</see> in the archive or null if there are no more entries. | |||
136 | /// </returns> | |||
137 | /// <remarks> | |||
138 | /// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called. | |||
139 | /// </remarks> | |||
140 | /// <exception cref="InvalidOperationException"> | |||
141 | /// Input stream is closed | |||
142 | /// </exception> | |||
143 | /// <exception cref="ZipException"> | |||
144 | /// Password is not set, password is invalid, compression method is invalid, | |||
145 | /// version required to extract is not supported | |||
146 | /// </exception> | |||
147 | public ZipEntry GetNextEntry() | |||
148 | { | |||
| 87 | 149 | if (crc == null) { | ||
| 0 | 150 | throw new InvalidOperationException("Closed."); | ||
151 | } | |||
152 | | |||
| 87 | 153 | if (entry != null) { | ||
| 14 | 154 | CloseEntry(); | ||
155 | } | |||
156 | | |||
| 87 | 157 | int header = inputBuffer.ReadLeInt(); | ||
158 | | |||
| 87 | 159 | if (header == ZipConstants.CentralHeaderSignature || | ||
| 87 | 160 | header == ZipConstants.EndOfCentralDirectorySignature || | ||
| 87 | 161 | header == ZipConstants.CentralHeaderDigitalSignature || | ||
| 87 | 162 | header == ZipConstants.ArchiveExtraDataSignature || | ||
| 87 | 163 | header == ZipConstants.Zip64CentralFileHeaderSignature) { | ||
164 | // No more individual entries exist | |||
| 6 | 165 | Close(); | ||
| 6 | 166 | return null; | ||
167 | } | |||
168 | | |||
169 | // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found | |||
170 | // Spanning signature is same as descriptor signature and is untested as yet. | |||
| 81 | 171 | if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature)) { | ||
| 0 | 172 | header = inputBuffer.ReadLeInt(); | ||
173 | } | |||
174 | | |||
| 81 | 175 | if (header != ZipConstants.LocalHeaderSignature) { | ||
| 0 | 176 | throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header)); | ||
177 | } | |||
178 | | |||
| 81 | 179 | var versionRequiredToExtract = (short)inputBuffer.ReadLeShort(); | ||
180 | | |||
| 81 | 181 | flags = inputBuffer.ReadLeShort(); | ||
| 81 | 182 | method = inputBuffer.ReadLeShort(); | ||
| 81 | 183 | var dostime = (uint)inputBuffer.ReadLeInt(); | ||
| 81 | 184 | int crc2 = inputBuffer.ReadLeInt(); | ||
| 81 | 185 | csize = inputBuffer.ReadLeInt(); | ||
| 81 | 186 | size = inputBuffer.ReadLeInt(); | ||
| 81 | 187 | int nameLen = inputBuffer.ReadLeShort(); | ||
| 81 | 188 | int extraLen = inputBuffer.ReadLeShort(); | ||
189 | | |||
| 81 | 190 | bool isCrypted = (flags & 1) == 1; | ||
191 | | |||
| 81 | 192 | byte[] buffer = new byte[nameLen]; | ||
| 81 | 193 | inputBuffer.ReadRawBuffer(buffer); | ||
194 | | |||
| 81 | 195 | string name = ZipConstants.ConvertToStringExt(flags, buffer); | ||
196 | | |||
| 81 | 197 | entry = new ZipEntry(name, versionRequiredToExtract); | ||
| 81 | 198 | entry.Flags = flags; | ||
199 | | |||
| 81 | 200 | entry.CompressionMethod = (CompressionMethod)method; | ||
201 | | |||
| 81 | 202 | if ((flags & 8) == 0) { | ||
| 44 | 203 | entry.Crc = crc2 & 0xFFFFFFFFL; | ||
| 44 | 204 | entry.Size = size & 0xFFFFFFFFL; | ||
| 44 | 205 | entry.CompressedSize = csize & 0xFFFFFFFFL; | ||
206 | | |||
| 44 | 207 | entry.CryptoCheckValue = (byte)((crc2 >> 24) & 0xff); | ||
208 | | |||
| 44 | 209 | } else { | ||
210 | | |||
211 | // This allows for GNU, WinZip and possibly other archives, the PKZIP spec | |||
212 | // says these values are zero under these circumstances. | |||
| 37 | 213 | if (crc2 != 0) { | ||
| 12 | 214 | entry.Crc = crc2 & 0xFFFFFFFFL; | ||
215 | } | |||
216 | | |||
| 37 | 217 | if (size != 0) { | ||
| 37 | 218 | entry.Size = size & 0xFFFFFFFFL; | ||
219 | } | |||
220 | | |||
| 37 | 221 | if (csize != 0) { | ||
| 37 | 222 | entry.CompressedSize = csize & 0xFFFFFFFFL; | ||
223 | } | |||
224 | | |||
| 37 | 225 | entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); | ||
226 | } | |||
227 | | |||
| 81 | 228 | entry.DosTime = dostime; | ||
229 | | |||
230 | // If local header requires Zip64 is true then the extended header should contain | |||
231 | // both values. | |||
232 | | |||
233 | // Handle extra data if present. This can set/alter some fields of the entry. | |||
| 81 | 234 | if (extraLen > 0) { | ||
| 78 | 235 | byte[] extra = new byte[extraLen]; | ||
| 78 | 236 | inputBuffer.ReadRawBuffer(extra); | ||
| 78 | 237 | entry.ExtraData = extra; | ||
238 | } | |||
239 | | |||
| 81 | 240 | entry.ProcessExtraData(true); | ||
| 81 | 241 | if (entry.CompressedSize >= 0) { | ||
| 57 | 242 | csize = entry.CompressedSize; | ||
243 | } | |||
244 | | |||
| 81 | 245 | if (entry.Size >= 0) { | ||
| 57 | 246 | size = entry.Size; | ||
247 | } | |||
248 | | |||
| 81 | 249 | if (method == (int)CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants. | ||
| 0 | 250 | throw new ZipException("Stored, but compressed != uncompressed"); | ||
251 | } | |||
252 | | |||
253 | // Determine how to handle reading of data if this is attempted. | |||
| 81 | 254 | if (entry.IsCompressionMethodSupported()) { | ||
| 81 | 255 | internalReader = new ReadDataHandler(InitialRead); | ||
| 81 | 256 | } else { | ||
| 0 | 257 | internalReader = new ReadDataHandler(ReadingNotSupported); | ||
258 | } | |||
259 | | |||
| 81 | 260 | return entry; | ||
261 | } | |||
262 | | |||
263 | /// <summary> | |||
264 | /// Read data descriptor at the end of compressed data. | |||
265 | /// </summary> | |||
266 | void ReadDataDescriptor() | |||
267 | { | |||
| 5 | 268 | if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature) { | ||
| 0 | 269 | throw new ZipException("Data descriptor signature not found"); | ||
270 | } | |||
271 | | |||
| 5 | 272 | entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL; | ||
273 | | |||
| 5 | 274 | if (entry.LocalHeaderRequiresZip64) { | ||
| 5 | 275 | csize = inputBuffer.ReadLeLong(); | ||
| 5 | 276 | size = inputBuffer.ReadLeLong(); | ||
| 5 | 277 | } else { | ||
| 0 | 278 | csize = inputBuffer.ReadLeInt(); | ||
| 0 | 279 | size = inputBuffer.ReadLeInt(); | ||
280 | } | |||
| 5 | 281 | entry.CompressedSize = csize; | ||
| 5 | 282 | entry.Size = size; | ||
| 5 | 283 | } | ||
284 | | |||
285 | /// <summary> | |||
286 | /// Complete cleanup as the final part of closing. | |||
287 | /// </summary> | |||
288 | /// <param name="testCrc">True if the crc value should be tested</param> | |||
289 | void CompleteCloseEntry(bool testCrc) | |||
290 | { | |||
| 32 | 291 | StopDecrypting(); | ||
292 | | |||
| 32 | 293 | if ((flags & 8) != 0) { | ||
| 5 | 294 | ReadDataDescriptor(); | ||
295 | } | |||
296 | | |||
| 32 | 297 | size = 0; | ||
298 | | |||
| 32 | 299 | if (testCrc && | ||
| 32 | 300 | ((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1)) { | ||
| 0 | 301 | throw new ZipException("CRC mismatch"); | ||
302 | } | |||
303 | | |||
| 32 | 304 | crc.Reset(); | ||
305 | | |||
| 32 | 306 | if (method == (int)CompressionMethod.Deflated) { | ||
| 25 | 307 | inf.Reset(); | ||
308 | } | |||
| 32 | 309 | entry = null; | ||
| 32 | 310 | } | ||
311 | | |||
312 | /// <summary> | |||
313 | /// Closes the current zip entry and moves to the next one. | |||
314 | /// </summary> | |||
315 | /// <exception cref="InvalidOperationException"> | |||
316 | /// The stream is closed | |||
317 | /// </exception> | |||
318 | /// <exception cref="ZipException"> | |||
319 | /// The Zip stream ends early | |||
320 | /// </exception> | |||
321 | public void CloseEntry() | |||
322 | { | |||
| 14 | 323 | if (crc == null) { | ||
| 0 | 324 | throw new InvalidOperationException("Closed"); | ||
325 | } | |||
326 | | |||
| 14 | 327 | if (entry == null) { | ||
| 0 | 328 | return; | ||
329 | } | |||
330 | | |||
| 14 | 331 | if (method == (int)CompressionMethod.Deflated) { | ||
| 9 | 332 | if ((flags & 8) != 0) { | ||
333 | // We don't know how much we must skip, read until end. | |||
| 0 | 334 | byte[] tmp = new byte[4096]; | ||
335 | | |||
336 | // Read will close this entry | |||
| 0 | 337 | while (Read(tmp, 0, tmp.Length) > 0) { | ||
338 | } | |||
| 0 | 339 | return; | ||
340 | } | |||
341 | | |||
| 9 | 342 | csize -= inf.TotalIn; | ||
| 9 | 343 | inputBuffer.Available += inf.RemainingInput; | ||
344 | } | |||
345 | | |||
| 14 | 346 | if ((inputBuffer.Available > csize) && (csize >= 0)) { | ||
| 14 | 347 | inputBuffer.Available = (int)((long)inputBuffer.Available - csize); | ||
| 14 | 348 | } else { | ||
| 0 | 349 | csize -= inputBuffer.Available; | ||
| 0 | 350 | inputBuffer.Available = 0; | ||
| 0 | 351 | while (csize != 0) { | ||
| 0 | 352 | long skipped = Skip(csize); | ||
353 | | |||
| 0 | 354 | if (skipped <= 0) { | ||
| 0 | 355 | throw new ZipException("Zip archive ends early."); | ||
356 | } | |||
357 | | |||
| 0 | 358 | csize -= skipped; | ||
359 | } | |||
360 | } | |||
361 | | |||
| 14 | 362 | CompleteCloseEntry(false); | ||
| 14 | 363 | } | ||
364 | | |||
365 | /// <summary> | |||
366 | /// Returns 1 if there is an entry available | |||
367 | /// Otherwise returns 0. | |||
368 | /// </summary> | |||
369 | public override int Available { | |||
370 | get { | |||
| 0 | 371 | return entry != null ? 1 : 0; | ||
372 | } | |||
373 | } | |||
374 | | |||
375 | /// <summary> | |||
376 | /// Returns the current size that can be read from the current entry if available | |||
377 | /// </summary> | |||
378 | /// <exception cref="ZipException">Thrown if the entry size is not known.</exception> | |||
379 | /// <exception cref="InvalidOperationException">Thrown if no entry is currently available.</exception> | |||
380 | public override long Length { | |||
381 | get { | |||
| 0 | 382 | if (entry != null) { | ||
| 0 | 383 | if (entry.Size >= 0) { | ||
| 0 | 384 | return entry.Size; | ||
385 | } else { | |||
| 0 | 386 | throw new ZipException("Length not available for the current entry"); | ||
387 | } | |||
388 | } else { | |||
| 0 | 389 | throw new InvalidOperationException("No current entry"); | ||
390 | } | |||
391 | } | |||
392 | | |||
393 | } | |||
394 | | |||
395 | /// <summary> | |||
396 | /// Reads a byte from the current zip entry. | |||
397 | /// </summary> | |||
398 | /// <returns> | |||
399 | /// The byte or -1 if end of stream is reached. | |||
400 | /// </returns> | |||
401 | public override int ReadByte() | |||
402 | { | |||
| 12 | 403 | byte[] b = new byte[1]; | ||
| 12 | 404 | if (Read(b, 0, 1) <= 0) { | ||
| 0 | 405 | return -1; | ||
406 | } | |||
| 12 | 407 | return b[0] & 0xff; | ||
408 | } | |||
409 | | |||
410 | /// <summary> | |||
411 | /// Handle attempts to read by throwing an <see cref="InvalidOperationException"/>. | |||
412 | /// </summary> | |||
413 | /// <param name="destination">The destination array to store data in.</param> | |||
414 | /// <param name="offset">The offset at which data read should be stored.</param> | |||
415 | /// <param name="count">The maximum number of bytes to read.</param> | |||
416 | /// <returns>Returns the number of bytes actually read.</returns> | |||
417 | int ReadingNotAvailable(byte[] destination, int offset, int count) | |||
418 | { | |||
| 0 | 419 | throw new InvalidOperationException("Unable to read from this stream"); | ||
420 | } | |||
421 | | |||
422 | /// <summary> | |||
423 | /// Handle attempts to read from this entry by throwing an exception | |||
424 | /// </summary> | |||
425 | int ReadingNotSupported(byte[] destination, int offset, int count) | |||
426 | { | |||
| 0 | 427 | throw new ZipException("The compression method for this entry is not supported"); | ||
428 | } | |||
429 | | |||
430 | /// <summary> | |||
431 | /// Perform the initial read on an entry which may include | |||
432 | /// reading encryption headers and setting up inflation. | |||
433 | /// </summary> | |||
434 | /// <param name="destination">The destination to fill with data read.</param> | |||
435 | /// <param name="offset">The offset to start reading at.</param> | |||
436 | /// <param name="count">The maximum number of bytes to read.</param> | |||
437 | /// <returns>The actual number of bytes read.</returns> | |||
438 | int InitialRead(byte[] destination, int offset, int count) | |||
439 | { | |||
| 70 | 440 | if (!CanDecompressEntry) { | ||
| 0 | 441 | throw new ZipException("Library cannot extract this entry. Version required is (" + entry.Version + ")"); | ||
442 | } | |||
443 | | |||
444 | // Handle encryption if required. | |||
| 70 | 445 | if (entry.IsCrypted) { | ||
| 24 | 446 | if (password == null) { | ||
| 0 | 447 | throw new ZipException("No password set."); | ||
448 | } | |||
449 | | |||
450 | // Generate and set crypto transform... | |||
| 24 | 451 | var managed = new PkzipClassicManaged(); | ||
| 24 | 452 | byte[] key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(password)); | ||
453 | | |||
| 24 | 454 | inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null); | ||
455 | | |||
| 24 | 456 | byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; | ||
| 24 | 457 | inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize); | ||
458 | | |||
| 24 | 459 | if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { | ||
| 0 | 460 | throw new ZipException("Invalid password"); | ||
461 | } | |||
462 | | |||
| 24 | 463 | if (csize >= ZipConstants.CryptoHeaderSize) { | ||
| 13 | 464 | csize -= ZipConstants.CryptoHeaderSize; | ||
| 24 | 465 | } else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0) { | ||
| 0 | 466 | throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize)); | ||
467 | } | |||
468 | } else { | |||
| 46 | 469 | inputBuffer.CryptoTransform = null; | ||
470 | } | |||
471 | | |||
| 70 | 472 | if ((csize > 0) || ((flags & (int)GeneralBitFlags.Descriptor) != 0)) { | ||
| 69 | 473 | if ((method == (int)CompressionMethod.Deflated) && (inputBuffer.Available > 0)) { | ||
| 66 | 474 | inputBuffer.SetInflaterInput(inf); | ||
475 | } | |||
476 | | |||
| 69 | 477 | internalReader = new ReadDataHandler(BodyRead); | ||
| 69 | 478 | return BodyRead(destination, offset, count); | ||
479 | } else { | |||
| 1 | 480 | internalReader = new ReadDataHandler(ReadingNotAvailable); | ||
| 1 | 481 | return 0; | ||
482 | } | |||
483 | } | |||
484 | | |||
485 | /// <summary> | |||
486 | /// Read a block of bytes from the stream. | |||
487 | /// </summary> | |||
488 | /// <param name="buffer">The destination for the bytes.</param> | |||
489 | /// <param name="offset">The index to start storing data.</param> | |||
490 | /// <param name="count">The number of bytes to attempt to read.</param> | |||
491 | /// <returns>Returns the number of bytes read.</returns> | |||
492 | /// <remarks>Zero bytes read means end of stream.</remarks> | |||
493 | public override int Read(byte[] buffer, int offset, int count) | |||
494 | { | |||
| 148 | 495 | if (buffer == null) { | ||
| 1 | 496 | throw new ArgumentNullException(nameof(buffer)); | ||
497 | } | |||
498 | | |||
| 147 | 499 | if (offset < 0) { | ||
| 1 | 500 | throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); | ||
501 | } | |||
502 | | |||
| 146 | 503 | if (count < 0) { | ||
| 1 | 504 | throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); | ||
505 | } | |||
506 | | |||
| 145 | 507 | if ((buffer.Length - offset) < count) { | ||
| 3 | 508 | throw new ArgumentException("Invalid offset/count combination"); | ||
509 | } | |||
510 | | |||
| 142 | 511 | return internalReader(buffer, offset, count); | ||
512 | } | |||
513 | | |||
514 | /// <summary> | |||
515 | /// Reads a block of bytes from the current zip entry. | |||
516 | /// </summary> | |||
517 | /// <returns> | |||
518 | /// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on en | |||
519 | /// </returns> | |||
520 | /// <exception name="IOException"> | |||
521 | /// An i/o error occured. | |||
522 | /// </exception> | |||
523 | /// <exception cref="ZipException"> | |||
524 | /// The deflated stream is corrupted. | |||
525 | /// </exception> | |||
526 | /// <exception cref="InvalidOperationException"> | |||
527 | /// The stream is not open. | |||
528 | /// </exception> | |||
529 | int BodyRead(byte[] buffer, int offset, int count) | |||
530 | { | |||
| 141 | 531 | if (crc == null) { | ||
| 0 | 532 | throw new InvalidOperationException("Closed"); | ||
533 | } | |||
534 | | |||
| 141 | 535 | if ((entry == null) || (count <= 0)) { | ||
| 46 | 536 | return 0; | ||
537 | } | |||
538 | | |||
| 95 | 539 | if (offset + count > buffer.Length) { | ||
| 0 | 540 | throw new ArgumentException("Offset + count exceeds buffer size"); | ||
541 | } | |||
542 | | |||
| 95 | 543 | bool finished = false; | ||
544 | | |||
| 95 | 545 | switch (method) { | ||
546 | case (int)CompressionMethod.Deflated: | |||
| 92 | 547 | count = base.Read(buffer, offset, count); | ||
| 92 | 548 | if (count <= 0) { | ||
| 16 | 549 | if (!inf.IsFinished) { | ||
| 0 | 550 | throw new ZipException("Inflater not finished!"); | ||
551 | } | |||
| 16 | 552 | inputBuffer.Available = inf.RemainingInput; | ||
553 | | |||
554 | // A csize of -1 is from an unpatched local header | |||
| 16 | 555 | if ((flags & 8) == 0 && | ||
| 16 | 556 | (inf.TotalIn != csize && csize != 0xFFFFFFFF && csize != -1 || inf.TotalOut != size)) { | ||
| 0 | 557 | throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut | ||
558 | } | |||
| 16 | 559 | inf.Reset(); | ||
| 16 | 560 | finished = true; | ||
561 | } | |||
| 16 | 562 | break; | ||
563 | | |||
564 | case (int)CompressionMethod.Stored: | |||
| 3 | 565 | if ((count > csize) && (csize >= 0)) { | ||
| 0 | 566 | count = (int)csize; | ||
567 | } | |||
568 | | |||
| 3 | 569 | if (count > 0) { | ||
| 3 | 570 | count = inputBuffer.ReadClearTextBuffer(buffer, offset, count); | ||
| 3 | 571 | if (count > 0) { | ||
| 3 | 572 | csize -= count; | ||
| 3 | 573 | size -= count; | ||
574 | } | |||
575 | } | |||
576 | | |||
| 3 | 577 | if (csize == 0) { | ||
| 2 | 578 | finished = true; | ||
| 2 | 579 | } else { | ||
| 1 | 580 | if (count < 0) { | ||
| 0 | 581 | throw new ZipException("EOF in stored block"); | ||
582 | } | |||
583 | } | |||
584 | break; | |||
585 | } | |||
586 | | |||
| 95 | 587 | if (count > 0) { | ||
| 79 | 588 | crc.Update(buffer, offset, count); | ||
589 | } | |||
590 | | |||
| 95 | 591 | if (finished) { | ||
| 18 | 592 | CompleteCloseEntry(true); | ||
593 | } | |||
594 | | |||
| 95 | 595 | return count; | ||
596 | } | |||
597 | | |||
598 | /// <summary> | |||
599 | /// Closes the zip input stream | |||
600 | /// </summary> | |||
601 | public override void Close() | |||
602 | { | |||
| 61 | 603 | internalReader = new ReadDataHandler(ReadingNotAvailable); | ||
| 61 | 604 | crc = null; | ||
| 61 | 605 | entry = null; | ||
606 | | |||
| 61 | 607 | base.Close(); | ||
| 61 | 608 | } | ||
609 | } | |||
610 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipNameTransform |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipNameTransform.cs |
| Covered lines: | 66 |
| Uncovered lines: | 11 |
| Coverable lines: | 77 |
| Total lines: | 220 |
| Line coverage: | 85.7% |
| Branch coverage: | 67.6% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor() | 1 | 100 | 100 |
| .ctor(...) | 1 | 100 | 100 |
| .cctor() | 1 | 100 | 100 |
| TransformDirectory(...) | 3 | 85.71 | 60 |
| TransformFile(...) | 9 | 77.78 | 70.59 |
| MakeValidName(...) | 5 | 84.62 | 88.89 |
| IsValidName(...) | 4 | 85.71 | 60 |
| IsValidName(...) | 3 | 100 | 100 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Text; | |||
4 | using ICSharpCode.SharpZipLib.Core; | |||
5 | | |||
6 | namespace ICSharpCode.SharpZipLib.Zip | |||
7 | { | |||
8 | /// <summary> | |||
9 | /// ZipNameTransform transforms names as per the Zip file naming convention. | |||
10 | /// </summary> | |||
11 | /// <remarks>The use of absolute names is supported although its use is not valid | |||
12 | /// according to Zip naming conventions, and should not be used if maximum compatability is desired.</remarks> | |||
13 | public class ZipNameTransform : INameTransform | |||
14 | { | |||
15 | #region Constructors | |||
16 | /// <summary> | |||
17 | /// Initialize a new instance of <see cref="ZipNameTransform"></see> | |||
18 | /// </summary> | |||
| 101 | 19 | public ZipNameTransform() | ||
20 | { | |||
| 101 | 21 | } | ||
22 | | |||
23 | /// <summary> | |||
24 | /// Initialize a new instance of <see cref="ZipNameTransform"></see> | |||
25 | /// </summary> | |||
26 | /// <param name="trimPrefix">The string to trim from the front of paths if found.</param> | |||
| 5 | 27 | public ZipNameTransform(string trimPrefix) | ||
28 | { | |||
| 5 | 29 | TrimPrefix = trimPrefix; | ||
| 5 | 30 | } | ||
31 | #endregion | |||
32 | | |||
33 | /// <summary> | |||
34 | /// Static constructor. | |||
35 | /// </summary> | |||
36 | static ZipNameTransform() | |||
37 | { | |||
38 | char[] invalidPathChars; | |||
| 1 | 39 | invalidPathChars = Path.GetInvalidPathChars(); | ||
| 1 | 40 | int howMany = invalidPathChars.Length + 2; | ||
41 | | |||
| 1 | 42 | InvalidEntryCharsRelaxed = new char[howMany]; | ||
| 1 | 43 | Array.Copy(invalidPathChars, 0, InvalidEntryCharsRelaxed, 0, invalidPathChars.Length); | ||
| 1 | 44 | InvalidEntryCharsRelaxed[howMany - 1] = '*'; | ||
| 1 | 45 | InvalidEntryCharsRelaxed[howMany - 2] = '?'; | ||
46 | | |||
| 1 | 47 | howMany = invalidPathChars.Length + 4; | ||
| 1 | 48 | InvalidEntryChars = new char[howMany]; | ||
| 1 | 49 | Array.Copy(invalidPathChars, 0, InvalidEntryChars, 0, invalidPathChars.Length); | ||
| 1 | 50 | InvalidEntryChars[howMany - 1] = ':'; | ||
| 1 | 51 | InvalidEntryChars[howMany - 2] = '\\'; | ||
| 1 | 52 | InvalidEntryChars[howMany - 3] = '*'; | ||
| 1 | 53 | InvalidEntryChars[howMany - 4] = '?'; | ||
| 1 | 54 | } | ||
55 | | |||
56 | /// <summary> | |||
57 | /// Transform a windows directory name according to the Zip file naming conventions. | |||
58 | /// </summary> | |||
59 | /// <param name="name">The directory name to transform.</param> | |||
60 | /// <returns>The transformed name.</returns> | |||
61 | public string TransformDirectory(string name) | |||
62 | { | |||
| 5 | 63 | name = TransformFile(name); | ||
| 4 | 64 | if (name.Length > 0) { | ||
| 4 | 65 | if (!name.EndsWith("/", StringComparison.Ordinal)) { | ||
| 4 | 66 | name += "/"; | ||
67 | } | |||
| 4 | 68 | } else { | ||
| 0 | 69 | throw new ZipException("Cannot have an empty directory name"); | ||
70 | } | |||
| 4 | 71 | return name; | ||
72 | } | |||
73 | | |||
74 | /// <summary> | |||
75 | /// Transform a windows file name according to the Zip file naming conventions. | |||
76 | /// </summary> | |||
77 | /// <param name="name">The file name to transform.</param> | |||
78 | /// <returns>The transformed name.</returns> | |||
79 | public string TransformFile(string name) | |||
80 | { | |||
| 65927 | 81 | if (name != null) { | ||
| 65927 | 82 | string lowerName = name.ToLower(); | ||
| 65927 | 83 | if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0)) { | ||
| 7 | 84 | name = name.Substring(trimPrefix_.Length); | ||
85 | } | |||
86 | | |||
| 65927 | 87 | name = name.Replace(@"\", "/"); | ||
| 65927 | 88 | name = WindowsPathUtils.DropPathRoot(name); | ||
89 | | |||
90 | // Drop any leading slashes. | |||
| 65932 | 91 | while ((name.Length > 0) && (name[0] == '/')) { | ||
| 5 | 92 | name = name.Remove(0, 1); | ||
93 | } | |||
94 | | |||
95 | // Drop any trailing slashes. | |||
| 65927 | 96 | while ((name.Length > 0) && (name[name.Length - 1] == '/')) { | ||
| 0 | 97 | name = name.Remove(name.Length - 1, 1); | ||
98 | } | |||
99 | | |||
100 | // Convert consecutive // characters to / | |||
| 65927 | 101 | int index = name.IndexOf("//", StringComparison.Ordinal); | ||
| 65927 | 102 | while (index >= 0) { | ||
| 0 | 103 | name = name.Remove(index, 1); | ||
| 0 | 104 | index = name.IndexOf("//", StringComparison.Ordinal); | ||
105 | } | |||
106 | | |||
| 65927 | 107 | name = MakeValidName(name, '_'); | ||
| 65926 | 108 | } else { | ||
| 0 | 109 | name = string.Empty; | ||
110 | } | |||
| 65926 | 111 | return name; | ||
112 | } | |||
113 | | |||
114 | /// <summary> | |||
115 | /// Get/set the path prefix to be trimmed from paths if present. | |||
116 | /// </summary> | |||
117 | /// <remarks>The prefix is trimmed before any conversion from | |||
118 | /// a windows path is done.</remarks> | |||
119 | public string TrimPrefix { | |||
| 0 | 120 | get { return trimPrefix_; } | ||
121 | set { | |||
| 5 | 122 | trimPrefix_ = value; | ||
| 5 | 123 | if (trimPrefix_ != null) { | ||
| 5 | 124 | trimPrefix_ = trimPrefix_.ToLower(); | ||
125 | } | |||
| 5 | 126 | } | ||
127 | } | |||
128 | | |||
129 | /// <summary> | |||
130 | /// Force a name to be valid by replacing invalid characters with a fixed value | |||
131 | /// </summary> | |||
132 | /// <param name="name">The name to force valid</param> | |||
133 | /// <param name="replacement">The replacement character to use.</param> | |||
134 | /// <returns>Returns a valid name</returns> | |||
135 | static string MakeValidName(string name, char replacement) | |||
136 | { | |||
| 65927 | 137 | int index = name.IndexOfAny(InvalidEntryChars); | ||
| 65927 | 138 | if (index >= 0) { | ||
| 2 | 139 | var builder = new StringBuilder(name); | ||
140 | | |||
| 5 | 141 | while (index >= 0) { | ||
| 3 | 142 | builder[index] = replacement; | ||
143 | | |||
| 3 | 144 | if (index >= name.Length) { | ||
| 0 | 145 | index = -1; | ||
| 0 | 146 | } else { | ||
| 3 | 147 | index = name.IndexOfAny(InvalidEntryChars, index + 1); | ||
148 | } | |||
149 | } | |||
| 2 | 150 | name = builder.ToString(); | ||
151 | } | |||
152 | | |||
| 65927 | 153 | if (name.Length > 0xffff) { | ||
| 1 | 154 | throw new PathTooLongException(); | ||
155 | } | |||
156 | | |||
| 65926 | 157 | return name; | ||
158 | } | |||
159 | | |||
160 | /// <summary> | |||
161 | /// Test a name to see if it is a valid name for a zip entry. | |||
162 | /// </summary> | |||
163 | /// <param name="name">The name to test.</param> | |||
164 | /// <param name="relaxed">If true checking is relaxed about windows file names and absolute paths.</param> | |||
165 | /// <returns>Returns true if the name is a valid zip name; false otherwise.</returns> | |||
166 | /// <remarks>Zip path names are actually in Unix format, and should only contain relative paths. | |||
167 | /// This means that any path stored should not contain a drive or | |||
168 | /// device letter, or a leading slash. All slashes should forward slashes '/'. | |||
169 | /// An empty name is valid for a file where the input comes from standard input. | |||
170 | /// A null name is not considered valid. | |||
171 | /// </remarks> | |||
172 | public static bool IsValidName(string name, bool relaxed) | |||
173 | { | |||
| 65916 | 174 | bool result = (name != null); | ||
175 | | |||
| 65916 | 176 | if (result) { | ||
| 65916 | 177 | if (relaxed) { | ||
| 65916 | 178 | result = name.IndexOfAny(InvalidEntryCharsRelaxed) < 0; | ||
| 65916 | 179 | } else { | ||
| 0 | 180 | result = | ||
| 0 | 181 | (name.IndexOfAny(InvalidEntryChars) < 0) && | ||
| 0 | 182 | (name.IndexOf('/') != 0); | ||
183 | } | |||
184 | } | |||
185 | | |||
| 65916 | 186 | return result; | ||
187 | } | |||
188 | | |||
189 | /// <summary> | |||
190 | /// Test a name to see if it is a valid name for a zip entry. | |||
191 | /// </summary> | |||
192 | /// <param name="name">The name to test.</param> | |||
193 | /// <returns>Returns true if the name is a valid zip name; false otherwise.</returns> | |||
194 | /// <remarks>Zip path names are actually in unix format, | |||
195 | /// and should only contain relative paths if a path is present. | |||
196 | /// This means that the path stored should not contain a drive or | |||
197 | /// device letter, or a leading slash. All slashes should forward slashes '/'. | |||
198 | /// An empty name is valid where the input comes from standard input. | |||
199 | /// A null name is not considered valid. | |||
200 | /// </remarks> | |||
201 | public static bool IsValidName(string name) | |||
202 | { | |||
| 2 | 203 | bool result = | ||
| 2 | 204 | (name != null) && | ||
| 2 | 205 | (name.IndexOfAny(InvalidEntryChars) < 0) && | ||
| 2 | 206 | (name.IndexOf('/') != 0) | ||
| 2 | 207 | ; | ||
| 1 | 208 | return result; | ||
209 | } | |||
210 | | |||
211 | #region Instance Fields | |||
212 | string trimPrefix_; | |||
213 | #endregion | |||
214 | | |||
215 | #region Class Fields | |||
216 | static readonly char[] InvalidEntryChars; | |||
217 | static readonly char[] InvalidEntryCharsRelaxed; | |||
218 | #endregion | |||
219 | } | |||
220 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipOutputStream |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipOutputStream.cs |
| Covered lines: | 285 |
| Uncovered lines: | 48 |
| Coverable lines: | 333 |
| Total lines: | 816 |
| Line coverage: | 85.5% |
| Branch coverage: | 78.4% |
| Method | Cyclomatic Complexity | Sequence Coverage | Branch Coverage |
|---|---|---|---|
| .ctor(...) | 1 | 100 | 100 |
| .ctor(...) | 1 | 0 | 0 |
| SetComment(...) | 2 | 100 | 100 |
| SetLevel(...) | 1 | 100 | 100 |
| GetLevel() | 1 | 100 | 100 |
| WriteLeShort(...) | 1 | 100 | 100 |
| WriteLeInt(...) | 1 | 100 | 100 |
| WriteLeLong(...) | 1 | 100 | 100 |
| PutNextEntry(...) | 40 | 88.70 | 86.30 |
| CloseEntry() | 18 | 85.25 | 77.14 |
| WriteEncryptionHeader(...) | 1 | 100 | 100 |
| AddExtraDataAES(...) | 1 | 0 | 0 |
| WriteAESHeader(...) | 1 | 0 | 0 |
| Write(...) | 9 | 75 | 64.71 |
| CopyAndEncrypt(...) | 4 | 100 | 80 |
| Finish() | 26 | 90.41 | 73.33 |
| # | Line | Line coverage | ||
|---|---|---|---|---|
1 | using System; | |||
2 | using System.IO; | |||
3 | using System.Collections; | |||
4 | using ICSharpCode.SharpZipLib.Checksum; | |||
5 | using ICSharpCode.SharpZipLib.Zip.Compression; | |||
6 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; | |||
7 | | |||
8 | namespace ICSharpCode.SharpZipLib.Zip | |||
9 | { | |||
10 | /// <summary> | |||
11 | /// This is a DeflaterOutputStream that writes the files into a zip | |||
12 | /// archive one after another. It has a special method to start a new | |||
13 | /// zip entry. The zip entries contains information about the file name | |||
14 | /// size, compressed size, CRC, etc. | |||
15 | /// | |||
16 | /// It includes support for Stored and Deflated entries. | |||
17 | /// This class is not thread safe. | |||
18 | /// <br/> | |||
19 | /// <br/>Author of the original java version : Jochen Hoenicke | |||
20 | /// </summary> | |||
21 | /// <example> This sample shows how to create a zip file | |||
22 | /// <code> | |||
23 | /// using System; | |||
24 | /// using System.IO; | |||
25 | /// | |||
26 | /// using ICSharpCode.SharpZipLib.Core; | |||
27 | /// using ICSharpCode.SharpZipLib.Zip; | |||
28 | /// | |||
29 | /// class MainClass | |||
30 | /// { | |||
31 | /// public static void Main(string[] args) | |||
32 | /// { | |||
33 | /// string[] filenames = Directory.GetFiles(args[0]); | |||
34 | /// byte[] buffer = new byte[4096]; | |||
35 | /// | |||
36 | /// using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) { | |||
37 | /// | |||
38 | /// s.SetLevel(9); // 0 - store only to 9 - means best compression | |||
39 | /// | |||
40 | /// foreach (string file in filenames) { | |||
41 | /// ZipEntry entry = new ZipEntry(file); | |||
42 | /// s.PutNextEntry(entry); | |||
43 | /// | |||
44 | /// using (FileStream fs = File.OpenRead(file)) { | |||
45 | /// StreamUtils.Copy(fs, s, buffer); | |||
46 | /// } | |||
47 | /// } | |||
48 | /// } | |||
49 | /// } | |||
50 | /// } | |||
51 | /// </code> | |||
52 | /// </example> | |||
53 | public class ZipOutputStream : DeflaterOutputStream | |||
54 | { | |||
55 | #region Constructors | |||
56 | /// <summary> | |||
57 | /// Creates a new Zip output stream, writing a zip archive. | |||
58 | /// </summary> | |||
59 | /// <param name="baseOutputStream"> | |||
60 | /// The output stream to which the archive contents are written. | |||
61 | /// </param> | |||
62 | public ZipOutputStream(Stream baseOutputStream) | |||
| 87 | 63 | : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)) | ||
64 | { | |||
| 87 | 65 | } | ||
66 | | |||
67 | /// <summary> | |||
68 | /// Creates a new Zip output stream, writing a zip archive. | |||
69 | /// </summary> | |||
70 | /// <param name="baseOutputStream">The output stream to which the archive contents are written.</param> | |||
71 | /// <param name="bufferSize">Size of the buffer to use.</param> | |||
72 | public ZipOutputStream(Stream baseOutputStream, int bufferSize) | |||
| 0 | 73 | : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize) | ||
74 | { | |||
| 0 | 75 | } | ||
76 | #endregion | |||
77 | | |||
78 | /// <summary> | |||
79 | /// Gets a flag value of true if the central header has been added for this archive; false if it has not been added. | |||
80 | /// </summary> | |||
81 | /// <remarks>No further entries can be added once this has been done.</remarks> | |||
82 | public bool IsFinished { | |||
83 | get { | |||
| 1 | 84 | return entries == null; | ||
85 | } | |||
86 | } | |||
87 | | |||
88 | /// <summary> | |||
89 | /// Set the zip file comment. | |||
90 | /// </summary> | |||
91 | /// <param name="comment"> | |||
92 | /// The comment text for the entire archive. | |||
93 | /// </param> | |||
94 | /// <exception name ="ArgumentOutOfRangeException"> | |||
95 | /// The converted comment is longer than 0xffff bytes. | |||
96 | /// </exception> | |||
97 | public void SetComment(string comment) | |||
98 | { | |||
99 | // TODO: Its not yet clear how to handle unicode comments here. | |||
| 8 | 100 | byte[] commentBytes = ZipConstants.ConvertToArray(comment); | ||
| 8 | 101 | if (commentBytes.Length > 0xffff) { | ||
| 1 | 102 | throw new ArgumentOutOfRangeException(nameof(comment)); | ||
103 | } | |||
| 7 | 104 | zipComment = commentBytes; | ||
| 7 | 105 | } | ||
106 | | |||
107 | /// <summary> | |||
108 | /// Sets the compression level. The new level will be activated | |||
109 | /// immediately. | |||
110 | /// </summary> | |||
111 | /// <param name="level">The new compression level (1 to 9).</param> | |||
112 | /// <exception cref="ArgumentOutOfRangeException"> | |||
113 | /// Level specified is not supported. | |||
114 | /// </exception> | |||
115 | /// <see cref="ICSharpCode.SharpZipLib.Zip.Compression.Deflater"/> | |||
116 | public void SetLevel(int level) | |||
117 | { | |||
| 55 | 118 | deflater_.SetLevel(level); | ||
| 55 | 119 | defaultCompressionLevel = level; | ||
| 55 | 120 | } | ||
121 | | |||
122 | /// <summary> | |||
123 | /// Get the current deflater compression level | |||
124 | /// </summary> | |||
125 | /// <returns>The current compression level</returns> | |||
126 | public int GetLevel() | |||
127 | { | |||
| 3 | 128 | return deflater_.GetLevel(); | ||
129 | } | |||
130 | | |||
131 | /// <summary> | |||
132 | /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. | |||
133 | /// </summary> | |||
134 | /// <remarks>Older archivers may not understand Zip64 extensions. | |||
135 | /// If backwards compatability is an issue be careful when adding <see cref="ZipEntry.Size">entries</see> to an arch | |||
136 | /// Setting this property to off is workable but less desirable as in those circumstances adding a file | |||
137 | /// larger then 4GB will fail.</remarks> | |||
138 | public UseZip64 UseZip64 { | |||
| 0 | 139 | get { return useZip64_; } | ||
| 14 | 140 | set { useZip64_ = value; } | ||
141 | } | |||
142 | | |||
143 | /// <summary> | |||
144 | /// Write an unsigned short in little endian byte order. | |||
145 | /// </summary> | |||
146 | private void WriteLeShort(int value) | |||
147 | { | |||
148 | unchecked { | |||
| 6369 | 149 | baseOutputStream_.WriteByte((byte)(value & 0xff)); | ||
| 6369 | 150 | baseOutputStream_.WriteByte((byte)((value >> 8) & 0xff)); | ||
151 | } | |||
| 6369 | 152 | } | ||
153 | | |||
154 | /// <summary> | |||
155 | /// Write an int in little endian byte order. | |||
156 | /// </summary> | |||
157 | private void WriteLeInt(int value) | |||
158 | { | |||
159 | unchecked { | |||
| 2286 | 160 | WriteLeShort(value); | ||
| 2286 | 161 | WriteLeShort(value >> 16); | ||
162 | } | |||
| 2286 | 163 | } | ||
164 | | |||
165 | /// <summary> | |||
166 | /// Write an int in little endian byte order. | |||
167 | /// </summary> | |||
168 | private void WriteLeLong(long value) | |||
169 | { | |||
170 | unchecked { | |||
| 268 | 171 | WriteLeInt((int)value); | ||
| 268 | 172 | WriteLeInt((int)(value >> 32)); | ||
173 | } | |||
| 268 | 174 | } | ||
175 | | |||
176 | /// <summary> | |||
177 | /// Starts a new Zip entry. It automatically closes the previous | |||
178 | /// entry if present. | |||
179 | /// All entry elements bar name are optional, but must be correct if present. | |||
180 | /// If the compression method is stored and the output is not patchable | |||
181 | /// the compression for that entry is automatically changed to deflate level 0 | |||
182 | /// </summary> | |||
183 | /// <param name="entry"> | |||
184 | /// the entry. | |||
185 | /// </param> | |||
186 | /// <exception cref="System.ArgumentNullException"> | |||
187 | /// if entry passed is null. | |||
188 | /// </exception> | |||
189 | /// <exception cref="System.IO.IOException"> | |||
190 | /// if an I/O error occured. | |||
191 | /// </exception> | |||
192 | /// <exception cref="System.InvalidOperationException"> | |||
193 | /// if stream was finished | |||
194 | /// </exception> | |||
195 | /// <exception cref="ZipException"> | |||
196 | /// Too many entries in the Zip file<br/> | |||
197 | /// Entry name is too long<br/> | |||
198 | /// Finish has already been called<br/> | |||
199 | /// </exception> | |||
200 | public void PutNextEntry(ZipEntry entry) | |||
201 | { | |||
| 130 | 202 | if (entry == null) { | ||
| 0 | 203 | throw new ArgumentNullException(nameof(entry)); | ||
204 | } | |||
205 | | |||
| 130 | 206 | if (entries == null) { | ||
| 1 | 207 | throw new InvalidOperationException("ZipOutputStream was finished"); | ||
208 | } | |||
209 | | |||
| 129 | 210 | if (curEntry != null) { | ||
| 48 | 211 | CloseEntry(); | ||
212 | } | |||
213 | | |||
| 129 | 214 | if (entries.Count == int.MaxValue) { | ||
| 0 | 215 | throw new ZipException("Too many entries for Zip file"); | ||
216 | } | |||
217 | | |||
| 129 | 218 | CompressionMethod method = entry.CompressionMethod; | ||
| 129 | 219 | int compressionLevel = defaultCompressionLevel; | ||
220 | | |||
221 | // Clear flags that the library manages internally | |||
| 129 | 222 | entry.Flags &= (int)GeneralBitFlags.UnicodeText; | ||
| 129 | 223 | patchEntryHeader = false; | ||
224 | | |||
225 | bool headerInfoAvailable; | |||
226 | | |||
227 | // No need to compress - definitely no data. | |||
| 129 | 228 | if (entry.Size == 0) { | ||
| 1 | 229 | entry.CompressedSize = entry.Size; | ||
| 1 | 230 | entry.Crc = 0; | ||
| 1 | 231 | method = CompressionMethod.Stored; | ||
| 1 | 232 | headerInfoAvailable = true; | ||
| 1 | 233 | } else { | ||
| 128 | 234 | headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc && entry.CompressedSize >= 0; | ||
235 | | |||
236 | // Switch to deflation if storing isnt possible. | |||
| 128 | 237 | if (method == CompressionMethod.Stored) { | ||
| 13 | 238 | if (!headerInfoAvailable) { | ||
| 13 | 239 | if (!CanPatchEntries) { | ||
240 | // Can't patch entries so storing is not possible. | |||
| 5 | 241 | method = CompressionMethod.Deflated; | ||
| 5 | 242 | compressionLevel = 0; | ||
243 | } | |||
| 5 | 244 | } else // entry.size must be > 0 | ||
245 | { | |||
| 0 | 246 | entry.CompressedSize = entry.Size; | ||
| 0 | 247 | headerInfoAvailable = entry.HasCrc; | ||
248 | } | |||
249 | } | |||
250 | } | |||
251 | | |||
| 129 | 252 | if (headerInfoAvailable == false) { | ||
| 128 | 253 | if (CanPatchEntries == false) { | ||
254 | // Only way to record size and compressed size is to append a data descriptor | |||
255 | // after compressed data. | |||
256 | | |||
257 | // Stored entries of this form have already been converted to deflating. | |||
| 32 | 258 | entry.Flags |= 8; | ||
| 32 | 259 | } else { | ||
| 96 | 260 | patchEntryHeader = true; | ||
261 | } | |||
262 | } | |||
263 | | |||
| 129 | 264 | if (Password != null) { | ||
| 33 | 265 | entry.IsCrypted = true; | ||
| 33 | 266 | if (entry.Crc < 0) { | ||
267 | // Need to append a data descriptor as the crc isnt available for use | |||
268 | // with encryption, the date is used instead. Setting the flag | |||
269 | // indicates this to the decompressor. | |||
| 29 | 270 | entry.Flags |= 8; | ||
271 | } | |||
272 | } | |||
273 | | |||
| 129 | 274 | entry.Offset = offset; | ||
| 129 | 275 | entry.CompressionMethod = (CompressionMethod)method; | ||
276 | | |||
| 129 | 277 | curMethod = method; | ||
| 129 | 278 | sizePatchPos = -1; | ||
279 | | |||
| 129 | 280 | if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic))) { | ||
| 121 | 281 | entry.ForceZip64(); | ||
282 | } | |||
283 | | |||
284 | // Write the local file header | |||
| 129 | 285 | WriteLeInt(ZipConstants.LocalHeaderSignature); | ||
286 | | |||
| 129 | 287 | WriteLeShort(entry.Version); | ||
| 129 | 288 | WriteLeShort(entry.Flags); | ||
| 129 | 289 | WriteLeShort((byte)entry.CompressionMethodForHeader); | ||
| 129 | 290 | WriteLeInt((int)entry.DosTime); | ||
291 | | |||
292 | // TODO: Refactor header writing. Its done in several places. | |||
| 129 | 293 | if (headerInfoAvailable) { | ||
| 1 | 294 | WriteLeInt((int)entry.Crc); | ||
| 1 | 295 | if (entry.LocalHeaderRequiresZip64) { | ||
| 1 | 296 | WriteLeInt(-1); | ||
| 1 | 297 | WriteLeInt(-1); | ||
| 1 | 298 | } else { | ||
| 0 | 299 | WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.Compressed | ||
| 0 | 300 | WriteLeInt((int)entry.Size); | ||
301 | } | |||
| 0 | 302 | } else { | ||
| 128 | 303 | if (patchEntryHeader) { | ||
| 96 | 304 | crcPatchPos = baseOutputStream_.Position; | ||
305 | } | |||
| 128 | 306 | WriteLeInt(0); // Crc | ||
307 | | |||
| 128 | 308 | if (patchEntryHeader) { | ||
| 96 | 309 | sizePatchPos = baseOutputStream_.Position; | ||
310 | } | |||
311 | | |||
312 | // For local header both sizes appear in Zip64 Extended Information | |||
| 128 | 313 | if (entry.LocalHeaderRequiresZip64 || patchEntryHeader) { | ||
| 125 | 314 | WriteLeInt(-1); | ||
| 125 | 315 | WriteLeInt(-1); | ||
| 125 | 316 | } else { | ||
| 3 | 317 | WriteLeInt(0); // Compressed size | ||
| 3 | 318 | WriteLeInt(0); // Uncompressed size | ||
319 | } | |||
320 | } | |||
321 | | |||
| 129 | 322 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | ||
323 | | |||
| 129 | 324 | if (name.Length > 0xFFFF) { | ||
| 0 | 325 | throw new ZipException("Entry name too long."); | ||
326 | } | |||
327 | | |||
| 129 | 328 | var ed = new ZipExtraData(entry.ExtraData); | ||
329 | | |||
| 129 | 330 | if (entry.LocalHeaderRequiresZip64) { | ||
| 122 | 331 | ed.StartNewEntry(); | ||
| 122 | 332 | if (headerInfoAvailable) { | ||
| 1 | 333 | ed.AddLeLong(entry.Size); | ||
| 1 | 334 | ed.AddLeLong(entry.CompressedSize); | ||
| 1 | 335 | } else { | ||
| 121 | 336 | ed.AddLeLong(-1); | ||
| 121 | 337 | ed.AddLeLong(-1); | ||
338 | } | |||
| 122 | 339 | ed.AddNewEntry(1); | ||
340 | | |||
| 122 | 341 | if (!ed.Find(1)) { | ||
| 0 | 342 | throw new ZipException("Internal error cant find extra data"); | ||
343 | } | |||
344 | | |||
| 122 | 345 | if (patchEntryHeader) { | ||
| 92 | 346 | sizePatchPos = ed.CurrentReadIndex; | ||
347 | } | |||
| 92 | 348 | } else { | ||
| 7 | 349 | ed.Delete(1); | ||
350 | } | |||
351 | | |||
| 129 | 352 | if (entry.AESKeySize > 0) { | ||
| 0 | 353 | AddExtraDataAES(entry, ed); | ||
354 | } | |||
| 129 | 355 | byte[] extra = ed.GetEntryData(); | ||
356 | | |||
| 129 | 357 | WriteLeShort(name.Length); | ||
| 129 | 358 | WriteLeShort(extra.Length); | ||
359 | | |||
| 129 | 360 | if (name.Length > 0) { | ||
| 129 | 361 | baseOutputStream_.Write(name, 0, name.Length); | ||
362 | } | |||
363 | | |||
| 128 | 364 | if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { | ||
| 91 | 365 | sizePatchPos += baseOutputStream_.Position; | ||
366 | } | |||
367 | | |||
| 128 | 368 | if (extra.Length > 0) { | ||
| 121 | 369 | baseOutputStream_.Write(extra, 0, extra.Length); | ||
370 | } | |||
371 | | |||
| 128 | 372 | offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length; | ||
373 | // Fix offsetOfCentraldir for AES | |||
| 128 | 374 | if (entry.AESKeySize > 0) | ||
| 0 | 375 | offset += entry.AESOverheadSize; | ||
376 | | |||
377 | // Activate the entry. | |||
| 128 | 378 | curEntry = entry; | ||
| 128 | 379 | crc.Reset(); | ||
| 128 | 380 | if (method == CompressionMethod.Deflated) { | ||
| 119 | 381 | deflater_.Reset(); | ||
| 119 | 382 | deflater_.SetLevel(compressionLevel); | ||
383 | } | |||
| 128 | 384 | size = 0; | ||
385 | | |||
| 128 | 386 | if (entry.IsCrypted) { | ||
| 33 | 387 | if (entry.AESKeySize > 0) { | ||
| 0 | 388 | WriteAESHeader(entry); | ||
| 0 | 389 | } else { | ||
| 33 | 390 | if (entry.Crc < 0) { // so testing Zip will says its ok | ||
| 29 | 391 | WriteEncryptionHeader(entry.DosTime << 16); | ||
| 29 | 392 | } else { | ||
| 4 | 393 | WriteEncryptionHeader(entry.Crc); | ||
394 | } | |||
395 | } | |||
396 | } | |||
| 99 | 397 | } | ||
398 | | |||
399 | /// <summary> | |||
400 | /// Closes the current entry, updating header and footer information as required | |||
401 | /// </summary> | |||
402 | /// <exception cref="System.IO.IOException"> | |||
403 | /// An I/O error occurs. | |||
404 | /// </exception> | |||
405 | /// <exception cref="System.InvalidOperationException"> | |||
406 | /// No entry is active. | |||
407 | /// </exception> | |||
408 | public void CloseEntry() | |||
409 | { | |||
| 128 | 410 | if (curEntry == null) { | ||
| 0 | 411 | throw new InvalidOperationException("No open entry"); | ||
412 | } | |||
413 | | |||
| 128 | 414 | long csize = size; | ||
415 | | |||
416 | // First finish the deflater, if appropriate | |||
| 128 | 417 | if (curMethod == CompressionMethod.Deflated) { | ||
| 119 | 418 | if (size >= 0) { | ||
| 119 | 419 | base.Finish(); | ||
| 119 | 420 | csize = deflater_.TotalOut; | ||
| 119 | 421 | } else { | ||
| 0 | 422 | deflater_.Reset(); | ||
423 | } | |||
424 | } | |||
425 | | |||
426 | // Write the AES Authentication Code (a hash of the compressed and encrypted data) | |||
| 128 | 427 | if (curEntry.AESKeySize > 0) { | ||
| 0 | 428 | baseOutputStream_.Write(AESAuthCode, 0, 10); | ||
429 | } | |||
430 | | |||
| 128 | 431 | if (curEntry.Size < 0) { | ||
| 121 | 432 | curEntry.Size = size; | ||
| 128 | 433 | } else if (curEntry.Size != size) { | ||
| 0 | 434 | throw new ZipException("size was " + size + ", but I expected " + curEntry.Size); | ||
435 | } | |||
436 | | |||
| 128 | 437 | if (curEntry.CompressedSize < 0) { | ||
| 127 | 438 | curEntry.CompressedSize = csize; | ||
| 128 | 439 | } else if (curEntry.CompressedSize != csize) { | ||
| 0 | 440 | throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize); | ||
441 | } | |||
442 | | |||
| 128 | 443 | if (curEntry.Crc < 0) { | ||
| 114 | 444 | curEntry.Crc = crc.Value; | ||
| 128 | 445 | } else if (curEntry.Crc != crc.Value) { | ||
| 0 | 446 | throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc); | ||
447 | } | |||
448 | | |||
| 128 | 449 | offset += csize; | ||
450 | | |||
| 128 | 451 | if (curEntry.IsCrypted) { | ||
| 33 | 452 | if (curEntry.AESKeySize > 0) { | ||
| 0 | 453 | curEntry.CompressedSize += curEntry.AESOverheadSize; | ||
454 | | |||
| 0 | 455 | } else { | ||
| 33 | 456 | curEntry.CompressedSize += ZipConstants.CryptoHeaderSize; | ||
457 | } | |||
458 | } | |||
459 | | |||
460 | // Patch the header if possible | |||
| 128 | 461 | if (patchEntryHeader) { | ||
| 95 | 462 | patchEntryHeader = false; | ||
463 | | |||
| 95 | 464 | long curPos = baseOutputStream_.Position; | ||
| 95 | 465 | baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin); | ||
| 95 | 466 | WriteLeInt((int)curEntry.Crc); | ||
467 | | |||
| 95 | 468 | if (curEntry.LocalHeaderRequiresZip64) { | ||
469 | | |||
| 91 | 470 | if (sizePatchPos == -1) { | ||
| 0 | 471 | throw new ZipException("Entry requires zip64 but this has been turned off"); | ||
472 | } | |||
473 | | |||
| 91 | 474 | baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin); | ||
| 91 | 475 | WriteLeLong(curEntry.Size); | ||
| 91 | 476 | WriteLeLong(curEntry.CompressedSize); | ||
| 91 | 477 | } else { | ||
| 4 | 478 | WriteLeInt((int)curEntry.CompressedSize); | ||
| 4 | 479 | WriteLeInt((int)curEntry.Size); | ||
480 | } | |||
| 95 | 481 | baseOutputStream_.Seek(curPos, SeekOrigin.Begin); | ||
482 | } | |||
483 | | |||
484 | // Add data descriptor if flagged as required | |||
| 128 | 485 | if ((curEntry.Flags & 8) != 0) { | ||
| 48 | 486 | WriteLeInt(ZipConstants.DataDescriptorSignature); | ||
| 48 | 487 | WriteLeInt(unchecked((int)curEntry.Crc)); | ||
488 | | |||
| 48 | 489 | if (curEntry.LocalHeaderRequiresZip64) { | ||
| 43 | 490 | WriteLeLong(curEntry.CompressedSize); | ||
| 43 | 491 | WriteLeLong(curEntry.Size); | ||
| 43 | 492 | offset += ZipConstants.Zip64DataDescriptorSize; | ||
| 43 | 493 | } else { | ||
| 5 | 494 | WriteLeInt((int)curEntry.CompressedSize); | ||
| 5 | 495 | WriteLeInt((int)curEntry.Size); | ||
| 5 | 496 | offset += ZipConstants.DataDescriptorSize; | ||
497 | } | |||
498 | } | |||
499 | | |||
| 128 | 500 | entries.Add(curEntry); | ||
| 128 | 501 | curEntry = null; | ||
| 128 | 502 | } | ||
503 | | |||
504 | void WriteEncryptionHeader(long crcValue) | |||
505 | { | |||
| 33 | 506 | offset += ZipConstants.CryptoHeaderSize; | ||
507 | | |||
| 33 | 508 | InitializePassword(Password); | ||
509 | | |||
| 33 | 510 | byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; | ||
| 33 | 511 | var rnd = new Random(); | ||
| 33 | 512 | rnd.NextBytes(cryptBuffer); | ||
| 33 | 513 | cryptBuffer[11] = (byte)(crcValue >> 24); | ||
514 | | |||
| 33 | 515 | EncryptBlock(cryptBuffer, 0, cryptBuffer.Length); | ||
| 33 | 516 | baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length); | ||
| 33 | 517 | } | ||
518 | | |||
519 | private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData) | |||
520 | { | |||
521 | | |||
522 | // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored. | |||
523 | const int VENDOR_VERSION = 2; | |||
524 | // Vendor ID is the two ASCII characters "AE". | |||
525 | const int VENDOR_ID = 0x4541; //not 6965; | |||
| 0 | 526 | extraData.StartNewEntry(); | ||
527 | // Pack AES extra data field see http://www.winzip.com/aes_info.htm | |||
528 | //extraData.AddLeShort(7); // Data size (currently 7) | |||
| 0 | 529 | extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2 | ||
| 0 | 530 | extraData.AddLeShort(VENDOR_ID); // "AE" | ||
| 0 | 531 | extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256 | ||
| 0 | 532 | extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file | ||
| 0 | 533 | extraData.AddNewEntry(0x9901); | ||
| 0 | 534 | } | ||
535 | | |||
536 | // Replaces WriteEncryptionHeader for AES | |||
537 | // | |||
538 | private void WriteAESHeader(ZipEntry entry) | |||
539 | { | |||
540 | byte[] salt; | |||
541 | byte[] pwdVerifier; | |||
| 0 | 542 | InitializeAESPassword(entry, Password, out salt, out pwdVerifier); | ||
543 | // File format for AES: | |||
544 | // Size (bytes) Content | |||
545 | // ------------ ------- | |||
546 | // Variable Salt value | |||
547 | // 2 Password verification value | |||
548 | // Variable Encrypted file data | |||
549 | // 10 Authentication code | |||
550 | // | |||
551 | // Value in the "compressed size" fields of the local file header and the central directory entry | |||
552 | // is the total size of all the items listed above. In other words, it is the total size of the | |||
553 | // salt value, password verification value, encrypted data, and authentication code. | |||
| 0 | 554 | baseOutputStream_.Write(salt, 0, salt.Length); | ||
| 0 | 555 | baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length); | ||
| 0 | 556 | } | ||
557 | | |||
558 | /// <summary> | |||
559 | /// Writes the given buffer to the current entry. | |||
560 | /// </summary> | |||
561 | /// <param name="buffer">The buffer containing data to write.</param> | |||
562 | /// <param name="offset">The offset of the first byte to write.</param> | |||
563 | /// <param name="count">The number of bytes to write.</param> | |||
564 | /// <exception cref="ZipException">Archive size is invalid</exception> | |||
565 | /// <exception cref="System.InvalidOperationException">No entry is active.</exception> | |||
566 | public override void Write(byte[] buffer, int offset, int count) | |||
567 | { | |||
| 4503 | 568 | if (curEntry == null) { | ||
| 0 | 569 | throw new InvalidOperationException("No open entry."); | ||
570 | } | |||
571 | | |||
| 4503 | 572 | if (buffer == null) { | ||
| 0 | 573 | throw new ArgumentNullException(nameof(buffer)); | ||
574 | } | |||
575 | | |||
| 4503 | 576 | if (offset < 0) { | ||
| 0 | 577 | throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); | ||
578 | } | |||
579 | | |||
| 4503 | 580 | if (count < 0) { | ||
| 0 | 581 | throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); | ||
582 | } | |||
583 | | |||
| 4503 | 584 | if ((buffer.Length - offset) < count) { | ||
| 0 | 585 | throw new ArgumentException("Invalid offset/count combination"); | ||
586 | } | |||
587 | | |||
| 4503 | 588 | crc.Update(buffer, offset, count); | ||
| 4502 | 589 | size += count; | ||
590 | | |||
| 4502 | 591 | switch (curMethod) { | ||
592 | case CompressionMethod.Deflated: | |||
| 4303 | 593 | base.Write(buffer, offset, count); | ||
| 4303 | 594 | break; | ||
595 | | |||
596 | case CompressionMethod.Stored: | |||
| 199 | 597 | if (Password != null) { | ||
| 99 | 598 | CopyAndEncrypt(buffer, offset, count); | ||
| 99 | 599 | } else { | ||
| 100 | 600 | baseOutputStream_.Write(buffer, offset, count); | ||
601 | } | |||
602 | break; | |||
603 | } | |||
| 100 | 604 | } | ||
605 | | |||
606 | void CopyAndEncrypt(byte[] buffer, int offset, int count) | |||
607 | { | |||
608 | const int CopyBufferSize = 4096; | |||
| 99 | 609 | byte[] localBuffer = new byte[CopyBufferSize]; | ||
| 198 | 610 | while (count > 0) { | ||
| 99 | 611 | int bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize; | ||
612 | | |||
| 99 | 613 | Array.Copy(buffer, offset, localBuffer, 0, bufferCount); | ||
| 99 | 614 | EncryptBlock(localBuffer, 0, bufferCount); | ||
| 99 | 615 | baseOutputStream_.Write(localBuffer, 0, bufferCount); | ||
| 99 | 616 | count -= bufferCount; | ||
| 99 | 617 | offset += bufferCount; | ||
618 | } | |||
| 99 | 619 | } | ||
620 | | |||
621 | /// <summary> | |||
622 | /// Finishes the stream. This will write the central directory at the | |||
623 | /// end of the zip file and flush the stream. | |||
624 | /// </summary> | |||
625 | /// <remarks> | |||
626 | /// This is automatically called when the stream is closed. | |||
627 | /// </remarks> | |||
628 | /// <exception cref="System.IO.IOException"> | |||
629 | /// An I/O error occurs. | |||
630 | /// </exception> | |||
631 | /// <exception cref="ZipException"> | |||
632 | /// Comment exceeds the maximum length<br/> | |||
633 | /// Entry name exceeds the maximum length | |||
634 | /// </exception> | |||
635 | public override void Finish() | |||
636 | { | |||
| 88 | 637 | if (entries == null) { | ||
| 2 | 638 | return; | ||
639 | } | |||
640 | | |||
| 86 | 641 | if (curEntry != null) { | ||
| 77 | 642 | CloseEntry(); | ||
643 | } | |||
644 | | |||
| 86 | 645 | long numEntries = entries.Count; | ||
| 86 | 646 | long sizeEntries = 0; | ||
647 | | |||
| 428 | 648 | foreach (ZipEntry entry in entries) { | ||
| 128 | 649 | WriteLeInt(ZipConstants.CentralHeaderSignature); | ||
| 128 | 650 | WriteLeShort(ZipConstants.VersionMadeBy); | ||
| 128 | 651 | WriteLeShort(entry.Version); | ||
| 128 | 652 | WriteLeShort(entry.Flags); | ||
| 128 | 653 | WriteLeShort((short)entry.CompressionMethodForHeader); | ||
| 128 | 654 | WriteLeInt((int)entry.DosTime); | ||
| 128 | 655 | WriteLeInt((int)entry.Crc); | ||
656 | | |||
| 128 | 657 | if (entry.IsZip64Forced() || | ||
| 128 | 658 | (entry.CompressedSize >= uint.MaxValue)) { | ||
| 121 | 659 | WriteLeInt(-1); | ||
| 121 | 660 | } else { | ||
| 7 | 661 | WriteLeInt((int)entry.CompressedSize); | ||
662 | } | |||
663 | | |||
| 128 | 664 | if (entry.IsZip64Forced() || | ||
| 128 | 665 | (entry.Size >= uint.MaxValue)) { | ||
| 121 | 666 | WriteLeInt(-1); | ||
| 121 | 667 | } else { | ||
| 7 | 668 | WriteLeInt((int)entry.Size); | ||
669 | } | |||
670 | | |||
| 128 | 671 | byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); | ||
672 | | |||
| 128 | 673 | if (name.Length > 0xffff) { | ||
| 0 | 674 | throw new ZipException("Name too long."); | ||
675 | } | |||
676 | | |||
| 128 | 677 | var ed = new ZipExtraData(entry.ExtraData); | ||
678 | | |||
| 128 | 679 | if (entry.CentralHeaderRequiresZip64) { | ||
| 121 | 680 | ed.StartNewEntry(); | ||
| 121 | 681 | if (entry.IsZip64Forced() || | ||
| 121 | 682 | (entry.Size >= 0xffffffff)) { | ||
| 121 | 683 | ed.AddLeLong(entry.Size); | ||
684 | } | |||
685 | | |||
| 121 | 686 | if (entry.IsZip64Forced() || | ||
| 121 | 687 | (entry.CompressedSize >= 0xffffffff)) { | ||
| 121 | 688 | ed.AddLeLong(entry.CompressedSize); | ||
689 | } | |||
690 | | |||
| 121 | 691 | if (entry.Offset >= 0xffffffff) { | ||
| 0 | 692 | ed.AddLeLong(entry.Offset); | ||
693 | } | |||
694 | | |||
| 121 | 695 | ed.AddNewEntry(1); | ||
| 121 | 696 | } else { | ||
| 7 | 697 | ed.Delete(1); | ||
698 | } | |||
699 | | |||
| 128 | 700 | if (entry.AESKeySize > 0) { | ||
| 0 | 701 | AddExtraDataAES(entry, ed); | ||
702 | } | |||
| 128 | 703 | byte[] extra = ed.GetEntryData(); | ||
704 | | |||
| 128 | 705 | byte[] entryComment = | ||
| 128 | 706 | (entry.Comment != null) ? | ||
| 128 | 707 | ZipConstants.ConvertToArray(entry.Flags, entry.Comment) : | ||
| 128 | 708 | new byte[0]; | ||
709 | | |||
| 128 | 710 | if (entryComment.Length > 0xffff) { | ||
| 0 | 711 | throw new ZipException("Comment too long."); | ||
712 | } | |||
713 | | |||
| 128 | 714 | WriteLeShort(name.Length); | ||
| 128 | 715 | WriteLeShort(extra.Length); | ||
| 128 | 716 | WriteLeShort(entryComment.Length); | ||
| 128 | 717 | WriteLeShort(0); // disk number | ||
| 128 | 718 | WriteLeShort(0); // internal file attributes | ||
719 | // external file attributes | |||
720 | | |||
| 128 | 721 | if (entry.ExternalFileAttributes != -1) { | ||
| 4 | 722 | WriteLeInt(entry.ExternalFileAttributes); | ||
| 4 | 723 | } else { | ||
| 124 | 724 | if (entry.IsDirectory) { // mark entry as directory (from nikolam.AT.perfectinfo.com) | ||
| 8 | 725 | WriteLeInt(16); | ||
| 8 | 726 | } else { | ||
| 116 | 727 | WriteLeInt(0); | ||
728 | } | |||
729 | } | |||
730 | | |||
| 128 | 731 | if (entry.Offset >= uint.MaxValue) { | ||
| 0 | 732 | WriteLeInt(-1); | ||
| 0 | 733 | } else { | ||
| 128 | 734 | WriteLeInt((int)entry.Offset); | ||
735 | } | |||
736 | | |||
| 128 | 737 | if (name.Length > 0) { | ||
| 128 | 738 | baseOutputStream_.Write(name, 0, name.Length); | ||
739 | } | |||
740 | | |||
| 128 | 741 | if (extra.Length > 0) { | ||
| 121 | 742 | baseOutputStream_.Write(extra, 0, extra.Length); | ||
743 | } | |||
744 | | |||
| 128 | 745 | if (entryComment.Length > 0) { | ||
| 0 | 746 | baseOutputStream_.Write(entryComment, 0, entryComment.Length); | ||
747 | } | |||
748 | | |||
| 128 | 749 | sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length; | ||
750 | } | |||
751 | | |||
| 86 | 752 | using (ZipHelperStream zhs = new ZipHelperStream(baseOutputStream_)) { | ||
| 86 | 753 | zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment); | ||
| 85 | 754 | } | ||
755 | | |||
| 85 | 756 | entries = null; | ||
| 85 | 757 | } | ||
758 | | |||
759 | #region Instance Fields | |||
760 | /// <summary> | |||
761 | /// The entries for the archive. | |||
762 | /// </summary> | |||
| 87 | 763 | ArrayList entries = new ArrayList(); | ||
764 | | |||
765 | /// <summary> | |||
766 | /// Used to track the crc of data added to entries. | |||
767 | /// </summary> | |||
| 87 | 768 | Crc32 crc = new Crc32(); | ||
769 | | |||
770 | /// <summary> | |||
771 | /// The current entry being added. | |||
772 | /// </summary> | |||
773 | ZipEntry curEntry; | |||
774 | | |||
| 87 | 775 | int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION; | ||
776 | | |||
| 87 | 777 | CompressionMethod curMethod = CompressionMethod.Deflated; | ||
778 | | |||
779 | /// <summary> | |||
780 | /// Used to track the size of data for an entry during writing. | |||
781 | /// </summary> | |||
782 | long size; | |||
783 | | |||
784 | /// <summary> | |||
785 | /// Offset to be recorded for each entry in the central header. | |||
786 | /// </summary> | |||
787 | long offset; | |||
788 | | |||
789 | /// <summary> | |||
790 | /// Comment for the entire archive recorded in central header. | |||
791 | /// </summary> | |||
| 87 | 792 | byte[] zipComment = new byte[0]; | ||
793 | | |||
794 | /// <summary> | |||
795 | /// Flag indicating that header patching is required for the current entry. | |||
796 | /// </summary> | |||
797 | bool patchEntryHeader; | |||
798 | | |||
799 | /// <summary> | |||
800 | /// Position to patch crc | |||
801 | /// </summary> | |||
| 87 | 802 | long crcPatchPos = -1; | ||
803 | | |||
804 | /// <summary> | |||
805 | /// Position to patch size. | |||
806 | /// </summary> | |||
| 87 | 807 | long sizePatchPos = -1; | ||
808 | | |||
809 | // Default is dynamic which is not backwards compatible and can cause problems | |||
810 | // with XP's built in compression which cant read Zip64 archives. | |||
811 | // However it does avoid the situation were a large file is added and cannot be completed correctly. | |||
812 | // NOTE: Setting the size for entries before they are added is the best solution! | |||
| 87 | 813 | UseZip64 useZip64_ = UseZip64.Dynamic; | ||
814 | #endregion | |||
815 | } | |||
816 | } |
| Class: | ICSharpCode.SharpZipLib.Zip.ZipTestResultHandler |
|---|---|
| Assembly: | ICSharpCode.SharpZipLib |
| File(s): | |
| Covered lines: | 0 |
| Uncovered lines: | 0 |
| Coverable lines: | 0 |
| Total lines: | 0 |
| Line coverage: |
No files found. This usually happens if a file isn't covered by a test or the class does not contain any sequence points (e.g. a class that only contains auto properties).
+| t |