Example #1
0
        /// <summary>
        ///   Finishes the stream.  This will write the central directory at the
        ///   end of the zip file and flush the stream.
        /// </summary>
        /// <remarks>
        ///   This is automatically called when the stream is closed.
        /// </remarks>
        /// <exception cref="System.IO.IOException">
        ///   An I/O error occurs.
        /// </exception>
        /// <exception cref="ZipException">
        ///   Comment exceeds the maximum length<br />
        ///   Entry name exceeds the maximum length
        /// </exception>
        public override void Finish()
        {
            if (entries == null)
            {
                return;
            }

            if (curEntry != null)
            {
                CloseEntry();
            }

            long numEntries  = entries.Count;
            long sizeEntries = 0;

            foreach (ZipEntry entry in entries)
            {
                WriteLeInt(ZipConstants.CentralHeaderSignature);
                WriteLeShort(ZipConstants.VersionMadeBy);
                WriteLeShort(entry.Version);
                WriteLeShort(entry.Flags);
                WriteLeShort((short)entry.CompressionMethod);
                WriteLeInt((int)entry.DosTime);
                WriteLeInt((int)entry.Crc);

                if (entry.IsZip64Forced() ||
                    (entry.CompressedSize >= uint.MaxValue))
                {
                    WriteLeInt(-1);
                }
                else
                {
                    WriteLeInt((int)entry.CompressedSize);
                }

                if (entry.IsZip64Forced() ||
                    (entry.Size >= uint.MaxValue))
                {
                    WriteLeInt(-1);
                }
                else
                {
                    WriteLeInt((int)entry.Size);
                }

                var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);

                if (name.Length > 0xffff)
                {
                    throw new ZipException("Name too long.");
                }

                var ed = new ZipExtraData(entry.ExtraData);

                if (entry.CentralHeaderRequiresZip64)
                {
                    ed.StartNewEntry();
                    if (entry.IsZip64Forced() ||
                        (entry.Size >= 0xffffffff))
                    {
                        ed.AddLeLong(entry.Size);
                    }

                    if (entry.IsZip64Forced() ||
                        (entry.CompressedSize >= 0xffffffff))
                    {
                        ed.AddLeLong(entry.CompressedSize);
                    }

                    if (entry.Offset >= 0xffffffff)
                    {
                        ed.AddLeLong(entry.Offset);
                    }

                    ed.AddNewEntry(1);
                }
                else
                {
                    ed.Delete(1);
                }

                var extra = ed.GetEntryData();

                var entryComment =
                    (entry.Comment != null)
            ? ZipConstants.ConvertToArray(entry.Flags, entry.Comment)
            : new byte[0];

                if (entryComment.Length > 0xffff)
                {
                    throw new ZipException("Comment too long.");
                }

                WriteLeShort(name.Length);
                WriteLeShort(extra.Length);
                WriteLeShort(entryComment.Length);
                WriteLeShort(0); // disk number
                WriteLeShort(0); // internal file attributes
                // external file attributes

                if (entry.ExternalFileAttributes != -1)
                {
                    WriteLeInt(entry.ExternalFileAttributes);
                }
                else
                {
                    WriteLeInt(entry.IsDirectory ? 16 : 0);
                }

                if (entry.Offset >= uint.MaxValue)
                {
                    WriteLeInt(-1);
                }
                else
                {
                    WriteLeInt((int)entry.Offset);
                }

                if (name.Length > 0)
                {
                    baseOutputStream_.Write(name, 0, name.Length);
                }

                if (extra.Length > 0)
                {
                    baseOutputStream_.Write(extra, 0, extra.Length);
                }

                if (entryComment.Length > 0)
                {
                    baseOutputStream_.Write(entryComment, 0, entryComment.Length);
                }

                sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
            }

            using (var zhs = new ZipHelperStream(baseOutputStream_))
            {
                zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment);
            }

            entries = null;
        }
