Added hammock project to debug streaming issues
[pithos-ms-client] / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Zip / ZipExtraData.cs
1 //
2 // ZipExtraData.cs
3 //
4 // Copyright 2004-2007 John Reilly
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 //
20 // Linking this library statically or dynamically with other modules is
21 // making a combined work based on this library.  Thus, the terms and
22 // conditions of the GNU General Public License cover the whole
23 // combination.
24 // 
25 // As a special exception, the copyright holders of this library give you
26 // permission to link this library with independent modules to produce an
27 // executable, regardless of the license terms of these independent
28 // modules, and to copy and distribute the resulting executable under
29 // terms of your choice, provided that you also meet, for each linked
30 // independent module, the terms and conditions of the license of that
31 // module.  An independent module is a module which is not derived from
32 // or based on this library.  If you modify this library, you may extend
33 // this exception to your version of the library, but you are not
34 // obligated to do so.  If you do not wish to do so, delete this
35 // exception statement from your version.
36
37 using System;
38 using System.Collections;
39 using System.IO;
40 using ICSharpCode.SharpZipLib.Zip;
41
42 namespace ICSharpCode.SharpZipLib.Silverlight.Zip
43 {
44     /// <summary>
45     /// ExtraData tagged value interface.
46     /// </summary>
47     public interface ITaggedData
48     {
49         /// <summary>
50         /// Get the ID for this tagged data value.
51         /// </summary>
52         short TagID { get; }
53
54         /// <summary>
55         /// Set the contents of this instance from the data passed.
56         /// </summary>
57         /// <param name="data">The data to extract contents from.</param>
58         /// <param name="offset">The offset to begin extracting data from.</param>
59         /// <param name="count">The number of bytes to extract.</param>
60         void SetData(byte[] data, int offset, int count);
61
62         /// <summary>
63         /// Get the data representing this instance.
64         /// </summary>
65         /// <returns>Returns the data for this instance.</returns>
66         byte[] GetData();
67     }
68
69     /// <summary>
70     /// A raw binary tagged value
71     /// </summary>
72     public class RawTaggedData : ITaggedData
73     {
74         /// <summary>
75         /// Initialise a new instance.
76         /// </summary>
77         /// <param name="tag">The tag ID.</param>
78         public RawTaggedData(short tag)
79         {
80             tag_ = tag;
81         }
82
83         #region ITaggedData Members
84
85         /// <summary>
86         /// Get the ID for this tagged data value.
87         /// </summary>
88         public short TagID 
89         { 
90             get { return tag_; }
91             set { tag_ = value; }
92         }
93
94         /// <summary>
95         /// Set the data from the raw values provided.
96         /// </summary>
97         /// <param name="data">The raw data to extract values from.</param>
98         /// <param name="offset">The index to start extracting values from.</param>
99         /// <param name="count">The number of bytes available.</param>
100         public void SetData(byte[] data, int offset, int count)
101         {
102             if( data==null )
103             {
104                 throw new ArgumentNullException("data");
105             }
106
107             data_=new byte[count];
108             Array.Copy(data, offset, data_, 0, count);
109         }
110
111         /// <summary>
112         /// Get the binary data representing this instance.
113         /// </summary>
114         /// <returns>The raw binary data representing this instance.</returns>
115         public byte[] GetData()
116         {
117             return data_;
118         }
119
120         #endregion
121
122         /// <summary>
123         /// Get /set the binary data representing this instance.
124         /// </summary>
125         /// <returns>The raw binary data representing this instance.</returns>
126         public byte[] Data
127         {
128             get { return data_; }
129             set { data_=value; }
130         }
131
132         #region Instance Fields
133         /// <summary>
134         /// The tag ID for this instance.
135         /// </summary>
136         protected short tag_;
137
138         byte[] data_;
139         #endregion
140     }
141
142     /// <summary>
143     /// Class representing extended unix date time values.
144     /// </summary>
145     public class ExtendedUnixData : ITaggedData
146     {
147         /// <summary>
148         /// Flags indicate which values are included in this instance.
149         /// </summary>
150         [Flags]
151         public enum Flags : byte
152         {
153             /// <summary>
154             /// The modification time is included
155             /// </summary>
156             ModificationTime = 0x01,
157                         
158             /// <summary>
159             /// The access time is included
160             /// </summary>
161             AccessTime = 0x02,
162                         
163             /// <summary>
164             /// The create time is included.
165             /// </summary>
166             CreateTime = 0x04,
167         }
168                 
169         #region ITaggedData Members
170
171         /// <summary>
172         /// Get the ID
173         /// </summary>
174         public short TagID
175         { 
176             get { return 0x5455; }
177         }
178                 
179         /// <summary>
180         /// Set the data from the raw values provided.
181         /// </summary>
182         /// <param name="data">The raw data to extract values from.</param>
183         /// <param name="index">The index to start extracting values from.</param>
184         /// <param name="count">The number of bytes available.</param>
185         public void SetData(byte[] data, int index, int count)
186         {
187             using (MemoryStream ms = new MemoryStream(data, index, count, false))
188             using (ZipHelperStream helperStream = new ZipHelperStream(ms))
189             {
190                 // bit 0           if set, modification time is present
191                 // bit 1           if set, access time is present
192                 // bit 2           if set, creation time is present
193                                 
194                 flags_ = (Flags)helperStream.ReadByte();
195                 if (((flags_ & Flags.ModificationTime) != 0) && (count >= 5))
196                 {
197                     int iTime = helperStream.ReadLEInt();
198
199                     modificationTime_ = (new System.DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime() +
200                                          new TimeSpan(0, 0, 0, iTime, 0)).ToLocalTime();
201                 }
202
203                 if ((flags_ & Flags.AccessTime) != 0)
204                 {
205                     int iTime = helperStream.ReadLEInt();
206
207                     lastAccessTime_ = (new System.DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime() +
208                                        new TimeSpan(0, 0, 0, iTime, 0)).ToLocalTime();
209                 }
210                                 
211                 if ((flags_ & Flags.CreateTime) != 0)
212                 {
213                     int iTime = helperStream.ReadLEInt();
214
215                     createTime_ = (new System.DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime() +
216                                    new TimeSpan(0, 0, 0, iTime, 0)).ToLocalTime();
217                 }
218             }
219         }
220
221         /// <summary>
222         /// Get the binary data representing this instance.
223         /// </summary>
224         /// <returns>The raw binary data representing this instance.</returns>
225         public byte[] GetData()
226         {
227             using (MemoryStream ms = new MemoryStream())
228             using (ZipHelperStream helperStream = new ZipHelperStream(ms))
229             {
230                 helperStream.IsStreamOwner = false;
231                 helperStream.WriteByte((byte)flags_);     // Flags
232                 if ( (flags_ & Flags.ModificationTime) != 0) {
233                     TimeSpan span = modificationTime_.ToUniversalTime() - new System.DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime();
234                     int seconds = (int)span.TotalSeconds;
235                     helperStream.WriteLEInt(seconds);
236                 }
237                 if ( (flags_ & Flags.AccessTime) != 0) {
238                     TimeSpan span = lastAccessTime_.ToUniversalTime() - new System.DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime();
239                     int seconds = (int)span.TotalSeconds;
240                     helperStream.WriteLEInt(seconds);
241                 }
242                 if ( (flags_ & Flags.CreateTime) != 0) {
243                     TimeSpan span = createTime_.ToUniversalTime() - new System.DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime();
244                     int seconds = (int)span.TotalSeconds;
245                     helperStream.WriteLEInt(seconds);
246                 }
247                 return ms.ToArray();
248             }
249         }
250
251         #endregion
252
253         /// <summary>
254         /// Test a <see cref="DateTime"> value to see if is valid and can be represented here.</see>
255         /// </summary>
256         /// <param name="value">The <see cref="DateTime">value</see> to test.</param>
257         /// <returns>Returns true if the value is valid and can be represented; false if not.</returns>
258         /// <remarks>The standard Unix time is a signed integer data type, directly encoding the Unix time number,
259         /// which is the number of seconds since 1970-01-01.
260         /// Being 32 bits means the values here cover a range of about 136 years.
261         /// The minimum representable time is 1901-12-13 20:45:52,
262         /// and the maximum representable time is 2038-01-19 03:14:07.
263         /// </remarks>
264         public static bool IsValidValue(DateTime value)
265         {
266             return (( value >= new DateTime(1901, 12, 13, 20, 45, 52)) || 
267                     ( value <= new DateTime(2038, 1, 19, 03, 14, 07) ));
268         }
269
270         /// <summary>
271         /// Get /set the Modification Time
272         /// </summary>
273         /// <exception cref="ArgumentOutOfRangeException"></exception>
274         /// <seealso cref="IsValidValue"></seealso>
275         public DateTime ModificationTime
276         {
277             get { return modificationTime_; }
278             set
279             {
280                 if ( !IsValidValue(value) ) {
281                     throw new ArgumentOutOfRangeException("value");
282                 }
283                                 
284                 flags_ |= Flags.ModificationTime;
285                 modificationTime_=value;
286             }
287         }
288
289         /// <summary>
290         /// Get / set the Access Time
291         /// </summary>
292         /// <exception cref="ArgumentOutOfRangeException"></exception>
293         /// <seealso cref="IsValidValue"></seealso>
294         public DateTime AccessTime
295         {
296             get { return lastAccessTime_; }
297             set { 
298                 if ( !IsValidValue(value) ) {
299                     throw new ArgumentOutOfRangeException("value");
300                 }
301                         
302                 flags_ |= Flags.AccessTime;
303                 lastAccessTime_=value; 
304             }
305         }
306
307         /// <summary>
308         /// Get / Set the Create Time
309         /// </summary>
310         /// <exception cref="ArgumentOutOfRangeException"></exception>
311         /// <seealso cref="IsValidValue"></seealso>
312         public DateTime CreateTime
313         {
314             get { return createTime_; }
315             set {
316                 if ( !IsValidValue(value) ) {
317                     throw new ArgumentOutOfRangeException("value");
318                 }
319                         
320                 flags_ |= Flags.CreateTime;
321                 createTime_=value;
322             }
323         }
324
325         /// <summary>
326         /// Get/set the <see cref="Flags">values</see> to include.
327         /// </summary>
328         Flags Include
329         {
330             get { return flags_; }
331             set { flags_ = value; }
332         }
333
334         #region Instance Fields
335         Flags flags_;
336         DateTime modificationTime_ = new DateTime(1970,1,1);
337         DateTime lastAccessTime_ = new DateTime(1970, 1, 1);
338         DateTime createTime_ = new DateTime(1970, 1, 1);
339         #endregion
340     }
341
342     /// <summary>
343     /// Class handling NT date time values.
344     /// </summary>
345     public class NTTaggedData : ITaggedData
346     {
347         /// <summary>
348         /// Get the ID for this tagged data value.
349         /// </summary>
350         public short TagID
351         { 
352             get { return 10; }
353         }
354
355         /// <summary>
356         /// Set the data from the raw values provided.
357         /// </summary>
358         /// <param name="data">The raw data to extract values from.</param>
359         /// <param name="index">The index to start extracting values from.</param>
360         /// <param name="count">The number of bytes available.</param>
361         public void SetData(byte[] data, int index, int count)
362         {
363             using (MemoryStream ms = new MemoryStream(data, index, count, false)) 
364             using (ZipHelperStream helperStream = new ZipHelperStream(ms))
365             {
366                 helperStream.ReadLEInt(); // Reserved
367                 while (helperStream.Position < helperStream.Length)
368                 {
369                     int ntfsTag = helperStream.ReadLEShort();
370                     int ntfsLength = helperStream.ReadLEShort();
371                     if (ntfsTag == 1)
372                     {
373                         if (ntfsLength >= 24)
374                         {
375                             long lastModificationTicks = helperStream.ReadLELong();
376                             lastModificationTime_ = DateTime.FromFileTime(lastModificationTicks);
377
378                             long lastAccessTicks = helperStream.ReadLELong();
379                             lastAccessTime_ = DateTime.FromFileTime(lastAccessTicks);
380
381                             long createTimeTicks = helperStream.ReadLELong();
382                             createTime_ = DateTime.FromFileTime(createTimeTicks);
383                         }
384                         break;
385                     }
386                     else
387                     {
388                         // An unknown NTFS tag so simply skip it.
389                         helperStream.Seek(ntfsLength, SeekOrigin.Current);
390                     }
391                 }
392             }
393         }
394
395         /// <summary>
396         /// Get the binary data representing this instance.
397         /// </summary>
398         /// <returns>The raw binary data representing this instance.</returns>
399         public byte[] GetData()
400         {
401             using (MemoryStream ms = new MemoryStream())
402             using (ZipHelperStream helperStream = new ZipHelperStream(ms))
403             {
404                 helperStream.IsStreamOwner = false;
405                 helperStream.WriteLEInt(0);       // Reserved
406                 helperStream.WriteLEShort(1);     // Tag
407                 helperStream.WriteLEShort(24);    // Length = 3 x 8.
408                 helperStream.WriteLELong(lastModificationTime_.ToFileTime());
409                 helperStream.WriteLELong(lastAccessTime_.ToFileTime());
410                 helperStream.WriteLELong(createTime_.ToFileTime());
411                 return ms.ToArray();
412             }
413         }
414
415         /// <summary>
416         /// Test a <see cref="DateTime"> valuie to see if is valid and can be represented here.</see>
417         /// </summary>
418         /// <param name="value">The <see cref="DateTime">value</see> to test.</param>
419         /// <returns>Returns true if the value is valid and can be represented; false if not.</returns>
420         /// <remarks>
421         /// NTFS filetimes are 64-bit unsigned integers, stored in Intel
422         /// (least significant byte first) byte order. They determine the
423         /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch",
424         /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit
425         /// </remarks>
426         public static bool IsValidValue(DateTime value)
427         {
428             bool result = true;
429             try
430             {
431                 value.ToFileTimeUtc();
432             }
433             catch
434             {
435                 result = false;
436             }
437             return result;
438         }
439                 
440         /// <summary>
441         /// Get/set the <see cref="DateTime">last modification time</see>.
442         /// </summary>
443         public DateTime LastModificationTime
444         {
445             get { return lastModificationTime_; }
446             set {
447                 if (! IsValidValue(value))
448                 {
449                     throw new ArgumentOutOfRangeException("value");
450                 }
451                 lastModificationTime_ = value;
452             }
453         }
454
455         /// <summary>
456         /// Get /set the <see cref="DateTime">create time</see>
457         /// </summary>
458         public DateTime CreateTime
459         {
460             get { return createTime_; }
461             set {
462                 if ( !IsValidValue(value)) {
463                     throw new ArgumentOutOfRangeException("value");
464                 }
465                 createTime_ = value;
466             }
467         }
468
469         /// <summary>
470         /// Get /set the <see cref="DateTime">last access time</see>.
471         /// </summary>
472         public DateTime LastAccessTime
473         {
474             get { return lastAccessTime_; }
475             set {
476                 if (!IsValidValue(value)) {
477                     throw new ArgumentOutOfRangeException("value");
478                 }
479                 lastAccessTime_ = value; 
480             }
481         }
482
483         #region Instance Fields
484         DateTime lastAccessTime_ = DateTime.FromFileTime(0);
485         DateTime lastModificationTime_ = DateTime.FromFileTime(0);
486         DateTime createTime_ = DateTime.FromFileTime(0);
487         #endregion
488     }
489
490     /// <summary>
491     /// A factory that creates <see cref="ITaggedData">tagged data</see> instances.
492     /// </summary>
493     interface ITaggedDataFactory
494     {
495         /// <summary>
496         /// Get data for a specific tag value.
497         /// </summary>
498         /// <param name="tag">The tag ID to find.</param>
499         /// <param name="data">The data to search.</param>
500         /// <param name="offset">The offset to begin extracting data from.</param>
501         /// <param name="count">The number of bytes to extract.</param>
502         /// <returns>The located <see cref="ITaggedData">value found</see>, or null if not found.</returns>
503         ITaggedData Create(short tag, byte[] data, int offset, int count);
504     }
505
506     /// 
507     /// <summary>
508     /// A class to handle the extra data field for Zip entries
509     /// </summary>
510     /// <remarks>
511     /// Extra data contains 0 or more values each prefixed by a header tag and length.
512     /// They contain zero or more bytes of actual data.
513     /// The data is held internally using a copy on write strategy.  This is more efficient but
514     /// means that for extra data created by passing in data can have the values modified by the caller
515     /// in some circumstances.
516     /// </remarks>
517     sealed public class ZipExtraData : IDisposable
518     {
519         #region Constructors
520         /// <summary>
521         /// Initialise a default instance.
522         /// </summary>
523         public ZipExtraData()
524         {
525             Clear();
526         }
527
528         /// <summary>
529         /// Initialise with known extra data.
530         /// </summary>
531         /// <param name="data">The extra data.</param>
532         public ZipExtraData(byte[] data)
533         {
534             if ( data == null )
535             {
536                 data_ = new byte[0];
537             }
538             else
539             {
540                 data_ = data;
541             }
542         }
543         #endregion
544
545         /// <summary>
546         /// Get the raw extra data value
547         /// </summary>
548         /// <returns>Returns the raw byte[] extra data this instance represents.</returns>
549         public byte[] GetEntryData()
550         {
551             if ( Length > ushort.MaxValue ) {
552                 throw new ZipException("Data exceeds maximum length");
553             }
554
555             return (byte[])data_.Clone();
556         }
557
558         /// <summary>
559         /// Clear the stored data.
560         /// </summary>
561         public void Clear()
562         {
563             if ( (data_ == null) || (data_.Length != 0) ) {
564                 data_ = new byte[0];
565             }
566         }
567
568         /// <summary>
569         /// Gets the current extra data length.
570         /// </summary>
571         public int Length
572         {
573             get { return data_.Length; }
574         }
575
576         /// <summary>
577         /// Get a read-only <see cref="Stream"/> for the associated tag.
578         /// </summary>
579         /// <param name="tag">The tag to locate data for.</param>
580         /// <returns>Returns a <see cref="Stream"/> containing tag data or null if no tag was found.</returns>
581         public Stream GetStreamForTag(int tag)
582         {
583             Stream result = null;
584             if ( Find(tag) ) {
585                 result = new MemoryStream(data_, index_, readValueLength_, false);
586             }
587             return result;
588         }
589
590         /// <summary>
591         /// Get the <see cref="ITaggedData">tagged data</see> for a tag.
592         /// </summary>
593         /// <param name="tag">The tag to search for.</param>
594         /// <returns>Returns a <see cref="ITaggedData">tagged value</see> or null if none found.</returns>
595         private ITaggedData GetData(short tag)
596         {
597             ITaggedData result = null;
598             if (Find(tag))
599             {
600                 result = Create(tag, data_, readValueStart_, readValueLength_);
601             }
602             return result;
603         }
604
605         ITaggedData Create(short tag, byte[] data, int offset, int count)
606         {
607             ITaggedData result = null;
608             switch ( tag )
609             {
610                 case 0x000A:
611                     result = new NTTaggedData();
612                     break;
613                 case 0x5455:
614                     result = new ExtendedUnixData();
615                     break;
616                 default:
617                     result = new RawTaggedData(tag);
618                     break;
619             }
620             result.SetData(data_, readValueStart_, readValueLength_);
621             return result;
622         }
623                 
624         /// <summary>
625         /// Get the length of the last value found by <see cref="Find"/>
626         /// </summary>
627         /// <remarks>This is only value if <see cref="Find"/> has previsouly returned true.</remarks>
628         public int ValueLength
629         {
630             get { return readValueLength_; }
631         }
632
633         /// <summary>
634         /// Get the index for the current read value.
635         /// </summary>
636         /// <remarks>This is only valid if <see cref="Find"/> has previously returned true.
637         /// Initially it will be the index of the first byte of actual data.  The value is updated after calls to
638         /// <see cref="ReadInt"/>, <see cref="ReadShort"/> and <see cref="ReadLong"/>. </remarks>
639         public int CurrentReadIndex
640         {
641             get { return index_; }
642         }
643
644         /// <summary>
645         /// Get the number of bytes remaining to be read for the current value;
646         /// </summary>
647         public int UnreadCount
648         {
649             get 
650             {
651                 if ((readValueStart_ > data_.Length) ||
652                     (readValueStart_ < 4) ) {
653                         throw new ZipException("Find must be called before calling a Read method");
654                     }
655
656                 return readValueStart_ + readValueLength_ - index_; 
657             }
658         }
659
660         /// <summary>
661         /// Find an extra data value
662         /// </summary>
663         /// <param name="headerID">The identifier for the value to find.</param>
664         /// <returns>Returns true if the value was found; false otherwise.</returns>
665         public bool Find(int headerID)
666         {
667             readValueStart_ = data_.Length;
668             readValueLength_ = 0;
669             index_ = 0;
670
671             int localLength = readValueStart_;
672             int localTag = headerID - 1;
673
674             // Trailing bytes that cant make up an entry (as there arent enough
675             // bytes for a tag and length) are ignored!
676             while ( (localTag != headerID) && (index_ < data_.Length - 3) ) {
677                 localTag = ReadShortInternal();
678                 localLength = ReadShortInternal();
679                 if ( localTag != headerID ) {
680                     index_ += localLength;
681                 }
682             }
683
684             bool result = (localTag == headerID) && ((index_ + localLength) <= data_.Length);
685
686             if ( result ) {
687                 readValueStart_ = index_;
688                 readValueLength_ = localLength;
689             }
690
691             return result;
692         }
693
694         /// <summary>
695         /// Add a new entry to extra data.
696         /// </summary>
697         /// <param name="taggedData">The <see cref="ITaggedData"/> value to add.</param>
698         public void AddEntry(ITaggedData taggedData)
699         {
700             if (taggedData == null)
701             {
702                 throw new ArgumentNullException("taggedData");
703             }
704             AddEntry(taggedData.TagID, taggedData.GetData());
705         }
706
707         /// <summary>
708         /// Add a new entry to extra data
709         /// </summary>
710         /// <param name="headerID">The ID for this entry.</param>
711         /// <param name="fieldData">The data to add.</param>
712         /// <remarks>If the ID already exists its contents are replaced.</remarks>
713         public void AddEntry(int headerID, byte[] fieldData)
714         {
715             if ( (headerID > ushort.MaxValue) || (headerID < 0)) {
716                 throw new ArgumentOutOfRangeException("headerID");
717             }
718
719             int addLength = (fieldData == null) ? 0 : fieldData.Length;
720
721             if ( addLength > ushort.MaxValue ) {
722                 throw new ArgumentOutOfRangeException("fieldData", "exceeds maximum length");
723             }
724
725             // Test for new length before adjusting data.
726             int newLength = data_.Length + addLength + 4;
727
728             if ( Find(headerID) )
729             {
730                 newLength -= (ValueLength + 4);
731             }
732
733             if ( newLength > ushort.MaxValue ) {
734                 throw new ZipException("Data exceeds maximum length");
735             }
736                         
737             Delete(headerID);
738
739             byte[] newData = new byte[newLength];
740             data_.CopyTo(newData, 0);
741             int index = data_.Length;
742             data_ = newData;
743             SetShort(ref index, headerID);
744             SetShort(ref index, addLength);
745             if ( fieldData != null ) {
746                 fieldData.CopyTo(newData, index);
747             }
748         }
749
750         /// <summary>
751         /// Start adding a new entry.
752         /// </summary>
753         /// <remarks>Add data using <see cref="AddData(byte[])"/>, <see cref="AddLeShort"/>, <see cref="AddLeInt"/>, or <see cref="AddLeLong"/>.
754         /// The new entry is completed and actually added by calling <see cref="AddNewEntry"/></remarks>
755         /// <seealso cref="AddEntry(ITaggedData)"/>
756         public void StartNewEntry()
757         {
758             newEntry_ = new MemoryStream();
759         }
760
761         /// <summary>
762         /// Add entry data added since <see cref="StartNewEntry"/> using the ID passed.
763         /// </summary>
764         /// <param name="headerID">The identifier to use for this entry.</param>
765         public void AddNewEntry(int headerID)
766         {
767             byte[] newData = newEntry_.ToArray();
768             newEntry_ = null;
769             AddEntry(headerID, newData);
770         }
771
772         /// <summary>
773         /// Add a byte of data to the pending new entry.
774         /// </summary>
775         /// <param name="data">The byte to add.</param>
776         /// <seealso cref="StartNewEntry"/>
777         public void AddData(byte data)
778         {
779             newEntry_.WriteByte(data);
780         }
781
782         /// <summary>
783         /// Add data to a pending new entry.
784         /// </summary>
785         /// <param name="data">The data to add.</param>
786         /// <seealso cref="StartNewEntry"/>
787         public void AddData(byte[] data)
788         {
789             if ( data == null ) {
790                 throw new ArgumentNullException("data");
791             }
792
793             newEntry_.Write(data, 0, data.Length);
794         }
795
796         /// <summary>
797         /// Add a short value in little endian order to the pending new entry.
798         /// </summary>
799         /// <param name="toAdd">The data to add.</param>
800         /// <seealso cref="StartNewEntry"/>
801         public void AddLeShort(int toAdd)
802         {
803             unchecked {
804                 newEntry_.WriteByte(( byte )toAdd);
805                 newEntry_.WriteByte(( byte )(toAdd >> 8));
806             }
807         }
808
809         /// <summary>
810         /// Add an integer value in little endian order to the pending new entry.
811         /// </summary>
812         /// <param name="toAdd">The data to add.</param>
813         /// <seealso cref="StartNewEntry"/>
814         public void AddLeInt(int toAdd)
815         {
816             unchecked {
817                 AddLeShort(( short )toAdd);
818                 AddLeShort(( short )(toAdd >> 16));
819             }
820         }
821
822         /// <summary>
823         /// Add a long value in little endian order to the pending new entry.
824         /// </summary>
825         /// <param name="toAdd">The data to add.</param>
826         /// <seealso cref="StartNewEntry"/>
827         public void AddLeLong(long toAdd)
828         {
829             unchecked {
830                 AddLeInt(( int )(toAdd & 0xffffffff));
831                 AddLeInt(( int )(toAdd >> 32));
832             }
833         }
834
835         /// <summary>
836         /// Delete an extra data field.
837         /// </summary>
838         /// <param name="headerID">The identifier of the field to delete.</param>
839         /// <returns>Returns true if the field was found and deleted.</returns>
840         public bool Delete(int headerID)
841         {
842             bool result = false;
843
844             if ( Find(headerID) ) {
845                 result = true;
846                 int trueStart = readValueStart_ - 4;
847
848                 byte[] newData = new byte[data_.Length - (ValueLength + 4)];
849                 Array.Copy(data_, 0, newData, 0, trueStart);
850
851                 int trueEnd = trueStart + ValueLength + 4;
852                 Array.Copy(data_, trueEnd, newData, trueStart, data_.Length - trueEnd);
853                 data_ = newData;
854             }
855             return result;
856         }
857
858         #region Reading Support
859         /// <summary>
860         /// Read a long in little endian form from the last <see cref="Find">found</see> data value
861         /// </summary>
862         /// <returns>Returns the long value read.</returns>
863         public long ReadLong()
864         {
865             ReadCheck(8);
866             return (ReadInt() & 0xffffffff) | ((( long )ReadInt()) << 32);
867         }
868
869         /// <summary>
870         /// Read an integer in little endian form from the last <see cref="Find">found</see> data value.
871         /// </summary>
872         /// <returns>Returns the integer read.</returns>
873         public int ReadInt()
874         {
875             ReadCheck(4);
876
877             int result = data_[index_] + (data_[index_ + 1] << 8) + 
878                          (data_[index_ + 2] << 16) + (data_[index_ + 3] << 24);
879             index_ += 4;
880             return result;
881         }
882
883         /// <summary>
884         /// Read a short value in little endian form from the last <see cref="Find">found</see> data value.
885         /// </summary>
886         /// <returns>Returns the short value read.</returns>
887         public int ReadShort()
888         {
889             ReadCheck(2);
890             int result = data_[index_] + (data_[index_ + 1] << 8);
891             index_ += 2;
892             return result;
893         }
894
895         /// <summary>
896         /// Read a byte from an extra data
897         /// </summary>
898         /// <returns>The byte value read or -1 if the end of data has been reached.</returns>
899         public int ReadByte()
900         {
901             int result = -1;
902             if ( (index_ < data_.Length) && (readValueStart_ + readValueLength_ > index_) ) {
903                 result = data_[index_];
904                 index_ += 1;
905             }
906             return result;
907         }
908
909         /// <summary>
910         /// Skip data during reading.
911         /// </summary>
912         /// <param name="amount">The number of bytes to skip.</param>
913         public void Skip(int amount)
914         {
915             ReadCheck(amount);
916             index_ += amount;
917         }
918
919         void ReadCheck(int length)
920         {
921             if ((readValueStart_ > data_.Length) ||
922                 (readValueStart_ < 4) ) {
923                     throw new ZipException("Find must be called before calling a Read method");
924                 }
925
926             if (index_ > readValueStart_ + readValueLength_ - length ) {
927                 throw new ZipException("End of extra data");
928             }
929         }
930
931         /// <summary>
932         /// Internal form of <see cref="ReadShort"/> that reads data at any location.
933         /// </summary>
934         /// <returns>Returns the short value read.</returns>
935         int ReadShortInternal()
936         {
937             if ( index_ > data_.Length - 2) {
938                 throw new ZipException("End of extra data");
939             }
940
941             int result = data_[index_] + (data_[index_ + 1] << 8);
942             index_ += 2;
943             return result;
944         }
945
946         void SetShort(ref int index, int source)
947         {
948             data_[index] = (byte)source;
949             data_[index + 1] = (byte)(source >> 8);
950             index += 2;
951         }
952
953         #endregion
954
955         #region IDisposable Members
956
957         /// <summary>
958         /// Dispose of this instance.
959         /// </summary>
960         public void Dispose()
961         {
962             if ( newEntry_ != null ) {
963                 newEntry_.Close();
964             }
965         }
966
967         #endregion
968
969         #region Instance Fields
970         int index_;
971         int readValueStart_;
972         int readValueLength_;
973
974         MemoryStream newEntry_;
975         byte[] data_;
976         #endregion
977     }
978 }