root / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Zip / ZipFile.cs @ 0eea575a
History | View | Annotate | Download (143.4 kB)
1 |
// ZipFile.cs |
---|---|
2 |
// |
3 |
// Copyright (C) 2001 Mike Krueger |
4 |
// Copyright (C) 2004 John Reilly |
5 |
// |
6 |
// This file was translated from java, it was part of the GNU Classpath |
7 |
// Copyright (C) 2001 Free Software Foundation, Inc. |
8 |
// |
9 |
// This program is free software; you can redistribute it and/or |
10 |
// modify it under the terms of the GNU General Public License |
11 |
// as published by the Free Software Foundation; either version 2 |
12 |
// of the License, or (at your option) any later version. |
13 |
// |
14 |
// This program is distributed in the hope that it will be useful, |
15 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 |
// GNU General Public License for more details. |
18 |
// |
19 |
// You should have received a copy of the GNU General Public License |
20 |
// along with this program; if not, write to the Free Software |
21 |
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
22 |
// |
23 |
// Linking this library statically or dynamically with other modules is |
24 |
// making a combined work based on this library. Thus, the terms and |
25 |
// conditions of the GNU General Public License cover the whole |
26 |
// combination. |
27 |
// |
28 |
// As a special exception, the copyright holders of this library give you |
29 |
// permission to link this library with independent modules to produce an |
30 |
// executable, regardless of the license terms of these independent |
31 |
// modules, and to copy and distribute the resulting executable under |
32 |
// terms of your choice, provided that you also meet, for each linked |
33 |
// independent module, the terms and conditions of the license of that |
34 |
// module. An independent module is a module which is not derived from |
35 |
// or based on this library. If you modify this library, you may extend |
36 |
// this exception to your version of the library, but you are not |
37 |
// obligated to do so. If you do not wish to do so, delete this |
38 |
// exception statement from your version. |
39 |
|
40 |
using System; |
41 |
using System.Collections; |
42 |
using System.Collections.Generic; |
43 |
using System.IO; |
44 |
using System.Text; |
45 |
using System.Globalization; |
46 |
|
47 |
using System.Security.Cryptography; |
48 |
using ICSharpCode.SharpZipLib.Silverlight.Checksums; |
49 |
using ICSharpCode.SharpZipLib.Silverlight.Core; |
50 |
using ICSharpCode.SharpZipLib.Silverlight.Compat; |
51 |
using ICSharpCode.SharpZipLib.Silverlight.Encryption; |
52 |
using ICSharpCode.SharpZipLib.Silverlight.Zip; |
53 |
using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression; |
54 |
using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression.Streams; |
55 |
using ICSharpCode.SharpZipLib.Zip; |
56 |
|
57 |
namespace ICSharpCode.SharpZipLib.Silverlight.Zip |
58 |
{ |
59 |
/// <summary> |
60 |
/// Arguments used with KeysRequiredEvent |
61 |
/// </summary> |
62 |
public class KeysRequiredEventArgs : EventArgs |
63 |
{ |
64 |
#region Constructors |
65 |
/// <summary> |
66 |
/// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> |
67 |
/// </summary> |
68 |
/// <param name="name">The name of the file for which keys are required.</param> |
69 |
public KeysRequiredEventArgs(string name) |
70 |
{ |
71 |
fileName = name; |
72 |
} |
73 |
|
74 |
/// <summary> |
75 |
/// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see> |
76 |
/// </summary> |
77 |
/// <param name="name">The name of the file for which keys are required.</param> |
78 |
/// <param name="keyValue">The current key value.</param> |
79 |
public KeysRequiredEventArgs(string name, byte[] keyValue) |
80 |
{ |
81 |
fileName = name; |
82 |
key = keyValue; |
83 |
} |
84 |
|
85 |
#endregion |
86 |
#region Properties |
87 |
/// <summary> |
88 |
/// Get the name of the file for which keys are required. |
89 |
/// </summary> |
90 |
public string FileName |
91 |
{ |
92 |
get { return fileName; } |
93 |
} |
94 |
|
95 |
/// <summary> |
96 |
/// Get/set the key value |
97 |
/// </summary> |
98 |
public byte[] Key |
99 |
{ |
100 |
get { return key; } |
101 |
set { key = value; } |
102 |
} |
103 |
#endregion |
104 |
#region Instance Fields |
105 |
string fileName; |
106 |
byte[] key; |
107 |
#endregion |
108 |
} |
109 |
|
110 |
/// <summary> |
111 |
/// The strategy to apply to testing. |
112 |
/// </summary> |
113 |
public enum TestStrategy |
114 |
{ |
115 |
/// <summary> |
116 |
/// Find the first error only. |
117 |
/// </summary> |
118 |
FindFirstError, |
119 |
/// <summary> |
120 |
/// Find all possible errors. |
121 |
/// </summary> |
122 |
FindAllErrors, |
123 |
} |
124 |
|
125 |
/// <summary> |
126 |
/// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing. |
127 |
/// </summary> |
128 |
/// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> |
129 |
public enum TestOperation |
130 |
{ |
131 |
/// <summary> |
132 |
/// Setting up testing. |
133 |
/// </summary> |
134 |
Initialising, |
135 |
|
136 |
/// <summary> |
137 |
/// Testing an individual entries header |
138 |
/// </summary> |
139 |
EntryHeader, |
140 |
|
141 |
/// <summary> |
142 |
/// Testing an individual entries data |
143 |
/// </summary> |
144 |
EntryData, |
145 |
|
146 |
/// <summary> |
147 |
/// Testing an individual entry has completed. |
148 |
/// </summary> |
149 |
EntryComplete, |
150 |
|
151 |
/// <summary> |
152 |
/// Running miscellaneous tests |
153 |
/// </summary> |
154 |
MiscellaneousTests, |
155 |
|
156 |
/// <summary> |
157 |
/// Testing is complete |
158 |
/// </summary> |
159 |
Complete, |
160 |
} |
161 |
|
162 |
/// <summary> |
163 |
/// Status returned returned by <see cref="ZipTestResultHandler"/> during testing. |
164 |
/// </summary> |
165 |
/// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso> |
166 |
public class TestStatus |
167 |
{ |
168 |
#region Constructors |
169 |
/// <summary> |
170 |
/// Initialise a new instance of <see cref="TestStatus"/> |
171 |
/// </summary> |
172 |
/// <param name="file">The <see cref="ZipFile"/> this status applies to.</param> |
173 |
public TestStatus(ZipFile file) |
174 |
{ |
175 |
file_ = file; |
176 |
} |
177 |
#endregion |
178 |
|
179 |
#region Properties |
180 |
|
181 |
/// <summary> |
182 |
/// Get the current <see cref="TestOperation"/> in progress. |
183 |
/// </summary> |
184 |
public TestOperation Operation |
185 |
{ |
186 |
get { return operation_; } |
187 |
} |
188 |
|
189 |
/// <summary> |
190 |
/// Get the <see cref="ZipFile"/> this status is applicable to. |
191 |
/// </summary> |
192 |
public ZipFile File |
193 |
{ |
194 |
get { return file_; } |
195 |
} |
196 |
|
197 |
/// <summary> |
198 |
/// Get the current/last entry tested. |
199 |
/// </summary> |
200 |
public ZipEntry Entry |
201 |
{ |
202 |
get { return entry_; } |
203 |
} |
204 |
|
205 |
/// <summary> |
206 |
/// Get the number of errors detected so far. |
207 |
/// </summary> |
208 |
public int ErrorCount |
209 |
{ |
210 |
get { return errorCount_; } |
211 |
} |
212 |
|
213 |
/// <summary> |
214 |
/// Get the number of bytes tested so far for the current entry. |
215 |
/// </summary> |
216 |
public long BytesTested |
217 |
{ |
218 |
get { return bytesTested_; } |
219 |
} |
220 |
|
221 |
/// <summary> |
222 |
/// Get a value indicating wether the last entry test was valid. |
223 |
/// </summary> |
224 |
public bool EntryValid |
225 |
{ |
226 |
get { return entryValid_; } |
227 |
} |
228 |
#endregion |
229 |
|
230 |
#region Internal API |
231 |
internal void AddError() |
232 |
{ |
233 |
errorCount_++; |
234 |
entryValid_ = false; |
235 |
} |
236 |
|
237 |
internal void SetOperation(TestOperation operation) |
238 |
{ |
239 |
operation_ = operation; |
240 |
} |
241 |
|
242 |
internal void SetEntry(ZipEntry entry) |
243 |
{ |
244 |
entry_ = entry; |
245 |
entryValid_ = true; |
246 |
bytesTested_ = 0; |
247 |
} |
248 |
|
249 |
internal void SetBytesTested(long value) |
250 |
{ |
251 |
bytesTested_ = value; |
252 |
} |
253 |
#endregion |
254 |
|
255 |
#region Instance Fields |
256 |
ZipFile file_; |
257 |
ZipEntry entry_; |
258 |
bool entryValid_; |
259 |
int errorCount_; |
260 |
long bytesTested_; |
261 |
TestOperation operation_; |
262 |
#endregion |
263 |
} |
264 |
|
265 |
/// <summary> |
266 |
/// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if supplied indicating current progress and status. |
267 |
/// </summary> |
268 |
/// <remarks>If the message is non-null an error has occured. If the message is null |
269 |
/// the operation as found in <see cref="TestStatus">status</see> has started.</remarks> |
270 |
public delegate void ZipTestResultHandler(TestStatus status, string message); |
271 |
|
272 |
/// <summary> |
273 |
/// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive. |
274 |
/// </summary> |
275 |
public enum FileUpdateMode |
276 |
{ |
277 |
/// <summary> |
278 |
/// Perform all updates on temporary files ensuring that the original file is saved. |
279 |
/// </summary> |
280 |
Safe, |
281 |
/// <summary> |
282 |
/// Update the archive directly, which is faster but less safe. |
283 |
/// </summary> |
284 |
Direct, |
285 |
} |
286 |
|
287 |
/// <summary> |
288 |
/// This class represents a Zip archive. You can ask for the contained |
289 |
/// entries, or get an input stream for a file entry. The entry is |
290 |
/// automatically decompressed. |
291 |
/// |
292 |
/// You can also update the archive adding or deleting entries. |
293 |
/// |
294 |
/// This class is thread safe for input: You can open input streams for arbitrary |
295 |
/// entries in different threads. |
296 |
/// <br/> |
297 |
/// <br/>Author of the original java version : Jochen Hoenicke |
298 |
/// </summary> |
299 |
/// <example> |
300 |
/// <code> |
301 |
/// using System; |
302 |
/// using System.Text; |
303 |
/// using System.Collections; |
304 |
/// using System.IO; |
305 |
/// |
306 |
/// using ICSharpCode.SharpZipLib.Zip; |
307 |
/// |
308 |
/// class MainClass |
309 |
/// { |
310 |
/// static public void Main(string[] args) |
311 |
/// { |
312 |
/// using (ZipFile zFile = new ZipFile(args[0])) { |
313 |
/// Console.WriteLine("Listing of : " + zFile.Name); |
314 |
/// Console.WriteLine(""); |
315 |
/// Console.WriteLine("Raw Size Size Date Time Name"); |
316 |
/// Console.WriteLine("-------- -------- -------- ------ ---------"); |
317 |
/// foreach (ZipEntry e in zFile) { |
318 |
/// if ( e.IsFile ) { |
319 |
/// DateTime d = e.DateTime; |
320 |
/// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, |
321 |
/// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), |
322 |
/// e.Name); |
323 |
/// } |
324 |
/// } |
325 |
/// } |
326 |
/// } |
327 |
/// } |
328 |
/// </code> |
329 |
/// </example> |
330 |
public class ZipFile : IEnumerable, IDisposable |
331 |
{ |
332 |
#region KeyHandling |
333 |
|
334 |
/// <summary> |
335 |
/// Delegate for handling keys/password setting during compresion/decompression. |
336 |
/// </summary> |
337 |
public delegate void KeysRequiredEventHandler( |
338 |
object sender, |
339 |
KeysRequiredEventArgs e |
340 |
); |
341 |
|
342 |
/// <summary> |
343 |
/// Event handler for handling encryption keys. |
344 |
/// </summary> |
345 |
public KeysRequiredEventHandler KeysRequired; |
346 |
|
347 |
/// <summary> |
348 |
/// Handles getting of encryption keys when required. |
349 |
/// </summary> |
350 |
/// <param name="fileName">The file for which encryption keys are required.</param> |
351 |
void OnKeysRequired(string fileName) |
352 |
{ |
353 |
if (KeysRequired != null) { |
354 |
KeysRequiredEventArgs krea = new KeysRequiredEventArgs(fileName, key); |
355 |
KeysRequired(this, krea); |
356 |
key = krea.Key; |
357 |
} |
358 |
} |
359 |
|
360 |
|
361 |
/// <summary> |
362 |
/// Get/set the encryption key value. |
363 |
/// </summary> |
364 |
byte[] Key |
365 |
{ |
366 |
get { return key; } |
367 |
set { key = value; } |
368 |
} |
369 |
|
370 |
/// <summary> |
371 |
/// Password to be used for encrypting/decrypting files. |
372 |
/// </summary> |
373 |
/// <remarks>Set to null if no password is required.</remarks> |
374 |
public string Password |
375 |
{ |
376 |
set |
377 |
{ |
378 |
if ( (value == null) || (value.Length == 0) ) { |
379 |
key = null; |
380 |
} |
381 |
else { |
382 |
key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value)); |
383 |
} |
384 |
} |
385 |
} |
386 |
|
387 |
/// <summary> |
388 |
/// Get a value indicating wether encryption keys are currently available. |
389 |
/// </summary> |
390 |
bool HaveKeys |
391 |
{ |
392 |
get { return key != null; } |
393 |
} |
394 |
#endregion |
395 |
|
396 |
#region Constructors |
397 |
/// <summary> |
398 |
/// Opens a Zip file with the given name for reading. |
399 |
/// </summary> |
400 |
/// <param name="name">The name of the file to open.</param> |
401 |
/// <exception cref="IOException"> |
402 |
/// An i/o error occurs |
403 |
/// </exception> |
404 |
/// <exception cref="ZipException"> |
405 |
/// The file doesn't contain a valid zip archive. |
406 |
/// </exception> |
407 |
public ZipFile(string name) |
408 |
{ |
409 |
name_ = name; |
410 |
|
411 |
baseStream_ = File.OpenRead(name); |
412 |
isStreamOwner = true; |
413 |
|
414 |
try { |
415 |
ReadEntries(); |
416 |
} |
417 |
catch { |
418 |
DisposeInternal(true); |
419 |
throw; |
420 |
} |
421 |
} |
422 |
|
423 |
/// <summary> |
424 |
/// Opens a Zip file reading the given <see cref="FileStream"/>. |
425 |
/// </summary> |
426 |
/// <param name="file">The <see cref="FileStream"/> to read archive data from.</param> |
427 |
/// <exception cref="IOException"> |
428 |
/// An i/o error occurs. |
429 |
/// </exception> |
430 |
/// <exception cref="ZipException"> |
431 |
/// The file doesn't contain a valid zip archive. |
432 |
/// </exception> |
433 |
public ZipFile(FileStream file) |
434 |
{ |
435 |
if ( file == null ) { |
436 |
throw new ArgumentNullException("file"); |
437 |
} |
438 |
|
439 |
if ( !file.CanSeek ) { |
440 |
throw new ArgumentException("Stream is not seekable", "file"); |
441 |
} |
442 |
|
443 |
baseStream_ = file; |
444 |
name_ = file.Name; |
445 |
isStreamOwner = true; |
446 |
|
447 |
try { |
448 |
ReadEntries(); |
449 |
} |
450 |
catch { |
451 |
DisposeInternal(true); |
452 |
throw; |
453 |
} |
454 |
} |
455 |
|
456 |
/// <summary> |
457 |
/// Opens a Zip file reading the given <see cref="Stream"/>. |
458 |
/// </summary> |
459 |
/// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> |
460 |
/// <exception cref="IOException"> |
461 |
/// An i/o error occurs |
462 |
/// </exception> |
463 |
/// <exception cref="ZipException"> |
464 |
/// The file doesn't contain a valid zip archive.<br/> |
465 |
/// The stream provided cannot seek |
466 |
/// </exception> |
467 |
public ZipFile(Stream stream) |
468 |
{ |
469 |
if ( stream == null ) { |
470 |
throw new ArgumentNullException("stream"); |
471 |
} |
472 |
|
473 |
if ( !stream.CanSeek ) { |
474 |
throw new ArgumentException("Stream is not seekable", "stream"); |
475 |
} |
476 |
|
477 |
baseStream_ = stream; |
478 |
isStreamOwner = true; |
479 |
|
480 |
if ( baseStream_.Length > 0 ) { |
481 |
try { |
482 |
ReadEntries(); |
483 |
} |
484 |
catch { |
485 |
DisposeInternal(true); |
486 |
throw; |
487 |
} |
488 |
} else { |
489 |
entries_ = new ZipEntry[0]; |
490 |
isNewArchive_ = true; |
491 |
} |
492 |
} |
493 |
|
494 |
/// <summary> |
495 |
/// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. |
496 |
/// </summary> |
497 |
internal ZipFile() |
498 |
{ |
499 |
entries_ = new ZipEntry[0]; |
500 |
isNewArchive_ = true; |
501 |
} |
502 |
|
503 |
#endregion |
504 |
|
505 |
#region Destructors and Closing |
506 |
/// <summary> |
507 |
/// Finalize this instance. |
508 |
/// </summary> |
509 |
~ZipFile() |
510 |
{ |
511 |
Dispose(false); |
512 |
} |
513 |
|
514 |
/// <summary> |
515 |
/// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying input stream. |
516 |
/// Once closed, no further instance methods should be called. |
517 |
/// </summary> |
518 |
/// <exception cref="System.IO.IOException"> |
519 |
/// An i/o error occurs. |
520 |
/// </exception> |
521 |
public void Close() |
522 |
{ |
523 |
DisposeInternal(true); |
524 |
GC.SuppressFinalize(this); |
525 |
} |
526 |
|
527 |
#endregion |
528 |
|
529 |
#region Creators |
530 |
/// <summary> |
531 |
/// Create a new <see cref="ZipFile"/> whose data will be stored in a file. |
532 |
/// </summary> |
533 |
/// <param name="fileName">The name of the archive to create.</param> |
534 |
/// <returns>Returns the newly created <see cref="ZipFile"/></returns> |
535 |
public static ZipFile Create(string fileName) |
536 |
{ |
537 |
if ( fileName == null ) { |
538 |
throw new ArgumentNullException("fileName"); |
539 |
} |
540 |
|
541 |
FileStream fs = File.Create(fileName); |
542 |
|
543 |
ZipFile result = new ZipFile(); |
544 |
result.name_ = fileName; |
545 |
result.baseStream_ = fs; |
546 |
result.isStreamOwner = true; |
547 |
return result; |
548 |
} |
549 |
|
550 |
/// <summary> |
551 |
/// Create a new <see cref="ZipFile"/> whose data will be stored on a stream. |
552 |
/// </summary> |
553 |
/// <param name="outStream">The stream providing data storage.</param> |
554 |
/// <returns>Returns the newly created <see cref="ZipFile"/></returns> |
555 |
public static ZipFile Create(Stream outStream) |
556 |
{ |
557 |
if ( outStream == null ) { |
558 |
throw new ArgumentNullException("outStream"); |
559 |
} |
560 |
|
561 |
if ( !outStream.CanWrite ) { |
562 |
throw new ArgumentException("Stream is not writeable", "outStream"); |
563 |
} |
564 |
|
565 |
if ( !outStream.CanSeek ) { |
566 |
throw new ArgumentException("Stream is not seekable", "outStream"); |
567 |
} |
568 |
|
569 |
ZipFile result = new ZipFile(); |
570 |
result.baseStream_ = outStream; |
571 |
return result; |
572 |
} |
573 |
|
574 |
#endregion |
575 |
|
576 |
#region Properties |
577 |
/// <summary> |
578 |
/// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. |
579 |
/// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called. |
580 |
/// </summary> |
581 |
/// <remarks> |
582 |
/// The default value is true in all cases. |
583 |
/// </remarks> |
584 |
public bool IsStreamOwner |
585 |
{ |
586 |
get { return isStreamOwner; } |
587 |
set { isStreamOwner = value; } |
588 |
} |
589 |
|
590 |
/// <summary> |
591 |
/// Get a value indicating wether |
592 |
/// this archive is embedded in another file or not. |
593 |
/// </summary> |
594 |
public bool IsEmbeddedArchive |
595 |
{ |
596 |
// Not strictly correct in all circumstances currently |
597 |
get { return offsetOfFirstEntry > 0; } |
598 |
} |
599 |
|
600 |
/// <summary> |
601 |
/// Get a value indicating that this archive is a new one. |
602 |
/// </summary> |
603 |
public bool IsNewArchive |
604 |
{ |
605 |
get { return isNewArchive_; } |
606 |
} |
607 |
|
608 |
/// <summary> |
609 |
/// Gets the comment for the zip file. |
610 |
/// </summary> |
611 |
public string ZipFileComment |
612 |
{ |
613 |
get { return comment_; } |
614 |
} |
615 |
|
616 |
/// <summary> |
617 |
/// Gets the name of this zip file. |
618 |
/// </summary> |
619 |
public string Name |
620 |
{ |
621 |
get { return name_; } |
622 |
} |
623 |
|
624 |
/// <summary> |
625 |
/// Gets the number of entries in this zip file. |
626 |
/// </summary> |
627 |
/// <exception cref="InvalidOperationException"> |
628 |
/// The Zip file has been closed. |
629 |
/// </exception> |
630 |
[Obsolete("Use the Count property instead")] |
631 |
public int Size |
632 |
{ |
633 |
get |
634 |
{ |
635 |
if (entries_ != null) { |
636 |
return entries_.Length; |
637 |
} else { |
638 |
throw new InvalidOperationException("ZipFile is closed"); |
639 |
} |
640 |
} |
641 |
} |
642 |
|
643 |
/// <summary> |
644 |
/// Get the number of entries contained in this <see cref="ZipFile"/>. |
645 |
/// </summary> |
646 |
public long Count |
647 |
{ |
648 |
get |
649 |
{ |
650 |
if (entries_ != null) { |
651 |
return entries_.Length; |
652 |
} else { |
653 |
throw new InvalidOperationException("ZipFile is closed"); |
654 |
} |
655 |
} |
656 |
} |
657 |
|
658 |
/// <summary> |
659 |
/// Indexer property for ZipEntries |
660 |
/// </summary> |
661 |
[System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] |
662 |
public ZipEntry this[int index] |
663 |
{ |
664 |
get { |
665 |
return (ZipEntry) entries_[index].Clone(); |
666 |
} |
667 |
} |
668 |
|
669 |
#endregion |
670 |
|
671 |
#region Input Handling |
672 |
/// <summary> |
673 |
/// Gets an enumerator for the Zip entries in this Zip file. |
674 |
/// </summary> |
675 |
/// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns> |
676 |
/// <exception cref="InvalidOperationException"> |
677 |
/// The Zip file has been closed. |
678 |
/// </exception> |
679 |
public IEnumerator GetEnumerator() |
680 |
{ |
681 |
if (entries_ == null) { |
682 |
throw new InvalidOperationException("ZipFile has closed"); |
683 |
} |
684 |
|
685 |
return new ZipEntryEnumerator(entries_); |
686 |
} |
687 |
|
688 |
/// <summary> |
689 |
/// Return the index of the entry with a matching name |
690 |
/// </summary> |
691 |
/// <param name="name">Entry name to find</param> |
692 |
/// <param name="ignoreCase">If true the comparison is case insensitive</param> |
693 |
/// <returns>The index position of the matching entry or -1 if not found</returns> |
694 |
/// <exception cref="InvalidOperationException"> |
695 |
/// The Zip file has been closed. |
696 |
/// </exception> |
697 |
public int FindEntry(string name, bool ignoreCase) |
698 |
{ |
699 |
if (entries_ == null) { |
700 |
throw new InvalidOperationException("ZipFile has been closed"); |
701 |
} |
702 |
|
703 |
// TODO: This will be slow as the next ice age for huge archives! |
704 |
for (int i = 0; i < entries_.Length; i++) { |
705 |
// BUG: Throws MissingMethodException! |
706 |
// CompareOptions options = (ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); |
707 |
// int comparison = string.Compare(name, entries_[i].Name, CultureInfo.InvariantCulture, options); |
708 |
if (name.Compare(entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) |
709 |
{ |
710 |
return i; |
711 |
} |
712 |
} |
713 |
return -1; |
714 |
} |
715 |
|
716 |
/// <summary> |
717 |
/// Searches for a zip entry in this archive with the given name. |
718 |
/// String comparisons are case insensitive |
719 |
/// </summary> |
720 |
/// <param name="name"> |
721 |
/// The name to find. May contain directory components separated by slashes ('/'). |
722 |
/// </param> |
723 |
/// <returns> |
724 |
/// A clone of the zip entry, or null if no entry with that name exists. |
725 |
/// </returns> |
726 |
/// <exception cref="InvalidOperationException"> |
727 |
/// The Zip file has been closed. |
728 |
/// </exception> |
729 |
public ZipEntry GetEntry(string name) |
730 |
{ |
731 |
if (entries_ == null) { |
732 |
throw new InvalidOperationException("ZipFile has been closed"); |
733 |
} |
734 |
|
735 |
int index = FindEntry(name, true); |
736 |
return (index >= 0) ? (ZipEntry) entries_[index].Clone() : null; |
737 |
} |
738 |
|
739 |
/// <summary> |
740 |
/// Gets an input stream for reading the given zip entry data in an uncompressed form. |
741 |
/// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). |
742 |
/// </summary> |
743 |
/// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> |
744 |
/// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> |
745 |
/// <exception cref="InvalidOperationException"> |
746 |
/// The ZipFile has already been closed |
747 |
/// </exception> |
748 |
/// <exception cref="ZipException"> |
749 |
/// The compression method for the entry is unknown |
750 |
/// </exception> |
751 |
/// <exception cref="IndexOutOfRangeException"> |
752 |
/// The entry is not found in the ZipFile |
753 |
/// </exception> |
754 |
public Stream GetInputStream(ZipEntry entry) |
755 |
{ |
756 |
if ( entry == null ) { |
757 |
throw new ArgumentNullException("entry"); |
758 |
} |
759 |
|
760 |
if ( entries_ == null ) { |
761 |
throw new InvalidOperationException("ZipFile has closed"); |
762 |
} |
763 |
|
764 |
long index = entry.ZipFileIndex; |
765 |
if ( (index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name) ) { |
766 |
index = FindEntry(entry.Name, true); |
767 |
if (index < 0) { |
768 |
throw new ZipException("Entry cannot be found"); |
769 |
} |
770 |
} |
771 |
return GetInputStream(index); |
772 |
} |
773 |
|
774 |
/// <summary> |
775 |
/// Creates an input stream reading a zip entry |
776 |
/// </summary> |
777 |
/// <param name="entryIndex">The index of the entry to obtain an input stream for.</param> |
778 |
/// <returns> |
779 |
/// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/> |
780 |
/// </returns> |
781 |
/// <exception cref="InvalidOperationException"> |
782 |
/// The ZipFile has already been closed |
783 |
/// </exception> |
784 |
/// <exception cref="ZipException"> |
785 |
/// The compression method for the entry is unknown |
786 |
/// </exception> |
787 |
/// <exception cref="IndexOutOfRangeException"> |
788 |
/// The entry is not found in the ZipFile |
789 |
/// </exception> |
790 |
public Stream GetInputStream(long entryIndex) |
791 |
{ |
792 |
if ( entries_ == null ) { |
793 |
throw new InvalidOperationException("ZipFile is not open"); |
794 |
} |
795 |
|
796 |
long start = LocateEntry(entries_[entryIndex]); |
797 |
CompressionMethod method = entries_[entryIndex].CompressionMethod; |
798 |
Stream result = new PartialInputStream(baseStream_, start, entries_[entryIndex].CompressedSize); |
799 |
|
800 |
if (entries_[entryIndex].IsCrypted == true) { |
801 |
#if NETCF_1_0 |
802 |
throw new ZipException("decryption not supported for Compact Framework 1.0"); |
803 |
#else |
804 |
result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); |
805 |
if (result == null) { |
806 |
throw new ZipException("Unable to decrypt this entry"); |
807 |
} |
808 |
#endif |
809 |
} |
810 |
|
811 |
switch (method) { |
812 |
case CompressionMethod.Stored: |
813 |
// read as is. |
814 |
break; |
815 |
|
816 |
case CompressionMethod.Deflated: |
817 |
// No need to worry about ownership and closing as underlying stream close does nothing. |
818 |
result = new InflaterInputStream(result, new Inflater(true)); |
819 |
break; |
820 |
|
821 |
default: |
822 |
throw new ZipException("Unsupported compression method " + method); |
823 |
} |
824 |
|
825 |
return result; |
826 |
} |
827 |
|
828 |
#endregion |
829 |
|
830 |
#region Archive Testing |
831 |
/// <summary> |
832 |
/// Test an archive for integrity/validity |
833 |
/// </summary> |
834 |
/// <param name="testData">Perform low level data Crc check</param> |
835 |
/// <returns>true if all tests pass, false otherwise</returns> |
836 |
/// <remarks>Testing will terminate on the first error found.</remarks> |
837 |
public bool TestArchive(bool testData) |
838 |
{ |
839 |
return TestArchive(testData, TestStrategy.FindFirstError, null); |
840 |
} |
841 |
|
842 |
/// <summary> |
843 |
/// Test an archive for integrity/validity |
844 |
/// </summary> |
845 |
/// <param name="testData">Perform low level data Crc check</param> |
846 |
/// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param> |
847 |
/// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param> |
848 |
/// <returns>true if all tests pass, false otherwise</returns> |
849 |
public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) |
850 |
{ |
851 |
TestStatus status = new TestStatus(this); |
852 |
|
853 |
if ( resultHandler != null ) { |
854 |
resultHandler(status, null); |
855 |
} |
856 |
|
857 |
HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; |
858 |
|
859 |
bool testing = true; |
860 |
|
861 |
try { |
862 |
int entryIndex = 0; |
863 |
|
864 |
while ( testing && (entryIndex < Count) ) { |
865 |
if ( resultHandler != null ) { |
866 |
status.SetEntry(this[entryIndex]); |
867 |
status.SetOperation(TestOperation.EntryHeader); |
868 |
resultHandler(status, null); |
869 |
} |
870 |
|
871 |
try { |
872 |
TestLocalHeader(this[entryIndex], test); |
873 |
} |
874 |
catch(ZipException ex) { |
875 |
status.AddError(); |
876 |
|
877 |
if ( resultHandler != null ) { |
878 |
resultHandler(status, |
879 |
string.Format("Exception during test - '{0}'", ex.Message)); |
880 |
} |
881 |
|
882 |
if ( strategy == TestStrategy.FindFirstError ) { |
883 |
testing = false; |
884 |
} |
885 |
} |
886 |
|
887 |
if ( testing && testData && this[entryIndex].IsFile ) { |
888 |
if ( resultHandler != null ) { |
889 |
status.SetOperation(TestOperation.EntryData); |
890 |
resultHandler(status, null); |
891 |
} |
892 |
|
893 |
Stream entryStream = this.GetInputStream(this[entryIndex]); |
894 |
|
895 |
Crc32 crc = new Crc32(); |
896 |
byte[] buffer = new byte[4096]; |
897 |
long totalBytes = 0; |
898 |
int bytesRead; |
899 |
while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) { |
900 |
crc.Update(buffer, 0, bytesRead); |
901 |
|
902 |
if ( resultHandler != null ) { |
903 |
totalBytes += bytesRead; |
904 |
status.SetBytesTested(totalBytes); |
905 |
resultHandler(status, null); |
906 |
} |
907 |
} |
908 |
|
909 |
if (this[entryIndex].Crc != crc.Value) { |
910 |
status.AddError(); |
911 |
|
912 |
if ( resultHandler != null ) { |
913 |
resultHandler(status, "CRC mismatch"); |
914 |
} |
915 |
|
916 |
if ( strategy == TestStrategy.FindFirstError ) { |
917 |
testing = false; |
918 |
} |
919 |
} |
920 |
|
921 |
if (( this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0 ) { |
922 |
ZipHelperStream helper = new ZipHelperStream(baseStream_); |
923 |
DescriptorData data = new DescriptorData(); |
924 |
helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); |
925 |
if (this[entryIndex].Crc != data.Crc) { |
926 |
status.AddError(); |
927 |
} |
928 |
|
929 |
if (this[entryIndex].CompressedSize != data.CompressedSize) { |
930 |
status.AddError(); |
931 |
} |
932 |
|
933 |
if (this[entryIndex].Size != data.Size) { |
934 |
status.AddError(); |
935 |
} |
936 |
} |
937 |
} |
938 |
|
939 |
if ( resultHandler != null ) { |
940 |
status.SetOperation(TestOperation.EntryComplete); |
941 |
resultHandler(status, null); |
942 |
} |
943 |
|
944 |
entryIndex += 1; |
945 |
} |
946 |
|
947 |
if ( resultHandler != null ) { |
948 |
status.SetOperation(TestOperation.MiscellaneousTests); |
949 |
resultHandler(status, null); |
950 |
} |
951 |
|
952 |
// TODO: the 'Corrina Johns' test where local headers are missing from |
953 |
// the central directory. They are therefore invisible to many archivers. |
954 |
} |
955 |
catch (Exception ex) { |
956 |
status.AddError(); |
957 |
|
958 |
if ( resultHandler != null ) { |
959 |
resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message)); |
960 |
} |
961 |
} |
962 |
|
963 |
if ( resultHandler != null ) { |
964 |
status.SetOperation(TestOperation.Complete); |
965 |
status.SetEntry(null); |
966 |
resultHandler(status, null); |
967 |
} |
968 |
|
969 |
return (status.ErrorCount == 0); |
970 |
} |
971 |
|
972 |
[Flags] |
973 |
enum HeaderTest |
974 |
{ |
975 |
Extract = 0x01, // Check that this header represents an entry whose data can be extracted |
976 |
Header = 0x02, // Check that this header contents are valid |
977 |
} |
978 |
|
979 |
/// <summary> |
980 |
/// Test a local header against that provided from the central directory |
981 |
/// </summary> |
982 |
/// <param name="entry"> |
983 |
/// The entry to test against |
984 |
/// </param> |
985 |
/// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> |
986 |
/// <returns>The offset of the entries data in the file</returns> |
987 |
long TestLocalHeader(ZipEntry entry, HeaderTest tests) |
988 |
{ |
989 |
lock(baseStream_) |
990 |
{ |
991 |
bool testHeader = (tests & HeaderTest.Header) != 0; |
992 |
bool testData = (tests & HeaderTest.Extract) != 0; |
993 |
|
994 |
baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); |
995 |
if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) { |
996 |
throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset)); |
997 |
} |
998 |
|
999 |
short extractVersion = ( short )ReadLEUshort(); |
1000 |
short localFlags = ( short )ReadLEUshort(); |
1001 |
short compressionMethod = ( short )ReadLEUshort(); |
1002 |
short fileTime = ( short )ReadLEUshort(); |
1003 |
short fileDate = ( short )ReadLEUshort(); |
1004 |
uint crcValue = ReadLEUint(); |
1005 |
long compressedSize = ReadLEUint(); |
1006 |
long size = ReadLEUint(); |
1007 |
int storedNameLength = ReadLEUshort(); |
1008 |
int extraDataLength = ReadLEUshort(); |
1009 |
|
1010 |
byte[] nameData = new byte[storedNameLength]; |
1011 |
StreamUtils.ReadFully(baseStream_, nameData); |
1012 |
|
1013 |
byte[] extraData = new byte[extraDataLength]; |
1014 |
StreamUtils.ReadFully(baseStream_, extraData); |
1015 |
|
1016 |
ZipExtraData ed = new ZipExtraData(extraData); |
1017 |
|
1018 |
// Extra data / zip64 checks |
1019 |
if (ed.Find(1)) |
1020 |
{ |
1021 |
// TODO Check for tag values being distinct.. Multiple zip64 tags means what? |
1022 |
|
1023 |
// Zip64 extra data but 'extract version' is too low |
1024 |
if (extractVersion < ZipConstants.VersionZip64) |
1025 |
{ |
1026 |
throw new ZipException( |
1027 |
string.Format("Extra data contains Zip64 information but version {0}.{1} is not high enough", |
1028 |
extractVersion / 10, extractVersion % 10)); |
1029 |
} |
1030 |
|
1031 |
// Zip64 extra data but size fields dont indicate its required. |
1032 |
if (((uint)size != uint.MaxValue) && ((uint)compressedSize != uint.MaxValue)) |
1033 |
{ |
1034 |
throw new ZipException("Entry sizes not correct for Zip64"); |
1035 |
} |
1036 |
|
1037 |
size = ed.ReadLong(); |
1038 |
compressedSize = ed.ReadLong(); |
1039 |
} |
1040 |
else |
1041 |
{ |
1042 |
// No zip64 extra data but entry requires it. |
1043 |
if ((extractVersion >= ZipConstants.VersionZip64) && |
1044 |
(((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) |
1045 |
{ |
1046 |
throw new ZipException("Required Zip64 extended information missing"); |
1047 |
} |
1048 |
} |
1049 |
|
1050 |
if ( testData ) { |
1051 |
if ( entry.IsFile ) { |
1052 |
if ( !entry.IsCompressionMethodSupported() ) { |
1053 |
throw new ZipException("Compression method not supported"); |
1054 |
} |
1055 |
|
1056 |
if ( (extractVersion > ZipConstants.VersionMadeBy) |
1057 |
|| ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64)) ) { |
1058 |
throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion)); |
1059 |
} |
1060 |
|
1061 |
if ( (localFlags & ( int )(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0 ) { |
1062 |
throw new ZipException("The library does not support the zip version required to extract this entry"); |
1063 |
} |
1064 |
} |
1065 |
} |
1066 |
|
1067 |
if ( testHeader ) { |
1068 |
if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. |
1069 |
(extractVersion != 10) && |
1070 |
(extractVersion != 11) && |
1071 |
(extractVersion != 20) && |
1072 |
(extractVersion != 21) && |
1073 |
(extractVersion != 25) && |
1074 |
(extractVersion != 27) && |
1075 |
(extractVersion != 45) && |
1076 |
(extractVersion != 46) && |
1077 |
(extractVersion != 50) && |
1078 |
(extractVersion != 51) && |
1079 |
(extractVersion != 52) && |
1080 |
(extractVersion != 61) && |
1081 |
(extractVersion != 62) && |
1082 |
(extractVersion != 63) |
1083 |
) { |
1084 |
throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion)); |
1085 |
} |
1086 |
|
1087 |
// Local entry flags dont have reserved bit set on. |
1088 |
if ( (localFlags & ( int )(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0 ) { |
1089 |
throw new ZipException("Reserved bit flags cannot be set."); |
1090 |
} |
1091 |
|
1092 |
// Encryption requires extract version >= 20 |
1093 |
if ( ((localFlags & ( int )GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20) ) { |
1094 |
throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); |
1095 |
} |
1096 |
|
1097 |
// Strong encryption requires encryption flag to be set and extract version >= 50. |
1098 |
if ( (localFlags & (int)GeneralBitFlags.StrongEncryption) != 0 ) { |
1099 |
if ( (localFlags & (int)GeneralBitFlags.Encrypted) == 0 ) { |
1100 |
throw new ZipException("Strong encryption flag set but encryption flag is not set"); |
1101 |
} |
1102 |
|
1103 |
if ( extractVersion < 50 ) { |
1104 |
throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); |
1105 |
} |
1106 |
} |
1107 |
|
1108 |
// Patched entries require extract version >= 27 |
1109 |
if ( ((localFlags & ( int )GeneralBitFlags.Patched) != 0) && (extractVersion < 27) ) { |
1110 |
throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); |
1111 |
} |
1112 |
|
1113 |
// Central header flags match local entry flags. |
1114 |
if ( localFlags != entry.Flags ) { |
1115 |
throw new ZipException("Central header/local header flags mismatch"); |
1116 |
} |
1117 |
|
1118 |
// Central header compression method matches local entry |
1119 |
if ( entry.CompressionMethod != ( CompressionMethod )compressionMethod ) { |
1120 |
throw new ZipException("Central header/local header compression method mismatch"); |
1121 |
} |
1122 |
|
1123 |
// Strong encryption and extract version match |
1124 |
if ( (localFlags & ( int )GeneralBitFlags.StrongEncryption) != 0 ) { |
1125 |
if ( extractVersion < 62 ) { |
1126 |
throw new ZipException("Strong encryption flag set but version not high enough"); |
1127 |
} |
1128 |
} |
1129 |
|
1130 |
if ( (localFlags & ( int )GeneralBitFlags.HeaderMasked) != 0 ) { |
1131 |
if ( (fileTime != 0) || (fileDate != 0) ) { |
1132 |
throw new ZipException("Header masked set but date/time values non-zero"); |
1133 |
} |
1134 |
} |
1135 |
|
1136 |
if ( (localFlags & ( int )GeneralBitFlags.Descriptor) == 0 ) { |
1137 |
if ( crcValue != (uint)entry.Crc ) { |
1138 |
throw new ZipException("Central header/local header crc mismatch"); |
1139 |
} |
1140 |
} |
1141 |
|
1142 |
// Crc valid for empty entry. |
1143 |
// This will also apply to streamed entries where size isnt known and the header cant be patched |
1144 |
if ( (size == 0) && (compressedSize == 0) ) { |
1145 |
if ( crcValue != 0 ) { |
1146 |
throw new ZipException("Invalid CRC for empty entry"); |
1147 |
} |
1148 |
} |
1149 |
|
1150 |
// TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings |
1151 |
// Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably |
1152 |
if ( entry.Name.Length > storedNameLength ) { |
1153 |
throw new ZipException("File name length mismatch"); |
1154 |
} |
1155 |
|
1156 |
// Name data has already been read convert it and compare. |
1157 |
string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); |
1158 |
|
1159 |
// Central directory and local entry name match |
1160 |
if ( localName != entry.Name ) { |
1161 |
throw new ZipException("Central header and local header file name mismatch"); |
1162 |
} |
1163 |
|
1164 |
// Directories have zero size. |
1165 |
if ( entry.IsDirectory ) { |
1166 |
if ( (compressedSize != 0) || (size != 0) ) { |
1167 |
throw new ZipException("Directory cannot have size"); |
1168 |
} |
1169 |
} |
1170 |
|
1171 |
if ( !ZipNameTransform.IsValidName(localName, true) ) { |
1172 |
throw new ZipException("Name is invalid"); |
1173 |
} |
1174 |
|
1175 |
} |
1176 |
|
1177 |
// Tests that apply to both data and header. |
1178 |
|
1179 |
// Size can be verified only if it is known in the local header. |
1180 |
// it will always be known in the central header. |
1181 |
if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0 || |
1182 |
(size != 0 || compressedSize != 0)) { |
1183 |
|
1184 |
if (size != entry.Size) { |
1185 |
throw new ZipException( |
1186 |
string.Format("Size mismatch between central header({0}) and local header({1})", |
1187 |
entry.Size, size)); |
1188 |
} |
1189 |
|
1190 |
if (compressedSize != entry.CompressedSize) { |
1191 |
throw new ZipException( |
1192 |
string.Format("Compressed size mismatch between central header({0}) and local header({1})", |
1193 |
entry.CompressedSize, compressedSize)); |
1194 |
} |
1195 |
} |
1196 |
|
1197 |
int extraLength = storedNameLength + extraDataLength; |
1198 |
return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; |
1199 |
} |
1200 |
} |
1201 |
|
1202 |
#endregion |
1203 |
|
1204 |
#region Updating |
1205 |
|
1206 |
const int DefaultBufferSize = 4096; |
1207 |
|
1208 |
/// <summary> |
1209 |
/// The kind of update to apply. |
1210 |
/// </summary> |
1211 |
enum UpdateCommand |
1212 |
{ |
1213 |
Copy, // Copy original file contents. |
1214 |
Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. |
1215 |
Add, // Add a new file to the archive. |
1216 |
} |
1217 |
|
1218 |
#region Properties |
1219 |
/// <summary> |
1220 |
/// Get / set the <see cref="INameTransform"/> to apply to names when updating. |
1221 |
/// </summary> |
1222 |
public INameTransform NameTransform |
1223 |
{ |
1224 |
get { |
1225 |
return updateEntryFactory_.NameTransform; |
1226 |
} |
1227 |
|
1228 |
set { |
1229 |
updateEntryFactory_.NameTransform = value; |
1230 |
} |
1231 |
} |
1232 |
|
1233 |
/// <summary> |
1234 |
/// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values |
1235 |
/// during updates. |
1236 |
/// </summary> |
1237 |
public IEntryFactory EntryFactory |
1238 |
{ |
1239 |
get { |
1240 |
return updateEntryFactory_; |
1241 |
} |
1242 |
|
1243 |
set { |
1244 |
if (value == null) { |
1245 |
updateEntryFactory_ = new ZipEntryFactory(); |
1246 |
} |
1247 |
else { |
1248 |
updateEntryFactory_ = value; |
1249 |
} |
1250 |
} |
1251 |
} |
1252 |
|
1253 |
/// <summary> |
1254 |
/// Get /set the buffer size to be used when updating this zip file. |
1255 |
/// </summary> |
1256 |
public int BufferSize |
1257 |
{ |
1258 |
get { return bufferSize_; } |
1259 |
set { |
1260 |
if ( value < 1024 ) { |
1261 |
#if NETCF_1_0 |
1262 |
throw new ArgumentOutOfRangeException("value"); |
1263 |
#else |
1264 |
throw new ArgumentOutOfRangeException("value", "cannot be below 1024"); |
1265 |
#endif |
1266 |
} |
1267 |
|
1268 |
if ( bufferSize_ != value ) { |
1269 |
bufferSize_ = value; |
1270 |
copyBuffer_ = null; |
1271 |
} |
1272 |
} |
1273 |
} |
1274 |
|
1275 |
/// <summary> |
1276 |
/// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>. |
1277 |
/// </summary> |
1278 |
public bool IsUpdating |
1279 |
{ |
1280 |
get { return updates_ != null; } |
1281 |
} |
1282 |
|
1283 |
/// <summary> |
1284 |
/// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. |
1285 |
/// </summary> |
1286 |
public UseZip64 UseZip64 |
1287 |
{ |
1288 |
get { return useZip64_; } |
1289 |
set { useZip64_ = value; } |
1290 |
} |
1291 |
|
1292 |
#endregion |
1293 |
|
1294 |
#region Immediate updating |
1295 |
// TBD: Direct form of updating |
1296 |
// |
1297 |
// public void Update(IEntryMatcher deleteMatcher) |
1298 |
// { |
1299 |
// } |
1300 |
// |
1301 |
// public void Update(IScanner addScanner) |
1302 |
// { |
1303 |
// } |
1304 |
#endregion |
1305 |
|
1306 |
#region Deferred Updating |
1307 |
/// <summary> |
1308 |
/// Begin updating this <see cref="ZipFile"/> archive. |
1309 |
/// </summary> |
1310 |
/// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</param> |
1311 |
/// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param> |
1312 |
public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) |
1313 |
{ |
1314 |
if ( IsEmbeddedArchive ) { |
1315 |
throw new ZipException ("Cannot update embedded/SFX archives"); |
1316 |
} |
1317 |
|
1318 |
if ( archiveStorage == null ) { |
1319 |
throw new ArgumentNullException("archiveStorage"); |
1320 |
} |
1321 |
|
1322 |
if ( dataSource == null ) { |
1323 |
throw new ArgumentNullException("dataSource"); |
1324 |
} |
1325 |
|
1326 |
archiveStorage_ = archiveStorage; |
1327 |
updateDataSource_ = dataSource; |
1328 |
|
1329 |
// NOTE: the baseStream_ may not currently support writing or seeking. |
1330 |
|
1331 |
updateIndex_ = new Dictionary<string, int>(); |
1332 |
|
1333 |
if ( entries_ != null ) { |
1334 |
updates_ = new List<ZipUpdate>(entries_.Length); |
1335 |
foreach(ZipEntry entry in entries_) { |
1336 |
ZipUpdate update = new ZipUpdate(entry); |
1337 |
updates_.Add(update); |
1338 |
int index = updates_.IndexOf(update); |
1339 |
updateIndex_.Add(entry.Name, index); |
1340 |
} |
1341 |
} |
1342 |
else { |
1343 |
updates_ = new List<ZipUpdate>(); |
1344 |
} |
1345 |
|
1346 |
updateCount_ = updates_.Count; |
1347 |
|
1348 |
contentsEdited_ = false; |
1349 |
commentEdited_ = false; |
1350 |
newComment_ = null; |
1351 |
} |
1352 |
|
1353 |
/// <summary> |
1354 |
/// Begin updating to this <see cref="ZipFile"/> archive. |
1355 |
/// </summary> |
1356 |
/// <param name="archiveStorage">The storage to use during the update.</param> |
1357 |
public void BeginUpdate(IArchiveStorage archiveStorage) |
1358 |
{ |
1359 |
BeginUpdate(archiveStorage, new DynamicDiskDataSource()); |
1360 |
} |
1361 |
|
1362 |
/// <summary> |
1363 |
/// Begin updating this <see cref="ZipFile"/> archive. |
1364 |
/// </summary> |
1365 |
/// <seealso cref="BeginUpdate(IArchiveStorage)"/> |
1366 |
/// <seealso cref="CommitUpdate"></seealso> |
1367 |
/// <seealso cref="AbortUpdate"></seealso> |
1368 |
public void BeginUpdate() |
1369 |
{ |
1370 |
if ( Name == null ) { |
1371 |
BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); |
1372 |
} |
1373 |
else { |
1374 |
BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); |
1375 |
} |
1376 |
} |
1377 |
|
1378 |
/// <summary> |
1379 |
/// Commit current updates, updating this archive. |
1380 |
/// </summary> |
1381 |
/// <seealso cref="BeginUpdate()"></seealso> |
1382 |
/// <seealso cref="AbortUpdate"></seealso> |
1383 |
public void CommitUpdate() |
1384 |
{ |
1385 |
CheckUpdating(); |
1386 |
|
1387 |
try |
1388 |
{ |
1389 |
updateIndex_.Clear(); |
1390 |
updateIndex_=null; |
1391 |
|
1392 |
if( contentsEdited_ ) { |
1393 |
RunUpdates(); |
1394 |
} |
1395 |
else if( commentEdited_ ) { |
1396 |
UpdateCommentOnly(); |
1397 |
} |
1398 |
else { |
1399 |
// Create an empty archive if none existed originally. |
1400 |
if( (entries_!=null)&&(entries_.Length==0) ) { |
1401 |
byte[] theComment=(newComment_!=null)?newComment_.RawComment:ZipConstants.ConvertToArray(comment_); |
1402 |
using( ZipHelperStream zhs=new ZipHelperStream(baseStream_) ) { |
1403 |
zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); |
1404 |
} |
1405 |
} |
1406 |
} |
1407 |
|
1408 |
} |
1409 |
finally { |
1410 |
PostUpdateCleanup(); |
1411 |
} |
1412 |
} |
1413 |
|
1414 |
/// <summary> |
1415 |
/// Abort updating leaving the archive unchanged. |
1416 |
/// </summary> |
1417 |
/// <seealso cref="BeginUpdate()"></seealso> |
1418 |
/// <seealso cref="CommitUpdate"></seealso> |
1419 |
public void AbortUpdate() |
1420 |
{ |
1421 |
PostUpdateCleanup(); |
1422 |
} |
1423 |
|
1424 |
/// <summary> |
1425 |
/// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>. |
1426 |
/// </summary> |
1427 |
/// <param name="comment">The comment to record.</param> |
1428 |
public void SetComment(string comment) |
1429 |
{ |
1430 |
CheckUpdating(); |
1431 |
|
1432 |
newComment_ = new ZipString(comment); |
1433 |
|
1434 |
if ( newComment_.RawLength > 0xffff ) { |
1435 |
newComment_ = null; |
1436 |
throw new ZipException("Comment length exceeds maximum - 65535"); |
1437 |
} |
1438 |
|
1439 |
// We dont take account of the original and current comment appearing to be the same |
1440 |
// as encoding may be different. |
1441 |
commentEdited_ = true; |
1442 |
} |
1443 |
|
1444 |
#endregion |
1445 |
|
1446 |
#region Adding Entries |
1447 |
|
1448 |
void AddUpdate(ZipUpdate update) |
1449 |
{ |
1450 |
contentsEdited_ = true; |
1451 |
|
1452 |
int index = FindExistingUpdate(update.Entry.Name); |
1453 |
|
1454 |
if (index >= 0) { |
1455 |
if ( updates_[index] == null ) { |
1456 |
updateCount_ += 1; |
1457 |
} |
1458 |
|
1459 |
// Direct replacement is faster than delete and add. |
1460 |
updates_[index] = update; |
1461 |
} |
1462 |
else { |
1463 |
updates_.Add(update); |
1464 |
index = updates_.IndexOf(update); |
1465 |
updateCount_ += 1; |
1466 |
updateIndex_.Add(update.Entry.Name, index); |
1467 |
} |
1468 |
} |
1469 |
|
1470 |
/// <summary> |
1471 |
/// Add a new entry to the archive. |
1472 |
/// </summary> |
1473 |
/// <param name="fileName">The name of the file to add.</param> |
1474 |
/// <param name="compressionMethod">The compression method to use.</param> |
1475 |
/// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param> |
1476 |
public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText ) |
1477 |
{ |
1478 |
if (fileName == null) { |
1479 |
throw new ArgumentNullException("fileName"); |
1480 |
} |
1481 |
|
1482 |
if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) { |
1483 |
throw new ZipException("Compression method not supported"); |
1484 |
} |
1485 |
|
1486 |
CheckUpdating(); |
1487 |
contentsEdited_ = true; |
1488 |
|
1489 |
ZipEntry entry = EntryFactory.MakeFileEntry(fileName); |
1490 |
entry.IsUnicodeText = useUnicodeText; |
1491 |
entry.CompressionMethod = compressionMethod; |
1492 |
|
1493 |
AddUpdate(new ZipUpdate(fileName, entry)); |
1494 |
} |
1495 |
|
1496 |
/// <summary> |
1497 |
/// Add a new entry to the archive. |
1498 |
/// </summary> |
1499 |
/// <param name="fileName">The name of the file to add.</param> |
1500 |
/// <param name="compressionMethod">The compression method to use.</param> |
1501 |
public void Add(string fileName, CompressionMethod compressionMethod) |
1502 |
{ |
1503 |
if ( fileName == null ) { |
1504 |
throw new ArgumentNullException("fileName"); |
1505 |
} |
1506 |
|
1507 |
if ( !ZipEntry.IsCompressionMethodSupported(compressionMethod) ) { |
1508 |
throw new ZipException("Compression method not supported"); |
1509 |
} |
1510 |
|
1511 |
CheckUpdating(); |
1512 |
contentsEdited_ = true; |
1513 |
|
1514 |
ZipEntry entry = EntryFactory.MakeFileEntry(fileName); |
1515 |
entry.CompressionMethod = compressionMethod; |
1516 |
AddUpdate(new ZipUpdate(fileName, entry)); |
1517 |
} |
1518 |
|
1519 |
/// <summary> |
1520 |
/// Add a file to the archive. |
1521 |
/// </summary> |
1522 |
/// <param name="fileName">The name of the file to add.</param> |
1523 |
public void Add(string fileName) |
1524 |
{ |
1525 |
if ( fileName == null ) { |
1526 |
throw new ArgumentNullException("fileName"); |
1527 |
} |
1528 |
|
1529 |
CheckUpdating(); |
1530 |
AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); |
1531 |
} |
1532 |
|
1533 |
/// <summary> |
1534 |
/// Add a file entry with data. |
1535 |
/// </summary> |
1536 |
/// <param name="dataSource">The source of the data for this entry.</param> |
1537 |
/// <param name="entryName">The name to give to the entry.</param> |
1538 |
public void Add(IStaticDataSource dataSource, string entryName) |
1539 |
{ |
1540 |
if ( dataSource == null ) { |
1541 |
throw new ArgumentNullException("dataSource"); |
1542 |
} |
1543 |
|
1544 |
CheckUpdating(); |
1545 |
AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName))); |
1546 |
} |
1547 |
|
1548 |
/// <summary> |
1549 |
/// Add a file entry with data. |
1550 |
/// </summary> |
1551 |
/// <param name="dataSource">The source of the data for this entry.</param> |
1552 |
/// <param name="entryName">The name to give to the entry.</param> |
1553 |
/// <param name="compressionMethod">The compression method to use.</param> |
1554 |
public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) |
1555 |
{ |
1556 |
if ( dataSource == null ) { |
1557 |
throw new ArgumentNullException("dataSource"); |
1558 |
} |
1559 |
|
1560 |
CheckUpdating(); |
1561 |
|
1562 |
ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); |
1563 |
entry.CompressionMethod = compressionMethod; |
1564 |
|
1565 |
AddUpdate(new ZipUpdate(dataSource, entry)); |
1566 |
} |
1567 |
|
1568 |
/// <summary> |
1569 |
/// Add a file entry with data. |
1570 |
/// </summary> |
1571 |
/// <param name="dataSource">The source of the data for this entry.</param> |
1572 |
/// <param name="entryName">The name to give to the entry.</param> |
1573 |
/// <param name="compressionMethod">The compression method to use.</param> |
1574 |
/// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param> |
1575 |
public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicodeText) |
1576 |
{ |
1577 |
if (dataSource == null) { |
1578 |
throw new ArgumentNullException("dataSource"); |
1579 |
} |
1580 |
|
1581 |
CheckUpdating(); |
1582 |
|
1583 |
ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); |
1584 |
entry.IsUnicodeText = useUnicodeText; |
1585 |
entry.CompressionMethod = compressionMethod; |
1586 |
|
1587 |
AddUpdate(new ZipUpdate(dataSource, entry)); |
1588 |
} |
1589 |
|
1590 |
/// <summary> |
1591 |
/// Add a <see cref="ZipEntry"/> that contains no data. |
1592 |
/// </summary> |
1593 |
/// <param name="entry">The entry to add.</param> |
1594 |
/// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> |
1595 |
public void Add(ZipEntry entry) |
1596 |
{ |
1597 |
if ( entry == null ) { |
1598 |
throw new ArgumentNullException("entry"); |
1599 |
} |
1600 |
|
1601 |
CheckUpdating(); |
1602 |
|
1603 |
if ( (entry.Size != 0) || (entry.CompressedSize != 0) ) { |
1604 |
throw new ZipException("Entry cannot have any data"); |
1605 |
} |
1606 |
|
1607 |
AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); |
1608 |
} |
1609 |
|
1610 |
/// <summary> |
1611 |
/// Add a directory entry to the archive. |
1612 |
/// </summary> |
1613 |
/// <param name="directoryName">The directory to add.</param> |
1614 |
public void AddDirectory(string directoryName) |
1615 |
{ |
1616 |
if ( directoryName == null ) { |
1617 |
throw new ArgumentNullException("directoryName"); |
1618 |
} |
1619 |
|
1620 |
CheckUpdating(); |
1621 |
|
1622 |
ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); |
1623 |
AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); |
1624 |
} |
1625 |
|
1626 |
#endregion |
1627 |
|
1628 |
#region Modifying Entries |
1629 |
/* Modify not yet ready for public consumption. |
1630 |
Direct modification of an entry should not overwrite original data before its read. |
1631 |
Safe mode is trivial in this sense. |
1632 |
public void Modify(ZipEntry original, ZipEntry updated) |
1633 |
{ |
1634 |
if ( original == null ) { |
1635 |
throw new ArgumentNullException("original"); |
1636 |
} |
1637 |
|
1638 |
if ( updated == null ) { |
1639 |
throw new ArgumentNullException("updated"); |
1640 |
} |
1641 |
|
1642 |
CheckUpdating(); |
1643 |
contentsEdited_ = true; |
1644 |
updates_.Add(new ZipUpdate(original, updated)); |
1645 |
} |
1646 |
*/ |
1647 |
#endregion |
1648 |
|
1649 |
#region Deleting Entries |
1650 |
/// <summary> |
1651 |
/// Delete an entry by name |
1652 |
/// </summary> |
1653 |
/// <param name="fileName">The filename to delete</param> |
1654 |
/// <returns>True if the entry was found and deleted; false otherwise.</returns> |
1655 |
public bool Delete(string fileName) |
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 |
} |
1667 |
else { |
1668 |
throw new ZipException("Cannot find entry to delete"); |
1669 |
} |
1670 |
return result; |
1671 |
} |
1672 |
|
1673 |
/// <summary> |
1674 |
/// Delete a <see cref="ZipEntry"/> from the archive. |
1675 |
/// </summary> |
1676 |
/// <param name="entry">The entry to delete.</param> |
1677 |
public void Delete(ZipEntry entry) |
1678 |
{ |
1679 |
CheckUpdating(); |
1680 |
|
1681 |
int index = FindExistingUpdate(entry); |
1682 |
if ( index >= 0 ) { |
1683 |
contentsEdited_ = true; |
1684 |
updates_[index] = null; |
1685 |
updateCount_ -= 1; |
1686 |
} |
1687 |
else { |
1688 |
throw new ZipException("Cannot find entry to delete"); |
1689 |
} |
1690 |
} |
1691 |
|
1692 |
#endregion |
1693 |
|
1694 |
#region Update Support |
1695 |
#region Writing Values/Headers |
1696 |
void WriteLEShort(int value) |
1697 |
{ |
1698 |
baseStream_.WriteByte(( byte )(value & 0xff)); |
1699 |
baseStream_.WriteByte(( byte )((value >> 8) & 0xff)); |
1700 |
} |
1701 |
|
1702 |
/// <summary> |
1703 |
/// Write an unsigned short in little endian byte order. |
1704 |
/// </summary> |
1705 |
void WriteLEUshort(ushort value) |
1706 |
{ |
1707 |
baseStream_.WriteByte(( byte )(value & 0xff)); |
1708 |
baseStream_.WriteByte(( byte )(value >> 8)); |
1709 |
} |
1710 |
|
1711 |
/// <summary> |
1712 |
/// Write an int in little endian byte order. |
1713 |
/// </summary> |
1714 |
void WriteLEInt(int value) |
1715 |
{ |
1716 |
WriteLEShort(value & 0xffff); |
1717 |
WriteLEShort(value >> 16); |
1718 |
} |
1719 |
|
1720 |
/// <summary> |
1721 |
/// Write an unsigned int in little endian byte order. |
1722 |
/// </summary> |
1723 |
void WriteLEUint(uint value) |
1724 |
{ |
1725 |
WriteLEUshort((ushort)(value & 0xffff)); |
1726 |
WriteLEUshort((ushort)(value >> 16)); |
1727 |
} |
1728 |
|
1729 |
/// <summary> |
1730 |
/// Write a long in little endian byte order. |
1731 |
/// </summary> |
1732 |
void WriteLeLong(long value) |
1733 |
{ |
1734 |
WriteLEInt(( int )(value & 0xffffffff)); |
1735 |
WriteLEInt(( int )(value >> 32)); |
1736 |
} |
1737 |
|
1738 |
void WriteLEUlong(ulong value) |
1739 |
{ |
1740 |
WriteLEUint(( uint )(value & 0xffffffff)); |
1741 |
WriteLEUint(( uint )(value >> 32)); |
1742 |
} |
1743 |
|
1744 |
void WriteLocalEntryHeader(ZipUpdate update) |
1745 |
{ |
1746 |
ZipEntry entry = update.OutEntry; |
1747 |
|
1748 |
// TODO: Local offset will require adjusting for multi-disk zip files. |
1749 |
entry.Offset = baseStream_.Position; |
1750 |
|
1751 |
// TODO: Need to clear any entry flags that dont make sense or throw an exception here. |
1752 |
if (update.Command != UpdateCommand.Copy) { |
1753 |
if (entry.CompressionMethod == CompressionMethod.Deflated) { |
1754 |
if (entry.Size == 0) { |
1755 |
// No need to compress - no data. |
1756 |
entry.CompressedSize = entry.Size; |
1757 |
entry.Crc = 0; |
1758 |
entry.CompressionMethod = CompressionMethod.Stored; |
1759 |
} |
1760 |
} |
1761 |
else if (entry.CompressionMethod == CompressionMethod.Stored) { |
1762 |
entry.Flags &= ~(int)GeneralBitFlags.Descriptor; |
1763 |
} |
1764 |
|
1765 |
if (HaveKeys) { |
1766 |
entry.IsCrypted = true; |
1767 |
if (entry.Crc < 0) { |
1768 |
entry.Flags |= (int)GeneralBitFlags.Descriptor; |
1769 |
} |
1770 |
} |
1771 |
else { |
1772 |
entry.IsCrypted = false; |
1773 |
} |
1774 |
|
1775 |
switch (useZip64_) { |
1776 |
case UseZip64.Dynamic: |
1777 |
if (entry.Size < 0) { |
1778 |
entry.ForceZip64(); |
1779 |
} |
1780 |
break; |
1781 |
|
1782 |
case UseZip64.On: |
1783 |
entry.ForceZip64(); |
1784 |
break; |
1785 |
|
1786 |
case UseZip64.Off: |
1787 |
// Do nothing. The entry itself may be using Zip64 independantly. |
1788 |
break; |
1789 |
} |
1790 |
} |
1791 |
|
1792 |
// Write the local file header |
1793 |
WriteLEInt(ZipConstants.LocalHeaderSignature); |
1794 |
|
1795 |
WriteLEShort(entry.Version); |
1796 |
WriteLEShort(entry.Flags); |
1797 |
|
1798 |
WriteLEShort((byte)entry.CompressionMethod); |
1799 |
WriteLEInt(( int )entry.DosTime); |
1800 |
|
1801 |
if ( !entry.HasCrc ) { |
1802 |
// Note patch address for updating CRC later. |
1803 |
update.CrcPatchOffset = baseStream_.Position; |
1804 |
WriteLEInt(( int )0); |
1805 |
} |
1806 |
else { |
1807 |
WriteLEInt(unchecked(( int )entry.Crc)); |
1808 |
} |
1809 |
|
1810 |
if (entry.LocalHeaderRequiresZip64) { |
1811 |
WriteLEInt(-1); |
1812 |
WriteLEInt(-1); |
1813 |
} |
1814 |
else { |
1815 |
if ( (entry.CompressedSize < 0) || (entry.Size < 0) ) { |
1816 |
update.SizePatchOffset = baseStream_.Position; |
1817 |
} |
1818 |
|
1819 |
WriteLEInt(( int )entry.CompressedSize); |
1820 |
WriteLEInt(( int )entry.Size); |
1821 |
} |
1822 |
|
1823 |
byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); |
1824 |
|
1825 |
if ( name.Length > 0xFFFF ) { |
1826 |
throw new ZipException("Entry name too long."); |
1827 |
} |
1828 |
|
1829 |
ZipExtraData ed = new ZipExtraData(entry.ExtraData); |
1830 |
|
1831 |
if ( entry.LocalHeaderRequiresZip64 ) { |
1832 |
ed.StartNewEntry(); |
1833 |
|
1834 |
// Local entry header always includes size and compressed size. |
1835 |
// NOTE the order of these fields is reversed when compared to the normal headers! |
1836 |
ed.AddLeLong(entry.Size); |
1837 |
ed.AddLeLong(entry.CompressedSize); |
1838 |
ed.AddNewEntry(1); |
1839 |
} |
1840 |
else { |
1841 |
ed.Delete(1); |
1842 |
} |
1843 |
|
1844 |
entry.ExtraData = ed.GetEntryData(); |
1845 |
|
1846 |
WriteLEShort(name.Length); |
1847 |
WriteLEShort(entry.ExtraData.Length); |
1848 |
|
1849 |
if ( name.Length > 0 ) { |
1850 |
baseStream_.Write(name, 0, name.Length); |
1851 |
} |
1852 |
|
1853 |
if ( entry.LocalHeaderRequiresZip64 ) { |
1854 |
if ( !ed.Find(1) ) { |
1855 |
throw new ZipException("Internal error cannot find extra data"); |
1856 |
} |
1857 |
|
1858 |
update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; |
1859 |
} |
1860 |
|
1861 |
if ( entry.ExtraData.Length > 0 ) { |
1862 |
baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); |
1863 |
} |
1864 |
} |
1865 |
|
1866 |
int WriteCentralDirectoryHeader(ZipEntry entry) |
1867 |
{ |
1868 |
if ( entry.CompressedSize < 0 ) { |
1869 |
throw new ZipException("Attempt to write central directory entry with unknown csize"); |
1870 |
} |
1871 |
|
1872 |
if ( entry.Size < 0 ) { |
1873 |
throw new ZipException("Attempt to write central directory entry with unknown size"); |
1874 |
} |
1875 |
|
1876 |
if ( entry.Crc < 0 ) { |
1877 |
throw new ZipException("Attempt to write central directory entry with unknown crc"); |
1878 |
} |
1879 |
|
1880 |
// Write the central file header |
1881 |
WriteLEInt(ZipConstants.CentralHeaderSignature); |
1882 |
|
1883 |
// Version made by |
1884 |
WriteLEShort(ZipConstants.VersionMadeBy); |
1885 |
|
1886 |
// Version required to extract |
1887 |
WriteLEShort(entry.Version); |
1888 |
|
1889 |
WriteLEShort(entry.Flags); |
1890 |
|
1891 |
unchecked { |
1892 |
WriteLEShort((byte)entry.CompressionMethod); |
1893 |
WriteLEInt((int)entry.DosTime); |
1894 |
WriteLEInt((int)entry.Crc); |
1895 |
} |
1896 |
|
1897 |
if ( (entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff) ) { |
1898 |
WriteLEInt(-1); |
1899 |
} |
1900 |
else { |
1901 |
WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); |
1902 |
} |
1903 |
|
1904 |
if ( (entry.IsZip64Forced()) || (entry.Size >= 0xffffffff) ) { |
1905 |
WriteLEInt(-1); |
1906 |
} |
1907 |
else { |
1908 |
WriteLEInt((int)entry.Size); |
1909 |
} |
1910 |
|
1911 |
byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); |
1912 |
|
1913 |
if ( name.Length > 0xFFFF ) { |
1914 |
throw new ZipException("Entry name is too long."); |
1915 |
} |
1916 |
|
1917 |
WriteLEShort(name.Length); |
1918 |
|
1919 |
// Central header extra data is different to local header version so regenerate. |
1920 |
ZipExtraData ed = new ZipExtraData(entry.ExtraData); |
1921 |
|
1922 |
if ( entry.CentralHeaderRequiresZip64 ) { |
1923 |
ed.StartNewEntry(); |
1924 |
|
1925 |
if ( (entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On) ) |
1926 |
{ |
1927 |
ed.AddLeLong(entry.Size); |
1928 |
} |
1929 |
|
1930 |
if ( (entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On) ) |
1931 |
{ |
1932 |
ed.AddLeLong(entry.CompressedSize); |
1933 |
} |
1934 |
|
1935 |
if ( entry.Offset >= 0xffffffff ) { |
1936 |
ed.AddLeLong(entry.Offset); |
1937 |
} |
1938 |
|
1939 |
// Number of disk on which this file starts isnt supported and is never written here. |
1940 |
ed.AddNewEntry(1); |
1941 |
} |
1942 |
else { |
1943 |
// Should have already be done when local header was added. |
1944 |
ed.Delete(1); |
1945 |
} |
1946 |
|
1947 |
byte[] centralExtraData = ed.GetEntryData(); |
1948 |
|
1949 |
WriteLEShort(centralExtraData.Length); |
1950 |
WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); |
1951 |
|
1952 |
WriteLEShort(0); // disk number |
1953 |
WriteLEShort(0); // internal file attributes |
1954 |
|
1955 |
// External file attributes... |
1956 |
if ( entry.ExternalFileAttributes != -1 ) { |
1957 |
WriteLEInt(entry.ExternalFileAttributes); |
1958 |
} |
1959 |
else { |
1960 |
if ( entry.IsDirectory ) { |
1961 |
WriteLEUint(16); |
1962 |
} |
1963 |
else { |
1964 |
WriteLEUint(0); |
1965 |
} |
1966 |
} |
1967 |
|
1968 |
if ( entry.Offset >= 0xffffffff ) { |
1969 |
WriteLEUint(0xffffffff); |
1970 |
} |
1971 |
else { |
1972 |
WriteLEUint((uint)(int)entry.Offset); |
1973 |
} |
1974 |
|
1975 |
if ( name.Length > 0 ) { |
1976 |
baseStream_.Write(name, 0, name.Length); |
1977 |
} |
1978 |
|
1979 |
if ( centralExtraData.Length > 0 ) { |
1980 |
baseStream_.Write(centralExtraData, 0, centralExtraData.Length); |
1981 |
} |
1982 |
|
1983 |
byte[] rawComment = (entry.Comment != null) ? Encoding.UTF8.GetBytes(entry.Comment) : new byte[0]; |
1984 |
|
1985 |
if ( rawComment.Length > 0 ) { |
1986 |
baseStream_.Write(rawComment, 0, rawComment.Length); |
1987 |
} |
1988 |
|
1989 |
return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; |
1990 |
} |
1991 |
#endregion |
1992 |
void PostUpdateCleanup() |
1993 |
{ |
1994 |
if( archiveStorage_!=null ) { |
1995 |
archiveStorage_.Dispose(); |
1996 |
archiveStorage_=null; |
1997 |
} |
1998 |
|
1999 |
updateDataSource_=null; |
2000 |
updates_ = null; |
2001 |
updateIndex_ = null; |
2002 |
} |
2003 |
|
2004 |
string GetTransformedFileName(string name) |
2005 |
{ |
2006 |
return (NameTransform != null) ? |
2007 |
NameTransform.TransformFile(name) : |
2008 |
name; |
2009 |
} |
2010 |
|
2011 |
string GetTransformedDirectoryName(string name) |
2012 |
{ |
2013 |
return (NameTransform != null) ? |
2014 |
NameTransform.TransformDirectory(name) : |
2015 |
name; |
2016 |
} |
2017 |
|
2018 |
/// <summary> |
2019 |
/// Get a raw memory buffer. |
2020 |
/// </summary> |
2021 |
/// <returns>Returns a raw memory buffer.</returns> |
2022 |
byte[] GetBuffer() |
2023 |
{ |
2024 |
if ( copyBuffer_ == null ) { |
2025 |
copyBuffer_ = new byte[bufferSize_]; |
2026 |
} |
2027 |
return copyBuffer_; |
2028 |
} |
2029 |
|
2030 |
void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) |
2031 |
{ |
2032 |
int bytesToCopy = GetDescriptorSize(update); |
2033 |
|
2034 |
if ( bytesToCopy > 0 ) { |
2035 |
byte[] buffer = GetBuffer(); |
2036 |
|
2037 |
while ( bytesToCopy > 0 ) { |
2038 |
int readSize = Math.Min(buffer.Length, bytesToCopy); |
2039 |
|
2040 |
int bytesRead = source.Read(buffer, 0, readSize); |
2041 |
if ( bytesRead > 0 ) { |
2042 |
dest.Write(buffer, 0, bytesRead); |
2043 |
bytesToCopy -= bytesRead; |
2044 |
} |
2045 |
else { |
2046 |
throw new ZipException("Unxpected end of stream"); |
2047 |
} |
2048 |
} |
2049 |
} |
2050 |
} |
2051 |
|
2052 |
void CopyBytes(ZipUpdate update, Stream destination, Stream source, |
2053 |
long bytesToCopy, bool updateCrc) |
2054 |
{ |
2055 |
if ( destination == source ) { |
2056 |
throw new InvalidOperationException("Destination and source are the same"); |
2057 |
} |
2058 |
|
2059 |
// NOTE: Compressed size is updated elsewhere. |
2060 |
Crc32 crc = new Crc32(); |
2061 |
byte[] buffer = GetBuffer(); |
2062 |
|
2063 |
long targetBytes = bytesToCopy; |
2064 |
long totalBytesRead = 0; |
2065 |
|
2066 |
int bytesRead; |
2067 |
do { |
2068 |
int readSize = buffer.Length; |
2069 |
|
2070 |
if ( bytesToCopy < readSize ) { |
2071 |
readSize = (int)bytesToCopy; |
2072 |
} |
2073 |
|
2074 |
bytesRead = source.Read(buffer, 0, readSize); |
2075 |
if ( bytesRead > 0 ) { |
2076 |
if ( updateCrc ) { |
2077 |
crc.Update(buffer, 0, bytesRead); |
2078 |
} |
2079 |
destination.Write(buffer, 0, bytesRead); |
2080 |
bytesToCopy -= bytesRead; |
2081 |
totalBytesRead += bytesRead; |
2082 |
} |
2083 |
} |
2084 |
while ( (bytesRead > 0) && (bytesToCopy > 0) ); |
2085 |
|
2086 |
if ( totalBytesRead != targetBytes ) { |
2087 |
throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)); |
2088 |
} |
2089 |
|
2090 |
if ( updateCrc ) { |
2091 |
update.OutEntry.Crc = crc.Value; |
2092 |
} |
2093 |
} |
2094 |
|
2095 |
/// <summary> |
2096 |
/// Get the size of the source descriptor for a <see cref="ZipUpdate"/>. |
2097 |
/// </summary> |
2098 |
/// <param name="update">The update to get the size for.</param> |
2099 |
/// <returns>The descriptor size, zero if there isnt one.</returns> |
2100 |
int GetDescriptorSize(ZipUpdate update) |
2101 |
{ |
2102 |
int result = 0; |
2103 |
if ( (update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { |
2104 |
result = ZipConstants.DataDescriptorSize - 4; |
2105 |
if ( update.Entry.LocalHeaderRequiresZip64 ) { |
2106 |
result = ZipConstants.Zip64DataDescriptorSize - 4; |
2107 |
} |
2108 |
} |
2109 |
return result; |
2110 |
} |
2111 |
|
2112 |
void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) |
2113 |
{ |
2114 |
int bytesToCopy = GetDescriptorSize(update); |
2115 |
|
2116 |
while ( bytesToCopy > 0 ) { |
2117 |
int readSize = (int)bytesToCopy; |
2118 |
byte[] buffer = GetBuffer(); |
2119 |
|
2120 |
stream.Position = sourcePosition; |
2121 |
int bytesRead = stream.Read(buffer, 0, readSize); |
2122 |
if ( bytesRead > 0 ) { |
2123 |
stream.Position = destinationPosition; |
2124 |
stream.Write(buffer, 0, bytesRead); |
2125 |
bytesToCopy -= bytesRead; |
2126 |
destinationPosition += bytesRead; |
2127 |
sourcePosition += bytesRead; |
2128 |
} |
2129 |
else { |
2130 |
throw new ZipException("Unxpected end of stream"); |
2131 |
} |
2132 |
} |
2133 |
} |
2134 |
|
2135 |
void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sourcePosition) |
2136 |
{ |
2137 |
long bytesToCopy = update.Entry.CompressedSize; |
2138 |
|
2139 |
// NOTE: Compressed size is updated elsewhere. |
2140 |
Crc32 crc = new Crc32(); |
2141 |
byte[] buffer = GetBuffer(); |
2142 |
|
2143 |
long targetBytes = bytesToCopy; |
2144 |
long totalBytesRead = 0; |
2145 |
|
2146 |
int bytesRead; |
2147 |
do |
2148 |
{ |
2149 |
int readSize = buffer.Length; |
2150 |
|
2151 |
if ( bytesToCopy < readSize ) { |
2152 |
readSize = (int)bytesToCopy; |
2153 |
} |
2154 |
|
2155 |
stream.Position = sourcePosition; |
2156 |
bytesRead = stream.Read(buffer, 0, readSize); |
2157 |
if ( bytesRead > 0 ) { |
2158 |
if ( updateCrc ) { |
2159 |
crc.Update(buffer, 0, bytesRead); |
2160 |
} |
2161 |
stream.Position = destinationPosition; |
2162 |
stream.Write(buffer, 0, bytesRead); |
2163 |
|
2164 |
destinationPosition += bytesRead; |
2165 |
sourcePosition += bytesRead; |
2166 |
bytesToCopy -= bytesRead; |
2167 |
totalBytesRead += bytesRead; |
2168 |
} |
2169 |
} |
2170 |
while ( (bytesRead > 0) && (bytesToCopy > 0) ); |
2171 |
|
2172 |
if ( totalBytesRead != targetBytes ) { |
2173 |
throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)); |
2174 |
} |
2175 |
|
2176 |
if ( updateCrc ) { |
2177 |
update.OutEntry.Crc = crc.Value; |
2178 |
} |
2179 |
} |
2180 |
|
2181 |
int FindExistingUpdate(ZipEntry entry) |
2182 |
{ |
2183 |
int result = -1; |
2184 |
string convertedName = GetTransformedFileName(entry.Name); |
2185 |
|
2186 |
if (updateIndex_.ContainsKey(convertedName)) { |
2187 |
result = (int)updateIndex_[convertedName]; |
2188 |
} |
2189 |
/* |
2190 |
// This is slow like the coming of the next ice age but takes less storage and may be useful |
2191 |
// for CF? |
2192 |
for (int index = 0; index < updates_.Count; ++index) |
2193 |
{ |
2194 |
ZipUpdate zu = ( ZipUpdate )updates_[index]; |
2195 |
if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && |
2196 |
(string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { |
2197 |
result = index; |
2198 |
break; |
2199 |
} |
2200 |
} |
2201 |
*/ |
2202 |
return result; |
2203 |
} |
2204 |
|
2205 |
int FindExistingUpdate(string fileName) |
2206 |
{ |
2207 |
int result = -1; |
2208 |
|
2209 |
string convertedName = GetTransformedFileName(fileName); |
2210 |
|
2211 |
if (updateIndex_.ContainsKey(convertedName)) { |
2212 |
result = (int)updateIndex_[convertedName]; |
2213 |
} |
2214 |
|
2215 |
/* |
2216 |
// This is slow like the coming of the next ice age but takes less storage and may be useful |
2217 |
// for CF? |
2218 |
for ( int index = 0; index < updates_.Count; ++index ) { |
2219 |
if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, |
2220 |
true, CultureInfo.InvariantCulture) == 0 ) { |
2221 |
result = index; |
2222 |
break; |
2223 |
} |
2224 |
} |
2225 |
*/ |
2226 |
|
2227 |
return result; |
2228 |
} |
2229 |
|
2230 |
/// <summary> |
2231 |
/// Get an output stream for the specified <see cref="ZipEntry"/> |
2232 |
/// </summary> |
2233 |
/// <param name="entry">The entry to get an output stream for.</param> |
2234 |
/// <returns>The output stream obtained for the entry.</returns> |
2235 |
Stream GetOutputStream(ZipEntry entry) |
2236 |
{ |
2237 |
Stream result = baseStream_; |
2238 |
|
2239 |
if ( entry.IsCrypted == true ) { |
2240 |
#if NETCF_1_0 |
2241 |
throw new ZipException("Encryption not supported for Compact Framework 1.0"); |
2242 |
#else |
2243 |
result = CreateAndInitEncryptionStream(result, entry); |
2244 |
#endif |
2245 |
} |
2246 |
|
2247 |
switch ( entry.CompressionMethod ) { |
2248 |
case CompressionMethod.Stored: |
2249 |
result = new UncompressedStream(result); |
2250 |
break; |
2251 |
|
2252 |
case CompressionMethod.Deflated: |
2253 |
DeflaterOutputStream dos = new DeflaterOutputStream(result, new Deflater(9, true)); |
2254 |
dos.IsStreamOwner = false; |
2255 |
result = dos; |
2256 |
break; |
2257 |
|
2258 |
default: |
2259 |
throw new ZipException("Unknown compression method " + entry.CompressionMethod); |
2260 |
} |
2261 |
return result; |
2262 |
} |
2263 |
|
2264 |
void AddEntry(ZipFile workFile, ZipUpdate update) |
2265 |
{ |
2266 |
Stream source = null; |
2267 |
|
2268 |
if ( update.Entry.IsFile ) { |
2269 |
source = update.GetSource(); |
2270 |
|
2271 |
if ( source == null ) { |
2272 |
source = updateDataSource_.GetSource(update.Entry, update.Filename); |
2273 |
} |
2274 |
} |
2275 |
|
2276 |
if ( source != null ) { |
2277 |
using ( source ) { |
2278 |
long sourceStreamLength = source.Length; |
2279 |
if ( update.OutEntry.Size < 0 ) { |
2280 |
update.OutEntry.Size = sourceStreamLength; |
2281 |
} |
2282 |
else { |
2283 |
// Check for errant entries. |
2284 |
if ( update.OutEntry.Size != sourceStreamLength ) { |
2285 |
throw new ZipException("Entry size/stream size mismatch"); |
2286 |
} |
2287 |
} |
2288 |
|
2289 |
workFile.WriteLocalEntryHeader(update); |
2290 |
|
2291 |
long dataStart = workFile.baseStream_.Position; |
2292 |
|
2293 |
using ( Stream output = workFile.GetOutputStream(update.OutEntry) ) { |
2294 |
CopyBytes(update, output, source, sourceStreamLength, true); |
2295 |
} |
2296 |
|
2297 |
long dataEnd = workFile.baseStream_.Position; |
2298 |
update.OutEntry.CompressedSize = dataEnd - dataStart; |
2299 |
|
2300 |
if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) |
2301 |
{ |
2302 |
ZipHelperStream helper = new ZipHelperStream(workFile.baseStream_); |
2303 |
helper.WriteDataDescriptor(update.OutEntry); |
2304 |
} |
2305 |
} |
2306 |
} |
2307 |
else { |
2308 |
workFile.WriteLocalEntryHeader(update); |
2309 |
update.OutEntry.CompressedSize = 0; |
2310 |
} |
2311 |
|
2312 |
} |
2313 |
|
2314 |
void ModifyEntry(ZipFile workFile, ZipUpdate update) |
2315 |
{ |
2316 |
workFile.WriteLocalEntryHeader(update); |
2317 |
long dataStart = workFile.baseStream_.Position; |
2318 |
|
2319 |
// TODO: This is slow if the changes don't effect the data!! |
2320 |
if ( update.Entry.IsFile && (update.Filename != null) ) { |
2321 |
using ( Stream output = workFile.GetOutputStream(update.OutEntry) ) { |
2322 |
using ( Stream source = this.GetInputStream(update.Entry) ) { |
2323 |
CopyBytes(update, output, source, source.Length, true); |
2324 |
} |
2325 |
} |
2326 |
} |
2327 |
|
2328 |
long dataEnd = workFile.baseStream_.Position; |
2329 |
update.Entry.CompressedSize = dataEnd - dataStart; |
2330 |
} |
2331 |
|
2332 |
void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) |
2333 |
{ |
2334 |
bool skipOver = false; |
2335 |
if ( update.Entry.Offset == destinationPosition ) { |
2336 |
skipOver = true; |
2337 |
} |
2338 |
|
2339 |
if ( !skipOver ) { |
2340 |
baseStream_.Position = destinationPosition; |
2341 |
workFile.WriteLocalEntryHeader(update); |
2342 |
destinationPosition = baseStream_.Position; |
2343 |
} |
2344 |
|
2345 |
long sourcePosition = 0; |
2346 |
|
2347 |
const int NameLengthOffset = 26; |
2348 |
|
2349 |
// TODO: Add base for SFX friendly handling |
2350 |
long entryDataOffset = update.Entry.Offset + NameLengthOffset; |
2351 |
|
2352 |
baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); |
2353 |
|
2354 |
// Clumsy way of handling retrieving the original name and extra data length for now. |
2355 |
// TODO: Stop re-reading name and data length in CopyEntryDirect. |
2356 |
uint nameLength = ReadLEUshort(); |
2357 |
uint extraLength = ReadLEUshort(); |
2358 |
|
2359 |
sourcePosition = baseStream_.Position + nameLength + extraLength; |
2360 |
|
2361 |
if ( skipOver ) { |
2362 |
destinationPosition += |
2363 |
(sourcePosition - entryDataOffset) + NameLengthOffset + // Header size |
2364 |
update.Entry.CompressedSize + GetDescriptorSize(update); |
2365 |
} |
2366 |
else { |
2367 |
if ( update.Entry.CompressedSize > 0 ) { |
2368 |
CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition ); |
2369 |
} |
2370 |
CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); |
2371 |
} |
2372 |
} |
2373 |
|
2374 |
void CopyEntry(ZipFile workFile, ZipUpdate update) |
2375 |
{ |
2376 |
workFile.WriteLocalEntryHeader(update); |
2377 |
|
2378 |
if ( update.Entry.CompressedSize > 0 ) { |
2379 |
const int NameLengthOffset = 26; |
2380 |
|
2381 |
long entryDataOffset = update.Entry.Offset + NameLengthOffset; |
2382 |
|
2383 |
// TODO: This wont work for SFX files! |
2384 |
baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); |
2385 |
|
2386 |
uint nameLength = ReadLEUshort(); |
2387 |
uint extraLength = ReadLEUshort(); |
2388 |
|
2389 |
baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); |
2390 |
|
2391 |
CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); |
2392 |
} |
2393 |
CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); |
2394 |
} |
2395 |
|
2396 |
void Reopen(Stream source) |
2397 |
{ |
2398 |
if ( source == null ) { |
2399 |
throw new ZipException("Failed to reopen archive - no source"); |
2400 |
} |
2401 |
|
2402 |
isNewArchive_ = false; |
2403 |
baseStream_ = source; |
2404 |
ReadEntries(); |
2405 |
} |
2406 |
|
2407 |
void Reopen() |
2408 |
{ |
2409 |
if (Name == null) { |
2410 |
throw new InvalidOperationException("Name is not known cannot Reopen"); |
2411 |
} |
2412 |
|
2413 |
Reopen(File.OpenRead(Name)); |
2414 |
} |
2415 |
|
2416 |
void UpdateCommentOnly() |
2417 |
{ |
2418 |
long baseLength = baseStream_.Length; |
2419 |
|
2420 |
ZipHelperStream updateFile = null; |
2421 |
|
2422 |
if ( archiveStorage_.UpdateMode == FileUpdateMode.Safe ) { |
2423 |
Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); |
2424 |
updateFile = new ZipHelperStream(copyStream); |
2425 |
updateFile.IsStreamOwner = true; |
2426 |
|
2427 |
baseStream_.Close(); |
2428 |
baseStream_ = null; |
2429 |
} |
2430 |
else { |
2431 |
if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) { |
2432 |
// TODO: archiveStorage wasnt originally intended for this use. |
2433 |
// Need to revisit this to tidy up handling as archive storage currently doesnt |
2434 |
// handle the original stream well. |
2435 |
// The problem is when using an existing zip archive with an in memory archive storage. |
2436 |
// The open stream wont support writing but the memory storage should open the same file not an in memory one. |
2437 |
|
2438 |
// Need to tidy up the archive storage interface and contract basically. |
2439 |
baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); |
2440 |
updateFile = new ZipHelperStream(baseStream_); |
2441 |
} |
2442 |
else { |
2443 |
baseStream_.Close(); |
2444 |
baseStream_ = null; |
2445 |
updateFile = new ZipHelperStream(Name); |
2446 |
} |
2447 |
} |
2448 |
|
2449 |
using ( updateFile ) { |
2450 |
long locatedCentralDirOffset = |
2451 |
updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, |
2452 |
baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); |
2453 |
if ( locatedCentralDirOffset < 0 ) { |
2454 |
throw new ZipException("Cannot find central directory"); |
2455 |
} |
2456 |
|
2457 |
const int CentralHeaderCommentSizeOffset = 16; |
2458 |
updateFile.Position += CentralHeaderCommentSizeOffset; |
2459 |
|
2460 |
byte[] rawComment = newComment_.RawComment; |
2461 |
|
2462 |
updateFile.WriteLEShort(rawComment.Length); |
2463 |
updateFile.Write(rawComment, 0, rawComment.Length); |
2464 |
updateFile.SetLength(updateFile.Position); |
2465 |
} |
2466 |
|
2467 |
if ( archiveStorage_.UpdateMode == FileUpdateMode.Safe ) { |
2468 |
Reopen(archiveStorage_.ConvertTemporaryToFinal()); |
2469 |
} |
2470 |
else { |
2471 |
ReadEntries(); |
2472 |
} |
2473 |
} |
2474 |
|
2475 |
/// <summary> |
2476 |
/// Class used to sort updates. |
2477 |
/// </summary> |
2478 |
class UpdateComparer : IComparer<ZipUpdate> |
2479 |
{ |
2480 |
/// <summary> |
2481 |
/// Compares two objects and returns a value indicating whether one is |
2482 |
/// less than, equal to or greater than the other. |
2483 |
/// </summary> |
2484 |
/// <param name="x">First object to compare</param> |
2485 |
/// <param name="y">Second object to compare.</param> |
2486 |
/// <returns>Compare result.</returns> |
2487 |
public int Compare( |
2488 |
ZipUpdate x, |
2489 |
ZipUpdate y) |
2490 |
{ |
2491 |
ZipUpdate zx = x as ZipUpdate; |
2492 |
ZipUpdate zy = y as ZipUpdate; |
2493 |
|
2494 |
int result; |
2495 |
|
2496 |
if (zx == null) { |
2497 |
if (zy == null) { |
2498 |
result = 0; |
2499 |
} |
2500 |
else { |
2501 |
result = -1; |
2502 |
} |
2503 |
} |
2504 |
else if (zy == null) { |
2505 |
result = 1; |
2506 |
} |
2507 |
else { |
2508 |
int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1; |
2509 |
int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1; |
2510 |
|
2511 |
result = xCmdValue - yCmdValue; |
2512 |
if (result == 0) { |
2513 |
long offsetDiff = zx.Entry.Offset - zy.Entry.Offset; |
2514 |
if (offsetDiff < 0) { |
2515 |
result = -1; |
2516 |
} |
2517 |
else if (offsetDiff == 0) { |
2518 |
result = 0; |
2519 |
} |
2520 |
else { |
2521 |
result = 1; |
2522 |
} |
2523 |
} |
2524 |
} |
2525 |
return result; |
2526 |
} |
2527 |
} |
2528 |
|
2529 |
void RunUpdates() |
2530 |
{ |
2531 |
long sizeEntries = 0; |
2532 |
long endOfStream = 0; |
2533 |
bool allOk = true; |
2534 |
bool directUpdate = false; |
2535 |
long destinationPosition = 0; // NOT SFX friendly |
2536 |
|
2537 |
ZipFile workFile; |
2538 |
|
2539 |
if ( IsNewArchive ) { |
2540 |
workFile = this; |
2541 |
workFile.baseStream_.Position = 0; |
2542 |
directUpdate = true; |
2543 |
} |
2544 |
else if ( archiveStorage_.UpdateMode == FileUpdateMode.Direct ) { |
2545 |
workFile = this; |
2546 |
workFile.baseStream_.Position = 0; |
2547 |
directUpdate = true; |
2548 |
|
2549 |
// Sort the updates by offset within copies/modifies, then adds. |
2550 |
// This ensures that data required by copies will not be overwritten. |
2551 |
updates_.Sort(new UpdateComparer()); |
2552 |
} |
2553 |
else { |
2554 |
workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); |
2555 |
workFile.UseZip64 = UseZip64; |
2556 |
|
2557 |
if (key != null) { |
2558 |
workFile.key = (byte[])key.Clone(); |
2559 |
} |
2560 |
} |
2561 |
|
2562 |
try { |
2563 |
foreach ( ZipUpdate update in updates_ ) { |
2564 |
if (update != null) { |
2565 |
switch (update.Command) { |
2566 |
case UpdateCommand.Copy: |
2567 |
if (directUpdate) { |
2568 |
CopyEntryDirect(workFile, update, ref destinationPosition); |
2569 |
} |
2570 |
else { |
2571 |
CopyEntry(workFile, update); |
2572 |
} |
2573 |
break; |
2574 |
|
2575 |
case UpdateCommand.Modify: |
2576 |
// TODO: Direct modifying of an entry will take some legwork. |
2577 |
ModifyEntry(workFile, update); |
2578 |
break; |
2579 |
|
2580 |
case UpdateCommand.Add: |
2581 |
if (!IsNewArchive && directUpdate) { |
2582 |
workFile.baseStream_.Position = destinationPosition; |
2583 |
} |
2584 |
|
2585 |
AddEntry(workFile, update); |
2586 |
|
2587 |
if (directUpdate) { |
2588 |
destinationPosition = workFile.baseStream_.Position; |
2589 |
} |
2590 |
break; |
2591 |
} |
2592 |
} |
2593 |
} |
2594 |
|
2595 |
if ( !IsNewArchive && directUpdate ) { |
2596 |
workFile.baseStream_.Position = destinationPosition; |
2597 |
} |
2598 |
|
2599 |
long centralDirOffset = workFile.baseStream_.Position; |
2600 |
|
2601 |
foreach ( ZipUpdate update in updates_ ) { |
2602 |
if (update != null) { |
2603 |
sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); |
2604 |
} |
2605 |
} |
2606 |
|
2607 |
byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_); |
2608 |
using ( ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_) ) { |
2609 |
zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); |
2610 |
} |
2611 |
|
2612 |
endOfStream = workFile.baseStream_.Position; |
2613 |
|
2614 |
// And now patch entries... |
2615 |
foreach ( ZipUpdate update in updates_ ) { |
2616 |
if (update != null) |
2617 |
{ |
2618 |
|
2619 |
// If the size of the entry is zero leave the crc as 0 as well. |
2620 |
// The calculated crc will be all bits on... |
2621 |
if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) { |
2622 |
workFile.baseStream_.Position = update.CrcPatchOffset; |
2623 |
workFile.WriteLEInt((int)update.OutEntry.Crc); |
2624 |
} |
2625 |
|
2626 |
if (update.SizePatchOffset > 0) { |
2627 |
workFile.baseStream_.Position = update.SizePatchOffset; |
2628 |
if (update.OutEntry.LocalHeaderRequiresZip64) { |
2629 |
workFile.WriteLeLong(update.OutEntry.Size); |
2630 |
workFile.WriteLeLong(update.OutEntry.CompressedSize); |
2631 |
} |
2632 |
else { |
2633 |
workFile.WriteLEInt((int)update.OutEntry.CompressedSize); |
2634 |
workFile.WriteLEInt((int)update.OutEntry.Size); |
2635 |
} |
2636 |
} |
2637 |
} |
2638 |
} |
2639 |
} |
2640 |
catch(Exception) { |
2641 |
allOk = false; |
2642 |
} |
2643 |
finally { |
2644 |
if ( directUpdate ) { |
2645 |
if ( allOk ) { |
2646 |
workFile.baseStream_.Flush(); |
2647 |
workFile.baseStream_.SetLength(endOfStream); |
2648 |
} |
2649 |
} |
2650 |
else { |
2651 |
workFile.Close(); |
2652 |
} |
2653 |
} |
2654 |
|
2655 |
if ( allOk ) { |
2656 |
if ( directUpdate ) { |
2657 |
isNewArchive_ = false; |
2658 |
workFile.baseStream_.Flush(); |
2659 |
ReadEntries(); |
2660 |
} |
2661 |
else { |
2662 |
baseStream_.Close(); |
2663 |
Reopen(archiveStorage_.ConvertTemporaryToFinal()); |
2664 |
} |
2665 |
} |
2666 |
else { |
2667 |
workFile.Close(); |
2668 |
if ( !directUpdate && (workFile.Name != null) ) { |
2669 |
File.Delete(workFile.Name); |
2670 |
} |
2671 |
} |
2672 |
} |
2673 |
|
2674 |
void CheckUpdating() |
2675 |
{ |
2676 |
if ( updates_ == null ) { |
2677 |
throw new ZipException("Cannot update until BeginUpdate has been called"); |
2678 |
} |
2679 |
} |
2680 |
|
2681 |
#endregion |
2682 |
|
2683 |
#region ZipUpdate class |
2684 |
/// <summary> |
2685 |
/// Represents a pending update to a Zip file. |
2686 |
/// </summary> |
2687 |
class ZipUpdate |
2688 |
{ |
2689 |
#region Constructors |
2690 |
public ZipUpdate(string fileName, ZipEntry entry) |
2691 |
{ |
2692 |
command_ = UpdateCommand.Add; |
2693 |
entry_ = entry; |
2694 |
filename_ = fileName; |
2695 |
} |
2696 |
|
2697 |
[Obsolete] |
2698 |
public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) |
2699 |
{ |
2700 |
command_ = UpdateCommand.Add; |
2701 |
entry_ = new ZipEntry(entryName); |
2702 |
entry_.CompressionMethod = compressionMethod; |
2703 |
filename_ = fileName; |
2704 |
} |
2705 |
|
2706 |
[Obsolete] |
2707 |
public ZipUpdate(string fileName, string entryName) |
2708 |
: this(fileName, entryName, CompressionMethod.Deflated) |
2709 |
{ |
2710 |
// Do nothing. |
2711 |
} |
2712 |
|
2713 |
[Obsolete] |
2714 |
public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) |
2715 |
{ |
2716 |
command_ = UpdateCommand.Add; |
2717 |
entry_ = new ZipEntry(entryName); |
2718 |
entry_.CompressionMethod = compressionMethod; |
2719 |
dataSource_ = dataSource; |
2720 |
} |
2721 |
|
2722 |
public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) |
2723 |
{ |
2724 |
command_ = UpdateCommand.Add; |
2725 |
entry_ = entry; |
2726 |
dataSource_ = dataSource; |
2727 |
} |
2728 |
|
2729 |
public ZipUpdate(ZipEntry original, ZipEntry updated) |
2730 |
{ |
2731 |
throw new ZipException("Modify not currently supported"); |
2732 |
/* |
2733 |
command_ = UpdateCommand.Modify; |
2734 |
entry_ = ( ZipEntry )original.Clone(); |
2735 |
outEntry_ = ( ZipEntry )updated.Clone(); |
2736 |
*/ |
2737 |
} |
2738 |
|
2739 |
public ZipUpdate(UpdateCommand command, ZipEntry entry) |
2740 |
{ |
2741 |
command_ = command; |
2742 |
entry_ = ( ZipEntry )entry.Clone(); |
2743 |
} |
2744 |
|
2745 |
|
2746 |
/// <summary> |
2747 |
/// Copy an existing entry. |
2748 |
/// </summary> |
2749 |
/// <param name="entry">The existing entry to copy.</param> |
2750 |
public ZipUpdate(ZipEntry entry) |
2751 |
: this(UpdateCommand.Copy, entry) |
2752 |
{ |
2753 |
// Do nothing. |
2754 |
} |
2755 |
#endregion |
2756 |
|
2757 |
/// <summary> |
2758 |
/// Get the <see cref="ZipEntry"/> for this update. |
2759 |
/// </summary> |
2760 |
/// <remarks>This is the source or original entry.</remarks> |
2761 |
public ZipEntry Entry |
2762 |
{ |
2763 |
get { return entry_; } |
2764 |
} |
2765 |
|
2766 |
/// <summary> |
2767 |
/// Get the <see cref="ZipEntry"/> that will be written to the updated/new file. |
2768 |
/// </summary> |
2769 |
public ZipEntry OutEntry |
2770 |
{ |
2771 |
get { |
2772 |
if ( outEntry_ == null ) { |
2773 |
outEntry_ = (ZipEntry)entry_.Clone(); |
2774 |
} |
2775 |
|
2776 |
return outEntry_; |
2777 |
} |
2778 |
} |
2779 |
|
2780 |
/// <summary> |
2781 |
/// Get the command for this update. |
2782 |
/// </summary> |
2783 |
public UpdateCommand Command |
2784 |
{ |
2785 |
get { return command_; } |
2786 |
} |
2787 |
|
2788 |
/// <summary> |
2789 |
/// Get the filename if any for this update. Null if none exists. |
2790 |
/// </summary> |
2791 |
public string Filename |
2792 |
{ |
2793 |
get { return filename_; } |
2794 |
} |
2795 |
|
2796 |
/// <summary> |
2797 |
/// Get/set the location of the size patch for this update. |
2798 |
/// </summary> |
2799 |
public long SizePatchOffset |
2800 |
{ |
2801 |
get { return sizePatchOffset_; } |
2802 |
set { sizePatchOffset_ = value; } |
2803 |
} |
2804 |
|
2805 |
/// <summary> |
2806 |
/// Get /set the location of the crc patch for this update. |
2807 |
/// </summary> |
2808 |
public long CrcPatchOffset |
2809 |
{ |
2810 |
get { return crcPatchOffset_; } |
2811 |
set { crcPatchOffset_ = value; } |
2812 |
} |
2813 |
|
2814 |
public Stream GetSource() |
2815 |
{ |
2816 |
Stream result = null; |
2817 |
if ( dataSource_ != null ) { |
2818 |
result = dataSource_.GetSource(); |
2819 |
} |
2820 |
|
2821 |
return result; |
2822 |
} |
2823 |
|
2824 |
#region Instance Fields |
2825 |
ZipEntry entry_; |
2826 |
ZipEntry outEntry_; |
2827 |
UpdateCommand command_; |
2828 |
IStaticDataSource dataSource_; |
2829 |
string filename_; |
2830 |
long sizePatchOffset_ = -1; |
2831 |
long crcPatchOffset_ = -1; |
2832 |
#endregion |
2833 |
} |
2834 |
|
2835 |
#endregion |
2836 |
#endregion |
2837 |
|
2838 |
#region Disposing |
2839 |
|
2840 |
#region IDisposable Members |
2841 |
void IDisposable.Dispose() |
2842 |
{ |
2843 |
Close(); |
2844 |
} |
2845 |
#endregion |
2846 |
|
2847 |
void DisposeInternal(bool disposing) |
2848 |
{ |
2849 |
if ( !isDisposed_ ) { |
2850 |
isDisposed_ = true; |
2851 |
entries_ = null; |
2852 |
if ( IsStreamOwner && (baseStream_ != null) ) { |
2853 |
lock(baseStream_) { |
2854 |
baseStream_.Close(); |
2855 |
} |
2856 |
} |
2857 |
} |
2858 |
} |
2859 |
|
2860 |
/// <summary> |
2861 |
/// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. |
2862 |
/// </summary> |
2863 |
/// <param name="disposing">true to release both managed and unmanaged resources; |
2864 |
/// false to release only unmanaged resources.</param> |
2865 |
protected virtual void Dispose(bool disposing) |
2866 |
{ |
2867 |
DisposeInternal(disposing); |
2868 |
} |
2869 |
|
2870 |
#endregion |
2871 |
|
2872 |
#region Internal routines |
2873 |
#region Reading |
2874 |
/// <summary> |
2875 |
/// Read an unsigned short in little endian byte order. |
2876 |
/// </summary> |
2877 |
/// <returns>Returns the value read.</returns> |
2878 |
/// <exception cref="EndOfStreamException"> |
2879 |
/// The stream ends prematurely |
2880 |
/// </exception> |
2881 |
ushort ReadLEUshort() |
2882 |
{ |
2883 |
int data1 = baseStream_.ReadByte(); |
2884 |
|
2885 |
if ( data1 < 0 ) { |
2886 |
throw new EndOfStreamException("End of stream"); |
2887 |
} |
2888 |
|
2889 |
int data2 = baseStream_.ReadByte(); |
2890 |
|
2891 |
if ( data2 < 0 ) { |
2892 |
throw new EndOfStreamException("End of stream"); |
2893 |
} |
2894 |
|
2895 |
|
2896 |
return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); |
2897 |
} |
2898 |
|
2899 |
/// <summary> |
2900 |
/// Read a uint in little endian byte order. |
2901 |
/// </summary> |
2902 |
/// <returns>Returns the value read.</returns> |
2903 |
/// <exception cref="IOException"> |
2904 |
/// An i/o error occurs. |
2905 |
/// </exception> |
2906 |
/// <exception cref="System.IO.EndOfStreamException"> |
2907 |
/// The file ends prematurely |
2908 |
/// </exception> |
2909 |
uint ReadLEUint() |
2910 |
{ |
2911 |
return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); |
2912 |
} |
2913 |
|
2914 |
ulong ReadLEUlong() |
2915 |
{ |
2916 |
return ReadLEUint() | ((ulong)ReadLEUint() << 32); |
2917 |
} |
2918 |
|
2919 |
#endregion |
2920 |
// NOTE this returns the offset of the first byte after the signature. |
2921 |
long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) |
2922 |
{ |
2923 |
using ( ZipHelperStream les = new ZipHelperStream(baseStream_) ) { |
2924 |
return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); |
2925 |
} |
2926 |
} |
2927 |
|
2928 |
/// <summary> |
2929 |
/// Search for and read the central directory of a zip file filling the entries array. |
2930 |
/// </summary> |
2931 |
/// <exception cref="System.IO.IOException"> |
2932 |
/// An i/o error occurs. |
2933 |
/// </exception> |
2934 |
/// <exception cref="ZipException"> |
2935 |
/// The central directory is malformed or cannot be found |
2936 |
/// </exception> |
2937 |
void ReadEntries() |
2938 |
{ |
2939 |
// Search for the End Of Central Directory. When a zip comment is |
2940 |
// present the directory will start earlier |
2941 |
// |
2942 |
// The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. |
2943 |
// This should be compatible with both SFX and ZIP files but has only been tested for Zip files |
2944 |
// If a SFX file has the Zip data attached as a resource and there are other resources occuring later then |
2945 |
// this could be invalid. |
2946 |
// Could also speed this up by reading memory in larger blocks. |
2947 |
|
2948 |
if (baseStream_.CanSeek == false) { |
2949 |
throw new ZipException("ZipFile stream must be seekable"); |
2950 |
} |
2951 |
|
2952 |
long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, |
2953 |
baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); |
2954 |
|
2955 |
if (locatedEndOfCentralDir < 0) { |
2956 |
throw new ZipException("Cannot find central directory"); |
2957 |
} |
2958 |
|
2959 |
// Read end of central directory record |
2960 |
ushort thisDiskNumber = ReadLEUshort(); |
2961 |
ushort startCentralDirDisk = ReadLEUshort(); |
2962 |
ulong entriesForThisDisk = ReadLEUshort(); |
2963 |
ulong entriesForWholeCentralDir = ReadLEUshort(); |
2964 |
ulong centralDirSize = ReadLEUint(); |
2965 |
long offsetOfCentralDir = ReadLEUint(); |
2966 |
uint commentSize = ReadLEUshort(); |
2967 |
|
2968 |
if ( commentSize > 0 ) { |
2969 |
byte[] comment = new byte[commentSize]; |
2970 |
|
2971 |
StreamUtils.ReadFully(baseStream_, comment); |
2972 |
comment_ = ZipConstants.ConvertToString(comment); |
2973 |
} |
2974 |
else { |
2975 |
comment_ = string.Empty; |
2976 |
} |
2977 |
|
2978 |
bool isZip64 = false; |
2979 |
|
2980 |
// Check if zip64 header information is required. |
2981 |
if ( (thisDiskNumber == 0xffff) || |
2982 |
(startCentralDirDisk == 0xffff) || |
2983 |
(entriesForThisDisk == 0xffff) || |
2984 |
(entriesForWholeCentralDir == 0xffff) || |
2985 |
(centralDirSize == 0xffffffff) || |
2986 |
(offsetOfCentralDir == 0xffffffff) ) { |
2987 |
isZip64 = true; |
2988 |
|
2989 |
long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, 0x1000); |
2990 |
if ( offset < 0 ) { |
2991 |
throw new ZipException("Cannot find Zip64 locator"); |
2992 |
} |
2993 |
|
2994 |
// number of the disk with the start of the zip64 end of central directory 4 bytes |
2995 |
// relative offset of the zip64 end of central directory record 8 bytes |
2996 |
// total number of disks 4 bytes |
2997 |
ReadLEUint(); // startDisk64 is not currently used |
2998 |
ulong offset64 = ReadLEUlong(); |
2999 |
uint totalDisks = ReadLEUint(); |
3000 |
|
3001 |
baseStream_.Position = (long)offset64; |
3002 |
long sig64 = ReadLEUint(); |
3003 |
|
3004 |
if ( sig64 != ZipConstants.Zip64CentralFileHeaderSignature ) { |
3005 |
throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); |
3006 |
} |
3007 |
|
3008 |
// NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. |
3009 |
ulong recordSize = ( ulong )ReadLEUlong(); |
3010 |
int versionMadeBy = ReadLEUshort(); |
3011 |
int versionToExtract = ReadLEUshort(); |
3012 |
uint thisDisk = ReadLEUint(); |
3013 |
uint centralDirDisk = ReadLEUint(); |
3014 |
entriesForThisDisk = ReadLEUlong(); |
3015 |
entriesForWholeCentralDir = ReadLEUlong(); |
3016 |
centralDirSize = ReadLEUlong(); |
3017 |
offsetOfCentralDir = (long)ReadLEUlong(); |
3018 |
|
3019 |
// NOTE: zip64 extensible data sector (variable size) is ignored. |
3020 |
} |
3021 |
|
3022 |
entries_ = new ZipEntry[entriesForThisDisk]; |
3023 |
|
3024 |
// SFX/embedded support, find the offset of the first entry vis the start of the stream |
3025 |
// This applies to Zip files that are appended to the end of an SFX stub. |
3026 |
// Or are appended as a resource to an executable. |
3027 |
// Zip files created by some archivers have the offsets altered to reflect the true offsets |
3028 |
// and so dont require any adjustment here... |
3029 |
// TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? |
3030 |
if ( !isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize)) ) { |
3031 |
offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); |
3032 |
if (offsetOfFirstEntry <= 0) { |
3033 |
throw new ZipException("Invalid embedded zip archive"); |
3034 |
} |
3035 |
} |
3036 |
|
3037 |
baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); |
3038 |
|
3039 |
for (ulong i = 0; i < entriesForThisDisk; i++) { |
3040 |
if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { |
3041 |
throw new ZipException("Wrong Central Directory signature"); |
3042 |
} |
3043 |
|
3044 |
int versionMadeBy = ReadLEUshort(); |
3045 |
int versionToExtract = ReadLEUshort(); |
3046 |
int bitFlags = ReadLEUshort(); |
3047 |
int method = ReadLEUshort(); |
3048 |
uint dostime = ReadLEUint(); |
3049 |
uint crc = ReadLEUint(); |
3050 |
long csize = (long)ReadLEUint(); |
3051 |
long size = (long)ReadLEUint(); |
3052 |
int nameLen = ReadLEUshort(); |
3053 |
int extraLen = ReadLEUshort(); |
3054 |
int commentLen = ReadLEUshort(); |
3055 |
|
3056 |
int diskStartNo = ReadLEUshort(); // Not currently used |
3057 |
int internalAttributes = ReadLEUshort(); // Not currently used |
3058 |
|
3059 |
uint externalAttributes = ReadLEUint(); |
3060 |
long offset = ReadLEUint(); |
3061 |
|
3062 |
byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; |
3063 |
|
3064 |
StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); |
3065 |
string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); |
3066 |
|
3067 |
ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method); |
3068 |
entry.Crc = crc & 0xffffffffL; |
3069 |
entry.Size = size & 0xffffffffL; |
3070 |
entry.CompressedSize = csize & 0xffffffffL; |
3071 |
entry.Flags = bitFlags; |
3072 |
entry.DosTime = (uint)dostime; |
3073 |
entry.ZipFileIndex = (long)i; |
3074 |
entry.Offset = offset; |
3075 |
entry.ExternalFileAttributes = (int)externalAttributes; |
3076 |
|
3077 |
if ((bitFlags & 8) == 0) { |
3078 |
entry.CryptoCheckValue = (byte)(crc >> 24); |
3079 |
} |
3080 |
else { |
3081 |
entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); |
3082 |
} |
3083 |
|
3084 |
if (extraLen > 0) { |
3085 |
byte[] extra = new byte[extraLen]; |
3086 |
StreamUtils.ReadFully(baseStream_, extra); |
3087 |
entry.ExtraData = extra; |
3088 |
} |
3089 |
|
3090 |
entry.ProcessExtraData(false); |
3091 |
|
3092 |
if (commentLen > 0) { |
3093 |
StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); |
3094 |
entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); |
3095 |
} |
3096 |
|
3097 |
entries_[i] = entry; |
3098 |
} |
3099 |
} |
3100 |
|
3101 |
/// <summary> |
3102 |
/// Locate the data for a given entry. |
3103 |
/// </summary> |
3104 |
/// <returns> |
3105 |
/// The start offset of the data. |
3106 |
/// </returns> |
3107 |
/// <exception cref="System.IO.EndOfStreamException"> |
3108 |
/// The stream ends prematurely |
3109 |
/// </exception> |
3110 |
/// <exception cref="ZipException"> |
3111 |
/// The local header signature is invalid, the entry and central header file name lengths are different |
3112 |
/// or the local and entry compression methods dont match |
3113 |
/// </exception> |
3114 |
long LocateEntry(ZipEntry entry) |
3115 |
{ |
3116 |
return TestLocalHeader(entry, HeaderTest.Extract); |
3117 |
} |
3118 |
|
3119 |
#if !NETCF_1_0 |
3120 |
Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) |
3121 |
{ |
3122 |
CryptoStream result = null; |
3123 |
|
3124 |
if ( (entry.Version < ZipConstants.VersionStrongEncryption) |
3125 |
|| (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { |
3126 |
PkzipClassicManaged classicManaged = new PkzipClassicManaged(); |
3127 |
|
3128 |
OnKeysRequired(entry.Name); |
3129 |
if (HaveKeys == false) { |
3130 |
throw new ZipException("No password available for encrypted stream"); |
3131 |
} |
3132 |
|
3133 |
result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); |
3134 |
CheckClassicPassword(result, entry); |
3135 |
} |
3136 |
else { |
3137 |
throw new ZipException("Decryption method not supported"); |
3138 |
} |
3139 |
|
3140 |
return result; |
3141 |
} |
3142 |
|
3143 |
Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) |
3144 |
{ |
3145 |
CryptoStream result = null; |
3146 |
if ( (entry.Version < ZipConstants.VersionStrongEncryption) |
3147 |
|| (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) { |
3148 |
PkzipClassicManaged classicManaged = new PkzipClassicManaged(); |
3149 |
|
3150 |
OnKeysRequired(entry.Name); |
3151 |
if (HaveKeys == false) { |
3152 |
throw new ZipException("No password available for encrypted stream"); |
3153 |
} |
3154 |
|
3155 |
// Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream |
3156 |
// which doesnt do this. |
3157 |
result = new CryptoStream(new UncompressedStream(baseStream), |
3158 |
classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); |
3159 |
|
3160 |
if ( (entry.Crc < 0) || (entry.Flags & 8) != 0) { |
3161 |
WriteEncryptionHeader(result, entry.DosTime << 16); |
3162 |
} |
3163 |
else { |
3164 |
WriteEncryptionHeader(result, entry.Crc); |
3165 |
} |
3166 |
} |
3167 |
return result; |
3168 |
} |
3169 |
|
3170 |
static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) |
3171 |
{ |
3172 |
byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; |
3173 |
StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); |
3174 |
if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { |
3175 |
throw new ZipException("Invalid password"); |
3176 |
} |
3177 |
} |
3178 |
#endif |
3179 |
|
3180 |
static void WriteEncryptionHeader(Stream stream, long crcValue) |
3181 |
{ |
3182 |
byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; |
3183 |
Random rnd = new Random(); |
3184 |
rnd.NextBytes(cryptBuffer); |
3185 |
cryptBuffer[11] = (byte)(crcValue >> 24); |
3186 |
stream.Write(cryptBuffer, 0, cryptBuffer.Length); |
3187 |
} |
3188 |
|
3189 |
#endregion |
3190 |
|
3191 |
#region Instance Fields |
3192 |
bool isDisposed_; |
3193 |
string name_; |
3194 |
string comment_; |
3195 |
Stream baseStream_; |
3196 |
bool isStreamOwner; |
3197 |
long offsetOfFirstEntry; |
3198 |
ZipEntry[] entries_; |
3199 |
byte[] key; |
3200 |
bool isNewArchive_; |
3201 |
|
3202 |
// Default is dynamic which is not backwards compatible and can cause problems |
3203 |
// with XP's built in compression which cant read Zip64 archives. |
3204 |
// However it does avoid the situation were a large file is added and cannot be completed correctly. |
3205 |
UseZip64 useZip64_ = UseZip64.Dynamic ; |
3206 |
|
3207 |
#region Zip Update Instance Fields |
3208 |
List<ZipUpdate> updates_; |
3209 |
long updateCount_; // Count is managed manually as updates_ can contain nulls! |
3210 |
Dictionary<string, int> updateIndex_; |
3211 |
IArchiveStorage archiveStorage_; |
3212 |
IDynamicDataSource updateDataSource_; |
3213 |
bool contentsEdited_; |
3214 |
int bufferSize_ = DefaultBufferSize; |
3215 |
byte[] copyBuffer_; |
3216 |
ZipString newComment_; |
3217 |
bool commentEdited_; |
3218 |
IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); |
3219 |
string tempDirectory_ = string.Empty; |
3220 |
#endregion |
3221 |
#endregion |
3222 |
|
3223 |
#region Support Classes |
3224 |
/// <summary> |
3225 |
/// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes. |
3226 |
/// </summary> |
3227 |
class ZipString |
3228 |
{ |
3229 |
#region Constructors |
3230 |
/// <summary> |
3231 |
/// Initialise a <see cref="ZipString"/> with a string. |
3232 |
/// </summary> |
3233 |
/// <param name="comment">The textual string form.</param> |
3234 |
public ZipString(string comment) |
3235 |
{ |
3236 |
comment_ = comment; |
3237 |
isSourceString_ = true; |
3238 |
} |
3239 |
|
3240 |
/// <summary> |
3241 |
/// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form. |
3242 |
/// </summary> |
3243 |
/// <param name="rawString"></param> |
3244 |
public ZipString(byte[] rawString) |
3245 |
{ |
3246 |
rawComment_ = rawString; |
3247 |
} |
3248 |
#endregion |
3249 |
|
3250 |
/// <summary> |
3251 |
/// Get a value indicating the original source of data for this instance. |
3252 |
/// True if the source was a string; false if the source was binary data. |
3253 |
/// </summary> |
3254 |
public bool IsSourceString |
3255 |
{ |
3256 |
get { return isSourceString_; } |
3257 |
} |
3258 |
|
3259 |
/// <summary> |
3260 |
/// Get the length of the comment when represented as raw bytes. |
3261 |
/// </summary> |
3262 |
public int RawLength |
3263 |
{ |
3264 |
get { |
3265 |
MakeBytesAvailable(); |
3266 |
return rawComment_.Length; |
3267 |
} |
3268 |
} |
3269 |
|
3270 |
/// <summary> |
3271 |
/// Get the comment in its 'raw' form as plain bytes. |
3272 |
/// </summary> |
3273 |
public byte[] RawComment |
3274 |
{ |
3275 |
get { |
3276 |
MakeBytesAvailable(); |
3277 |
return (byte[])rawComment_.Clone(); |
3278 |
} |
3279 |
} |
3280 |
|
3281 |
/// <summary> |
3282 |
/// Reset the comment to its initial state. |
3283 |
/// </summary> |
3284 |
public void Reset() |
3285 |
{ |
3286 |
if ( isSourceString_ ) { |
3287 |
rawComment_ = null; |
3288 |
} |
3289 |
else { |
3290 |
comment_ = null; |
3291 |
} |
3292 |
} |
3293 |
|
3294 |
void MakeTextAvailable() |
3295 |
{ |
3296 |
if ( comment_ == null ) { |
3297 |
comment_ = ZipConstants.ConvertToString(rawComment_); |
3298 |
} |
3299 |
} |
3300 |
|
3301 |
void MakeBytesAvailable() |
3302 |
{ |
3303 |
if ( rawComment_ == null ) { |
3304 |
rawComment_ = ZipConstants.ConvertToArray(comment_); |
3305 |
} |
3306 |
} |
3307 |
|
3308 |
/// <summary> |
3309 |
/// Implicit conversion of comment to a string. |
3310 |
/// </summary> |
3311 |
/// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param> |
3312 |
/// <returns>The textual equivalent for the input value.</returns> |
3313 |
static public implicit operator string(ZipString zipString) |
3314 |
{ |
3315 |
zipString.MakeTextAvailable(); |
3316 |
return zipString.comment_; |
3317 |
} |
3318 |
|
3319 |
#region Instance Fields |
3320 |
string comment_; |
3321 |
byte[] rawComment_; |
3322 |
bool isSourceString_; |
3323 |
#endregion |
3324 |
} |
3325 |
|
3326 |
/// <summary> |
3327 |
/// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see> |
3328 |
/// </summary> |
3329 |
class ZipEntryEnumerator : IEnumerator |
3330 |
{ |
3331 |
#region Constructors |
3332 |
public ZipEntryEnumerator(ZipEntry[] entries) |
3333 |
{ |
3334 |
array = entries; |
3335 |
} |
3336 |
|
3337 |
#endregion |
3338 |
#region IEnumerator Members |
3339 |
public object Current |
3340 |
{ |
3341 |
get { |
3342 |
return array[index]; |
3343 |
} |
3344 |
} |
3345 |
|
3346 |
public void Reset() |
3347 |
{ |
3348 |
index = -1; |
3349 |
} |
3350 |
|
3351 |
public bool MoveNext() |
3352 |
{ |
3353 |
return (++index < array.Length); |
3354 |
} |
3355 |
#endregion |
3356 |
#region Instance Fields |
3357 |
ZipEntry[] array; |
3358 |
int index = -1; |
3359 |
#endregion |
3360 |
} |
3361 |
|
3362 |
/// <summary> |
3363 |
/// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data |
3364 |
/// to and flush, but cannot read, seek or do anything else to. |
3365 |
/// </summary> |
3366 |
class UncompressedStream : Stream |
3367 |
{ |
3368 |
#region Constructors |
3369 |
public UncompressedStream(Stream baseStream) |
3370 |
{ |
3371 |
baseStream_ = baseStream; |
3372 |
} |
3373 |
|
3374 |
#endregion |
3375 |
|
3376 |
/// <summary> |
3377 |
/// Close this stream instance. |
3378 |
/// </summary> |
3379 |
public override void Close() |
3380 |
{ |
3381 |
// Do nothing |
3382 |
} |
3383 |
|
3384 |
/// <summary> |
3385 |
/// Gets a value indicating whether the current stream supports reading. |
3386 |
/// </summary> |
3387 |
public override bool CanRead |
3388 |
{ |
3389 |
get { |
3390 |
return false; |
3391 |
} |
3392 |
} |
3393 |
|
3394 |
/// <summary> |
3395 |
/// Write any buffered data to underlying storage. |
3396 |
/// </summary> |
3397 |
public override void Flush() |
3398 |
{ |
3399 |
baseStream_.Flush(); |
3400 |
} |
3401 |
|
3402 |
/// <summary> |
3403 |
/// Gets a value indicating whether the current stream supports writing. |
3404 |
/// </summary> |
3405 |
public override bool CanWrite |
3406 |
{ |
3407 |
get { |
3408 |
return baseStream_.CanWrite; |
3409 |
} |
3410 |
} |
3411 |
|
3412 |
/// <summary> |
3413 |
/// Gets a value indicating whether the current stream supports seeking. |
3414 |
/// </summary> |
3415 |
public override bool CanSeek |
3416 |
{ |
3417 |
get { |
3418 |
return false; |
3419 |
} |
3420 |
} |
3421 |
|
3422 |
/// <summary> |
3423 |
/// Get the length in bytes of the stream. |
3424 |
/// </summary> |
3425 |
public override long Length |
3426 |
{ |
3427 |
get { |
3428 |
return 0; |
3429 |
} |
3430 |
} |
3431 |
|
3432 |
/// <summary> |
3433 |
/// Gets or sets the position within the current stream. |
3434 |
/// </summary> |
3435 |
public override long Position |
3436 |
{ |
3437 |
get { |
3438 |
return baseStream_.Position; |
3439 |
} |
3440 |
|
3441 |
set |
3442 |
{ |
3443 |
} |
3444 |
} |
3445 |
|
3446 |
public override int Read(byte[] buffer, int offset, int count) |
3447 |
{ |
3448 |
return 0; |
3449 |
} |
3450 |
|
3451 |
public override long Seek(long offset, SeekOrigin origin) |
3452 |
{ |
3453 |
return 0; |
3454 |
} |
3455 |
|
3456 |
public override void SetLength(long value) |
3457 |
{ |
3458 |
} |
3459 |
|
3460 |
public override void Write(byte[] buffer, int offset, int count) |
3461 |
{ |
3462 |
baseStream_.Write(buffer, offset, count); |
3463 |
} |
3464 |
|
3465 |
#region Instance Fields |
3466 |
Stream baseStream_; |
3467 |
#endregion |
3468 |
} |
3469 |
|
3470 |
/// <summary> |
3471 |
/// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/> |
3472 |
/// whose data is only a part or subsection of a file. |
3473 |
/// </summary> |
3474 |
class PartialInputStream : InflaterInputStream |
3475 |
{ |
3476 |
#region Constructors |
3477 |
/// <summary> |
3478 |
/// Initialise a new instance of the <see cref="PartialInputStream"/> class. |
3479 |
/// </summary> |
3480 |
/// <param name="baseStream">The underlying stream to use for IO.</param> |
3481 |
/// <param name="start">The start of the partial data.</param> |
3482 |
/// <param name="length">The length of the partial data.</param> |
3483 |
public PartialInputStream(Stream baseStream, long start, long length) |
3484 |
: base(baseStream) |
3485 |
{ |
3486 |
baseStream_ = baseStream; |
3487 |
filepos_ = start; |
3488 |
end_ = start + length; |
3489 |
} |
3490 |
|
3491 |
#endregion |
3492 |
|
3493 |
/// <summary> |
3494 |
/// Skip the specified number of input bytes. |
3495 |
/// </summary> |
3496 |
/// <param name="count">The maximum number of input bytes to skip.</param> |
3497 |
/// <returns>The actuial number of input bytes skipped.</returns> |
3498 |
public long SkipBytes(long count) |
3499 |
{ |
3500 |
if (count < 0) { |
3501 |
#if NETCF_1_0 |
3502 |
throw new ArgumentOutOfRangeException("count"); |
3503 |
#else |
3504 |
throw new ArgumentOutOfRangeException("count", "is less than zero"); |
3505 |
#endif |
3506 |
} |
3507 |
|
3508 |
if (count > end_ - filepos_) { |
3509 |
count = end_ - filepos_; |
3510 |
} |
3511 |
|
3512 |
filepos_ += count; |
3513 |
return count; |
3514 |
} |
3515 |
|
3516 |
public override int Available |
3517 |
{ |
3518 |
get { |
3519 |
long amount = end_ - filepos_; |
3520 |
if (amount > int.MaxValue) { |
3521 |
return int.MaxValue; |
3522 |
} |
3523 |
|
3524 |
return (int) amount; |
3525 |
} |
3526 |
} |
3527 |
|
3528 |
/// <summary> |
3529 |
/// Read a byte from this stream. |
3530 |
/// </summary> |
3531 |
/// <returns>Returns the byte read or -1 on end of stream.</returns> |
3532 |
public override int ReadByte() |
3533 |
{ |
3534 |
if (filepos_ == end_) { |
3535 |
// -1 is the correct value at end of stream. |
3536 |
return -1; |
3537 |
} |
3538 |
|
3539 |
lock( baseStream_ ) { |
3540 |
baseStream_.Seek(filepos_++, SeekOrigin.Begin); |
3541 |
return baseStream_.ReadByte(); |
3542 |
} |
3543 |
} |
3544 |
|
3545 |
/// <summary> |
3546 |
/// Close this <see cref="PartialInputStream">partial input stream</see>. |
3547 |
/// </summary> |
3548 |
/// <remarks> |
3549 |
/// The underlying stream is not closed. Close the parent ZipFile class to do that. |
3550 |
/// </remarks> |
3551 |
public override void Close() |
3552 |
{ |
3553 |
// Do nothing at all! |
3554 |
} |
3555 |
|
3556 |
public override int Read(byte[] buffer, int offset, int count) |
3557 |
{ |
3558 |
if (count > end_ - filepos_) { |
3559 |
count = (int) (end_ - filepos_); |
3560 |
if (count == 0) { |
3561 |
return 0; |
3562 |
} |
3563 |
} |
3564 |
|
3565 |
lock(baseStream_) { |
3566 |
baseStream_.Seek(filepos_, SeekOrigin.Begin); |
3567 |
int readCount = baseStream_.Read(buffer, offset, count); |
3568 |
if (readCount > 0) { |
3569 |
filepos_ += readCount; |
3570 |
} |
3571 |
return readCount; |
3572 |
} |
3573 |
} |
3574 |
|
3575 |
#region Instance Fields |
3576 |
Stream baseStream_; |
3577 |
long filepos_; |
3578 |
long end_; |
3579 |
#endregion |
3580 |
} |
3581 |
#endregion |
3582 |
} |
3583 |
|
3584 |
/// <summary> |
3585 |
/// Provides a static way to obtain a source of data for an entry. |
3586 |
/// </summary> |
3587 |
public interface IStaticDataSource |
3588 |
{ |
3589 |
/// <summary> |
3590 |
/// Get a source of data by creating a new stream. |
3591 |
/// </summary> |
3592 |
/// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> |
3593 |
/// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> |
3594 |
Stream GetSource(); |
3595 |
} |
3596 |
|
3597 |
/// <summary> |
3598 |
/// Represents a source of data that can dynamically provide |
3599 |
/// multiple <see cref="Stream">data sources</see> based on the parameters passed. |
3600 |
/// </summary> |
3601 |
public interface IDynamicDataSource |
3602 |
{ |
3603 |
/// <summary> |
3604 |
/// Get a data source. |
3605 |
/// </summary> |
3606 |
/// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param> |
3607 |
/// <param name="name">The name for data if known.</param> |
3608 |
/// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns> |
3609 |
/// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks> |
3610 |
Stream GetSource(ZipEntry entry, string name); |
3611 |
} |
3612 |
|
3613 |
/// <summary> |
3614 |
/// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk. |
3615 |
/// </summary> |
3616 |
class StaticDiskDataSource : IStaticDataSource |
3617 |
{ |
3618 |
/// <summary> |
3619 |
/// Initialise a new instnace of <see cref="StaticDiskDataSource"/> |
3620 |
/// </summary> |
3621 |
/// <param name="fileName">The name of the file to obtain data from.</param> |
3622 |
public StaticDiskDataSource(string fileName) |
3623 |
{ |
3624 |
fileName_ = fileName; |
3625 |
} |
3626 |
|
3627 |
#region IDataSource Members |
3628 |
|
3629 |
/// <summary> |
3630 |
/// Get a <see cref="Stream"/> providing data. |
3631 |
/// </summary> |
3632 |
/// <returns>Returns a <see cref="Stream"/> provising data.</returns> |
3633 |
public Stream GetSource() |
3634 |
{ |
3635 |
return File.OpenRead(fileName_); |
3636 |
} |
3637 |
|
3638 |
#endregion |
3639 |
#region Instance Fields |
3640 |
string fileName_; |
3641 |
#endregion |
3642 |
} |
3643 |
|
3644 |
/// <summary> |
3645 |
/// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk. |
3646 |
/// </summary> |
3647 |
class DynamicDiskDataSource : IDynamicDataSource |
3648 |
{ |
3649 |
/// <summary> |
3650 |
/// Initialise a default instance of <see cref="DynamicDiskDataSource"/>. |
3651 |
/// </summary> |
3652 |
public DynamicDiskDataSource() |
3653 |
{ |
3654 |
} |
3655 |
|
3656 |
#region IDataSource Members |
3657 |
/// <summary> |
3658 |
/// Get a <see cref="Stream"/> providing data for an entry. |
3659 |
/// </summary> |
3660 |
/// <param name="entry">The entry to provide data for.</param> |
3661 |
/// <param name="name">The file name for data if known.</param> |
3662 |
/// <returns>Returns a stream providing data; or null if not available</returns> |
3663 |
public Stream GetSource(ZipEntry entry, string name) |
3664 |
{ |
3665 |
Stream result = null; |
3666 |
|
3667 |
if ( name != null ) { |
3668 |
result = File.OpenRead(name); |
3669 |
} |
3670 |
|
3671 |
return result; |
3672 |
} |
3673 |
|
3674 |
#endregion |
3675 |
} |
3676 |
|
3677 |
/// <summary> |
3678 |
/// Defines facilities for data storage when updating Zip Archives. |
3679 |
/// </summary> |
3680 |
public interface IArchiveStorage |
3681 |
{ |
3682 |
/// <summary> |
3683 |
/// Get the <see cref="FileUpdateMode"/> to apply during updates. |
3684 |
/// </summary> |
3685 |
FileUpdateMode UpdateMode { get; } |
3686 |
|
3687 |
/// <summary> |
3688 |
/// Get an empty <see cref="Stream"/> that can be used for temporary output. |
3689 |
/// </summary> |
3690 |
/// <returns>Returns a temporary output <see cref="Stream"/></returns> |
3691 |
/// <seealso cref="ConvertTemporaryToFinal"></seealso> |
3692 |
Stream GetTemporaryOutput(); |
3693 |
|
3694 |
/// <summary> |
3695 |
/// Convert a temporary output stream to a final stream. |
3696 |
/// </summary> |
3697 |
/// <returns>The resulting final <see cref="Stream"/></returns> |
3698 |
/// <seealso cref="GetTemporaryOutput"/> |
3699 |
Stream ConvertTemporaryToFinal(); |
3700 |
|
3701 |
/// <summary> |
3702 |
/// Make a temporary copy of the original stream. |
3703 |
/// </summary> |
3704 |
/// <param name="stream">The <see cref="Stream"/> to copy.</param> |
3705 |
/// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> |
3706 |
Stream MakeTemporaryCopy(Stream stream); |
3707 |
|
3708 |
/// <summary> |
3709 |
/// Return a stream suitable for performing direct updates on the original source. |
3710 |
/// </summary> |
3711 |
/// <param name="stream">The current stream.</param> |
3712 |
/// <returns>Returns a stream suitable for direct updating.</returns> |
3713 |
/// <remarks>This may be the current stream passed.</remarks> |
3714 |
Stream OpenForDirectUpdate(Stream stream); |
3715 |
|
3716 |
/// <summary> |
3717 |
/// Dispose of this instance. |
3718 |
/// </summary> |
3719 |
void Dispose(); |
3720 |
} |
3721 |
|
3722 |
/// <summary> |
3723 |
/// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance. |
3724 |
/// </summary> |
3725 |
abstract public class BaseArchiveStorage : IArchiveStorage |
3726 |
{ |
3727 |
#region Constructors |
3728 |
/// <summary> |
3729 |
/// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class. |
3730 |
/// </summary> |
3731 |
/// <param name="updateMode">The update mode.</param> |
3732 |
public BaseArchiveStorage(FileUpdateMode updateMode) |
3733 |
{ |
3734 |
updateMode_ = updateMode; |
3735 |
} |
3736 |
#endregion |
3737 |
|
3738 |
#region IArchiveStorage Members |
3739 |
|
3740 |
/// <summary> |
3741 |
/// Gets a temporary output <see cref="Stream"/> |
3742 |
/// </summary> |
3743 |
/// <returns>Returns the temporary output stream.</returns> |
3744 |
/// <seealso cref="ConvertTemporaryToFinal"></seealso> |
3745 |
public abstract Stream GetTemporaryOutput(); |
3746 |
|
3747 |
/// <summary> |
3748 |
/// Converts the temporary <see cref="Stream"/> to its final form. |
3749 |
/// </summary> |
3750 |
/// <returns>Returns a <see cref="Stream"/> that can be used to read |
3751 |
/// the final storage for the archive.</returns> |
3752 |
/// <seealso cref="GetTemporaryOutput"/> |
3753 |
public abstract Stream ConvertTemporaryToFinal(); |
3754 |
|
3755 |
/// <summary> |
3756 |
/// Make a temporary copy of a <see cref="Stream"/>. |
3757 |
/// </summary> |
3758 |
/// <param name="stream">The <see cref="Stream"/> to make a copy of.</param> |
3759 |
/// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> |
3760 |
public abstract Stream MakeTemporaryCopy(Stream stream); |
3761 |
|
3762 |
/// <summary> |
3763 |
/// Return a stream suitable for performing direct updates on the original source. |
3764 |
/// </summary> |
3765 |
/// <param name="stream">The <see cref="Stream"/> to open for direct update.</param> |
3766 |
/// <returns>Returns a stream suitable for direct updating.</returns> |
3767 |
public abstract Stream OpenForDirectUpdate(Stream stream); |
3768 |
|
3769 |
/// <summary> |
3770 |
/// Disposes this instance. |
3771 |
/// </summary> |
3772 |
public abstract void Dispose(); |
3773 |
|
3774 |
/// <summary> |
3775 |
/// Gets the update mode applicable. |
3776 |
/// </summary> |
3777 |
/// <value>The update mode.</value> |
3778 |
public FileUpdateMode UpdateMode |
3779 |
{ |
3780 |
get { |
3781 |
return updateMode_; |
3782 |
} |
3783 |
} |
3784 |
|
3785 |
#endregion |
3786 |
|
3787 |
#region Instance Fields |
3788 |
FileUpdateMode updateMode_; |
3789 |
#endregion |
3790 |
} |
3791 |
|
3792 |
/// <summary> |
3793 |
/// An <see cref="IArchiveStorage"/> implementation suitable for hard disks. |
3794 |
/// </summary> |
3795 |
public class DiskArchiveStorage : BaseArchiveStorage |
3796 |
{ |
3797 |
#region Constructors |
3798 |
/// <summary> |
3799 |
/// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. |
3800 |
/// </summary> |
3801 |
/// <param name="file">The file.</param> |
3802 |
/// <param name="updateMode">The update mode.</param> |
3803 |
public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) |
3804 |
: base(updateMode) |
3805 |
{ |
3806 |
if ( file.Name == null ) { |
3807 |
throw new ZipException("Cant handle non file archives"); |
3808 |
} |
3809 |
|
3810 |
fileName_ = file.Name; |
3811 |
} |
3812 |
|
3813 |
/// <summary> |
3814 |
/// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class. |
3815 |
/// </summary> |
3816 |
/// <param name="file">The file.</param> |
3817 |
public DiskArchiveStorage(ZipFile file) |
3818 |
: this(file, FileUpdateMode.Safe) |
3819 |
{ |
3820 |
} |
3821 |
#endregion |
3822 |
|
3823 |
#region IArchiveStorage Members |
3824 |
|
3825 |
/// <summary> |
3826 |
/// Gets a temporary output <see cref="Stream"/> for performing updates on. |
3827 |
/// </summary> |
3828 |
/// <returns>Returns the temporary output stream.</returns> |
3829 |
public override Stream GetTemporaryOutput() |
3830 |
{ |
3831 |
if ( temporaryName_ != null ) { |
3832 |
temporaryName_ = GetTempFileName(temporaryName_, true); |
3833 |
temporaryStream_ = File.OpenWrite(temporaryName_); |
3834 |
} |
3835 |
else { |
3836 |
// Determine where to place files based on internal strategy. |
3837 |
// Currently this is always done in system temp directory. |
3838 |
temporaryName_ = Path.GetTempFileName(); |
3839 |
temporaryStream_ = File.OpenWrite(temporaryName_); |
3840 |
} |
3841 |
|
3842 |
return temporaryStream_; |
3843 |
} |
3844 |
|
3845 |
/// <summary> |
3846 |
/// Converts a temporary <see cref="Stream"/> to its final form. |
3847 |
/// </summary> |
3848 |
/// <returns>Returns a <see cref="Stream"/> that can be used to read |
3849 |
/// the final storage for the archive.</returns> |
3850 |
public override Stream ConvertTemporaryToFinal() |
3851 |
{ |
3852 |
if ( temporaryStream_ == null ) { |
3853 |
throw new ZipException("No temporary stream has been created"); |
3854 |
} |
3855 |
|
3856 |
Stream result = null; |
3857 |
|
3858 |
string moveTempName = GetTempFileName(fileName_, false); |
3859 |
bool newFileCreated = false; |
3860 |
|
3861 |
try { |
3862 |
temporaryStream_.Close(); |
3863 |
File.Move(fileName_, moveTempName); |
3864 |
File.Move(temporaryName_, fileName_); |
3865 |
newFileCreated = true; |
3866 |
File.Delete(moveTempName); |
3867 |
|
3868 |
result = File.OpenRead(fileName_); |
3869 |
} |
3870 |
catch(Exception) { |
3871 |
result = null; |
3872 |
|
3873 |
// Try to roll back changes... |
3874 |
if ( !newFileCreated ) { |
3875 |
File.Move(moveTempName, fileName_); |
3876 |
File.Delete(temporaryName_); |
3877 |
} |
3878 |
|
3879 |
throw; |
3880 |
} |
3881 |
|
3882 |
return result; |
3883 |
} |
3884 |
|
3885 |
/// <summary> |
3886 |
/// Make a temporary copy of a stream. |
3887 |
/// </summary> |
3888 |
/// <param name="stream">The <see cref="Stream"/> to copy.</param> |
3889 |
/// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> |
3890 |
public override Stream MakeTemporaryCopy(Stream stream) |
3891 |
{ |
3892 |
stream.Close(); |
3893 |
|
3894 |
temporaryName_ = GetTempFileName(fileName_, true); |
3895 |
File.Copy(fileName_, temporaryName_, true); |
3896 |
|
3897 |
temporaryStream_ = new FileStream(temporaryName_, |
3898 |
FileMode.Open, |
3899 |
FileAccess.ReadWrite); |
3900 |
return temporaryStream_; |
3901 |
} |
3902 |
|
3903 |
/// <summary> |
3904 |
/// Return a stream suitable for performing direct updates on the original source. |
3905 |
/// </summary> |
3906 |
/// <param name="current">The current stream.</param> |
3907 |
/// <returns>Returns a stream suitable for direct updating.</returns> |
3908 |
/// <remarks>If the <paramref name="current"/> stream is not null this is used as is.</remarks> |
3909 |
public override Stream OpenForDirectUpdate(Stream current) |
3910 |
{ |
3911 |
Stream result; |
3912 |
if ((current == null) || !current.CanWrite) |
3913 |
{ |
3914 |
if (current != null) { |
3915 |
current.Close(); |
3916 |
} |
3917 |
|
3918 |
result = new FileStream(fileName_, |
3919 |
FileMode.Open, |
3920 |
FileAccess.ReadWrite); |
3921 |
} |
3922 |
else |
3923 |
{ |
3924 |
result = current; |
3925 |
} |
3926 |
|
3927 |
return result; |
3928 |
} |
3929 |
|
3930 |
/// <summary> |
3931 |
/// Disposes this instance. |
3932 |
/// </summary> |
3933 |
public override void Dispose() |
3934 |
{ |
3935 |
if ( temporaryStream_ != null ) { |
3936 |
temporaryStream_.Close(); |
3937 |
} |
3938 |
} |
3939 |
|
3940 |
#endregion |
3941 |
|
3942 |
#region Internal routines |
3943 |
string GetTempFileName(string original, bool makeTempFile) |
3944 |
{ |
3945 |
string result = null; |
3946 |
|
3947 |
if ( original == null ) { |
3948 |
result = Path.GetTempFileName(); |
3949 |
} |
3950 |
else { |
3951 |
int counter = 0; |
3952 |
int suffixSeed = DateTime.Now.Second; |
3953 |
|
3954 |
while ( result == null ) { |
3955 |
counter += 1; |
3956 |
string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter); |
3957 |
if ( !File.Exists(newName) ) { |
3958 |
if ( makeTempFile) { |
3959 |
try { |
3960 |
// Try and create the file. |
3961 |
using ( FileStream stream = File.Create(newName) ) { |
3962 |
} |
3963 |
result = newName; |
3964 |
} |
3965 |
catch { |
3966 |
suffixSeed = DateTime.Now.Second; |
3967 |
} |
3968 |
} |
3969 |
else { |
3970 |
result = newName; |
3971 |
} |
3972 |
} |
3973 |
} |
3974 |
} |
3975 |
return result; |
3976 |
} |
3977 |
#endregion |
3978 |
|
3979 |
#region Instance Fields |
3980 |
Stream temporaryStream_; |
3981 |
string fileName_; |
3982 |
string temporaryName_; |
3983 |
#endregion |
3984 |
} |
3985 |
|
3986 |
/// <summary> |
3987 |
/// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams. |
3988 |
/// </summary> |
3989 |
public class MemoryArchiveStorage : BaseArchiveStorage |
3990 |
{ |
3991 |
#region Constructors |
3992 |
/// <summary> |
3993 |
/// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. |
3994 |
/// </summary> |
3995 |
public MemoryArchiveStorage() |
3996 |
: base(FileUpdateMode.Direct) |
3997 |
{ |
3998 |
} |
3999 |
|
4000 |
/// <summary> |
4001 |
/// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class. |
4002 |
/// </summary> |
4003 |
/// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param> |
4004 |
/// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks> |
4005 |
public MemoryArchiveStorage(FileUpdateMode updateMode) |
4006 |
: base(updateMode) |
4007 |
{ |
4008 |
} |
4009 |
|
4010 |
#endregion |
4011 |
|
4012 |
#region Properties |
4013 |
/// <summary> |
4014 |
/// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called. |
4015 |
/// </summary> |
4016 |
public MemoryStream FinalStream |
4017 |
{ |
4018 |
get { return finalStream_; } |
4019 |
} |
4020 |
|
4021 |
#endregion |
4022 |
|
4023 |
#region IArchiveStorage Members |
4024 |
|
4025 |
/// <summary> |
4026 |
/// Gets the temporary output <see cref="Stream"/> |
4027 |
/// </summary> |
4028 |
/// <returns>Returns the temporary output stream.</returns> |
4029 |
public override Stream GetTemporaryOutput() |
4030 |
{ |
4031 |
temporaryStream_ = new MemoryStream(); |
4032 |
return temporaryStream_; |
4033 |
} |
4034 |
|
4035 |
/// <summary> |
4036 |
/// Converts the temporary <see cref="Stream"/> to its final form. |
4037 |
/// </summary> |
4038 |
/// <returns>Returns a <see cref="Stream"/> that can be used to read |
4039 |
/// the final storage for the archive.</returns> |
4040 |
public override Stream ConvertTemporaryToFinal() |
4041 |
{ |
4042 |
if ( temporaryStream_ == null ) { |
4043 |
throw new ZipException("No temporary stream has been created"); |
4044 |
} |
4045 |
|
4046 |
finalStream_ = new MemoryStream(temporaryStream_.ToArray()); |
4047 |
return finalStream_; |
4048 |
} |
4049 |
|
4050 |
/// <summary> |
4051 |
/// Make a temporary copy of the original stream. |
4052 |
/// </summary> |
4053 |
/// <param name="stream">The <see cref="Stream"/> to copy.</param> |
4054 |
/// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns> |
4055 |
public override Stream MakeTemporaryCopy(Stream stream) |
4056 |
{ |
4057 |
temporaryStream_ = new MemoryStream(); |
4058 |
stream.Position = 0; |
4059 |
StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); |
4060 |
return temporaryStream_; |
4061 |
} |
4062 |
|
4063 |
/// <summary> |
4064 |
/// Return a stream suitable for performing direct updates on the original source. |
4065 |
/// </summary> |
4066 |
/// <param name="stream">The original source stream</param> |
4067 |
/// <returns>Returns a stream suitable for direct updating.</returns> |
4068 |
/// <remarks>If the <paramref name="stream"/> passed is not null this is used; |
4069 |
/// otherwise a new <see cref="MemoryStream"/> is returned.</remarks> |
4070 |
public override Stream OpenForDirectUpdate(Stream stream) |
4071 |
{ |
4072 |
Stream result; |
4073 |
if ((stream == null) || !stream.CanWrite) { |
4074 |
|
4075 |
result = new MemoryStream(); |
4076 |
|
4077 |
if (stream != null) { |
4078 |
stream.Position = 0; |
4079 |
StreamUtils.Copy(stream, result, new byte[4096]); |
4080 |
|
4081 |
stream.Close(); |
4082 |
} |
4083 |
} |
4084 |
else { |
4085 |
result = stream; |
4086 |
} |
4087 |
|
4088 |
return result; |
4089 |
} |
4090 |
|
4091 |
/// <summary> |
4092 |
/// Disposes this instance. |
4093 |
/// </summary> |
4094 |
public override void Dispose() |
4095 |
{ |
4096 |
if ( temporaryStream_ != null ) { |
4097 |
temporaryStream_.Close(); |
4098 |
} |
4099 |
} |
4100 |
|
4101 |
#endregion |
4102 |
|
4103 |
#region Instance Fields |
4104 |
MemoryStream temporaryStream_; |
4105 |
MemoryStream finalStream_; |
4106 |
#endregion |
4107 |
} |
4108 |
} |