Example #1
0
        // There is a small chance that something very weird could happen here. The code calling into this function
        // will ask for a value from the extra field if the field was masked with FF's. It's theoretically possible
        // that a field was FF's legitimately, and the writer didn't decide to write the corresponding extra field.
        // Also, at the same time, other fields were masked with FF's to indicate looking in the zip64 record.
        // Then, the search for the zip64 record will fail because the expected size is wrong,
        // and a nulled out Zip64ExtraField will be returned. Thus, even though there was Zip64 data,
        // it will not be used. It is questionable whether this situation is possible to detect

        // unlike the other functions that have try-pattern semantics, these functions always return a
        // Zip64ExtraField. If a Zip64 extra field actually doesn't exist, all of the fields in the
        // returned struct will be null
        //
        // If there are more than one Zip64 extra fields, we take the first one that has the expected size
        //
        public static Zip64ExtraField GetJustZip64Block(Stream extraFieldStream,
                                                        bool readUncompressedSize, bool readCompressedSize,
                                                        bool readLocalHeaderOffset, bool readStartDiskNumber)
        {
            Zip64ExtraField zip64Field;

            using (BinaryReader reader = new BinaryReader(extraFieldStream))
            {
                ZipGenericExtraField currentExtraField;
                while (ZipGenericExtraField.TryReadBlock(reader, extraFieldStream.Length, out currentExtraField))
                {
                    if (TryGetZip64BlockFromGenericExtraField(currentExtraField, readUncompressedSize,
                                                              readCompressedSize, readLocalHeaderOffset, readStartDiskNumber, out zip64Field))
                    {
                        return(zip64Field);
                    }
                }
            }

            zip64Field = new Zip64ExtraField();

            zip64Field._compressedSize    = null;
            zip64Field._uncompressedSize  = null;
            zip64Field._localHeaderOffset = null;
            zip64Field._startDiskNumber   = null;

            return(zip64Field);
        }
Example #2
0
        // shouldn't ever read the byte at position endExtraField
        // assumes we are positioned at the beginning of an extra field subfield
        public static bool TryReadBlock(BinaryReader reader, long endExtraField, out ZipGenericExtraField field)
        {
            field = new ZipGenericExtraField();

            // not enough bytes to read tag + size
            if (endExtraField - reader.BaseStream.Position < 4)
            {
                return(false);
            }

            field._tag  = reader.ReadUInt16();
            field._size = reader.ReadUInt16();

            // not enough bytes to read the data
            if (endExtraField - reader.BaseStream.Position < field._size)
            {
                return(false);
            }

            field._data = reader.ReadBytes(field._size);
            return(true);
        }
Example #3
0
        public static List <ZipGenericExtraField> GetExtraFields(BinaryReader reader)
        {
            // assumes that TrySkipBlock has already been called, so we don't have to validate twice

            List <ZipGenericExtraField> result;

            const int OffsetToFilenameLength = 26; // from the point before the signature

            reader.BaseStream.Seek(OffsetToFilenameLength, SeekOrigin.Current);

            ushort filenameLength   = reader.ReadUInt16();
            ushort extraFieldLength = reader.ReadUInt16();

            reader.BaseStream.Seek(filenameLength, SeekOrigin.Current);


            using (Stream str = new SubReadStream(reader.BaseStream, reader.BaseStream.Position, extraFieldLength))
            {
                result = ZipGenericExtraField.ParseExtraField(str);
            }
            Zip64ExtraField.RemoveZip64Blocks(result);

            return(result);
        }
