root / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Zip / ZipOutputStream.cs @ 0eea575a
History | View | Annotate | Download (30.2 kB)
1 |
// ZipOutputStream.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.Generic; |
42 |
using System.IO; |
43 |
using ICSharpCode.SharpZipLib.Silverlight.Checksums; |
44 |
using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression; |
45 |
using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression.Streams; |
46 |
using ICSharpCode.SharpZipLib.Zip; |
47 |
|
48 |
namespace ICSharpCode.SharpZipLib.Silverlight.Zip |
49 |
{ |
50 |
/// <summary> |
51 |
/// This is a DeflaterOutputStream that writes the files into a zip |
52 |
/// archive one after another. It has a special method to start a new |
53 |
/// zip entry. The zip entries contains information about the file name |
54 |
/// size, compressed size, CRC, etc. |
55 |
/// |
56 |
/// It includes support for Stored and Deflated entries. |
57 |
/// This class is not thread safe. |
58 |
/// <br/> |
59 |
/// <br/>Author of the original java version : Jochen Hoenicke |
60 |
/// </summary> |
61 |
/// <example> This sample shows how to create a zip file |
62 |
/// <code> |
63 |
/// using System; |
64 |
/// using System.IO; |
65 |
/// |
66 |
/// using ICSharpCode.SharpZipLib.Core; |
67 |
/// using ICSharpCode.SharpZipLib.Zip; |
68 |
/// |
69 |
/// class MainClass |
70 |
/// { |
71 |
/// public static void Main(string[] args) |
72 |
/// { |
73 |
/// string[] filenames = Directory.GetFiles(args[0]); |
74 |
/// byte[] buffer = new byte[4096]; |
75 |
/// |
76 |
/// using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) { |
77 |
/// |
78 |
/// s.SetLevel(9); // 0 - store only to 9 - means best compression |
79 |
/// |
80 |
/// foreach (string file in filenames) { |
81 |
/// ZipEntry entry = new ZipEntry(file); |
82 |
/// s.PutNextEntry(entry); |
83 |
/// |
84 |
/// using (FileStream fs = File.OpenRead(file)) { |
85 |
/// StreamUtils.Copy(fs, s, buffer); |
86 |
/// } |
87 |
/// } |
88 |
/// } |
89 |
/// } |
90 |
/// } |
91 |
/// </code> |
92 |
/// </example> |
93 |
public class ZipOutputStream : DeflaterOutputStream |
94 |
{ |
95 |
#region Constructors |
96 |
|
97 |
/// <summary> |
98 |
/// Creates a new Zip output stream, writing a zip archive. |
99 |
/// </summary> |
100 |
/// <param name="baseOutputStream"> |
101 |
/// The output stream to which the archive contents are written. |
102 |
/// </param> |
103 |
public ZipOutputStream(Stream baseOutputStream) |
104 |
: base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)) |
105 |
{ |
106 |
} |
107 |
|
108 |
#endregion |
109 |
|
110 |
/// <summary> |
111 |
/// Gets a flag value of true if the central header has been added for this archive; false if it has not been added. |
112 |
/// </summary> |
113 |
/// <remarks>No further entries can be added once this has been done.</remarks> |
114 |
public bool IsFinished |
115 |
{ |
116 |
get { return entries == null; } |
117 |
} |
118 |
|
119 |
/// <summary> |
120 |
/// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. |
121 |
/// </summary> |
122 |
/// <remarks>Older archivers may not understand Zip64 extensions. |
123 |
/// If backwards compatability is an issue be careful when adding <see cref="ZipEntry.Size">entries</see> to an archive. |
124 |
/// Setting this property to off is workable but less desirable as in those circumstances adding a file |
125 |
/// larger then 4GB will fail.</remarks> |
126 |
public UseZip64 UseZip64 |
127 |
{ |
128 |
get { return useZip64_; } |
129 |
set { useZip64_ = value; } |
130 |
} |
131 |
|
132 |
/// <summary> |
133 |
/// Set the zip file comment. |
134 |
/// </summary> |
135 |
/// <param name="comment"> |
136 |
/// The comment text for the entire archive. |
137 |
/// </param> |
138 |
/// <exception name ="ArgumentOutOfRangeException"> |
139 |
/// The converted comment is longer than 0xffff bytes. |
140 |
/// </exception> |
141 |
public void SetComment(string comment) |
142 |
{ |
143 |
// TODO: Its not yet clear how to handle unicode comments here. |
144 |
var commentBytes = ZipConstants.ConvertToArray(comment); |
145 |
if (commentBytes.Length > 0xffff) |
146 |
{ |
147 |
throw new ArgumentOutOfRangeException("comment"); |
148 |
} |
149 |
zipComment = commentBytes; |
150 |
} |
151 |
|
152 |
/// <summary> |
153 |
/// Sets the compression level. The new level will be activated |
154 |
/// immediately. |
155 |
/// </summary> |
156 |
/// <param name="level">The new compression level (1 to 9).</param> |
157 |
/// <exception cref="ArgumentOutOfRangeException"> |
158 |
/// Level specified is not supported. |
159 |
/// </exception> |
160 |
/// <see cref="Deflater"/> |
161 |
public void SetLevel(int level) |
162 |
{ |
163 |
deflater_.SetLevel(level); |
164 |
defaultCompressionLevel = level; |
165 |
} |
166 |
|
167 |
/// <summary> |
168 |
/// Get the current deflater compression level |
169 |
/// </summary> |
170 |
/// <returns>The current compression level</returns> |
171 |
public int GetLevel() |
172 |
{ |
173 |
return deflater_.GetLevel(); |
174 |
} |
175 |
|
176 |
/// <summary> |
177 |
/// Write an unsigned short in little endian byte order. |
178 |
/// </summary> |
179 |
private void WriteLeShort(int value) |
180 |
{ |
181 |
unchecked |
182 |
{ |
183 |
baseOutputStream_.WriteByte((byte) (value & 0xff)); |
184 |
baseOutputStream_.WriteByte((byte) ((value >> 8) & 0xff)); |
185 |
} |
186 |
} |
187 |
|
188 |
/// <summary> |
189 |
/// Write an int in little endian byte order. |
190 |
/// </summary> |
191 |
private void WriteLeInt(int value) |
192 |
{ |
193 |
unchecked |
194 |
{ |
195 |
WriteLeShort(value); |
196 |
WriteLeShort(value >> 16); |
197 |
} |
198 |
} |
199 |
|
200 |
/// <summary> |
201 |
/// Write an int in little endian byte order. |
202 |
/// </summary> |
203 |
private void WriteLeLong(long value) |
204 |
{ |
205 |
unchecked |
206 |
{ |
207 |
WriteLeInt((int) value); |
208 |
WriteLeInt((int) (value >> 32)); |
209 |
} |
210 |
} |
211 |
|
212 |
/// <summary> |
213 |
/// Starts a new Zip entry. It automatically closes the previous |
214 |
/// entry if present. |
215 |
/// All entry elements bar name are optional, but must be correct if present. |
216 |
/// If the compression method is stored and the output is not patchable |
217 |
/// the compression for that entry is automatically changed to deflate level 0 |
218 |
/// </summary> |
219 |
/// <param name="entry"> |
220 |
/// the entry. |
221 |
/// </param> |
222 |
/// <exception cref="System.ArgumentNullException"> |
223 |
/// if entry passed is null. |
224 |
/// </exception> |
225 |
/// <exception cref="System.IO.IOException"> |
226 |
/// if an I/O error occured. |
227 |
/// </exception> |
228 |
/// <exception cref="System.InvalidOperationException"> |
229 |
/// if stream was finished |
230 |
/// </exception> |
231 |
/// <exception cref="ZipException"> |
232 |
/// Too many entries in the Zip file<br/> |
233 |
/// Entry name is too long<br/> |
234 |
/// Finish has already been called<br/> |
235 |
/// </exception> |
236 |
public void PutNextEntry(ZipEntry entry) |
237 |
{ |
238 |
if (entry == null) |
239 |
{ |
240 |
throw new ArgumentNullException("entry"); |
241 |
} |
242 |
|
243 |
if (entries == null) |
244 |
{ |
245 |
throw new InvalidOperationException("ZipOutputStream was finished"); |
246 |
} |
247 |
|
248 |
if (curEntry != null) |
249 |
{ |
250 |
CloseEntry(); |
251 |
} |
252 |
|
253 |
if (entries.Count == int.MaxValue) |
254 |
{ |
255 |
throw new ZipException("Too many entries for Zip file"); |
256 |
} |
257 |
|
258 |
var method = entry.CompressionMethod; |
259 |
var compressionLevel = defaultCompressionLevel; |
260 |
|
261 |
// Clear flags that the library manages internally |
262 |
entry.Flags &= (int) GeneralBitFlags.UnicodeText; |
263 |
patchEntryHeader = false; |
264 |
var headerInfoAvailable = true; |
265 |
|
266 |
if (method == CompressionMethod.Stored) |
267 |
{ |
268 |
// Cant store values in a data descriptor as you cant extract stored files |
269 |
// if the length isnt known. |
270 |
entry.Flags &= ~8; |
271 |
if (entry.CompressedSize >= 0) |
272 |
{ |
273 |
if (entry.Size < 0) |
274 |
{ |
275 |
entry.Size = entry.CompressedSize; |
276 |
} |
277 |
else if (entry.Size != entry.CompressedSize) |
278 |
{ |
279 |
throw new ZipException("Method STORED, but compressed size != size"); |
280 |
} |
281 |
} |
282 |
else |
283 |
{ |
284 |
if (entry.Size >= 0) |
285 |
{ |
286 |
entry.CompressedSize = entry.Size; |
287 |
} |
288 |
} |
289 |
|
290 |
if (entry.Size < 0 || entry.Crc < 0) |
291 |
{ |
292 |
if (CanPatchEntries) |
293 |
{ |
294 |
headerInfoAvailable = false; |
295 |
} |
296 |
else |
297 |
{ |
298 |
// Can't patch entries so storing is not possible. |
299 |
method = CompressionMethod.Deflated; |
300 |
compressionLevel = 0; |
301 |
} |
302 |
} |
303 |
} |
304 |
|
305 |
if (method == CompressionMethod.Deflated) |
306 |
{ |
307 |
if (entry.Size == 0) |
308 |
{ |
309 |
// No need to compress - no data. |
310 |
entry.CompressedSize = entry.Size; |
311 |
entry.Crc = 0; |
312 |
method = CompressionMethod.Stored; |
313 |
} |
314 |
else if ((entry.CompressedSize < 0) || (entry.Size < 0) || (entry.Crc < 0)) |
315 |
{ |
316 |
headerInfoAvailable = false; |
317 |
} |
318 |
} |
319 |
|
320 |
if (headerInfoAvailable == false) |
321 |
{ |
322 |
if (CanPatchEntries == false) |
323 |
{ |
324 |
// Only way to record size and compressed size is to append a data descriptor |
325 |
// after compressed data. |
326 |
entry.Flags |= 8; |
327 |
} |
328 |
else |
329 |
{ |
330 |
patchEntryHeader = true; |
331 |
} |
332 |
} |
333 |
|
334 |
if (Password != null) |
335 |
{ |
336 |
entry.IsCrypted = true; |
337 |
if (entry.Crc < 0) |
338 |
{ |
339 |
// Need to append a data descriptor as the crc isnt available for use |
340 |
// with encryption, the date is used instead. Setting the flag |
341 |
// indicates this to the decompressor. |
342 |
entry.Flags |= 8; |
343 |
} |
344 |
} |
345 |
|
346 |
entry.Offset = _offset; |
347 |
entry.CompressionMethod = method; |
348 |
|
349 |
curMethod = method; |
350 |
sizePatchPos = -1; |
351 |
|
352 |
if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic))) |
353 |
{ |
354 |
entry.ForceZip64(); |
355 |
} |
356 |
|
357 |
// Write the local file header |
358 |
WriteLeInt(ZipConstants.LocalHeaderSignature); |
359 |
|
360 |
WriteLeShort(entry.Version); |
361 |
WriteLeShort(entry.Flags); |
362 |
WriteLeShort((byte) method); |
363 |
WriteLeInt((int) entry.DosTime); |
364 |
|
365 |
// TODO: Refactor header writing. Its done in several places. |
366 |
if (headerInfoAvailable) |
367 |
{ |
368 |
WriteLeInt((int) entry.Crc); |
369 |
if (entry.LocalHeaderRequiresZip64) |
370 |
{ |
371 |
WriteLeInt(-1); |
372 |
WriteLeInt(-1); |
373 |
} |
374 |
else |
375 |
{ |
376 |
WriteLeInt(entry.IsCrypted |
377 |
? (int) entry.CompressedSize + ZipConstants.CryptoHeaderSize |
378 |
: (int) entry.CompressedSize); |
379 |
WriteLeInt((int) entry.Size); |
380 |
} |
381 |
} |
382 |
else |
383 |
{ |
384 |
if (patchEntryHeader) |
385 |
{ |
386 |
crcPatchPos = baseOutputStream_.Position; |
387 |
} |
388 |
WriteLeInt(0); // Crc |
389 |
|
390 |
if (patchEntryHeader) |
391 |
{ |
392 |
sizePatchPos = baseOutputStream_.Position; |
393 |
} |
394 |
|
395 |
// For local header both sizes appear in Zip64 Extended Information |
396 |
if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) |
397 |
{ |
398 |
WriteLeInt(-1); |
399 |
WriteLeInt(-1); |
400 |
} |
401 |
else |
402 |
{ |
403 |
WriteLeInt(0); // Compressed size |
404 |
WriteLeInt(0); // Uncompressed size |
405 |
} |
406 |
} |
407 |
|
408 |
var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); |
409 |
|
410 |
if (name.Length > 0xFFFF) |
411 |
{ |
412 |
throw new ZipException("Entry name too long."); |
413 |
} |
414 |
|
415 |
var ed = new ZipExtraData(entry.ExtraData); |
416 |
|
417 |
if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) |
418 |
{ |
419 |
ed.StartNewEntry(); |
420 |
if (headerInfoAvailable) |
421 |
{ |
422 |
ed.AddLeLong(entry.Size); |
423 |
ed.AddLeLong(entry.CompressedSize); |
424 |
} |
425 |
else |
426 |
{ |
427 |
ed.AddLeLong(-1); |
428 |
ed.AddLeLong(-1); |
429 |
} |
430 |
ed.AddNewEntry(1); |
431 |
|
432 |
if (!ed.Find(1)) |
433 |
{ |
434 |
throw new ZipException("Internal error cant find extra data"); |
435 |
} |
436 |
|
437 |
if (patchEntryHeader) |
438 |
{ |
439 |
sizePatchPos = ed.CurrentReadIndex; |
440 |
} |
441 |
} |
442 |
else |
443 |
{ |
444 |
ed.Delete(1); |
445 |
} |
446 |
|
447 |
var extra = ed.GetEntryData(); |
448 |
|
449 |
WriteLeShort(name.Length); |
450 |
WriteLeShort(extra.Length); |
451 |
|
452 |
if (name.Length > 0) |
453 |
{ |
454 |
baseOutputStream_.Write(name, 0, name.Length); |
455 |
} |
456 |
|
457 |
if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) |
458 |
{ |
459 |
sizePatchPos += baseOutputStream_.Position; |
460 |
} |
461 |
|
462 |
if (extra.Length > 0) |
463 |
{ |
464 |
baseOutputStream_.Write(extra, 0, extra.Length); |
465 |
} |
466 |
|
467 |
_offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length; |
468 |
|
469 |
// Activate the entry. |
470 |
curEntry = entry; |
471 |
crc.Reset(); |
472 |
if (method == CompressionMethod.Deflated) |
473 |
{ |
474 |
deflater_.Reset(); |
475 |
deflater_.SetLevel(compressionLevel); |
476 |
} |
477 |
size = 0; |
478 |
|
479 |
if (entry.IsCrypted) |
480 |
{ |
481 |
if (entry.Crc < 0) |
482 |
{ |
483 |
// so testing Zip will says its ok |
484 |
WriteEncryptionHeader(entry.DosTime << 16); |
485 |
} |
486 |
else |
487 |
{ |
488 |
WriteEncryptionHeader(entry.Crc); |
489 |
} |
490 |
} |
491 |
} |
492 |
|
493 |
/// <summary> |
494 |
/// Closes the current entry, updating header and footer information as required |
495 |
/// </summary> |
496 |
/// <exception cref="System.IO.IOException"> |
497 |
/// An I/O error occurs. |
498 |
/// </exception> |
499 |
/// <exception cref="System.InvalidOperationException"> |
500 |
/// No entry is active. |
501 |
/// </exception> |
502 |
public void CloseEntry() |
503 |
{ |
504 |
if (curEntry == null) |
505 |
{ |
506 |
throw new InvalidOperationException("No open entry"); |
507 |
} |
508 |
|
509 |
// First finish the deflater, if appropriate |
510 |
if (curMethod == CompressionMethod.Deflated) |
511 |
{ |
512 |
base.Finish(); |
513 |
} |
514 |
|
515 |
var csize = (curMethod == CompressionMethod.Deflated) ? deflater_.TotalOut : size; |
516 |
|
517 |
if (curEntry.Size < 0) |
518 |
{ |
519 |
curEntry.Size = size; |
520 |
} |
521 |
else if (curEntry.Size != size) |
522 |
{ |
523 |
throw new ZipException("size was " + size + ", but I expected " + curEntry.Size); |
524 |
} |
525 |
|
526 |
if (curEntry.CompressedSize < 0) |
527 |
{ |
528 |
curEntry.CompressedSize = csize; |
529 |
} |
530 |
else if (curEntry.CompressedSize != csize) |
531 |
{ |
532 |
throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize); |
533 |
} |
534 |
|
535 |
if (curEntry.Crc < 0) |
536 |
{ |
537 |
curEntry.Crc = crc.Value; |
538 |
} |
539 |
else if (curEntry.Crc != crc.Value) |
540 |
{ |
541 |
throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc); |
542 |
} |
543 |
|
544 |
_offset += csize; |
545 |
|
546 |
if (curEntry.IsCrypted) |
547 |
{ |
548 |
curEntry.CompressedSize += ZipConstants.CryptoHeaderSize; |
549 |
} |
550 |
|
551 |
// Patch the header if possible |
552 |
if (patchEntryHeader) |
553 |
{ |
554 |
patchEntryHeader = false; |
555 |
|
556 |
var curPos = baseOutputStream_.Position; |
557 |
baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin); |
558 |
WriteLeInt((int) curEntry.Crc); |
559 |
|
560 |
if (curEntry.LocalHeaderRequiresZip64) |
561 |
{ |
562 |
if (sizePatchPos == -1) |
563 |
{ |
564 |
throw new ZipException("Entry requires zip64 but this has been turned off"); |
565 |
} |
566 |
|
567 |
baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin); |
568 |
WriteLeLong(curEntry.Size); |
569 |
WriteLeLong(curEntry.CompressedSize); |
570 |
} |
571 |
else |
572 |
{ |
573 |
WriteLeInt((int) curEntry.CompressedSize); |
574 |
WriteLeInt((int) curEntry.Size); |
575 |
} |
576 |
baseOutputStream_.Seek(curPos, SeekOrigin.Begin); |
577 |
} |
578 |
|
579 |
// Add data descriptor if flagged as required |
580 |
if ((curEntry.Flags & 8) != 0) |
581 |
{ |
582 |
WriteLeInt(ZipConstants.DataDescriptorSignature); |
583 |
WriteLeInt(unchecked((int) curEntry.Crc)); |
584 |
|
585 |
if (curEntry.LocalHeaderRequiresZip64) |
586 |
{ |
587 |
WriteLeLong(curEntry.CompressedSize); |
588 |
WriteLeLong(curEntry.Size); |
589 |
_offset += ZipConstants.Zip64DataDescriptorSize; |
590 |
} |
591 |
else |
592 |
{ |
593 |
WriteLeInt((int) curEntry.CompressedSize); |
594 |
WriteLeInt((int) curEntry.Size); |
595 |
_offset += ZipConstants.DataDescriptorSize; |
596 |
} |
597 |
} |
598 |
|
599 |
entries.Add(curEntry); |
600 |
curEntry = null; |
601 |
} |
602 |
|
603 |
private void WriteEncryptionHeader(long crcValue) |
604 |
{ |
605 |
_offset += ZipConstants.CryptoHeaderSize; |
606 |
|
607 |
InitializePassword(Password); |
608 |
|
609 |
var cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; |
610 |
var rnd = new Random(); |
611 |
rnd.NextBytes(cryptBuffer); |
612 |
cryptBuffer[11] = (byte) (crcValue >> 24); |
613 |
|
614 |
EncryptBlock(cryptBuffer, 0, cryptBuffer.Length); |
615 |
baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length); |
616 |
} |
617 |
|
618 |
/// <summary> |
619 |
/// Writes the given buffer to the current entry. |
620 |
/// </summary> |
621 |
/// <param name="buffer">The buffer containing data to write.</param> |
622 |
/// <param name="offset">The offset of the first byte to write.</param> |
623 |
/// <param name="count">The number of bytes to write.</param> |
624 |
/// <exception cref="ZipException">Archive size is invalid</exception> |
625 |
/// <exception cref="System.InvalidOperationException">No entry is active.</exception> |
626 |
public override void Write(byte[] buffer, int offset, int count) |
627 |
{ |
628 |
if (curEntry == null) |
629 |
{ |
630 |
throw new InvalidOperationException("No open entry."); |
631 |
} |
632 |
|
633 |
if (buffer == null) |
634 |
{ |
635 |
throw new ArgumentNullException("buffer"); |
636 |
} |
637 |
|
638 |
if (offset < 0) |
639 |
{ |
640 |
throw new ArgumentOutOfRangeException("offset", "Cannot be negative"); |
641 |
} |
642 |
|
643 |
if (count < 0) |
644 |
{ |
645 |
throw new ArgumentOutOfRangeException("count", "Cannot be negative"); |
646 |
} |
647 |
|
648 |
if ((buffer.Length - offset) < count) |
649 |
{ |
650 |
throw new ArgumentException("Invalid offset/count combination"); |
651 |
} |
652 |
|
653 |
crc.Update(buffer, offset, count); |
654 |
size += count; |
655 |
|
656 |
switch (curMethod) |
657 |
{ |
658 |
case CompressionMethod.Deflated: |
659 |
base.Write(buffer, offset, count); |
660 |
break; |
661 |
|
662 |
case CompressionMethod.Stored: |
663 |
if (Password != null) |
664 |
{ |
665 |
CopyAndEncrypt(buffer, offset, count); |
666 |
} |
667 |
else |
668 |
{ |
669 |
baseOutputStream_.Write(buffer, offset, count); |
670 |
} |
671 |
break; |
672 |
} |
673 |
} |
674 |
|
675 |
private void CopyAndEncrypt(byte[] buffer, int offset, int count) |
676 |
{ |
677 |
const int CopyBufferSize = 4096; |
678 |
var localBuffer = new byte[CopyBufferSize]; |
679 |
while (count > 0) |
680 |
{ |
681 |
var bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize; |
682 |
|
683 |
Array.Copy(buffer, offset, localBuffer, 0, bufferCount); |
684 |
EncryptBlock(localBuffer, 0, bufferCount); |
685 |
baseOutputStream_.Write(localBuffer, 0, bufferCount); |
686 |
count -= bufferCount; |
687 |
offset += bufferCount; |
688 |
} |
689 |
} |
690 |
|
691 |
/// <summary> |
692 |
/// Finishes the stream. This will write the central directory at the |
693 |
/// end of the zip file and flush the stream. |
694 |
/// </summary> |
695 |
/// <remarks> |
696 |
/// This is automatically called when the stream is closed. |
697 |
/// </remarks> |
698 |
/// <exception cref="System.IO.IOException"> |
699 |
/// An I/O error occurs. |
700 |
/// </exception> |
701 |
/// <exception cref="ZipException"> |
702 |
/// Comment exceeds the maximum length<br/> |
703 |
/// Entry name exceeds the maximum length |
704 |
/// </exception> |
705 |
public override void Finish() |
706 |
{ |
707 |
if (entries == null) |
708 |
{ |
709 |
return; |
710 |
} |
711 |
|
712 |
if (curEntry != null) |
713 |
{ |
714 |
CloseEntry(); |
715 |
} |
716 |
|
717 |
long numEntries = entries.Count; |
718 |
long sizeEntries = 0; |
719 |
|
720 |
foreach (var entry in entries) |
721 |
{ |
722 |
WriteLeInt(ZipConstants.CentralHeaderSignature); |
723 |
WriteLeShort(ZipConstants.VersionMadeBy); |
724 |
WriteLeShort(entry.Version); |
725 |
WriteLeShort(entry.Flags); |
726 |
WriteLeShort((short) entry.CompressionMethod); |
727 |
WriteLeInt((int) entry.DosTime); |
728 |
WriteLeInt((int) entry.Crc); |
729 |
|
730 |
if (entry.IsZip64Forced() || |
731 |
(entry.CompressedSize >= uint.MaxValue)) |
732 |
{ |
733 |
WriteLeInt(-1); |
734 |
} |
735 |
else |
736 |
{ |
737 |
WriteLeInt((int) entry.CompressedSize); |
738 |
} |
739 |
|
740 |
if (entry.IsZip64Forced() || |
741 |
(entry.Size >= uint.MaxValue)) |
742 |
{ |
743 |
WriteLeInt(-1); |
744 |
} |
745 |
else |
746 |
{ |
747 |
WriteLeInt((int) entry.Size); |
748 |
} |
749 |
|
750 |
var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); |
751 |
|
752 |
if (name.Length > 0xffff) |
753 |
{ |
754 |
throw new ZipException("Name too long."); |
755 |
} |
756 |
|
757 |
var ed = new ZipExtraData(entry.ExtraData); |
758 |
|
759 |
if (entry.CentralHeaderRequiresZip64) |
760 |
{ |
761 |
ed.StartNewEntry(); |
762 |
if (entry.IsZip64Forced() || |
763 |
(entry.Size >= 0xffffffff)) |
764 |
{ |
765 |
ed.AddLeLong(entry.Size); |
766 |
} |
767 |
|
768 |
if (entry.IsZip64Forced() || |
769 |
(entry.CompressedSize >= 0xffffffff)) |
770 |
{ |
771 |
ed.AddLeLong(entry.CompressedSize); |
772 |
} |
773 |
|
774 |
if (entry.Offset >= 0xffffffff) |
775 |
{ |
776 |
ed.AddLeLong(entry.Offset); |
777 |
} |
778 |
|
779 |
ed.AddNewEntry(1); |
780 |
} |
781 |
else |
782 |
{ |
783 |
ed.Delete(1); |
784 |
} |
785 |
|
786 |
var extra = ed.GetEntryData(); |
787 |
|
788 |
var entryComment = |
789 |
(entry.Comment != null) |
790 |
? |
791 |
ZipConstants.ConvertToArray(entry.Flags, entry.Comment) |
792 |
: |
793 |
new byte[0]; |
794 |
|
795 |
if (entryComment.Length > 0xffff) |
796 |
{ |
797 |
throw new ZipException("Comment too long."); |
798 |
} |
799 |
|
800 |
WriteLeShort(name.Length); |
801 |
WriteLeShort(extra.Length); |
802 |
WriteLeShort(entryComment.Length); |
803 |
WriteLeShort(0); // disk number |
804 |
WriteLeShort(0); // internal file attributes |
805 |
// external file attributes |
806 |
|
807 |
if (entry.ExternalFileAttributes != -1) |
808 |
{ |
809 |
WriteLeInt(entry.ExternalFileAttributes); |
810 |
} |
811 |
else |
812 |
{ |
813 |
if (entry.IsDirectory) |
814 |
{ |
815 |
// mark entry as directory (from nikolam.AT.perfectinfo.com) |
816 |
WriteLeInt(16); |
817 |
} |
818 |
else |
819 |
{ |
820 |
WriteLeInt(0); |
821 |
} |
822 |
} |
823 |
|
824 |
if (entry.Offset >= uint.MaxValue) |
825 |
{ |
826 |
WriteLeInt(-1); |
827 |
} |
828 |
else |
829 |
{ |
830 |
WriteLeInt((int) entry.Offset); |
831 |
} |
832 |
|
833 |
if (name.Length > 0) |
834 |
{ |
835 |
baseOutputStream_.Write(name, 0, name.Length); |
836 |
} |
837 |
|
838 |
if (extra.Length > 0) |
839 |
{ |
840 |
baseOutputStream_.Write(extra, 0, extra.Length); |
841 |
} |
842 |
|
843 |
if (entryComment.Length > 0) |
844 |
{ |
845 |
baseOutputStream_.Write(entryComment, 0, entryComment.Length); |
846 |
} |
847 |
|
848 |
sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length; |
849 |
} |
850 |
|
851 |
using (var zhs = new ZipHelperStream(baseOutputStream_)) |
852 |
{ |
853 |
zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, _offset, zipComment); |
854 |
} |
855 |
|
856 |
entries = null; |
857 |
} |
858 |
|
859 |
#region Instance Fields |
860 |
|
861 |
/// <summary> |
862 |
/// Used to track the crc of data added to entries. |
863 |
/// </summary> |
864 |
private readonly Crc32 crc = new Crc32(); |
865 |
|
866 |
/// <summary> |
867 |
/// Position to patch crc |
868 |
/// </summary> |
869 |
private long crcPatchPos = -1; |
870 |
|
871 |
/// <summary> |
872 |
/// The current entry being added. |
873 |
/// </summary> |
874 |
private ZipEntry curEntry; |
875 |
|
876 |
private CompressionMethod curMethod = CompressionMethod.Deflated; |
877 |
private int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION; |
878 |
|
879 |
/// <summary> |
880 |
/// The entries for the archive. |
881 |
/// </summary> |
882 |
private List<ZipEntry> entries = new List<ZipEntry>(); |
883 |
|
884 |
/// <summary> |
885 |
/// Offset to be recorded for each entry in the central header. |
886 |
/// </summary> |
887 |
private long _offset; |
888 |
|
889 |
/// <summary> |
890 |
/// Flag indicating that header patching is required for the current entry. |
891 |
/// </summary> |
892 |
private bool patchEntryHeader; |
893 |
|
894 |
/// <summary> |
895 |
/// Used to track the size of data for an entry during writing. |
896 |
/// </summary> |
897 |
private long size; |
898 |
|
899 |
/// <summary> |
900 |
/// Position to patch size. |
901 |
/// </summary> |
902 |
private long sizePatchPos = -1; |
903 |
|
904 |
// Default is dynamic which is not backwards compatible and can cause problems |
905 |
// with XP's built in compression which cant read Zip64 archives. |
906 |
// However it does avoid the situation were a large file is added and cannot be completed correctly. |
907 |
// NOTE: Setting the size for entries before they are added is the best solution! |
908 |
private UseZip64 useZip64_ = UseZip64.Dynamic; |
909 |
|
910 |
/// <summary> |
911 |
/// Comment for the entire archive recorded in central header. |
912 |
/// </summary> |
913 |
private byte[] zipComment = new byte[0]; |
914 |
|
915 |
#endregion |
916 |
} |
917 |
} |