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