Example #2
0
        /// <summary>
        ///   Process extra data fields updating the entry based on the contents.
        /// </summary>
        /// <param name="localHeader">
        ///   True if the extra data fields should be handled
        ///   for a local header, rather than for a central header.
        /// </param>
        internal void ProcessExtraData(bool localHeader)
        {
            var extraData = new ZipExtraData(extra);

            if (extraData.Find(0x0001))
            {
                if ((versionToExtract & 0xff) < ZipConstants.VersionZip64)
                {
                    throw new ZipException("Zip64 Extended information found but version is not valid");
                }

                // The recorded size will change but remember that this is zip64.
                forceZip64_ = true;

                if (extraData.ValueLength < 4)
                {
                    throw new ZipException("Extra data extended Zip64 information length is invalid");
                }

                if (localHeader || (size == uint.MaxValue))
                {
                    size = (ulong)extraData.ReadLong();
                }

                if (localHeader || (compressedSize == uint.MaxValue))
                {
                    compressedSize = (ulong)extraData.ReadLong();
                }

                if (!localHeader && (offset == uint.MaxValue))
                {
                    offset = extraData.ReadLong();
                }
            }
            else
            {
                if (
                    ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) &&
                    ((size == uint.MaxValue) || (compressedSize == uint.MaxValue))
                    )
                {
                    throw new ZipException("Zip64 Extended information required but is missing.");
                }
            }

            if (extraData.Find(10))
            {
                // No room for any tags.
                if (extraData.ValueLength < 8)
                {
                    throw new ZipException("NTFS Extra data invalid");
                }

                extraData.ReadInt(); // Reserved

                while (extraData.UnreadCount >= 4)
                {
                    var ntfsTag    = extraData.ReadShort();
                    var ntfsLength = extraData.ReadShort();
                    if (ntfsTag == 1)
                    {
                        if (ntfsLength >= 24)
                        {
                            var lastModification = extraData.ReadLong();
                            //long lastAccess = extraData.ReadLong();
                            //long createTime = extraData.ReadLong();
                            extraData.Skip(16);

                            DateTime = DateTime.FromFileTime(lastModification);
                        }
                        break;
                    }
                    // An unknown NTFS tag so simply skip it.
                    extraData.Skip(ntfsLength);
                }
            }
            else if (extraData.Find(0x5455))
            {
                var length = extraData.ValueLength;
                var flags  = extraData.ReadByte();

                // Can include other times but these are ignored.  Length of data should
                // actually be 1 + 4 * no of bits in flags.
                if (((flags & 1) != 0) && (length >= 5))
                {
                    var iTime = extraData.ReadInt();

                    DateTime = (new DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime() +
                                new TimeSpan(0, 0, 0, iTime, 0)).ToLocalTime();
                }
            }
        }
