1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Text;
9 
10 namespace System.IO.Compression
11 {
12     // The disposable fields that this class owns get disposed when the ZipArchive it belongs to gets disposed
13     [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
14     public partial class ZipArchiveEntry
15     {
16         private const ushort DefaultVersionToExtract = 10;
17 
18         // The maximum index of our buffers, from the maximum index of a byte array
19         private const int MaxSingleBufferSize = 0x7FFFFFC7;
20 
21         private ZipArchive _archive;
22         private readonly bool _originallyInArchive;
23         private readonly int _diskNumberStart;
24         private readonly ZipVersionMadeByPlatform _versionMadeByPlatform;
25         private ZipVersionNeededValues _versionMadeBySpecification;
26         private ZipVersionNeededValues _versionToExtract;
27         private BitFlagValues _generalPurposeBitFlag;
28         private CompressionMethodValues _storedCompressionMethod;
29         private DateTimeOffset _lastModified;
30         private long _compressedSize;
31         private long _uncompressedSize;
32         private long _offsetOfLocalHeader;
33         private long? _storedOffsetOfCompressedData;
34         private uint _crc32;
35         // An array of buffers, each a maximum of MaxSingleBufferSize in size
36         private byte[][] _compressedBytes;
37         private MemoryStream _storedUncompressedData;
38         private bool _currentlyOpenForWrite;
39         private bool _everOpenedForWrite;
40         private Stream _outstandingWriteStream;
41         private uint _externalFileAttr;
42         private string _storedEntryName;
43         private byte[] _storedEntryNameBytes;
44         // only apply to update mode
45         private List<ZipGenericExtraField> _cdUnknownExtraFields;
46         private List<ZipGenericExtraField> _lhUnknownExtraFields;
47         private byte[] _fileComment;
48         private CompressionLevel? _compressionLevel;
49 
50         // Initializes, attaches it to archive
ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)51         internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd)
52         {
53             _archive = archive;
54 
55             _originallyInArchive = true;
56 
57             _diskNumberStart = cd.DiskNumberStart;
58             _versionMadeByPlatform = (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility;
59             _versionMadeBySpecification = (ZipVersionNeededValues)cd.VersionMadeBySpecification;
60             _versionToExtract = (ZipVersionNeededValues)cd.VersionNeededToExtract;
61             _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag;
62             CompressionMethod = (CompressionMethodValues)cd.CompressionMethod;
63             _lastModified = new DateTimeOffset(ZipHelper.DosTimeToDateTime(cd.LastModified));
64             _compressedSize = cd.CompressedSize;
65             _uncompressedSize = cd.UncompressedSize;
66             _externalFileAttr = cd.ExternalFileAttributes;
67             _offsetOfLocalHeader = cd.RelativeOffsetOfLocalHeader;
68             // we don't know this yet: should be _offsetOfLocalHeader + 30 + _storedEntryNameBytes.Length + extrafieldlength
69             // but entryname/extra length could be different in LH
70             _storedOffsetOfCompressedData = null;
71             _crc32 = cd.Crc32;
72 
73             _compressedBytes = null;
74             _storedUncompressedData = null;
75             _currentlyOpenForWrite = false;
76             _everOpenedForWrite = false;
77             _outstandingWriteStream = null;
78 
79             FullName = DecodeEntryName(cd.Filename);
80 
81             _lhUnknownExtraFields = null;
82             // the cd should have these as null if we aren't in Update mode
83             _cdUnknownExtraFields = cd.ExtraFields;
84             _fileComment = cd.FileComment;
85 
86             _compressionLevel = null;
87         }
88 
89         // Initializes new entry
ZipArchiveEntry(ZipArchive archive, string entryName, CompressionLevel compressionLevel)90         internal ZipArchiveEntry(ZipArchive archive, string entryName, CompressionLevel compressionLevel)
91             : this(archive, entryName)
92         {
93             _compressionLevel = compressionLevel;
94         }
95 
96         // Initializes new entry
ZipArchiveEntry(ZipArchive archive, string entryName)97         internal ZipArchiveEntry(ZipArchive archive, string entryName)
98         {
99             _archive = archive;
100 
101             _originallyInArchive = false;
102 
103             _diskNumberStart = 0;
104             _versionMadeByPlatform = CurrentZipPlatform;
105             _versionMadeBySpecification = ZipVersionNeededValues.Default;
106             _versionToExtract = ZipVersionNeededValues.Default; // this must happen before following two assignment
107             _generalPurposeBitFlag = 0;
108             CompressionMethod = CompressionMethodValues.Deflate;
109             _lastModified = DateTimeOffset.Now;
110 
111             _compressedSize = 0; // we don't know these yet
112             _uncompressedSize = 0;
113             _externalFileAttr = 0;
114             _offsetOfLocalHeader = 0;
115             _storedOffsetOfCompressedData = null;
116             _crc32 = 0;
117 
118             _compressedBytes = null;
119             _storedUncompressedData = null;
120             _currentlyOpenForWrite = false;
121             _everOpenedForWrite = false;
122             _outstandingWriteStream = null;
123 
124             FullName = entryName;
125 
126             _cdUnknownExtraFields = null;
127             _lhUnknownExtraFields = null;
128             _fileComment = null;
129 
130             _compressionLevel = null;
131 
132             if (_storedEntryNameBytes.Length > ushort.MaxValue)
133                 throw new ArgumentException(SR.EntryNamesTooLong);
134 
135             // grab the stream if we're in create mode
136             if (_archive.Mode == ZipArchiveMode.Create)
137             {
138                 _archive.AcquireArchiveStream(this);
139             }
140         }
141 
142         /// <summary>
143         /// The ZipArchive that this entry belongs to. If this entry has been deleted, this will return null.
144         /// </summary>
145         public ZipArchive Archive => _archive;
146 
147         [CLSCompliant(false)]
148         public uint Crc32 => _crc32;
149 
150         /// <summary>
151         /// The compressed size of the entry. If the archive that the entry belongs to is in Create mode, attempts to get this property will always throw an exception. If the archive that the entry belongs to is in update mode, this property will only be valid if the entry has not been opened.
152         /// </summary>
153         /// <exception cref="InvalidOperationException">This property is not available because the entry has been written to or modified.</exception>
154         public long CompressedLength
155         {
156             get
157             {
158                 if (_everOpenedForWrite)
159                     throw new InvalidOperationException(SR.LengthAfterWrite);
160                 return _compressedSize;
161             }
162         }
163 
164         public int ExternalAttributes
165         {
166             get
167             {
168                 return (int)_externalFileAttr;
169             }
170             set
171             {
172                 ThrowIfInvalidArchive();
173                 _externalFileAttr = (uint)value;
174             }
175         }
176 
177         /// <summary>
178         /// The relative path of the entry as stored in the Zip archive. Note that Zip archives allow any string to be the path of the entry, including invalid and absolute paths.
179         /// </summary>
180         public string FullName
181         {
182             get
183             {
184                 return _storedEntryName;
185             }
186 
187             private set
188             {
189                 if (value == null)
190                     throw new ArgumentNullException(nameof(FullName));
191 
192                 bool isUTF8;
193                 _storedEntryNameBytes = EncodeEntryName(value, out isUTF8);
194                 _storedEntryName = value;
195 
196                 if (isUTF8)
197                     _generalPurposeBitFlag |= BitFlagValues.UnicodeFileName;
198                 else
199                     _generalPurposeBitFlag &= ~BitFlagValues.UnicodeFileName;
200 
201                 if (ParseFileName(value, _versionMadeByPlatform) == "")
202                     VersionToExtractAtLeast(ZipVersionNeededValues.ExplicitDirectory);
203             }
204         }
205 
206         /// <summary>
207         /// The last write time of the entry as stored in the Zip archive. When setting this property, the DateTime will be converted to the
208         /// Zip timestamp format, which supports a resolution of two seconds. If the data in the last write time field is not a valid Zip timestamp,
209         /// an indicator value of 1980 January 1 at midnight will be returned.
210         /// </summary>
211         /// <exception cref="NotSupportedException">An attempt to set this property was made, but the ZipArchive that this entry belongs to was
212         /// opened in read-only mode.</exception>
213         /// <exception cref="ArgumentOutOfRangeException">An attempt was made to set this property to a value that cannot be represented in the
214         /// Zip timestamp format. The earliest date/time that can be represented is 1980 January 1 0:00:00 (midnight), and the last date/time
215         /// that can be represented is 2107 December 31 23:59:58 (one second before midnight).</exception>
216         public DateTimeOffset LastWriteTime
217         {
218             get
219             {
220                 return _lastModified;
221             }
222             set
223             {
224                 ThrowIfInvalidArchive();
225                 if (_archive.Mode == ZipArchiveMode.Read)
226                     throw new NotSupportedException(SR.ReadOnlyArchive);
227                 if (_archive.Mode == ZipArchiveMode.Create && _everOpenedForWrite)
228                     throw new IOException(SR.FrozenAfterWrite);
229                 if (value.DateTime.Year < ZipHelper.ValidZipDate_YearMin || value.DateTime.Year > ZipHelper.ValidZipDate_YearMax)
230                     throw new ArgumentOutOfRangeException(nameof(value), SR.DateTimeOutOfRange);
231 
232                 _lastModified = value;
233             }
234         }
235 
236         /// <summary>
237         /// The uncompressed size of the entry. This property is not valid in Create mode, and it is only valid in Update mode if the entry has not been opened.
238         /// </summary>
239         /// <exception cref="InvalidOperationException">This property is not available because the entry has been written to or modified.</exception>
240         public long Length
241         {
242             get
243             {
244                 if (_everOpenedForWrite)
245                     throw new InvalidOperationException(SR.LengthAfterWrite);
246                 return _uncompressedSize;
247             }
248         }
249 
250         /// <summary>
251         /// The filename of the entry. This is equivalent to the substring of Fullname that follows the final directory separator character.
252         /// </summary>
253         public string Name => ParseFileName(FullName, _versionMadeByPlatform);
254 
255         /// <summary>
256         /// Deletes the entry from the archive.
257         /// </summary>
258         /// <exception cref="IOException">The entry is already open for reading or writing.</exception>
259         /// <exception cref="NotSupportedException">The ZipArchive that this entry belongs to was opened in a mode other than ZipArchiveMode.Update. </exception>
260         /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
Delete()261         public void Delete()
262         {
263             if (_archive == null)
264                 return;
265 
266             if (_currentlyOpenForWrite)
267                 throw new IOException(SR.DeleteOpenEntry);
268 
269             if (_archive.Mode != ZipArchiveMode.Update)
270                 throw new NotSupportedException(SR.DeleteOnlyInUpdate);
271 
272             _archive.ThrowIfDisposed();
273 
274             _archive.RemoveEntry(this);
275             _archive = null;
276             UnloadStreams();
277         }
278 
279         /// <summary>
280         /// Opens the entry. If the archive that the entry belongs to was opened in Read mode, the returned stream will be readable, and it may or may not be seekable. If Create mode, the returned stream will be writable and not seekable. If Update mode, the returned stream will be readable, writable, seekable, and support SetLength.
281         /// </summary>
282         /// <returns>A Stream that represents the contents of the entry.</returns>
283         /// <exception cref="IOException">The entry is already currently open for writing. -or- The entry has been deleted from the archive. -or- The archive that this entry belongs to was opened in ZipArchiveMode.Create, and this entry has already been written to once.</exception>
284         /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read. -or- The entry has been compressed using a compression method that is not supported.</exception>
285         /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
Open()286         public Stream Open()
287         {
288             ThrowIfInvalidArchive();
289 
290             switch (_archive.Mode)
291             {
292                 case ZipArchiveMode.Read:
293                     return OpenInReadMode(checkOpenable: true);
294                 case ZipArchiveMode.Create:
295                     return OpenInWriteMode();
296                 case ZipArchiveMode.Update:
297                 default:
298                     Debug.Assert(_archive.Mode == ZipArchiveMode.Update);
299                     return OpenInUpdateMode();
300             }
301         }
302 
303         /// <summary>
304         /// Returns the FullName of the entry.
305         /// </summary>
306         /// <returns>FullName of the entry</returns>
ToString()307         public override string ToString()
308         {
309             return FullName;
310         }
311 
312         // Only allow opening ZipArchives with large ZipArchiveEntries in update mode when running in a 64-bit process.
313         // This is for compatibility with old behavior that threw an exception for all process bitnesses, because this
314         // will not work in a 32-bit process.
315         private static readonly bool s_allowLargeZipArchiveEntriesInUpdateMode = IntPtr.Size > 4;
316 
317         internal bool EverOpenedForWrite => _everOpenedForWrite;
318 
319         private long OffsetOfCompressedData
320         {
321             get
322             {
323                 if (_storedOffsetOfCompressedData == null)
324                 {
325                     _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
326                     // by calling this, we are using local header _storedEntryNameBytes.Length and extraFieldLength
327                     // to find start of data, but still using central directory size information
328                     if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveReader))
329                         throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
330                     _storedOffsetOfCompressedData = _archive.ArchiveStream.Position;
331                 }
332                 return _storedOffsetOfCompressedData.Value;
333             }
334         }
335 
336         private MemoryStream UncompressedData
337         {
338             get
339             {
340                 if (_storedUncompressedData == null)
341                 {
342                     // this means we have never opened it before
343 
344                     // if _uncompressedSize > int.MaxValue, it's still okay, because MemoryStream will just
345                     // grow as data is copied into it
346                     _storedUncompressedData = new MemoryStream((int)_uncompressedSize);
347 
348                     if (_originallyInArchive)
349                     {
350                         using (Stream decompressor = OpenInReadMode(false))
351                         {
352                             try
353                             {
354                                 decompressor.CopyTo(_storedUncompressedData);
355                             }
356                             catch (InvalidDataException)
357                             {
358                                 // this is the case where the archive say the entry is deflate, but deflateStream
359                                 // throws an InvalidDataException. This property should only be getting accessed in
360                                 // Update mode, so we want to make sure _storedUncompressedData stays null so
361                                 // that later when we dispose the archive, this entry loads the compressedBytes, and
362                                 // copies them straight over
363                                 _storedUncompressedData.Dispose();
364                                 _storedUncompressedData = null;
365                                 _currentlyOpenForWrite = false;
366                                 _everOpenedForWrite = false;
367                                 throw;
368                             }
369                         }
370                     }
371 
372                     // if they start modifying it, we should make sure it will get deflated
373                     CompressionMethod = CompressionMethodValues.Deflate;
374                 }
375 
376                 return _storedUncompressedData;
377             }
378         }
379 
380         private CompressionMethodValues CompressionMethod
381         {
382             get { return _storedCompressionMethod; }
383             set
384             {
385                 if (value == CompressionMethodValues.Deflate)
386                     VersionToExtractAtLeast(ZipVersionNeededValues.Deflate);
387                 else if (value == CompressionMethodValues.Deflate64)
388                     VersionToExtractAtLeast(ZipVersionNeededValues.Deflate64);
389                 _storedCompressionMethod = value;
390             }
391         }
392 
DecodeEntryName(byte[] entryNameBytes)393         private string DecodeEntryName(byte[] entryNameBytes)
394         {
395             Debug.Assert(entryNameBytes != null);
396 
397             Encoding readEntryNameEncoding;
398             if ((_generalPurposeBitFlag & BitFlagValues.UnicodeFileName) == 0)
399             {
400                 readEntryNameEncoding = _archive == null ?
401                     Encoding.UTF8 :
402                     _archive.EntryNameEncoding ?? Encoding.UTF8;
403             }
404             else
405             {
406                 readEntryNameEncoding = Encoding.UTF8;
407             }
408 
409             return readEntryNameEncoding.GetString(entryNameBytes);
410         }
411 
EncodeEntryName(string entryName, out bool isUTF8)412         private byte[] EncodeEntryName(string entryName, out bool isUTF8)
413         {
414             Debug.Assert(entryName != null);
415 
416             Encoding writeEntryNameEncoding;
417             if (_archive != null && _archive.EntryNameEncoding != null)
418                 writeEntryNameEncoding = _archive.EntryNameEncoding;
419             else
420                 writeEntryNameEncoding = ZipHelper.RequiresUnicode(entryName) ? Encoding.UTF8 : Encoding.ASCII;
421 
422             isUTF8 = writeEntryNameEncoding.Equals(Encoding.UTF8);
423             return writeEntryNameEncoding.GetBytes(entryName);
424         }
425 
426         // does almost everything you need to do to forget about this entry
427         // writes the local header/data, gets rid of all the data,
428         // closes all of the streams except for the very outermost one that
429         // the user holds on to and is responsible for closing
430         //
431         // after calling this, and only after calling this can we be guaranteed
432         // that we are reading to write the central directory
433         //
434         // should only throw an exception in extremely exceptional cases because it is called from dispose
WriteAndFinishLocalEntry()435         internal void WriteAndFinishLocalEntry()
436         {
437             CloseStreams();
438             WriteLocalFileHeaderAndDataIfNeeded();
439             UnloadStreams();
440         }
441 
442         // should only throw an exception in extremely exceptional cases because it is called from dispose
WriteCentralDirectoryFileHeader()443         internal void WriteCentralDirectoryFileHeader()
444         {
445             // This part is simple, because we should definitely know the sizes by this time
446             BinaryWriter writer = new BinaryWriter(_archive.ArchiveStream);
447 
448             // _entryname only gets set when we read in or call moveTo. MoveTo does a check, and
449             // reading in should not be able to produce an entryname longer than ushort.MaxValue
450             Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
451 
452             // decide if we need the Zip64 extra field:
453             Zip64ExtraField zip64ExtraField = new Zip64ExtraField();
454             uint compressedSizeTruncated, uncompressedSizeTruncated, offsetOfLocalHeaderTruncated;
455 
456             bool zip64Needed = false;
457 
458             if (SizesTooLarge()
459 #if DEBUG_FORCE_ZIP64
460                 || _archive._forceZip64
461 #endif
462                 )
463             {
464                 zip64Needed = true;
465                 compressedSizeTruncated = ZipHelper.Mask32Bit;
466                 uncompressedSizeTruncated = ZipHelper.Mask32Bit;
467 
468                 // If we have one of the sizes, the other must go in there as speced for LH, but not necessarily for CH, but we do it anyways
469                 zip64ExtraField.CompressedSize = _compressedSize;
470                 zip64ExtraField.UncompressedSize = _uncompressedSize;
471             }
472             else
473             {
474                 compressedSizeTruncated = (uint)_compressedSize;
475                 uncompressedSizeTruncated = (uint)_uncompressedSize;
476             }
477 
478 
479             if (_offsetOfLocalHeader > uint.MaxValue
480 #if DEBUG_FORCE_ZIP64
481                 || _archive._forceZip64
482 #endif
483                 )
484             {
485                 zip64Needed = true;
486                 offsetOfLocalHeaderTruncated = ZipHelper.Mask32Bit;
487 
488                 // If we have one of the sizes, the other must go in there as speced for LH, but not necessarily for CH, but we do it anyways
489                 zip64ExtraField.LocalHeaderOffset = _offsetOfLocalHeader;
490             }
491             else
492             {
493                 offsetOfLocalHeaderTruncated = (uint)_offsetOfLocalHeader;
494             }
495 
496             if (zip64Needed)
497                 VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
498 
499             // determine if we can fit zip64 extra field and original extra fields all in
500             int bigExtraFieldLength = (zip64Needed ? zip64ExtraField.TotalSize : 0)
501                                       + (_cdUnknownExtraFields != null ? ZipGenericExtraField.TotalSize(_cdUnknownExtraFields) : 0);
502             ushort extraFieldLength;
503             if (bigExtraFieldLength > ushort.MaxValue)
504             {
505                 extraFieldLength = (ushort)(zip64Needed ? zip64ExtraField.TotalSize : 0);
506                 _cdUnknownExtraFields = null;
507             }
508             else
509             {
510                 extraFieldLength = (ushort)bigExtraFieldLength;
511             }
512 
513             writer.Write(ZipCentralDirectoryFileHeader.SignatureConstant);      // Central directory file header signature  (4 bytes)
514             writer.Write((byte)_versionMadeBySpecification);                    // Version made by Specification (version)  (1 byte)
515             writer.Write((byte)CurrentZipPlatform);                             // Version made by Compatibility (type)     (1 byte)
516             writer.Write((ushort)_versionToExtract);                            // Minimum version needed to extract        (2 bytes)
517             writer.Write((ushort)_generalPurposeBitFlag);                       // General Purpose bit flag                 (2 bytes)
518             writer.Write((ushort)CompressionMethod);                            // The Compression method                   (2 bytes)
519             writer.Write(ZipHelper.DateTimeToDosTime(_lastModified.DateTime));  // File last modification time and date     (4 bytes)
520             writer.Write(_crc32);                                               // CRC-32                                   (4 bytes)
521             writer.Write(compressedSizeTruncated);                              // Compressed Size                          (4 bytes)
522             writer.Write(uncompressedSizeTruncated);                            // Uncompressed Size                        (4 bytes)
523             writer.Write((ushort)_storedEntryNameBytes.Length);                 // File Name Length                         (2 bytes)
524             writer.Write(extraFieldLength);                                     // Extra Field Length                       (2 bytes)
525 
526             // This should hold because of how we read it originally in ZipCentralDirectoryFileHeader:
527             Debug.Assert((_fileComment == null) || (_fileComment.Length <= ushort.MaxValue));
528 
529             writer.Write(_fileComment != null ? (ushort)_fileComment.Length : (ushort)0); // file comment length
530             writer.Write((ushort)0); // disk number start
531             writer.Write((ushort)0); // internal file attributes
532             writer.Write(_externalFileAttr); // external file attributes
533             writer.Write(offsetOfLocalHeaderTruncated); // offset of local header
534 
535             writer.Write(_storedEntryNameBytes);
536 
537             // write extra fields
538             if (zip64Needed)
539                 zip64ExtraField.WriteBlock(_archive.ArchiveStream);
540             if (_cdUnknownExtraFields != null)
541                 ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _archive.ArchiveStream);
542 
543             if (_fileComment != null)
544                 writer.Write(_fileComment);
545         }
546 
547         // returns false if fails, will get called on every entry before closing in update mode
548         // can throw InvalidDataException
LoadLocalHeaderExtraFieldAndCompressedBytesIfNeeded()549         internal bool LoadLocalHeaderExtraFieldAndCompressedBytesIfNeeded()
550         {
551             string message;
552             // we should have made this exact call in _archive.Init through ThrowIfOpenable
553             Debug.Assert(IsOpenable(false, true, out message));
554 
555             // load local header's extra fields. it will be null if we couldn't read for some reason
556             if (_originallyInArchive)
557             {
558                 _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
559 
560                 _lhUnknownExtraFields = ZipLocalFileHeader.GetExtraFields(_archive.ArchiveReader);
561             }
562 
563             if (!_everOpenedForWrite && _originallyInArchive)
564             {
565                 // we know that it is openable at this point
566 
567                 _compressedBytes = new byte[(_compressedSize / MaxSingleBufferSize) + 1][];
568                 for (int i = 0; i < _compressedBytes.Length - 1; i++)
569                 {
570                     _compressedBytes[i] = new byte[MaxSingleBufferSize];
571                 }
572                 _compressedBytes[_compressedBytes.Length - 1] = new byte[_compressedSize % MaxSingleBufferSize];
573 
574                 _archive.ArchiveStream.Seek(OffsetOfCompressedData, SeekOrigin.Begin);
575 
576                 for (int i = 0; i < _compressedBytes.Length - 1; i++)
577                 {
578                     ZipHelper.ReadBytes(_archive.ArchiveStream, _compressedBytes[i], MaxSingleBufferSize);
579                 }
580                 ZipHelper.ReadBytes(_archive.ArchiveStream, _compressedBytes[_compressedBytes.Length - 1], (int)(_compressedSize % MaxSingleBufferSize));
581             }
582 
583             return true;
584         }
585 
ThrowIfNotOpenable(bool needToUncompress, bool needToLoadIntoMemory)586         internal void ThrowIfNotOpenable(bool needToUncompress, bool needToLoadIntoMemory)
587         {
588             string message;
589             if (!IsOpenable(needToUncompress, needToLoadIntoMemory, out message))
590                 throw new InvalidDataException(message);
591         }
592 
GetDataCompressor(Stream backingStream, bool leaveBackingStreamOpen, EventHandler onClose)593         private CheckSumAndSizeWriteStream GetDataCompressor(Stream backingStream, bool leaveBackingStreamOpen, EventHandler onClose)
594         {
595             // stream stack: backingStream -> DeflateStream -> CheckSumWriteStream
596 
597             // we should always be compressing with deflate. Stored is used for empty files, but we don't actually
598             // call through this function for that - we just write the stored value in the header
599             Debug.Assert(CompressionMethod == CompressionMethodValues.Deflate);
600 
601             Stream compressorStream = _compressionLevel.HasValue ?
602                 new DeflateStream(backingStream, _compressionLevel.Value, leaveBackingStreamOpen) :
603                 new DeflateStream(backingStream, CompressionMode.Compress, leaveBackingStreamOpen);
604 
605             bool isIntermediateStream = true;
606 
607             bool leaveCompressorStreamOpenOnClose = leaveBackingStreamOpen && !isIntermediateStream;
608             var checkSumStream = new CheckSumAndSizeWriteStream(
609                 compressorStream,
610                 backingStream,
611                 leaveCompressorStreamOpenOnClose,
612                 this,
613                 onClose,
614                 (long initialPosition, long currentPosition, uint checkSum, Stream backing, ZipArchiveEntry thisRef, EventHandler closeHandler) =>
615                 {
616                     thisRef._crc32 = checkSum;
617                     thisRef._uncompressedSize = currentPosition;
618                     thisRef._compressedSize = backing.Position - initialPosition;
619                     closeHandler?.Invoke(thisRef, EventArgs.Empty);
620                 });
621 
622             return checkSumStream;
623         }
624 
GetDataDecompressor(Stream compressedStreamToRead)625         private Stream GetDataDecompressor(Stream compressedStreamToRead)
626         {
627             Stream uncompressedStream = null;
628             switch (CompressionMethod)
629             {
630                 case CompressionMethodValues.Deflate:
631                     uncompressedStream = new DeflateStream(compressedStreamToRead, CompressionMode.Decompress);
632                     break;
633                 case CompressionMethodValues.Deflate64:
634                     uncompressedStream = new DeflateManagedStream(compressedStreamToRead, CompressionMethodValues.Deflate64);
635                     break;
636                 case CompressionMethodValues.Stored:
637                 default:
638                     // we can assume that only deflate/deflate64/stored are allowed because we assume that
639                     // IsOpenable is checked before this function is called
640                     Debug.Assert(CompressionMethod == CompressionMethodValues.Stored);
641 
642                     uncompressedStream = compressedStreamToRead;
643                     break;
644             }
645 
646             return uncompressedStream;
647         }
648 
OpenInReadMode(bool checkOpenable)649         private Stream OpenInReadMode(bool checkOpenable)
650         {
651             if (checkOpenable)
652                 ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: false);
653 
654             Stream compressedStream = new SubReadStream(_archive.ArchiveStream, OffsetOfCompressedData, _compressedSize);
655             return GetDataDecompressor(compressedStream);
656         }
657 
OpenInWriteMode()658         private Stream OpenInWriteMode()
659         {
660             if (_everOpenedForWrite)
661                 throw new IOException(SR.CreateModeWriteOnceAndOneEntryAtATime);
662 
663             // we assume that if another entry grabbed the archive stream, that it set this entry's _everOpenedForWrite property to true by calling WriteLocalFileHeaderIfNeeed
664             _archive.DebugAssertIsStillArchiveStreamOwner(this);
665 
666             _everOpenedForWrite = true;
667             CheckSumAndSizeWriteStream crcSizeStream = GetDataCompressor(_archive.ArchiveStream, true, (object o, EventArgs e) =>
668             {
669                 // release the archive stream
670                 var entry = (ZipArchiveEntry)o;
671                 entry._archive.ReleaseArchiveStream(entry);
672                 entry._outstandingWriteStream = null;
673             });
674             _outstandingWriteStream = new DirectToArchiveWriterStream(crcSizeStream, this);
675 
676             return new WrappedStream(baseStream: _outstandingWriteStream, closeBaseStream: true);
677         }
678 
OpenInUpdateMode()679         private Stream OpenInUpdateMode()
680         {
681             if (_currentlyOpenForWrite)
682                 throw new IOException(SR.UpdateModeOneStream);
683 
684             ThrowIfNotOpenable(needToUncompress: true, needToLoadIntoMemory: true);
685 
686             _everOpenedForWrite = true;
687             _currentlyOpenForWrite = true;
688             // always put it at the beginning for them
689             UncompressedData.Seek(0, SeekOrigin.Begin);
690             return new WrappedStream(UncompressedData, this, thisRef =>
691             {
692                 // once they close, we know uncompressed length, but still not compressed length
693                 // so we don't fill in any size information
694                 // those fields get figured out when we call GetCompressor as we write it to
695                 // the actual archive
696                 thisRef._currentlyOpenForWrite = false;
697             });
698         }
699 
IsOpenable(bool needToUncompress, bool needToLoadIntoMemory, out string message)700         private bool IsOpenable(bool needToUncompress, bool needToLoadIntoMemory, out string message)
701         {
702             message = null;
703 
704             if (_originallyInArchive)
705             {
706                 if (needToUncompress)
707                 {
708                     if (CompressionMethod != CompressionMethodValues.Stored &&
709                         CompressionMethod != CompressionMethodValues.Deflate &&
710                         CompressionMethod != CompressionMethodValues.Deflate64)
711                     {
712                         switch (CompressionMethod)
713                         {
714                             case CompressionMethodValues.BZip2:
715                             case CompressionMethodValues.LZMA:
716                                 message = SR.Format(SR.UnsupportedCompressionMethod, CompressionMethod.ToString());
717                                 break;
718                             default:
719                                 message = SR.UnsupportedCompression;
720                                 break;
721                         }
722                         return false;
723                     }
724                 }
725                 if (_diskNumberStart != _archive.NumberOfThisDisk)
726                 {
727                     message = SR.SplitSpanned;
728                     return false;
729                 }
730                 if (_offsetOfLocalHeader > _archive.ArchiveStream.Length)
731                 {
732                     message = SR.LocalFileHeaderCorrupt;
733                     return false;
734                 }
735                 _archive.ArchiveStream.Seek(_offsetOfLocalHeader, SeekOrigin.Begin);
736                 if (!ZipLocalFileHeader.TrySkipBlock(_archive.ArchiveReader))
737                 {
738                     message = SR.LocalFileHeaderCorrupt;
739                     return false;
740                 }
741                 // when this property gets called, some duplicated work
742                 if (OffsetOfCompressedData + _compressedSize > _archive.ArchiveStream.Length)
743                 {
744                     message = SR.LocalFileHeaderCorrupt;
745                     return false;
746                 }
747                 // This limitation originally existed because a) it is unreasonable to load > 4GB into memory
748                 // but also because the stream reading functions make it hard.  This has been updated to handle
749                 // this scenario in a 64-bit process using multiple buffers, delivered first as an OOB for
750                 // compatibility.
751                 if (needToLoadIntoMemory)
752                 {
753                     if (_compressedSize > int.MaxValue)
754                     {
755                         if (!s_allowLargeZipArchiveEntriesInUpdateMode)
756                         {
757                             message = SR.EntryTooLarge;
758                             return false;
759                         }
760                     }
761                 }
762             }
763 
764             return true;
765         }
766 
SizesTooLarge()767         private bool SizesTooLarge() => _compressedSize > uint.MaxValue || _uncompressedSize > uint.MaxValue;
768 
769         // return value is true if we allocated an extra field for 64 bit headers, un/compressed size
WriteLocalFileHeader(bool isEmptyFile)770         private bool WriteLocalFileHeader(bool isEmptyFile)
771         {
772             BinaryWriter writer = new BinaryWriter(_archive.ArchiveStream);
773 
774             // _entryname only gets set when we read in or call moveTo. MoveTo does a check, and
775             // reading in should not be able to produce an entryname longer than ushort.MaxValue
776             Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);
777 
778             // decide if we need the Zip64 extra field:
779             Zip64ExtraField zip64ExtraField = new Zip64ExtraField();
780             bool zip64Used = false;
781             uint compressedSizeTruncated, uncompressedSizeTruncated;
782 
783             // if we already know that we have an empty file don't worry about anything, just do a straight shot of the header
784             if (isEmptyFile)
785             {
786                 CompressionMethod = CompressionMethodValues.Stored;
787                 compressedSizeTruncated = 0;
788                 uncompressedSizeTruncated = 0;
789                 Debug.Assert(_compressedSize == 0);
790                 Debug.Assert(_uncompressedSize == 0);
791                 Debug.Assert(_crc32 == 0);
792             }
793             else
794             {
795                 // if we have a non-seekable stream, don't worry about sizes at all, and just set the right bit
796                 // if we are using the data descriptor, then sizes and crc should be set to 0 in the header
797                 if (_archive.Mode == ZipArchiveMode.Create && _archive.ArchiveStream.CanSeek == false && !isEmptyFile)
798                 {
799                     _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
800                     zip64Used = false;
801                     compressedSizeTruncated = 0;
802                     uncompressedSizeTruncated = 0;
803                     // the crc should not have been set if we are in create mode, but clear it just to be sure
804                     Debug.Assert(_crc32 == 0);
805                 }
806                 else // if we are not in streaming mode, we have to decide if we want to write zip64 headers
807                 {
808                     if (SizesTooLarge()
809 #if DEBUG_FORCE_ZIP64
810                         || (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update)
811 #endif
812                         )
813                     {
814                         zip64Used = true;
815                         compressedSizeTruncated = ZipHelper.Mask32Bit;
816                         uncompressedSizeTruncated = ZipHelper.Mask32Bit;
817 
818                         // prepare Zip64 extra field object. If we have one of the sizes, the other must go in there
819                         zip64ExtraField.CompressedSize = _compressedSize;
820                         zip64ExtraField.UncompressedSize = _uncompressedSize;
821 
822                         VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
823                     }
824                     else
825                     {
826                         zip64Used = false;
827                         compressedSizeTruncated = (uint)_compressedSize;
828                         uncompressedSizeTruncated = (uint)_uncompressedSize;
829                     }
830                 }
831             }
832 
833             // save offset
834             _offsetOfLocalHeader = writer.BaseStream.Position;
835 
836             // calculate extra field. if zip64 stuff + original extraField aren't going to fit, dump the original extraField, because this is more important
837             int bigExtraFieldLength = (zip64Used ? zip64ExtraField.TotalSize : 0)
838                                       + (_lhUnknownExtraFields != null ? ZipGenericExtraField.TotalSize(_lhUnknownExtraFields) : 0);
839             ushort extraFieldLength;
840             if (bigExtraFieldLength > ushort.MaxValue)
841             {
842                 extraFieldLength = (ushort)(zip64Used ? zip64ExtraField.TotalSize : 0);
843                 _lhUnknownExtraFields = null;
844             }
845             else
846             {
847                 extraFieldLength = (ushort)bigExtraFieldLength;
848             }
849 
850             // write header
851             writer.Write(ZipLocalFileHeader.SignatureConstant);
852             writer.Write((ushort)_versionToExtract);
853             writer.Write((ushort)_generalPurposeBitFlag);
854             writer.Write((ushort)CompressionMethod);
855             writer.Write(ZipHelper.DateTimeToDosTime(_lastModified.DateTime)); // uint
856             writer.Write(_crc32); // uint
857             writer.Write(compressedSizeTruncated); // uint
858             writer.Write(uncompressedSizeTruncated); // uint
859             writer.Write((ushort)_storedEntryNameBytes.Length);
860             writer.Write(extraFieldLength); // ushort
861 
862             writer.Write(_storedEntryNameBytes);
863 
864             if (zip64Used)
865                 zip64ExtraField.WriteBlock(_archive.ArchiveStream);
866             if (_lhUnknownExtraFields != null)
867                 ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _archive.ArchiveStream);
868 
869             return zip64Used;
870         }
871 
WriteLocalFileHeaderAndDataIfNeeded()872         private void WriteLocalFileHeaderAndDataIfNeeded()
873         {
874             // _storedUncompressedData gets frozen here, and is what gets written to the file
875             if (_storedUncompressedData != null || _compressedBytes != null)
876             {
877                 if (_storedUncompressedData != null)
878                 {
879                     _uncompressedSize = _storedUncompressedData.Length;
880 
881                     //The compressor fills in CRC and sizes
882                     //The DirectToArchiveWriterStream writes headers and such
883                     using (Stream entryWriter = new DirectToArchiveWriterStream(
884                                                     GetDataCompressor(_archive.ArchiveStream, true, null),
885                                                     this))
886                     {
887                         _storedUncompressedData.Seek(0, SeekOrigin.Begin);
888                         _storedUncompressedData.CopyTo(entryWriter);
889                         _storedUncompressedData.Dispose();
890                         _storedUncompressedData = null;
891                     }
892                 }
893                 else
894                 {
895                     // we know the sizes at this point, so just go ahead and write the headers
896                     if (_uncompressedSize == 0)
897                         CompressionMethod = CompressionMethodValues.Stored;
898                     WriteLocalFileHeader(isEmptyFile: false);
899                     foreach (byte[] compressedBytes in _compressedBytes)
900                     {
901                         _archive.ArchiveStream.Write(compressedBytes, 0, compressedBytes.Length);
902                     }
903                 }
904             }
905             else // there is no data in the file, but if we are in update mode, we still need to write a header
906             {
907                 if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite)
908                 {
909                     _everOpenedForWrite = true;
910                     WriteLocalFileHeader(isEmptyFile: true);
911                 }
912             }
913         }
914 
915         // Using _offsetOfLocalHeader, seeks back to where CRC and sizes should be in the header,
916         // writes them, then seeks back to where you started
917         // Assumes that the stream is currently at the end of the data
WriteCrcAndSizesInLocalHeader(bool zip64HeaderUsed)918         private void WriteCrcAndSizesInLocalHeader(bool zip64HeaderUsed)
919         {
920             long finalPosition = _archive.ArchiveStream.Position;
921             BinaryWriter writer = new BinaryWriter(_archive.ArchiveStream);
922 
923             bool zip64Needed = SizesTooLarge()
924 #if DEBUG_FORCE_ZIP64
925                 || _archive._forceZip64
926 #endif
927             ;
928 
929             bool pretendStreaming = zip64Needed && !zip64HeaderUsed;
930 
931             uint compressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_compressedSize;
932             uint uncompressedSizeTruncated = zip64Needed ? ZipHelper.Mask32Bit : (uint)_uncompressedSize;
933 
934             // first step is, if we need zip64, but didn't allocate it, pretend we did a stream write, because
935             // we can't go back and give ourselves the space that the extra field needs.
936             // we do this by setting the correct property in the bit flag
937             if (pretendStreaming)
938             {
939                 _generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
940 
941                 _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.OffsetToBitFlagFromHeaderStart,
942                                             SeekOrigin.Begin);
943                 writer.Write((ushort)_generalPurposeBitFlag);
944             }
945 
946             // next step is fill out the 32-bit size values in the normal header. we can't assume that
947             // they are correct. we also write the CRC
948             _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.OffsetToCrcFromHeaderStart,
949                                             SeekOrigin.Begin);
950             if (!pretendStreaming)
951             {
952                 writer.Write(_crc32);
953                 writer.Write(compressedSizeTruncated);
954                 writer.Write(uncompressedSizeTruncated);
955             }
956             else // but if we are pretending to stream, we want to fill in with zeroes
957             {
958                 writer.Write((uint)0);
959                 writer.Write((uint)0);
960                 writer.Write((uint)0);
961             }
962 
963             // next step: if we wrote the 64 bit header initially, a different implementation might
964             // try to read it, even if the 32-bit size values aren't masked. thus, we should always put the
965             // correct size information in there. note that order of uncomp/comp is switched, and these are
966             // 64-bit values
967             // also, note that in order for this to be correct, we have to insure that the zip64 extra field
968             // is always the first extra field that is written
969             if (zip64HeaderUsed)
970             {
971                 _archive.ArchiveStream.Seek(_offsetOfLocalHeader + ZipLocalFileHeader.SizeOfLocalHeader
972                                             + _storedEntryNameBytes.Length + Zip64ExtraField.OffsetToFirstField,
973                                             SeekOrigin.Begin);
974                 writer.Write(_uncompressedSize);
975                 writer.Write(_compressedSize);
976 
977                 _archive.ArchiveStream.Seek(finalPosition, SeekOrigin.Begin);
978             }
979 
980             // now go to the where we were. assume that this is the end of the data
981             _archive.ArchiveStream.Seek(finalPosition, SeekOrigin.Begin);
982 
983             // if we are pretending we did a stream write, we want to write the data descriptor out
984             // the data descriptor can have 32-bit sizes or 64-bit sizes. In this case, we always use
985             // 64-bit sizes
986             if (pretendStreaming)
987             {
988                 writer.Write(_crc32);
989                 writer.Write(_compressedSize);
990                 writer.Write(_uncompressedSize);
991             }
992         }
993 
WriteDataDescriptor()994         private void WriteDataDescriptor()
995         {
996             // data descriptor can be 32-bit or 64-bit sizes. 32-bit is more compatible, so use that if possible
997             // signature is optional but recommended by the spec
998 
999             BinaryWriter writer = new BinaryWriter(_archive.ArchiveStream);
1000 
1001             writer.Write(ZipLocalFileHeader.DataDescriptorSignature);
1002             writer.Write(_crc32);
1003             if (SizesTooLarge())
1004             {
1005                 writer.Write(_compressedSize);
1006                 writer.Write(_uncompressedSize);
1007             }
1008             else
1009             {
1010                 writer.Write((uint)_compressedSize);
1011                 writer.Write((uint)_uncompressedSize);
1012             }
1013         }
1014 
UnloadStreams()1015         private void UnloadStreams()
1016         {
1017             if (_storedUncompressedData != null)
1018                 _storedUncompressedData.Dispose();
1019             _compressedBytes = null;
1020             _outstandingWriteStream = null;
1021         }
1022 
CloseStreams()1023         private void CloseStreams()
1024         {
1025             // if the user left the stream open, close the underlying stream for them
1026             if (_outstandingWriteStream != null)
1027             {
1028                 _outstandingWriteStream.Dispose();
1029             }
1030         }
1031 
VersionToExtractAtLeast(ZipVersionNeededValues value)1032         private void VersionToExtractAtLeast(ZipVersionNeededValues value)
1033         {
1034             if (_versionToExtract < value)
1035             {
1036                 _versionToExtract = value;
1037             }
1038             if (_versionMadeBySpecification < value)
1039             {
1040                 _versionMadeBySpecification = value;
1041             }
1042         }
1043 
ThrowIfInvalidArchive()1044         private void ThrowIfInvalidArchive()
1045         {
1046             if (_archive == null)
1047                 throw new InvalidOperationException(SR.DeletedEntry);
1048             _archive.ThrowIfDisposed();
1049         }
1050 
1051         /// <summary>
1052         /// Gets the file name of the path based on Windows path separator characters
1053         /// </summary>
GetFileName_Windows(string path)1054         private static string GetFileName_Windows(string path)
1055         {
1056             int length = path.Length;
1057             for (int i = length; --i >= 0;)
1058             {
1059                 char ch = path[i];
1060                 if (ch == '\\' || ch == '/' || ch == ':')
1061                     return path.Substring(i + 1);
1062             }
1063             return path;
1064         }
1065 
1066         /// <summary>
1067         /// Gets the file name of the path based on Unix path separator characters
1068         /// </summary>
GetFileName_Unix(string path)1069         private static string GetFileName_Unix(string path)
1070         {
1071             int length = path.Length;
1072             for (int i = length; --i >= 0;)
1073                 if (path[i] == '/')
1074                     return path.Substring(i + 1);
1075             return path;
1076         }
1077 
1078         private sealed partial class DirectToArchiveWriterStream : Stream
1079         {
1080             private long _position;
1081             private CheckSumAndSizeWriteStream _crcSizeStream;
1082             private bool _everWritten;
1083             private bool _isDisposed;
1084             private ZipArchiveEntry _entry;
1085             private bool _usedZip64inLH;
1086             private bool _canWrite;
1087 
1088             // makes the assumption that somewhere down the line, crcSizeStream is eventually writing directly to the archive
1089             // this class calls other functions on ZipArchiveEntry that write directly to the archive
DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, ZipArchiveEntry entry)1090             public DirectToArchiveWriterStream(CheckSumAndSizeWriteStream crcSizeStream, ZipArchiveEntry entry)
1091             {
1092                 _position = 0;
1093                 _crcSizeStream = crcSizeStream;
1094                 _everWritten = false;
1095                 _isDisposed = false;
1096                 _entry = entry;
1097                 _usedZip64inLH = false;
1098                 _canWrite = true;
1099             }
1100 
1101             public override long Length
1102             {
1103                 get
1104                 {
1105                     ThrowIfDisposed();
1106                     throw new NotSupportedException(SR.SeekingNotSupported);
1107                 }
1108             }
1109             public override long Position
1110             {
1111                 get
1112                 {
1113                     ThrowIfDisposed();
1114                     return _position;
1115                 }
1116                 set
1117                 {
1118                     ThrowIfDisposed();
1119                     throw new NotSupportedException(SR.SeekingNotSupported);
1120                 }
1121             }
1122 
1123             public override bool CanRead => false;
1124             public override bool CanSeek => false;
1125             public override bool CanWrite => _canWrite;
1126 
ThrowIfDisposed()1127             private void ThrowIfDisposed()
1128             {
1129                 if (_isDisposed)
1130                     throw new ObjectDisposedException(GetType().ToString(), SR.HiddenStreamName);
1131             }
1132 
Read(byte[] buffer, int offset, int count)1133             public override int Read(byte[] buffer, int offset, int count)
1134             {
1135                 ThrowIfDisposed();
1136                 throw new NotSupportedException(SR.ReadingNotSupported);
1137             }
1138 
Seek(long offset, SeekOrigin origin)1139             public override long Seek(long offset, SeekOrigin origin)
1140             {
1141                 ThrowIfDisposed();
1142                 throw new NotSupportedException(SR.SeekingNotSupported);
1143             }
1144 
SetLength(long value)1145             public override void SetLength(long value)
1146             {
1147                 ThrowIfDisposed();
1148                 throw new NotSupportedException(SR.SetLengthRequiresSeekingAndWriting);
1149             }
1150 
1151             // careful: assumes that write is the only way to write to the stream, if writebyte/beginwrite are implemented
1152             // they must set _everWritten, etc.
Write(byte[] buffer, int offset, int count)1153             public override void Write(byte[] buffer, int offset, int count)
1154             {
1155                 //we can't pass the argument checking down a level
1156                 if (buffer == null)
1157                     throw new ArgumentNullException(nameof(buffer));
1158                 if (offset < 0)
1159                     throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentNeedNonNegative);
1160                 if (count < 0)
1161                     throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentNeedNonNegative);
1162                 if ((buffer.Length - offset) < count)
1163                     throw new ArgumentException(SR.OffsetLengthInvalid);
1164 
1165                 ThrowIfDisposed();
1166                 Debug.Assert(CanWrite);
1167 
1168                 // if we're not actually writing anything, we don't want to trigger the header
1169                 if (count == 0)
1170                     return;
1171 
1172                 if (!_everWritten)
1173                 {
1174                     _everWritten = true;
1175                     // write local header, we are good to go
1176                     _usedZip64inLH = _entry.WriteLocalFileHeader(isEmptyFile: false);
1177                 }
1178 
1179                 _crcSizeStream.Write(buffer, offset, count);
1180                 _position += count;
1181             }
1182 
Flush()1183             public override void Flush()
1184             {
1185                 ThrowIfDisposed();
1186                 Debug.Assert(CanWrite);
1187 
1188                 _crcSizeStream.Flush();
1189             }
1190 
Dispose(bool disposing)1191             protected override void Dispose(bool disposing)
1192             {
1193                 if (disposing && !_isDisposed)
1194                 {
1195                     _crcSizeStream.Dispose(); // now we have size/crc info
1196 
1197                     if (!_everWritten)
1198                     {
1199                         // write local header, no data, so we use stored
1200                         _entry.WriteLocalFileHeader(isEmptyFile: true);
1201                     }
1202                     else
1203                     {
1204                         // go back and finish writing
1205                         if (_entry._archive.ArchiveStream.CanSeek)
1206                             // finish writing local header if we have seek capabilities
1207 
1208                             _entry.WriteCrcAndSizesInLocalHeader(_usedZip64inLH);
1209                         else
1210                             // write out data descriptor if we don't have seek capabilities
1211                             _entry.WriteDataDescriptor();
1212                     }
1213                     _canWrite = false;
1214                     _isDisposed = true;
1215                 }
1216 
1217                 base.Dispose(disposing);
1218             }
1219         }
1220 
1221         [Flags]
1222         private enum BitFlagValues : ushort { DataDescriptor = 0x8, UnicodeFileName = 0x800 }
1223 
1224         internal enum CompressionMethodValues : ushort { Stored = 0x0, Deflate = 0x8, Deflate64 = 0x9, BZip2 = 0xC, LZMA = 0xE }
1225     }
1226 }
1227