00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040 using System;
00041 using System.IO;
00042 using System.Collections;
00043 using System.Text;
00044
00045 using ICSharpCode.SharpZipLib.Checksums;
00046 using ICSharpCode.SharpZipLib.Zip.Compression;
00047 using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
00048
00049 namespace ICSharpCode.SharpZipLib.Zip
00050 {
00099 public class ZipOutputStream : DeflaterOutputStream
00100 {
00101 private ArrayList entries = new ArrayList();
00102 private Crc32 crc = new Crc32();
00103 private ZipEntry curEntry = null;
00104
00105 int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION;
00106 CompressionMethod curMethod = CompressionMethod.Deflated;
00107
00108
00109 private long size;
00110 private long offset = 0;
00111
00112 private byte[] zipComment = new byte[0];
00113
00118 public bool IsFinished {
00119 get {
00120 return entries == null;
00121 }
00122 }
00123
00130 public ZipOutputStream(Stream baseOutputStream) : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true))
00131 {
00132 }
00133
00143 public void SetComment(string comment)
00144 {
00145 byte[] commentBytes = ZipConstants.ConvertToArray(comment);
00146 if (commentBytes.Length > 0xffff) {
00147 throw new ArgumentOutOfRangeException("comment");
00148 }
00149 zipComment = commentBytes;
00150 }
00151
00160 public void SetLevel(int level)
00161 {
00162 defaultCompressionLevel = level;
00163 def.SetLevel(level);
00164 }
00165
00170 public int GetLevel()
00171 {
00172 return def.GetLevel();
00173 }
00174
00178 private void WriteLeShort(int value)
00179 {
00180 baseOutputStream.WriteByte((byte)(value & 0xff));
00181 baseOutputStream.WriteByte((byte)((value >> 8) & 0xff));
00182 }
00183
00187 private void WriteLeInt(int value)
00188 {
00189 WriteLeShort(value);
00190 WriteLeShort(value >> 16);
00191 }
00192
00196 private void WriteLeLong(long value)
00197 {
00198 WriteLeInt((int)value);
00199 WriteLeInt((int)(value >> 32));
00200 }
00201
00202
00203 bool patchEntryHeader = false;
00204
00205 long headerPatchPos = -1;
00206
00228 public void PutNextEntry(ZipEntry entry)
00229 {
00230 if (entries == null) {
00231 throw new InvalidOperationException("ZipOutputStream was finished");
00232 }
00233
00234 if (curEntry != null) {
00235 CloseEntry();
00236 }
00237
00238 if (entries.Count >= 0xffff) {
00239 throw new ZipException("Too many entries for Zip file");
00240 }
00241
00242 CompressionMethod method = entry.CompressionMethod;
00243 int compressionLevel = defaultCompressionLevel;
00244
00245 entry.Flags = 0;
00246 patchEntryHeader = false;
00247 bool headerInfoAvailable = true;
00248
00249 if (method == CompressionMethod.Stored) {
00250 if (entry.CompressedSize >= 0) {
00251 if (entry.Size < 0) {
00252 entry.Size = entry.CompressedSize;
00253 } else if (entry.Size != entry.CompressedSize) {
00254 throw new ZipException("Method STORED, but compressed size != size");
00255 }
00256 } else {
00257 if (entry.Size >= 0) {
00258 entry.CompressedSize = entry.Size;
00259 }
00260 }
00261
00262 if (entry.Size < 0 || entry.Crc < 0) {
00263 if (CanPatchEntries == true) {
00264 headerInfoAvailable = false;
00265 }
00266 else {
00267
00268 method = CompressionMethod.Deflated;
00269 compressionLevel = 0;
00270 }
00271 }
00272 }
00273
00274 if (method == CompressionMethod.Deflated) {
00275 if (entry.Size == 0) {
00276
00277 entry.CompressedSize = entry.Size;
00278 entry.Crc = 0;
00279 method = CompressionMethod.Stored;
00280 } else if (entry.CompressedSize < 0 || entry.Size < 0 || entry.Crc < 0) {
00281 headerInfoAvailable = false;
00282 }
00283 }
00284
00285 if (headerInfoAvailable == false) {
00286 if (CanPatchEntries == false) {
00287 entry.Flags |= 8;
00288 } else {
00289 patchEntryHeader = true;
00290 }
00291 }
00292
00293 if (Password != null) {
00294 entry.IsCrypted = true;
00295 if (entry.Crc < 0) {
00296
00297 entry.Flags |= 8;
00298 }
00299 }
00300 entry.Offset = (int)offset;
00301 entry.CompressionMethod = (CompressionMethod)method;
00302
00303 curMethod = method;
00304
00305
00306 WriteLeInt(ZipConstants.LOCSIG);
00307
00308 WriteLeShort(entry.Version);
00309 WriteLeShort(entry.Flags);
00310 WriteLeShort((byte)method);
00311 WriteLeInt((int)entry.DosTime);
00312 if (headerInfoAvailable == true) {
00313 WriteLeInt((int)entry.Crc);
00314 WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CRYPTO_HEADER_SIZE : (int)entry.CompressedSize);
00315 WriteLeInt((int)entry.Size);
00316 } else {
00317 if (patchEntryHeader == true) {
00318 headerPatchPos = baseOutputStream.Position;
00319 }
00320 WriteLeInt(0);
00321 WriteLeInt(0);
00322 WriteLeInt(0);
00323 }
00324
00325 byte[] name = ZipConstants.ConvertToArray(entry.Name);
00326
00327 if (name.Length > 0xFFFF) {
00328 throw new ZipException("Entry name too long.");
00329 }
00330
00331 byte[] extra = entry.ExtraData;
00332 if (extra == null) {
00333 extra = new byte[0];
00334 }
00335
00336 if (extra.Length > 0xFFFF) {
00337 throw new ZipException("Extra data too long.");
00338 }
00339
00340 WriteLeShort(name.Length);
00341 WriteLeShort(extra.Length);
00342 baseOutputStream.Write(name, 0, name.Length);
00343 baseOutputStream.Write(extra, 0, extra.Length);
00344
00345 offset += ZipConstants.LOCHDR + name.Length + extra.Length;
00346
00347
00348 curEntry = entry;
00349 crc.Reset();
00350 if (method == CompressionMethod.Deflated) {
00351 def.Reset();
00352 def.SetLevel(compressionLevel);
00353 }
00354 size = 0;
00355
00356 if (entry.IsCrypted == true) {
00357 if (entry.Crc < 0) {
00358 WriteEncryptionHeader(entry.DosTime << 16);
00359 } else {
00360 WriteEncryptionHeader(entry.Crc);
00361 }
00362 }
00363 }
00364
00374 public void CloseEntry()
00375 {
00376 if (curEntry == null) {
00377 throw new InvalidOperationException("No open entry");
00378 }
00379
00380
00381 if (curMethod == CompressionMethod.Deflated) {
00382 base.Finish();
00383 }
00384
00385 long csize = curMethod == CompressionMethod.Deflated ? def.TotalOut : size;
00386
00387 if (curEntry.Size < 0) {
00388 curEntry.Size = size;
00389 } else if (curEntry.Size != size) {
00390 throw new ZipException("size was " + size + ", but I expected " + curEntry.Size);
00391 }
00392
00393 if (curEntry.CompressedSize < 0) {
00394 curEntry.CompressedSize = csize;
00395 } else if (curEntry.CompressedSize != csize) {
00396 throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize);
00397 }
00398
00399 if (curEntry.Crc < 0) {
00400 curEntry.Crc = crc.Value;
00401 } else if (curEntry.Crc != crc.Value) {
00402 throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc);
00403 }
00404
00405 offset += csize;
00406
00407 if (offset > 0xffffffff) {
00408 throw new ZipException("Maximum Zip file size exceeded");
00409 }
00410
00411 if (curEntry.IsCrypted == true) {
00412 curEntry.CompressedSize += ZipConstants.CRYPTO_HEADER_SIZE;
00413 }
00414
00415
00416 if (patchEntryHeader == true) {
00417 long curPos = baseOutputStream.Position;
00418 baseOutputStream.Seek(headerPatchPos, SeekOrigin.Begin);
00419 WriteLeInt((int)curEntry.Crc);
00420 WriteLeInt((int)curEntry.CompressedSize);
00421 WriteLeInt((int)curEntry.Size);
00422 baseOutputStream.Seek(curPos, SeekOrigin.Begin);
00423 patchEntryHeader = false;
00424 }
00425
00426
00427 if ((curEntry.Flags & 8) != 0) {
00428 WriteLeInt(ZipConstants.EXTSIG);
00429 WriteLeInt((int)curEntry.Crc);
00430 WriteLeInt((int)curEntry.CompressedSize);
00431 WriteLeInt((int)curEntry.Size);
00432 offset += ZipConstants.EXTHDR;
00433 }
00434
00435 entries.Add(curEntry);
00436 curEntry = null;
00437 }
00438
00439 void WriteEncryptionHeader(long crcValue)
00440 {
00441 offset += ZipConstants.CRYPTO_HEADER_SIZE;
00442
00443 InitializePassword(Password);
00444
00445 byte[] cryptBuffer = new byte[ZipConstants.CRYPTO_HEADER_SIZE];
00446 Random rnd = new Random();
00447 rnd.NextBytes(cryptBuffer);
00448 cryptBuffer[11] = (byte)(crcValue >> 24);
00449
00450 EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
00451 baseOutputStream.Write(cryptBuffer, 0, cryptBuffer.Length);
00452 }
00453
00463 public override void Write(byte[] b, int off, int len)
00464 {
00465 if (curEntry == null) {
00466 throw new InvalidOperationException("No open entry.");
00467 }
00468
00469 if (len <= 0)
00470 return;
00471
00472 crc.Update(b, off, len);
00473 size += len;
00474
00475 if (size > 0xffffffff || size < 0) {
00476 throw new ZipException("Maximum entry size exceeded");
00477 }
00478
00479
00480 switch (curMethod) {
00481 case CompressionMethod.Deflated:
00482 base.Write(b, off, len);
00483 break;
00484
00485 case CompressionMethod.Stored:
00486 if (Password != null) {
00487 byte[] buf = new byte[len];
00488 Array.Copy(b, off, buf, 0, len);
00489 EncryptBlock(buf, 0, len);
00490 baseOutputStream.Write(buf, off, len);
00491 } else {
00492 baseOutputStream.Write(b, off, len);
00493 }
00494 break;
00495 }
00496 }
00497
00512 public override void Finish()
00513 {
00514 if (entries == null) {
00515 return;
00516 }
00517
00518 if (curEntry != null) {
00519 CloseEntry();
00520 }
00521
00522 int numEntries = 0;
00523 int sizeEntries = 0;
00524
00525 foreach (ZipEntry entry in entries) {
00526 CompressionMethod method = entry.CompressionMethod;
00527 WriteLeInt(ZipConstants.CENSIG);
00528 WriteLeShort(ZipConstants.VERSION_MADE_BY);
00529 WriteLeShort(entry.Version);
00530 WriteLeShort(entry.Flags);
00531 WriteLeShort((short)method);
00532 WriteLeInt((int)entry.DosTime);
00533 WriteLeInt((int)entry.Crc);
00534 WriteLeInt((int)entry.CompressedSize);
00535 WriteLeInt((int)entry.Size);
00536
00537 byte[] name = ZipConstants.ConvertToArray(entry.Name);
00538
00539 if (name.Length > 0xffff) {
00540 throw new ZipException("Name too long.");
00541 }
00542
00543 byte[] extra = entry.ExtraData;
00544 if (extra == null) {
00545 extra = new byte[0];
00546 }
00547
00548 byte[] entryComment = entry.Comment != null ? ZipConstants.ConvertToArray(entry.Comment) : new byte[0];
00549 if (entryComment.Length > 0xffff) {
00550 throw new ZipException("Comment too long.");
00551 }
00552
00553 WriteLeShort(name.Length);
00554 WriteLeShort(extra.Length);
00555 WriteLeShort(entryComment.Length);
00556 WriteLeShort(0);
00557 WriteLeShort(0);
00558
00559
00560 if (entry.ExternalFileAttributes != -1) {
00561 WriteLeInt(entry.ExternalFileAttributes);
00562 } else {
00563 if (entry.IsDirectory) {
00564 WriteLeInt(16);
00565 } else {
00566 WriteLeInt(0);
00567 }
00568 }
00569
00570 WriteLeInt(entry.Offset);
00571
00572 baseOutputStream.Write(name, 0, name.Length);
00573 baseOutputStream.Write(extra, 0, extra.Length);
00574 baseOutputStream.Write(entryComment, 0, entryComment.Length);
00575 ++numEntries;
00576 sizeEntries += ZipConstants.CENHDR + name.Length + extra.Length + entryComment.Length;
00577 }
00578
00579 WriteLeInt(ZipConstants.ENDSIG);
00580 WriteLeShort(0);
00581 WriteLeShort(0);
00582 WriteLeShort(numEntries);
00583 WriteLeShort(numEntries);
00584 WriteLeInt(sizeEntries);
00585 WriteLeInt((int)offset);
00586 WriteLeShort(zipComment.Length);
00587 baseOutputStream.Write(zipComment, 0, zipComment.Length);
00588 baseOutputStream.Flush();
00589 entries = null;
00590 }
00591 }
00592 }