Example #3
0
    /// <summary>
    ///   Test a local header against that provided from the central directory
    /// </summary>
    /// <param name="entry">
    ///   The entry to test against
    /// </param>
    /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param>
    /// <returns>The offset of the entries data in the file</returns>
    private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
    {
      lock (baseStream_)
      {
        var testHeader = (tests & HeaderTest.Header) != 0;
        var testData = (tests & HeaderTest.Extract) != 0;

        baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin);
        if ((int) ReadLEUint() != ZipConstants.LocalHeaderSignature)
        {
          throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset));
        }

        var extractVersion = (short) ReadLEUshort();
        var localFlags = (short) ReadLEUshort();
        var compressionMethod = (short) ReadLEUshort();
        var fileTime = (short) ReadLEUshort();
        var fileDate = (short) ReadLEUshort();
        var crcValue = ReadLEUint();
        long compressedSize = ReadLEUint();
        long size = ReadLEUint();
        int storedNameLength = ReadLEUshort();
        int extraDataLength = ReadLEUshort();

        var nameData = new byte[storedNameLength];
        StreamUtils.ReadFully(baseStream_, nameData);

        var extraData = new byte[extraDataLength];
        StreamUtils.ReadFully(baseStream_, extraData);

        var localExtraData = new ZipExtraData(extraData);

        // Extra data / zip64 checks
        if (localExtraData.Find(1))
        {
          // TODO Check for tag values being distinct..  Multiple zip64 tags means what?

          // Zip64 extra data but 'extract version' is too low
          if (extractVersion < ZipConstants.VersionZip64)
          {
            throw new ZipException(
              string.Format("Extra data contains Zip64 information but version {0}.{1} is not high enough",
                            extractVersion/10, extractVersion%10));
          }

          // Zip64 extra data but size fields dont indicate its required.
          if (((uint) size != uint.MaxValue) && ((uint) compressedSize != uint.MaxValue))
          {
            throw new ZipException("Entry sizes not correct for Zip64");
          }

          size = localExtraData.ReadLong();
          compressedSize = localExtraData.ReadLong();

          if ((localFlags & (int) GeneralBitFlags.Descriptor) != 0)
          {
            // These may be valid if patched later
            if ((size != -1) && (size != entry.Size))
            {
              throw new ZipException("Size invalid for descriptor");
            }

            if ((compressedSize != -1) && (compressedSize != entry.CompressedSize))
            {
              throw new ZipException("Compressed size invalid for descriptor");
            }
          }
        }
        else
        {
          // No zip64 extra data but entry requires it.
          if ((extractVersion >= ZipConstants.VersionZip64) &&
              (((uint) size == uint.MaxValue) || ((uint) compressedSize == uint.MaxValue)))
          {
            throw new ZipException("Required Zip64 extended information missing");
          }
        }

        if (testData)
        {
          if (entry.IsFile)
          {
            if (!entry.IsCompressionMethodSupported())
            {
              throw new ZipException("Compression method not supported");
            }

            if ((extractVersion > ZipConstants.VersionMadeBy)
                || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64)))
            {
              throw new ZipException(string.Format("Version required to extract this entry not supported ({0})",
                                                   extractVersion));
            }

            if ((localFlags &
                 (int)
                   (GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress |
                    GeneralBitFlags.HeaderMasked)) != 0)
            {
              throw new ZipException("The library does not support the zip version required to extract this entry");
            }
          }
        }

        if (testHeader)
        {
          if ((extractVersion <= 63) && // Ignore later versions as we dont know about them..
              (extractVersion != 10) &&
              (extractVersion != 11) &&
              (extractVersion != 20) &&
              (extractVersion != 21) &&
              (extractVersion != 25) &&
              (extractVersion != 27) &&
              (extractVersion != 45) &&
              (extractVersion != 46) &&
              (extractVersion != 50) &&
              (extractVersion != 51) &&
              (extractVersion != 52) &&
              (extractVersion != 61) &&
              (extractVersion != 62) &&
              (extractVersion != 63)
            )
          {
            throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})",
                                                 extractVersion));
          }

          // Local entry flags dont have reserved bit set on.
          if ((localFlags &
               (int)
                 (GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) !=
              0)
          {
            throw new ZipException("Reserved bit flags cannot be set.");
          }

          // Encryption requires extract version >= 20
          if (((localFlags & (int) GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20))
          {
            throw new ZipException(
              string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
          }

          // Strong encryption requires encryption flag to be set and extract version >= 50.
          if ((localFlags & (int) GeneralBitFlags.StrongEncryption) != 0)
          {
            if ((localFlags & (int) GeneralBitFlags.Encrypted) == 0)
            {
              throw new ZipException("Strong encryption flag set but encryption flag is not set");
            }

            if (extractVersion < 50)
            {
              throw new ZipException(
                string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
            }
          }

          // Patched entries require extract version >= 27
          if (((localFlags & (int) GeneralBitFlags.Patched) != 0) && (extractVersion < 27))
          {
            throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion));
          }

          // Central header flags match local entry flags.
          if (localFlags != entry.Flags)
          {
            throw new ZipException("Central header/local header flags mismatch");
          }

          // Central header compression method matches local entry
          if (entry.CompressionMethod != (CompressionMethod) compressionMethod)
          {
            throw new ZipException("Central header/local header compression method mismatch");
          }

          if (entry.Version != extractVersion)
          {
            throw new ZipException("Extract version mismatch");
          }

          // Strong encryption and extract version match
          if ((localFlags & (int) GeneralBitFlags.StrongEncryption) != 0)
          {
            if (extractVersion < 62)
            {
              throw new ZipException("Strong encryption flag set but version not high enough");
            }
          }

          if ((localFlags & (int) GeneralBitFlags.HeaderMasked) != 0)
          {
            if ((fileTime != 0) || (fileDate != 0))
            {
              throw new ZipException("Header masked set but date/time values non-zero");
            }
          }

          if ((localFlags & (int) GeneralBitFlags.Descriptor) == 0)
          {
            if (crcValue != (uint) entry.Crc)
            {
              throw new ZipException("Central header/local header crc mismatch");
            }
          }

          // Crc valid for empty entry.
          // This will also apply to streamed entries where size isnt known and the header cant be patched
          if ((size == 0) && (compressedSize == 0))
          {
            if (crcValue != 0)
            {
              throw new ZipException("Invalid CRC for empty entry");
            }
          }

          // TODO: make test more correct...  can't compare lengths as was done originally as this can fail for MBCS strings
          // Assuming a code page at this point is not valid?  Best is to store the name length in the ZipEntry probably
          if (entry.Name.Length > storedNameLength)
          {
            throw new ZipException("File name length mismatch");
          }

          // Name data has already been read convert it and compare.
          var localName = ZipConstants.ConvertToStringExt(localFlags, nameData);

          // Central directory and local entry name match
          if (localName != entry.Name)
          {
            throw new ZipException("Central header and local header file name mismatch");
          }

          // Directories have zero actual size but can have compressed size
          if (entry.IsDirectory)
          {
            if (size > 0)
            {
              throw new ZipException("Directory cannot have size");
            }

            // There may be other cases where the compressed size can be greater than this?
            // If so until details are known we will be strict.
            if (compressedSize > 2)
            {
              // When not compressed the directory size can validly be 2 bytes
              // if the true size wasnt known when data was originally being written.
              // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes
              throw new ZipException("Directory compressed size invalid");
            }
          }

          if (!ZipNameTransform.IsValidName(localName, true))
          {
            throw new ZipException("Name is invalid");
          }
        }

        // Tests that apply to both data and header.

        // Size can be verified only if it is known in the local header.
        // it will always be known in the central header.
        if (((localFlags & (int) GeneralBitFlags.Descriptor) == 0) ||
            ((size > 0) || (compressedSize > 0)))
        {
          if (size != entry.Size)
          {
            throw new ZipException(
              string.Format("Size mismatch between central header({0}) and local header({1})",
                            entry.Size, size));
          }

          if (compressedSize != entry.CompressedSize)
          {
            throw new ZipException(
              string.Format("Compressed size mismatch between central header({0}) and local header({1})",
                            entry.CompressedSize, compressedSize));
          }
        }

        var extraLength = storedNameLength + extraDataLength;
        return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength;
      }
    }