Example #4
0
        // should only throw an exception in extremely exceptional cases because it is called from dispose
        internal void WriteCentralDirectoryFileHeader()
        {
            // This part is simple, because we should definitely know the sizes by this time
            BinaryWriter writer = new BinaryWriter(_archive.ArchiveStream);

            // _entryname only gets set when we read in or call moveTo. MoveTo does a check, and
            // reading in should not be able to produce an entryname longer than ushort.MaxValue
            Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue);

            // decide if we need the Zip64 extra field:
            Zip64ExtraField zip64ExtraField = new Zip64ExtraField();
            uint            compressedSizeTruncated, uncompressedSizeTruncated, offsetOfLocalHeaderTruncated;

            bool zip64Needed = false;

            if (SizesTooLarge()
#if DEBUG_FORCE_ZIP64
                || _archive._forceZip64
#endif
                )
            {
                zip64Needed               = true;
                compressedSizeTruncated   = ZipHelper.Mask32Bit;
                uncompressedSizeTruncated = ZipHelper.Mask32Bit;

                // 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
                zip64ExtraField.CompressedSize   = _compressedSize;
                zip64ExtraField.UncompressedSize = _uncompressedSize;
            }
            else
            {
                compressedSizeTruncated   = ( uint )_compressedSize;
                uncompressedSizeTruncated = ( uint )_uncompressedSize;
            }


            if (_offsetOfLocalHeader > uint.MaxValue
#if DEBUG_FORCE_ZIP64
                || _archive._forceZip64
#endif
                )
            {
                zip64Needed = true;
                offsetOfLocalHeaderTruncated = ZipHelper.Mask32Bit;

                // 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
                zip64ExtraField.LocalHeaderOffset = _offsetOfLocalHeader;
            }
            else
            {
                offsetOfLocalHeaderTruncated = ( uint )_offsetOfLocalHeader;
            }

            if (zip64Needed)
            {
                VersionToExtractAtLeast(ZipVersionNeededValues.Zip64);
            }

            // determine if we can fit zip64 extra field and original extra fields all in
            int bigExtraFieldLength = (zip64Needed ? zip64ExtraField.TotalSize : 0)
                                      + (_cdUnknownExtraFields != null ? ZipGenericExtraField.TotalSize(_cdUnknownExtraFields) : 0);
            ushort extraFieldLength;
            if (bigExtraFieldLength > ushort.MaxValue)
            {
                extraFieldLength      = ( ushort )(zip64Needed ? zip64ExtraField.TotalSize : 0);
                _cdUnknownExtraFields = null;
            }
            else
            {
                extraFieldLength = ( ushort )bigExtraFieldLength;
            }

            writer.Write(ZipCentralDirectoryFileHeader.SignatureConstant);     // Central directory file header signature  (4 bytes)
            writer.Write(( byte )_versionMadeBySpecification);                 // Version made by Specification (version)  (1 byte)
            writer.Write(( byte )CurrentZipPlatform);                          // Version made by Compatibility (type)     (1 byte)
            writer.Write(( ushort )_versionToExtract);                         // Minimum version needed to extract        (2 bytes)
            writer.Write(( ushort )_generalPurposeBitFlag);                    // General Purpose bit flag                 (2 bytes)
            writer.Write(( ushort )CompressionMethod);                         // The Compression method                   (2 bytes)
            writer.Write(ZipHelper.DateTimeToDosTime(_lastModified.DateTime)); // File last modification time and date     (4 bytes)
            writer.Write(_crc32);                                              // CRC-32                                   (4 bytes)
            writer.Write(compressedSizeTruncated);                             // Compressed Size                          (4 bytes)
            writer.Write(uncompressedSizeTruncated);                           // Uncompressed Size                        (4 bytes)
            writer.Write(( ushort )_storedEntryNameBytes.Length);              // File Name Length                         (2 bytes)
            writer.Write(extraFieldLength);                                    // Extra Field Length                       (2 bytes)

            // This should hold because of how we read it originally in ZipCentralDirectoryFileHeader:
            Debug.Assert((_fileComment == null) || (_fileComment.Length <= ushort.MaxValue));

            writer.Write(_fileComment != null ? ( ushort )_fileComment.Length : ( ushort )0); // file comment length
            writer.Write(( ushort )0);                                                        // disk number start
            writer.Write(( ushort )0);                                                        // internal file attributes
            writer.Write(_externalFileAttr);                                                  // external file attributes
            writer.Write(offsetOfLocalHeaderTruncated);                                       // offset of local header

            writer.Write(_storedEntryNameBytes);

            // write extra fields
            if (zip64Needed)
            {
                zip64ExtraField.WriteBlock(_archive.ArchiveStream);
            }
            if (_cdUnknownExtraFields != null)
            {
                ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _archive.ArchiveStream);
            }

            if (_fileComment != null)
            {
                writer.Write(_fileComment);
            }
        }
