Summary

Class:ICSharpCode.SharpZipLib.Zip.ExtendedUnixData
Assembly:ICSharpCode.SharpZipLib
File(s):C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipExtraData.cs
Covered lines:32
Uncovered lines:30
Coverable lines:62
Total lines:896
Line coverage:51.6%
Branch coverage:30%

Metrics

MethodCyclomatic ComplexitySequence CoverageBranch Coverage
SetData(...)756.2533.33
GetData()666.6757.14
IsValidValue(...)2100100
.ctor()1100100

File(s)

C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipExtraData.cs

#LineLine coverage
 1using System;
 2using System.IO;
 3
 4namespace ICSharpCode.SharpZipLib.Zip
 5{
 6  // TODO: Sort out wether tagged data is useful and what a good implementation might look like.
 7  // Its just a sketch of an idea at the moment.
 8
 9  /// <summary>
 10  /// ExtraData tagged value interface.
 11  /// </summary>
 12  public interface ITaggedData
 13  {
 14    /// <summary>
 15    /// Get the ID for this tagged data value.
 16    /// </summary>
 17    short TagID { get; }
 18
 19    /// <summary>
 20    /// Set the contents of this instance from the data passed.
 21    /// </summary>
 22    /// <param name="data">The data to extract contents from.</param>
 23    /// <param name="offset">The offset to begin extracting data from.</param>
 24    /// <param name="count">The number of bytes to extract.</param>
 25    void SetData(byte[] data, int offset, int count);
 26
 27    /// <summary>
 28    /// Get the data representing this instance.
 29    /// </summary>
 30    /// <returns>Returns the data for this instance.</returns>
 31    byte[] GetData();
 32  }
 33
 34  /// <summary>
 35  /// A raw binary tagged value
 36  /// </summary>
 37  public class RawTaggedData : ITaggedData
 38  {
 39    /// <summary>
 40    /// Initialise a new instance.
 41    /// </summary>
 42    /// <param name="tag">The tag ID.</param>
 43    public RawTaggedData(short tag)
 44    {
 45      _tag = tag;
 46    }
 47
 48    #region ITaggedData Members
 49
 50    /// <summary>
 51    /// Get the ID for this tagged data value.
 52    /// </summary>
 53    public short TagID {
 54      get { return _tag; }
 55      set { _tag = value; }
 56    }
 57
 58    /// <summary>
 59    /// Set the data from the raw values provided.
 60    /// </summary>
 61    /// <param name="data">The raw data to extract values from.</param>
 62    /// <param name="offset">The index to start extracting values from.</param>
 63    /// <param name="count">The number of bytes available.</param>
 64    public void SetData(byte[] data, int offset, int count)
 65    {
 66      if (data == null) {
 67        throw new ArgumentNullException(nameof(data));
 68      }
 69
 70      _data = new byte[count];
 71      Array.Copy(data, offset, _data, 0, count);
 72    }
 73
 74    /// <summary>
 75    /// Get the binary data representing this instance.
 76    /// </summary>
 77    /// <returns>The raw binary data representing this instance.</returns>
 78    public byte[] GetData()
 79    {
 80      return _data;
 81    }
 82
 83    #endregion
 84
 85    /// <summary>
 86    /// Get /set the binary data representing this instance.
 87    /// </summary>
 88    /// <returns>The raw binary data representing this instance.</returns>
 89    public byte[] Data {
 90      get { return _data; }
 91      set { _data = value; }
 92    }
 93
 94    #region Instance Fields
 95    /// <summary>
 96    /// The tag ID for this instance.
 97    /// </summary>
 98    short _tag;
 99
 100    byte[] _data;
 101    #endregion
 102  }
 103
 104  /// <summary>
 105  /// Class representing extended unix date time values.
 106  /// </summary>
 107  public class ExtendedUnixData : ITaggedData
 108  {
 109    /// <summary>
 110    /// Flags indicate which values are included in this instance.
 111    /// </summary>
 112    [Flags]
 113    public enum Flags : byte
 114    {
 115      /// <summary>
 116      /// The modification time is included
 117      /// </summary>
 118      ModificationTime = 0x01,
 119
 120      /// <summary>
 121      /// The access time is included
 122      /// </summary>
 123      AccessTime = 0x02,
 124
 125      /// <summary>
 126      /// The create time is included.
 127      /// </summary>
 128      CreateTime = 0x04,
 129    }
 130
 131    #region ITaggedData Members
 132
 133    /// <summary>
 134    /// Get the ID
 135    /// </summary>
 136    public short TagID {
 66038137      get { return 0x5455; }
 138    }
 139
 140    /// <summary>
 141    /// Set the data from the raw values provided.
 142    /// </summary>
 143    /// <param name="data">The raw data to extract values from.</param>
 144    /// <param name="index">The index to start extracting values from.</param>
 145    /// <param name="count">The number of bytes available.</param>
 146    public void SetData(byte[] data, int index, int count)
 147    {
 1148      using (MemoryStream ms = new MemoryStream(data, index, count, false))
 1149      using (ZipHelperStream helperStream = new ZipHelperStream(ms)) {
 150        // bit 0           if set, modification time is present
 151        // bit 1           if set, access time is present
 152        // bit 2           if set, creation time is present
 153
 1154        _flags = (Flags)helperStream.ReadByte();
 1155         if (((_flags & Flags.ModificationTime) != 0))
 156        {
 1157          int iTime = helperStream.ReadLEInt();
 158
 1159          _modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
 1160            new TimeSpan(0, 0, 0, iTime, 0);
 161
 162          // Central-header version is truncated after modification time
 2163           if (count <= 5) return;
 164        }
 165
 0166         if ((_flags & Flags.AccessTime) != 0) {
 0167          int iTime = helperStream.ReadLEInt();
 168
 0169          _lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
 0170            new TimeSpan(0, 0, 0, iTime, 0);
 171        }
 172
 0173         if ((_flags & Flags.CreateTime) != 0) {
 0174          int iTime = helperStream.ReadLEInt();
 175
 0176          _createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) +
 0177            new TimeSpan(0, 0, 0, iTime, 0);
 178        }
 0179      }
 1180    }
 181
 182    /// <summary>
 183    /// Get the binary data representing this instance.
 184    /// </summary>
 185    /// <returns>The raw binary data representing this instance.</returns>
 186    public byte[] GetData()
 187    {
 1188      using (MemoryStream ms = new MemoryStream())
 1189      using (ZipHelperStream helperStream = new ZipHelperStream(ms)) {
 1190        helperStream.IsStreamOwner = false;
 1191        helperStream.WriteByte((byte)_flags);     // Flags
 1192         if ((_flags & Flags.ModificationTime) != 0) {
 1193          TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 1194          var seconds = (int)span.TotalSeconds;
 1195          helperStream.WriteLEInt(seconds);
 196        }
 1197         if ((_flags & Flags.AccessTime) != 0) {
 0198          TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 0199          var seconds = (int)span.TotalSeconds;
 0200          helperStream.WriteLEInt(seconds);
 201        }
 1202         if ((_flags & Flags.CreateTime) != 0) {
 0203          TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 0204          var seconds = (int)span.TotalSeconds;
 0205          helperStream.WriteLEInt(seconds);
 206        }
 1207        return ms.ToArray();
 208      }
 1209    }
 210
 211    #endregion
 212
 213    /// <summary>
 214    /// Test a <see cref="DateTime"> value to see if is valid and can be represented here.</see>
 215    /// </summary>
 216    /// <param name="value">The <see cref="DateTime">value</see> to test.</param>
 217    /// <returns>Returns true if the value is valid and can be represented; false if not.</returns>
 218    /// <remarks>The standard Unix time is a signed integer data type, directly encoding the Unix time number,
 219    /// which is the number of seconds since 1970-01-01.
 220    /// Being 32 bits means the values here cover a range of about 136 years.
 221    /// The minimum representable time is 1901-12-13 20:45:52,
 222    /// and the maximum representable time is 2038-01-19 03:14:07.
 223    /// </remarks>
 224    public static bool IsValidValue(DateTime value)
 225    {
 2226      return ((value >= new DateTime(1901, 12, 13, 20, 45, 52)) ||
 2227          (value <= new DateTime(2038, 1, 19, 03, 14, 07)));
 228    }
 229
 230    /// <summary>
 231    /// Get /set the Modification Time
 232    /// </summary>
 233    /// <exception cref="ArgumentOutOfRangeException"></exception>
 234    /// <seealso cref="IsValidValue"></seealso>
 235    public DateTime ModificationTime {
 4236      get { return _modificationTime; }
 237      set {
 2238         if (!IsValidValue(value)) {
 0239          throw new ArgumentOutOfRangeException(nameof(value));
 240        }
 241
 2242        _flags |= Flags.ModificationTime;
 2243        _modificationTime = value;
 2244      }
 245    }
 246
 247    /// <summary>
 248    /// Get / set the Access Time
 249    /// </summary>
 250    /// <exception cref="ArgumentOutOfRangeException"></exception>
 251    /// <seealso cref="IsValidValue"></seealso>
 252    public DateTime AccessTime {
 0253      get { return _lastAccessTime; }
 254      set {
 0255         if (!IsValidValue(value)) {
 0256          throw new ArgumentOutOfRangeException(nameof(value));
 257        }
 258
 0259        _flags |= Flags.AccessTime;
 0260        _lastAccessTime = value;
 0261      }
 262    }
 263
 264    /// <summary>
 265    /// Get / Set the Create Time
 266    /// </summary>
 267    /// <exception cref="ArgumentOutOfRangeException"></exception>
 268    /// <seealso cref="IsValidValue"></seealso>
 269    public DateTime CreateTime {
 0270      get { return _createTime; }
 271      set {
 0272         if (!IsValidValue(value)) {
 0273          throw new ArgumentOutOfRangeException(nameof(value));
 274        }
 275
 0276        _flags |= Flags.CreateTime;
 0277        _createTime = value;
 0278      }
 279    }
 280
 281    /// <summary>
 282    /// Get/set the <see cref="Flags">values</see> to include.
 283    /// </summary>
 284    public Flags Include
 285    {
 0286      get { return _flags; }
 0287      set { _flags = value; }
 288    }
 289
 290    #region Instance Fields
 291    Flags _flags;
 66038292    DateTime _modificationTime = new DateTime(1970, 1, 1);
 66038293    DateTime _lastAccessTime = new DateTime(1970, 1, 1);
 66038294    DateTime _createTime = new DateTime(1970, 1, 1);
 295    #endregion
 296  }
 297
 298  /// <summary>
 299  /// Class handling NT date time values.
 300  /// </summary>
 301  public class NTTaggedData : ITaggedData
 302  {
 303    /// <summary>
 304    /// Get the ID for this tagged data value.
 305    /// </summary>
 306    public short TagID {
 307      get { return 10; }
 308    }
 309
 310    /// <summary>
 311    /// Set the data from the raw values provided.
 312    /// </summary>
 313    /// <param name="data">The raw data to extract values from.</param>
 314    /// <param name="index">The index to start extracting values from.</param>
 315    /// <param name="count">The number of bytes available.</param>
 316    public void SetData(byte[] data, int index, int count)
 317    {
 318      using (MemoryStream ms = new MemoryStream(data, index, count, false))
 319      using (ZipHelperStream helperStream = new ZipHelperStream(ms)) {
 320        helperStream.ReadLEInt(); // Reserved
 321        while (helperStream.Position < helperStream.Length) {
 322          int ntfsTag = helperStream.ReadLEShort();
 323          int ntfsLength = helperStream.ReadLEShort();
 324          if (ntfsTag == 1) {
 325            if (ntfsLength >= 24) {
 326              long lastModificationTicks = helperStream.ReadLELong();
 327              _lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks);
 328
 329              long lastAccessTicks = helperStream.ReadLELong();
 330              _lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks);
 331
 332              long createTimeTicks = helperStream.ReadLELong();
 333              _createTime = DateTime.FromFileTimeUtc(createTimeTicks);
 334            }
 335            break;
 336          } else {
 337            // An unknown NTFS tag so simply skip it.
 338            helperStream.Seek(ntfsLength, SeekOrigin.Current);
 339          }
 340        }
 341      }
 342    }
 343
 344    /// <summary>
 345    /// Get the binary data representing this instance.
 346    /// </summary>
 347    /// <returns>The raw binary data representing this instance.</returns>
 348    public byte[] GetData()
 349    {
 350      using (MemoryStream ms = new MemoryStream())
 351      using (ZipHelperStream helperStream = new ZipHelperStream(ms)) {
 352        helperStream.IsStreamOwner = false;
 353        helperStream.WriteLEInt(0);       // Reserved
 354        helperStream.WriteLEShort(1);     // Tag
 355        helperStream.WriteLEShort(24);    // Length = 3 x 8.
 356        helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc());
 357        helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc());
 358        helperStream.WriteLELong(_createTime.ToFileTimeUtc());
 359        return ms.ToArray();
 360      }
 361    }
 362
 363    /// <summary>
 364    /// Test a <see cref="DateTime"> valuie to see if is valid and can be represented here.</see>
 365    /// </summary>
 366    /// <param name="value">The <see cref="DateTime">value</see> to test.</param>
 367    /// <returns>Returns true if the value is valid and can be represented; false if not.</returns>
 368    /// <remarks>
 369    /// NTFS filetimes are 64-bit unsigned integers, stored in Intel
 370    /// (least significant byte first) byte order. They determine the
 371    /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch",
 372    /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit
 373    /// </remarks>
 374    public static bool IsValidValue(DateTime value)
 375    {
 376      bool result = true;
 377      try {
 378        value.ToFileTimeUtc();
 379      } catch {
 380        result = false;
 381      }
 382      return result;
 383    }
 384
 385    /// <summary>
 386    /// Get/set the <see cref="DateTime">last modification time</see>.
 387    /// </summary>
 388    public DateTime LastModificationTime {
 389      get { return _lastModificationTime; }
 390      set {
 391        if (!IsValidValue(value)) {
 392          throw new ArgumentOutOfRangeException(nameof(value));
 393        }
 394        _lastModificationTime = value;
 395      }
 396    }
 397
 398    /// <summary>
 399    /// Get /set the <see cref="DateTime">create time</see>
 400    /// </summary>
 401    public DateTime CreateTime {
 402      get { return _createTime; }
 403      set {
 404        if (!IsValidValue(value)) {
 405          throw new ArgumentOutOfRangeException(nameof(value));
 406        }
 407        _createTime = value;
 408      }
 409    }
 410
 411    /// <summary>
 412    /// Get /set the <see cref="DateTime">last access time</see>.
 413    /// </summary>
 414    public DateTime LastAccessTime {
 415      get { return _lastAccessTime; }
 416      set {
 417        if (!IsValidValue(value)) {
 418          throw new ArgumentOutOfRangeException(nameof(value));
 419        }
 420        _lastAccessTime = value;
 421      }
 422    }
 423
 424    #region Instance Fields
 425    DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0);
 426    DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0);
 427    DateTime _createTime = DateTime.FromFileTimeUtc(0);
 428    #endregion
 429  }
 430
 431  /// <summary>
 432  /// A factory that creates <see cref="ITaggedData">tagged data</see> instances.
 433  /// </summary>
 434  interface ITaggedDataFactory
 435  {
 436    /// <summary>
 437    /// Get data for a specific tag value.
 438    /// </summary>
 439    /// <param name="tag">The tag ID to find.</param>
 440    /// <param name="data">The data to search.</param>
 441    /// <param name="offset">The offset to begin extracting data from.</param>
 442    /// <param name="count">The number of bytes to extract.</param>
 443    /// <returns>The located <see cref="ITaggedData">value found</see>, or null if not found.</returns>
 444    ITaggedData Create(short tag, byte[] data, int offset, int count);
 445  }
 446
 447  ///
 448  /// <summary>
 449  /// A class to handle the extra data field for Zip entries
 450  /// </summary>
 451  /// <remarks>
 452  /// Extra data contains 0 or more values each prefixed by a header tag and length.
 453  /// They contain zero or more bytes of actual data.
 454  /// The data is held internally using a copy on write strategy.  This is more efficient but
 455  /// means that for extra data created by passing in data can have the values modified by the caller
 456  /// in some circumstances.
 457  /// </remarks>
 458  sealed public class ZipExtraData : IDisposable
 459  {
 460    #region Constructors
 461    /// <summary>
 462    /// Initialise a default instance.
 463    /// </summary>
 464    public ZipExtraData()
 465    {
 466      Clear();
 467    }
 468
 469    /// <summary>
 470    /// Initialise with known extra data.
 471    /// </summary>
 472    /// <param name="data">The extra data.</param>
 473    public ZipExtraData(byte[] data)
 474    {
 475      if (data == null) {
 476        _data = new byte[0];
 477      } else {
 478        _data = data;
 479      }
 480    }
 481    #endregion
 482
 483    /// <summary>
 484    /// Get the raw extra data value
 485    /// </summary>
 486    /// <returns>Returns the raw byte[] extra data this instance represents.</returns>
 487    public byte[] GetEntryData()
 488    {
 489      if (Length > ushort.MaxValue) {
 490        throw new ZipException("Data exceeds maximum length");
 491      }
 492
 493      return (byte[])_data.Clone();
 494    }
 495
 496    /// <summary>
 497    /// Clear the stored data.
 498    /// </summary>
 499    public void Clear()
 500    {
 501      if ((_data == null) || (_data.Length != 0)) {
 502        _data = new byte[0];
 503      }
 504    }
 505
 506    /// <summary>
 507    /// Gets the current extra data length.
 508    /// </summary>
 509    public int Length {
 510      get { return _data.Length; }
 511    }
 512
 513    /// <summary>
 514    /// Get a read-only <see cref="Stream"/> for the associated tag.
 515    /// </summary>
 516    /// <param name="tag">The tag to locate data for.</param>
 517    /// <returns>Returns a <see cref="Stream"/> containing tag data or null if no tag was found.</returns>
 518    public Stream GetStreamForTag(int tag)
 519    {
 520      Stream result = null;
 521      if (Find(tag)) {
 522        result = new MemoryStream(_data, _index, _readValueLength, false);
 523      }
 524      return result;
 525    }
 526
 527    /// <summary>
 528    /// Get the <see cref="ITaggedData">tagged data</see> for a tag.
 529    /// </summary>
 530    /// <typeparam name="T">The tag to search for.</typeparam>
 531    /// <returns>Returns a <see cref="ITaggedData">tagged value</see> or null if none found.</returns>
 532    public T GetData<T>()
 533      where T : class, ITaggedData, new()
 534    {
 535      T result = new T();
 536      if (Find(result.TagID))
 537      {
 538        result.SetData(_data, _readValueStart, _readValueLength);
 539        return result;
 540      }
 541      else return null;
 542    }
 543
 544    /// <summary>
 545    /// Get the length of the last value found by <see cref="Find"/>
 546    /// </summary>
 547    /// <remarks>This is only valid if <see cref="Find"/> has previously returned true.</remarks>
 548    public int ValueLength {
 549      get { return _readValueLength; }
 550    }
 551
 552    /// <summary>
 553    /// Get the index for the current read value.
 554    /// </summary>
 555    /// <remarks>This is only valid if <see cref="Find"/> has previously returned true.
 556    /// Initially the result will be the index of the first byte of actual data.  The value is updated after calls to
 557    /// <see cref="ReadInt"/>, <see cref="ReadShort"/> and <see cref="ReadLong"/>. </remarks>
 558    public int CurrentReadIndex {
 559      get { return _index; }
 560    }
 561
 562    /// <summary>
 563    /// Get the number of bytes remaining to be read for the current value;
 564    /// </summary>
 565    public int UnreadCount {
 566      get {
 567        if ((_readValueStart > _data.Length) ||
 568          (_readValueStart < 4)) {
 569          throw new ZipException("Find must be called before calling a Read method");
 570        }
 571
 572        return _readValueStart + _readValueLength - _index;
 573      }
 574    }
 575
 576    /// <summary>
 577    /// Find an extra data value
 578    /// </summary>
 579    /// <param name="headerID">The identifier for the value to find.</param>
 580    /// <returns>Returns true if the value was found; false otherwise.</returns>
 581    public bool Find(int headerID)
 582    {
 583      _readValueStart = _data.Length;
 584      _readValueLength = 0;
 585      _index = 0;
 586
 587      int localLength = _readValueStart;
 588      int localTag = headerID - 1;
 589
 590      // Trailing bytes that cant make up an entry (as there arent enough
 591      // bytes for a tag and length) are ignored!
 592      while ((localTag != headerID) && (_index < _data.Length - 3)) {
 593        localTag = ReadShortInternal();
 594        localLength = ReadShortInternal();
 595        if (localTag != headerID) {
 596          _index += localLength;
 597        }
 598      }
 599
 600      bool result = (localTag == headerID) && ((_index + localLength) <= _data.Length);
 601
 602      if (result) {
 603        _readValueStart = _index;
 604        _readValueLength = localLength;
 605      }
 606
 607      return result;
 608    }
 609
 610    /// <summary>
 611    /// Add a new entry to extra data.
 612    /// </summary>
 613    /// <param name="taggedData">The <see cref="ITaggedData"/> value to add.</param>
 614    public void AddEntry(ITaggedData taggedData)
 615    {
 616      if (taggedData == null) {
 617        throw new ArgumentNullException(nameof(taggedData));
 618      }
 619      AddEntry(taggedData.TagID, taggedData.GetData());
 620    }
 621
 622    /// <summary>
 623    /// Add a new entry to extra data
 624    /// </summary>
 625    /// <param name="headerID">The ID for this entry.</param>
 626    /// <param name="fieldData">The data to add.</param>
 627    /// <remarks>If the ID already exists its contents are replaced.</remarks>
 628    public void AddEntry(int headerID, byte[] fieldData)
 629    {
 630      if ((headerID > ushort.MaxValue) || (headerID < 0)) {
 631        throw new ArgumentOutOfRangeException(nameof(headerID));
 632      }
 633
 634      int addLength = (fieldData == null) ? 0 : fieldData.Length;
 635
 636      if (addLength > ushort.MaxValue) {
 637        throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length");
 638      }
 639
 640      // Test for new length before adjusting data.
 641      int newLength = _data.Length + addLength + 4;
 642
 643      if (Find(headerID)) {
 644        newLength -= (ValueLength + 4);
 645      }
 646
 647      if (newLength > ushort.MaxValue) {
 648        throw new ZipException("Data exceeds maximum length");
 649      }
 650
 651      Delete(headerID);
 652
 653      byte[] newData = new byte[newLength];
 654      _data.CopyTo(newData, 0);
 655      int index = _data.Length;
 656      _data = newData;
 657      SetShort(ref index, headerID);
 658      SetShort(ref index, addLength);
 659      if (fieldData != null) {
 660        fieldData.CopyTo(newData, index);
 661      }
 662    }
 663
 664    /// <summary>
 665    /// Start adding a new entry.
 666    /// </summary>
 667    /// <remarks>Add data using <see cref="AddData(byte[])"/>, <see cref="AddLeShort"/>, <see cref="AddLeInt"/>, or <see
 668    /// The new entry is completed and actually added by calling <see cref="AddNewEntry"/></remarks>
 669    /// <seealso cref="AddEntry(ITaggedData)"/>
 670    public void StartNewEntry()
 671    {
 672      _newEntry = new MemoryStream();
 673    }
 674
 675    /// <summary>
 676    /// Add entry data added since <see cref="StartNewEntry"/> using the ID passed.
 677    /// </summary>
 678    /// <param name="headerID">The identifier to use for this entry.</param>
 679    public void AddNewEntry(int headerID)
 680    {
 681      byte[] newData = _newEntry.ToArray();
 682      _newEntry = null;
 683      AddEntry(headerID, newData);
 684    }
 685
 686    /// <summary>
 687    /// Add a byte of data to the pending new entry.
 688    /// </summary>
 689    /// <param name="data">The byte to add.</param>
 690    /// <seealso cref="StartNewEntry"/>
 691    public void AddData(byte data)
 692    {
 693      _newEntry.WriteByte(data);
 694    }
 695
 696    /// <summary>
 697    /// Add data to a pending new entry.
 698    /// </summary>
 699    /// <param name="data">The data to add.</param>
 700    /// <seealso cref="StartNewEntry"/>
 701    public void AddData(byte[] data)
 702    {
 703      if (data == null) {
 704        throw new ArgumentNullException(nameof(data));
 705      }
 706
 707      _newEntry.Write(data, 0, data.Length);
 708    }
 709
 710    /// <summary>
 711    /// Add a short value in little endian order to the pending new entry.
 712    /// </summary>
 713    /// <param name="toAdd">The data to add.</param>
 714    /// <seealso cref="StartNewEntry"/>
 715    public void AddLeShort(int toAdd)
 716    {
 717      unchecked {
 718        _newEntry.WriteByte((byte)toAdd);
 719        _newEntry.WriteByte((byte)(toAdd >> 8));
 720      }
 721    }
 722
 723    /// <summary>
 724    /// Add an integer value in little endian order to the pending new entry.
 725    /// </summary>
 726    /// <param name="toAdd">The data to add.</param>
 727    /// <seealso cref="StartNewEntry"/>
 728    public void AddLeInt(int toAdd)
 729    {
 730      unchecked {
 731        AddLeShort((short)toAdd);
 732        AddLeShort((short)(toAdd >> 16));
 733      }
 734    }
 735
 736    /// <summary>
 737    /// Add a long value in little endian order to the pending new entry.
 738    /// </summary>
 739    /// <param name="toAdd">The data to add.</param>
 740    /// <seealso cref="StartNewEntry"/>
 741    public void AddLeLong(long toAdd)
 742    {
 743      unchecked {
 744        AddLeInt((int)(toAdd & 0xffffffff));
 745        AddLeInt((int)(toAdd >> 32));
 746      }
 747    }
 748
 749    /// <summary>
 750    /// Delete an extra data field.
 751    /// </summary>
 752    /// <param name="headerID">The identifier of the field to delete.</param>
 753    /// <returns>Returns true if the field was found and deleted.</returns>
 754    public bool Delete(int headerID)
 755    {
 756      bool result = false;
 757
 758      if (Find(headerID)) {
 759        result = true;
 760        int trueStart = _readValueStart - 4;
 761
 762        byte[] newData = new byte[_data.Length - (ValueLength + 4)];
 763        Array.Copy(_data, 0, newData, 0, trueStart);
 764
 765        int trueEnd = trueStart + ValueLength + 4;
 766        Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd);
 767        _data = newData;
 768      }
 769      return result;
 770    }
 771
 772    #region Reading Support
 773    /// <summary>
 774    /// Read a long in little endian form from the last <see cref="Find">found</see> data value
 775    /// </summary>
 776    /// <returns>Returns the long value read.</returns>
 777    public long ReadLong()
 778    {
 779      ReadCheck(8);
 780      return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32);
 781    }
 782
 783    /// <summary>
 784    /// Read an integer in little endian form from the last <see cref="Find">found</see> data value.
 785    /// </summary>
 786    /// <returns>Returns the integer read.</returns>
 787    public int ReadInt()
 788    {
 789      ReadCheck(4);
 790
 791      int result = _data[_index] + (_data[_index + 1] << 8) +
 792        (_data[_index + 2] << 16) + (_data[_index + 3] << 24);
 793      _index += 4;
 794      return result;
 795    }
 796
 797    /// <summary>
 798    /// Read a short value in little endian form from the last <see cref="Find">found</see> data value.
 799    /// </summary>
 800    /// <returns>Returns the short value read.</returns>
 801    public int ReadShort()
 802    {
 803      ReadCheck(2);
 804      int result = _data[_index] + (_data[_index + 1] << 8);
 805      _index += 2;
 806      return result;
 807    }
 808
 809    /// <summary>
 810    /// Read a byte from an extra data
 811    /// </summary>
 812    /// <returns>The byte value read or -1 if the end of data has been reached.</returns>
 813    public int ReadByte()
 814    {
 815      int result = -1;
 816      if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index)) {
 817        result = _data[_index];
 818        _index += 1;
 819      }
 820      return result;
 821    }
 822
 823    /// <summary>
 824    /// Skip data during reading.
 825    /// </summary>
 826    /// <param name="amount">The number of bytes to skip.</param>
 827    public void Skip(int amount)
 828    {
 829      ReadCheck(amount);
 830      _index += amount;
 831    }
 832
 833    void ReadCheck(int length)
 834    {
 835      if ((_readValueStart > _data.Length) ||
 836        (_readValueStart < 4)) {
 837        throw new ZipException("Find must be called before calling a Read method");
 838      }
 839
 840      if (_index > _readValueStart + _readValueLength - length) {
 841        throw new ZipException("End of extra data");
 842      }
 843
 844      if (_index + length < 4) {
 845        throw new ZipException("Cannot read before start of tag");
 846      }
 847    }
 848
 849    /// <summary>
 850    /// Internal form of <see cref="ReadShort"/> that reads data at any location.
 851    /// </summary>
 852    /// <returns>Returns the short value read.</returns>
 853    int ReadShortInternal()
 854    {
 855      if (_index > _data.Length - 2) {
 856        throw new ZipException("End of extra data");
 857      }
 858
 859      int result = _data[_index] + (_data[_index + 1] << 8);
 860      _index += 2;
 861      return result;
 862    }
 863
 864    void SetShort(ref int index, int source)
 865    {
 866      _data[index] = (byte)source;
 867      _data[index + 1] = (byte)(source >> 8);
 868      index += 2;
 869    }
 870
 871    #endregion
 872
 873    #region IDisposable Members
 874
 875    /// <summary>
 876    /// Dispose of this instance.
 877    /// </summary>
 878    public void Dispose()
 879    {
 880      if (_newEntry != null) {
 881        _newEntry.Close();
 882      }
 883    }
 884
 885    #endregion
 886
 887    #region Instance Fields
 888    int _index;
 889    int _readValueStart;
 890    int _readValueLength;
 891
 892    MemoryStream _newEntry;
 893    byte[] _data;
 894    #endregion
 895  }
 896}