Example #4
0
        /// <summary>
        ///   Starts a new Zip entry. It automatically closes the previous
        ///   entry if present.
        ///   All entry elements bar name are optional, but must be correct if present.
        ///   If the compression method is stored and the output is not patchable
        ///   the compression for that entry is automatically changed to deflate level 0
        /// </summary>
        /// <param name="entry">
        ///   the entry.
        /// </param>
        /// <exception cref="System.ArgumentNullException">
        ///   if entry passed is null.
        /// </exception>
        /// <exception cref="System.IO.IOException">
        ///   if an I/O error occured.
        /// </exception>
        /// <exception cref="System.InvalidOperationException">
        ///   if stream was finished
        /// </exception>
        /// <exception cref="ZipException">
        ///   Too many entries in the Zip file<br />
        ///   Entry name is too long<br />
        ///   Finish has already been called<br />
        /// </exception>
        public void PutNextEntry(ZipEntry entry)
        {
            if (entry == null)
            {
                throw new ArgumentNullException("entry");
            }

            if (entries == null)
            {
                throw new InvalidOperationException("ZipOutputStream was finished");
            }

            if (curEntry != null)
            {
                CloseEntry();
            }

            if (entries.Count == int.MaxValue)
            {
                throw new ZipException("Too many entries for Zip file");
            }

            var method           = entry.CompressionMethod;
            var compressionLevel = defaultCompressionLevel;

            // Clear flags that the library manages internally
            entry.Flags     &= (int)GeneralBitFlags.UnicodeText;
            patchEntryHeader = false;

            bool headerInfoAvailable;

            // No need to compress - definitely no data.
            if (entry.Size == 0)
            {
                entry.CompressedSize = entry.Size;
                entry.Crc            = 0;
                method = CompressionMethod.Stored;
                headerInfoAvailable = true;
            }
            else
            {
                headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc;

                // Switch to deflation if storing isnt possible.
                if (method == CompressionMethod.Stored)
                {
                    if (!headerInfoAvailable)
                    {
                        if (!CanPatchEntries)
                        {
                            // Can't patch entries so storing is not possible.
                            method           = CompressionMethod.Deflated;
                            compressionLevel = 0;
                        }
                    }
                    else // entry.size must be > 0
                    {
                        entry.CompressedSize = entry.Size;
                        headerInfoAvailable  = entry.HasCrc;
                    }
                }
            }

            if (headerInfoAvailable == false)
            {
                if (CanPatchEntries == false)
                {
                    // Only way to record size and compressed size is to append a data descriptor
                    // after compressed data.

                    // Stored entries of this form have already been converted to deflating.
                    entry.Flags |= 8;
                }
                else
                {
                    patchEntryHeader = true;
                }
            }

            entry.Offset            = offset;
            entry.CompressionMethod = method;

            curMethod    = method;
            sizePatchPos = -1;

            if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic)))
            {
                entry.ForceZip64();
            }

            // Write the local file header
            WriteLeInt(ZipConstants.LocalHeaderSignature);

            WriteLeShort(entry.Version);
            WriteLeShort(entry.Flags);
            WriteLeShort((byte)method);
            WriteLeInt((int)entry.DosTime);

            // TODO: Refactor header writing.  Its done in several places.
            if (headerInfoAvailable)
            {
                WriteLeInt((int)entry.Crc);
                if (entry.LocalHeaderRequiresZip64)
                {
                    WriteLeInt(-1);
                    WriteLeInt(-1);
                }
                else
                {
                    WriteLeInt((int)entry.CompressedSize);
                    WriteLeInt((int)entry.Size);
                }
            }
            else
            {
                if (patchEntryHeader)
                {
                    crcPatchPos = baseOutputStream_.Position;
                }
                WriteLeInt(0); // Crc

                if (patchEntryHeader)
                {
                    sizePatchPos = baseOutputStream_.Position;
                }

                // For local header both sizes appear in Zip64 Extended Information
                if (entry.LocalHeaderRequiresZip64 || patchEntryHeader)
                {
                    WriteLeInt(-1);
                    WriteLeInt(-1);
                }
                else
                {
                    WriteLeInt(0); // Compressed size
                    WriteLeInt(0); // Uncompressed size
                }
            }

            var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);

            if (name.Length > 0xFFFF)
            {
                throw new ZipException("Entry name too long.");
            }

            var ed = new ZipExtraData(entry.ExtraData);

            if (entry.LocalHeaderRequiresZip64)
            {
                ed.StartNewEntry();
                if (headerInfoAvailable)
                {
                    ed.AddLeLong(entry.Size);
                    ed.AddLeLong(entry.CompressedSize);
                }
                else
                {
                    ed.AddLeLong(-1);
                    ed.AddLeLong(-1);
                }
                ed.AddNewEntry(1);

                if (!ed.Find(1))
                {
                    throw new ZipException("Internal error cant find extra data");
                }

                if (patchEntryHeader)
                {
                    sizePatchPos = ed.CurrentReadIndex;
                }
            }
            else
            {
                ed.Delete(1);
            }

            var extra = ed.GetEntryData();

            WriteLeShort(name.Length);
            WriteLeShort(extra.Length);

            if (name.Length > 0)
            {
                baseOutputStream_.Write(name, 0, name.Length);
            }

            if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
            {
                sizePatchPos += baseOutputStream_.Position;
            }

            if (extra.Length > 0)
            {
                baseOutputStream_.Write(extra, 0, extra.Length);
            }

            offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;

            // Activate the entry.
            curEntry = entry;
            crc.Reset();
            if (method == CompressionMethod.Deflated)
            {
                deflater_.Reset();
                deflater_.SetLevel(compressionLevel);
            }
            size = 0;
        }
