Statistics
| Branch: | Revision:

root / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Zip / ZipExtraData.cs @ 0eea575a

History | View | Annotate | Download (33.2 kB)

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
}