Example #5
0
        // if saveExtraFieldsAndComments is false, FileComment and ExtraFields will be null
        // in either case, the zip64 extra field info will be incorporated into other fields
        public static bool TryReadBlock(BinaryReader reader, bool saveExtraFieldsAndComments, out ZipCentralDirectoryFileHeader header)
        {
            header = new ZipCentralDirectoryFileHeader();

            if (reader.ReadUInt32() != SignatureConstant)
            {
                return(false);
            }
            header.VersionMadeBySpecification = reader.ReadByte();
            header.VersionMadeByCompatibility = reader.ReadByte();
            header.VersionNeededToExtract     = reader.ReadUInt16();
            header.GeneralPurposeBitFlag      = reader.ReadUInt16();
            header.CompressionMethod          = reader.ReadUInt16();
            header.LastModified = reader.ReadUInt32();
            header.Crc32        = reader.ReadUInt32();
            uint compressedSizeSmall   = reader.ReadUInt32();
            uint uncompressedSizeSmall = reader.ReadUInt32();

            header.FilenameLength    = reader.ReadUInt16();
            header.ExtraFieldLength  = reader.ReadUInt16();
            header.FileCommentLength = reader.ReadUInt16();
            ushort diskNumberStartSmall = reader.ReadUInt16();

            header.InternalFileAttributes = reader.ReadUInt16();
            header.ExternalFileAttributes = reader.ReadUInt32();
            uint relativeOffsetOfLocalHeaderSmall = reader.ReadUInt32();

            header.Filename = reader.ReadBytes(header.FilenameLength);

            bool uncompressedSizeInZip64 = uncompressedSizeSmall == ZipHelper.Mask32Bit;
            bool compressedSizeInZip64   = compressedSizeSmall == ZipHelper.Mask32Bit;
            bool relativeOffsetInZip64   = relativeOffsetOfLocalHeaderSmall == ZipHelper.Mask32Bit;
            bool diskNumberStartInZip64  = diskNumberStartSmall == ZipHelper.Mask16Bit;

            Zip64ExtraField zip64;

            long endExtraFields = reader.BaseStream.Position + header.ExtraFieldLength;

            using (Stream str = new SubReadStream(reader.BaseStream, reader.BaseStream.Position, header.ExtraFieldLength))
            {
                if (saveExtraFieldsAndComments)
                {
                    header.ExtraFields = ZipGenericExtraField.ParseExtraField(str);
                    zip64 = Zip64ExtraField.GetAndRemoveZip64Block(header.ExtraFields,
                                                                   uncompressedSizeInZip64, compressedSizeInZip64,
                                                                   relativeOffsetInZip64, diskNumberStartInZip64);
                }
                else
                {
                    header.ExtraFields = null;
                    zip64 = Zip64ExtraField.GetJustZip64Block(str,
                                                              uncompressedSizeInZip64, compressedSizeInZip64,
                                                              relativeOffsetInZip64, diskNumberStartInZip64);
                }
            }

            // There are zip files that have malformed ExtraField blocks in which GetJustZip64Block() silently bails out without reading all the way to the end
            // of the ExtraField block. Thus we must force the stream's position to the proper place.
            reader.BaseStream.AdvanceToPosition(endExtraFields);

            if (saveExtraFieldsAndComments)
            {
                header.FileComment = reader.ReadBytes(header.FileCommentLength);
            }
            else
            {
                reader.BaseStream.Position += header.FileCommentLength;
                header.FileComment          = null;
            }

            header.UncompressedSize = zip64.UncompressedSize == null
                                                    ? uncompressedSizeSmall
                                                    : zip64.UncompressedSize.Value;
            header.CompressedSize = zip64.CompressedSize == null
                                                    ? compressedSizeSmall
                                                    : zip64.CompressedSize.Value;
            header.RelativeOffsetOfLocalHeader = zip64.LocalHeaderOffset == null
                                                    ? relativeOffsetOfLocalHeaderSmall
                                                    : zip64.LocalHeaderOffset.Value;
            header.DiskNumberStart = zip64.StartDiskNumber == null
                                                    ? diskNumberStartSmall
                                                    : zip64.StartDiskNumber.Value;

            return(true);
        }
Example #6
0
        private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField extraField,
                                                                  bool readUncompressedSize, bool readCompressedSize,
                                                                  bool readLocalHeaderOffset, bool readStartDiskNumber,
                                                                  out Zip64ExtraField zip64Block)
        {
            zip64Block = new Zip64ExtraField();

            zip64Block._compressedSize    = null;
            zip64Block._uncompressedSize  = null;
            zip64Block._localHeaderOffset = null;
            zip64Block._startDiskNumber   = null;

            if (extraField.Tag != TagConstant)
            {
                return(false);
            }

            // this pattern needed because nested using blocks trigger CA2202
            MemoryStream ms = null;

            try
            {
                ms = new MemoryStream(extraField.Data);
                using (BinaryReader reader = new BinaryReader(ms))
                {
                    ms = null;

                    zip64Block._size = extraField.Size;

                    ushort expectedSize = 0;

                    if (readUncompressedSize)
                    {
                        expectedSize += 8;
                    }
                    if (readCompressedSize)
                    {
                        expectedSize += 8;
                    }
                    if (readLocalHeaderOffset)
                    {
                        expectedSize += 8;
                    }
                    if (readStartDiskNumber)
                    {
                        expectedSize += 4;
                    }

                    // if it is not the expected size, perhaps there is another extra field that matches
                    if (expectedSize != zip64Block._size)
                    {
                        return(false);
                    }

                    if (readUncompressedSize)
                    {
                        zip64Block._uncompressedSize = reader.ReadInt64();
                    }
                    if (readCompressedSize)
                    {
                        zip64Block._compressedSize = reader.ReadInt64();
                    }
                    if (readLocalHeaderOffset)
                    {
                        zip64Block._localHeaderOffset = reader.ReadInt64();
                    }
                    if (readStartDiskNumber)
                    {
                        zip64Block._startDiskNumber = reader.ReadInt32();
                    }

                    // original values are unsigned, so implies value is too big to fit in signed integer
                    if (zip64Block._uncompressedSize < 0)
                    {
                        throw new InvalidDataException("file uncompressed size too big");
                    }
                    if (zip64Block._compressedSize < 0)
                    {
                        throw new InvalidDataException("file compressed size too big");
                    }
                    if (zip64Block._localHeaderOffset < 0)
                    {
                        throw new InvalidDataException("file local header offset too big");
                    }
                    if (zip64Block._startDiskNumber < 0)
                    {
                        throw new InvalidDataException("file start dis number too big");
                    }

                    return(true);
                }
            }
            finally
            {
                if (ms != null)
                {
                    ms.Dispose();
                }
            }
        }