Example #5
0
    private int WriteCentralDirectoryHeader(ZipEntry entry)
    {
      if (entry.CompressedSize < 0)
      {
        throw new ZipException("Attempt to write central directory entry with unknown csize");
      }

      if (entry.Size < 0)
      {
        throw new ZipException("Attempt to write central directory entry with unknown size");
      }

      if (entry.Crc < 0)
      {
        throw new ZipException("Attempt to write central directory entry with unknown crc");
      }

      // Write the central file header
      WriteLEInt(ZipConstants.CentralHeaderSignature);

      // Version made by
      WriteLEShort(ZipConstants.VersionMadeBy);

      // Version required to extract
      WriteLEShort(entry.Version);

      WriteLEShort(entry.Flags);

      unchecked
      {
        WriteLEShort((byte) entry.CompressionMethod);
        WriteLEInt((int) entry.DosTime);
        WriteLEInt((int) entry.Crc);
      }

      if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff))
      {
        WriteLEInt(-1);
      }
      else
      {
        WriteLEInt((int) (entry.CompressedSize & 0xffffffff));
      }

      if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff))
      {
        WriteLEInt(-1);
      }
      else
      {
        WriteLEInt((int) entry.Size);
      }

      var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);

      if (name.Length > 0xFFFF)
      {
        throw new ZipException("Entry name is too long.");
      }

      WriteLEShort(name.Length);

      // Central header extra data is different to local header version so regenerate.
      var ed = new ZipExtraData(entry.ExtraData);

      if (entry.CentralHeaderRequiresZip64)
      {
        ed.StartNewEntry();

        if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On))
        {
          ed.AddLeLong(entry.Size);
        }

        if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On))
        {
          ed.AddLeLong(entry.CompressedSize);
        }

        if (entry.Offset >= 0xffffffff)
        {
          ed.AddLeLong(entry.Offset);
        }

        // Number of disk on which this file starts isnt supported and is never written here.
        ed.AddNewEntry(1);
      }
      else
      {
        // Should have already be done when local header was added.
        ed.Delete(1);
      }

      var centralExtraData = ed.GetEntryData();

      WriteLEShort(centralExtraData.Length);
      WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0);

      WriteLEShort(0); // disk number
      WriteLEShort(0); // internal file attributes

      // External file attributes...
      if (entry.ExternalFileAttributes != -1)
      {
        WriteLEInt(entry.ExternalFileAttributes);
      }
      else
      {
        WriteLEUint(entry.IsDirectory ? (uint) 16 : 0);
      }

      if (entry.Offset >= 0xffffffff)
      {
        WriteLEUint(0xffffffff);
      }
      else
      {
        WriteLEUint((uint) (int) entry.Offset);
      }

      if (name.Length > 0)
      {
        baseStream_.Write(name, 0, name.Length);
      }

      if (centralExtraData.Length > 0)
      {
        baseStream_.Write(centralExtraData, 0, centralExtraData.Length);
      }

      var rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0];

      if (rawComment.Length > 0)
      {
        baseStream_.Write(rawComment, 0, rawComment.Length);
      }

      return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length;
    }
Example #6
0
    private void WriteLocalEntryHeader(ZipUpdate update)
    {
      var entry = update.OutEntry;

      // TODO: Local offset will require adjusting for multi-disk zip files.
      entry.Offset = baseStream_.Position;

      // TODO: Need to clear any entry flags that dont make sense or throw an exception here.
      if (update.Command != UpdateCommand.Copy)
      {
        if (entry.CompressionMethod == CompressionMethod.Deflated)
        {
          if (entry.Size == 0)
          {
            // No need to compress - no data.
            entry.CompressedSize = entry.Size;
            entry.Crc = 0;
            entry.CompressionMethod = CompressionMethod.Stored;
          }
        }
        else if (entry.CompressionMethod == CompressionMethod.Stored)
        {
          entry.Flags &= ~(int) GeneralBitFlags.Descriptor;
        }

        switch (useZip64_)
        {
          case UseZip64.Dynamic:
            if (entry.Size < 0)
            {
              entry.ForceZip64();
            }
            break;

          case UseZip64.On:
            entry.ForceZip64();
            break;

          case UseZip64.Off:
            // Do nothing.  The entry itself may be using Zip64 independantly.
            break;
        }
      }

      // Write the local file header
      WriteLEInt(ZipConstants.LocalHeaderSignature);

      WriteLEShort(entry.Version);
      WriteLEShort(entry.Flags);

      WriteLEShort((byte) entry.CompressionMethod);
      WriteLEInt((int) entry.DosTime);

      if (!entry.HasCrc)
      {
        // Note patch address for updating CRC later.
        update.CrcPatchOffset = baseStream_.Position;
        WriteLEInt(0);
      }
      else
      {
        WriteLEInt(unchecked((int) entry.Crc));
      }

      if (entry.LocalHeaderRequiresZip64)
      {
        WriteLEInt(-1);
        WriteLEInt(-1);
      }
      else
      {
        if ((entry.CompressedSize < 0) || (entry.Size < 0))
        {
          update.SizePatchOffset = baseStream_.Position;
        }

        WriteLEInt((int) entry.CompressedSize);
        WriteLEInt((int) entry.Size);
      }

      var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);

      if (name.Length > 0xFFFF)
      {
        throw new ZipException("Entry name too long.");
      }

      var ed = new ZipExtraData(entry.ExtraData);

      if (entry.LocalHeaderRequiresZip64)
      {
        ed.StartNewEntry();

        // Local entry header always includes size and compressed size.
        // NOTE the order of these fields is reversed when compared to the normal headers!
        ed.AddLeLong(entry.Size);
        ed.AddLeLong(entry.CompressedSize);
        ed.AddNewEntry(1);
      }
      else
      {
        ed.Delete(1);
      }

      entry.ExtraData = ed.GetEntryData();

      WriteLEShort(name.Length);
      WriteLEShort(entry.ExtraData.Length);

      if (name.Length > 0)
      {
        baseStream_.Write(name, 0, name.Length);
      }

      if (entry.LocalHeaderRequiresZip64)
      {
        if (!ed.Find(1))
        {
          throw new ZipException("Internal error cannot find extra data");
        }

        update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex;
      }

      if (entry.ExtraData.Length > 0)
      {
        baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length);
      }
    }
Example #7
0
    /// <summary>
    ///   Process extra data fields updating the entry based on the contents.
    /// </summary>
    /// <param name="localHeader">
    ///   True if the extra data fields should be handled
    ///   for a local header, rather than for a central header.
    /// </param>
    internal void ProcessExtraData(bool localHeader)
    {
      var extraData = new ZipExtraData(extra);

      if (extraData.Find(0x0001))
      {
        if ((versionToExtract & 0xff) < ZipConstants.VersionZip64)
        {
          throw new ZipException("Zip64 Extended information found but version is not valid");
        }

        // The recorded size will change but remember that this is zip64.
        forceZip64_ = true;

        if (extraData.ValueLength < 4)
        {
          throw new ZipException("Extra data extended Zip64 information length is invalid");
        }

        if (localHeader || (size == uint.MaxValue))
        {
          size = (ulong) extraData.ReadLong();
        }

        if (localHeader || (compressedSize == uint.MaxValue))
        {
          compressedSize = (ulong) extraData.ReadLong();
        }

        if (!localHeader && (offset == uint.MaxValue))
        {
          offset = extraData.ReadLong();
        }
      }
      else
      {
        if (
          ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) &&
          ((size == uint.MaxValue) || (compressedSize == uint.MaxValue))
          )
        {
          throw new ZipException("Zip64 Extended information required but is missing.");
        }
      }

      if (extraData.Find(10))
      {
        // No room for any tags.
        if (extraData.ValueLength < 8)
        {
          throw new ZipException("NTFS Extra data invalid");
        }

        extraData.ReadInt(); // Reserved

        while (extraData.UnreadCount >= 4)
        {
          var ntfsTag = extraData.ReadShort();
          var ntfsLength = extraData.ReadShort();
          if (ntfsTag == 1)
          {
            if (ntfsLength >= 24)
            {
              var lastModification = extraData.ReadLong();
              //long lastAccess = extraData.ReadLong();
              //long createTime = extraData.ReadLong();
              extraData.Skip(16);

              DateTime = DateTime.FromFileTime(lastModification);
            }
            break;
          }
          // An unknown NTFS tag so simply skip it.
          extraData.Skip(ntfsLength);
        }
      }
      else if (extraData.Find(0x5455))
      {
        var length = extraData.ValueLength;
        var flags = extraData.ReadByte();

        // Can include other times but these are ignored.  Length of data should
        // actually be 1 + 4 * no of bits in flags.
        if (((flags & 1) != 0) && (length >= 5))
        {
          var iTime = extraData.ReadInt();

          DateTime = (new DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime() +
                      new TimeSpan(0, 0, 0, iTime, 0)).ToLocalTime();
        }
      }
    }
Example #8
0
    /// <summary>
    ///   Finishes the stream.  This will write the central directory at the
    ///   end of the zip file and flush the stream.
    /// </summary>
    /// <remarks>
    ///   This is automatically called when the stream is closed.
    /// </remarks>
    /// <exception cref="System.IO.IOException">
    ///   An I/O error occurs.
    /// </exception>
    /// <exception cref="ZipException">
    ///   Comment exceeds the maximum length<br />
    ///   Entry name exceeds the maximum length
    /// </exception>
    public override void Finish()
    {
      if (entries == null)
      {
        return;
      }

      if (curEntry != null)
      {
        CloseEntry();
      }

      long numEntries = entries.Count;
      long sizeEntries = 0;

      foreach (ZipEntry entry in entries)
      {
        WriteLeInt(ZipConstants.CentralHeaderSignature);
        WriteLeShort(ZipConstants.VersionMadeBy);
        WriteLeShort(entry.Version);
        WriteLeShort(entry.Flags);
        WriteLeShort((short) entry.CompressionMethod);
        WriteLeInt((int) entry.DosTime);
        WriteLeInt((int) entry.Crc);

        if (entry.IsZip64Forced() ||
            (entry.CompressedSize >= uint.MaxValue))
        {
          WriteLeInt(-1);
        }
        else
        {
          WriteLeInt((int) entry.CompressedSize);
        }

        if (entry.IsZip64Forced() ||
            (entry.Size >= uint.MaxValue))
        {
          WriteLeInt(-1);
        }
        else
        {
          WriteLeInt((int) entry.Size);
        }

        var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);

        if (name.Length > 0xffff)
        {
          throw new ZipException("Name too long.");
        }

        var ed = new ZipExtraData(entry.ExtraData);

        if (entry.CentralHeaderRequiresZip64)
        {
          ed.StartNewEntry();
          if (entry.IsZip64Forced() ||
              (entry.Size >= 0xffffffff))
          {
            ed.AddLeLong(entry.Size);
          }

          if (entry.IsZip64Forced() ||
              (entry.CompressedSize >= 0xffffffff))
          {
            ed.AddLeLong(entry.CompressedSize);
          }

          if (entry.Offset >= 0xffffffff)
          {
            ed.AddLeLong(entry.Offset);
          }

          ed.AddNewEntry(1);
        }
        else
        {
          ed.Delete(1);
        }

        var extra = ed.GetEntryData();

        var entryComment =
          (entry.Comment != null)
            ? ZipConstants.ConvertToArray(entry.Flags, entry.Comment)
            : new byte[0];

        if (entryComment.Length > 0xffff)
        {
          throw new ZipException("Comment too long.");
        }

        WriteLeShort(name.Length);
        WriteLeShort(extra.Length);
        WriteLeShort(entryComment.Length);
        WriteLeShort(0); // disk number
        WriteLeShort(0); // internal file attributes
        // external file attributes

        if (entry.ExternalFileAttributes != -1)
        {
          WriteLeInt(entry.ExternalFileAttributes);
        }
        else
        {
          WriteLeInt(entry.IsDirectory ? 16 : 0);
        }

        if (entry.Offset >= uint.MaxValue)
        {
          WriteLeInt(-1);
        }
        else
        {
          WriteLeInt((int) entry.Offset);
        }

        if (name.Length > 0)
        {
          baseOutputStream_.Write(name, 0, name.Length);
        }

        if (extra.Length > 0)
        {
          baseOutputStream_.Write(extra, 0, extra.Length);
        }

        if (entryComment.Length > 0)
        {
          baseOutputStream_.Write(entryComment, 0, entryComment.Length);
        }

        sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
      }

      using (var zhs = new ZipHelperStream(baseOutputStream_))
      {
        zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment);
      }

      entries = null;
    }
Example #9
0
    /// <summary>
    ///   Starts a new Zip entry. It automatically closes the previous
    ///   entry if present.
    ///   All entry elements bar name are optional, but must be correct if present.
    ///   If the compression method is stored and the output is not patchable
    ///   the compression for that entry is automatically changed to deflate level 0
    /// </summary>
    /// <param name="entry">
    ///   the entry.
    /// </param>
    /// <exception cref="System.ArgumentNullException">
    ///   if entry passed is null.
    /// </exception>
    /// <exception cref="System.IO.IOException">
    ///   if an I/O error occured.
    /// </exception>
    /// <exception cref="System.InvalidOperationException">
    ///   if stream was finished
    /// </exception>
    /// <exception cref="ZipException">
    ///   Too many entries in the Zip file<br />
    ///   Entry name is too long<br />
    ///   Finish has already been called<br />
    /// </exception>
    public void PutNextEntry(ZipEntry entry)
    {
      if (entry == null)
      {
        throw new ArgumentNullException("entry");
      }

      if (entries == null)
      {
        throw new InvalidOperationException("ZipOutputStream was finished");
      }

      if (curEntry != null)
      {
        CloseEntry();
      }

      if (entries.Count == int.MaxValue)
      {
        throw new ZipException("Too many entries for Zip file");
      }

      var method = entry.CompressionMethod;
      var compressionLevel = defaultCompressionLevel;

      // Clear flags that the library manages internally
      entry.Flags &= (int) GeneralBitFlags.UnicodeText;
      patchEntryHeader = false;

      bool headerInfoAvailable;

      // No need to compress - definitely no data.
      if (entry.Size == 0)
      {
        entry.CompressedSize = entry.Size;
        entry.Crc = 0;
        method = CompressionMethod.Stored;
        headerInfoAvailable = true;
      }
      else
      {
        headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc;

        // Switch to deflation if storing isnt possible.
        if (method == CompressionMethod.Stored)
        {
          if (!headerInfoAvailable)
          {
            if (!CanPatchEntries)
            {
              // Can't patch entries so storing is not possible.
              method = CompressionMethod.Deflated;
              compressionLevel = 0;
            }
          }
          else // entry.size must be > 0
          {
            entry.CompressedSize = entry.Size;
            headerInfoAvailable = entry.HasCrc;
          }
        }
      }

      if (headerInfoAvailable == false)
      {
        if (CanPatchEntries == false)
        {
          // Only way to record size and compressed size is to append a data descriptor
          // after compressed data.

          // Stored entries of this form have already been converted to deflating.
          entry.Flags |= 8;
        }
        else
        {
          patchEntryHeader = true;
        }
      }

      entry.Offset = offset;
      entry.CompressionMethod = method;

      curMethod = method;
      sizePatchPos = -1;

      if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic)))
      {
        entry.ForceZip64();
      }

      // Write the local file header
      WriteLeInt(ZipConstants.LocalHeaderSignature);

      WriteLeShort(entry.Version);
      WriteLeShort(entry.Flags);
      WriteLeShort((byte) method);
      WriteLeInt((int) entry.DosTime);

      // TODO: Refactor header writing.  Its done in several places.
      if (headerInfoAvailable)
      {
        WriteLeInt((int) entry.Crc);
        if (entry.LocalHeaderRequiresZip64)
        {
          WriteLeInt(-1);
          WriteLeInt(-1);
        }
        else
        {
          WriteLeInt((int) entry.CompressedSize);
          WriteLeInt((int) entry.Size);
        }
      }
      else
      {
        if (patchEntryHeader)
        {
          crcPatchPos = baseOutputStream_.Position;
        }
        WriteLeInt(0); // Crc

        if (patchEntryHeader)
        {
          sizePatchPos = baseOutputStream_.Position;
        }

        // For local header both sizes appear in Zip64 Extended Information
        if (entry.LocalHeaderRequiresZip64 || patchEntryHeader)
        {
          WriteLeInt(-1);
          WriteLeInt(-1);
        }
        else
        {
          WriteLeInt(0); // Compressed size
          WriteLeInt(0); // Uncompressed size
        }
      }

      var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);

      if (name.Length > 0xFFFF)
      {
        throw new ZipException("Entry name too long.");
      }

      var ed = new ZipExtraData(entry.ExtraData);

      if (entry.LocalHeaderRequiresZip64)
      {
        ed.StartNewEntry();
        if (headerInfoAvailable)
        {
          ed.AddLeLong(entry.Size);
          ed.AddLeLong(entry.CompressedSize);
        }
        else
        {
          ed.AddLeLong(-1);
          ed.AddLeLong(-1);
        }
        ed.AddNewEntry(1);

        if (!ed.Find(1))
        {
          throw new ZipException("Internal error cant find extra data");
        }

        if (patchEntryHeader)
        {
          sizePatchPos = ed.CurrentReadIndex;
        }
      }
      else
      {
        ed.Delete(1);
      }

      var extra = ed.GetEntryData();

      WriteLeShort(name.Length);
      WriteLeShort(extra.Length);

      if (name.Length > 0)
      {
        baseOutputStream_.Write(name, 0, name.Length);
      }

      if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
      {
        sizePatchPos += baseOutputStream_.Position;
      }

      if (extra.Length > 0)
      {
        baseOutputStream_.Write(extra, 0, extra.Length);
      }

      offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;

      // Activate the entry.
      curEntry = entry;
      crc.Reset();
      if (method == CompressionMethod.Deflated)
      {
        deflater_.Reset();
        deflater_.SetLevel(compressionLevel);
      }
      size = 0;
    }