C#.net如何识别zip文件

20
我目前正在使用SharpZip API来处理我的zip文件条目。它非常适用于压缩和解压缩。但是,我无法确定一个文件是否为zip文件。我需要知道是否有一种方法可以检测文件流是否可以被解压缩。最初我使用了
FileStream lFileStreamIn = File.OpenRead(mSourceFile);
lZipFile = new ZipFile(lFileStreamIn);
ZipInputStream lZipStreamTester = new ZipInputStream(lFileStreamIn, mBufferSize);// not working
lZipStreamTester.Read(lBuffer, 0, 0);
if (lZipStreamTester.CanDecompressEntry)
{

LZipStreamTester每次都会变成null,导致if语句失败。我试过使用/不使用缓冲区。有人能解释一下为什么吗?我知道我可以检查文件扩展名。但我需要比那更明确的方法。我也知道zip有一个魔数#(PK something),但这并不能保证它总是存在,因为这不是格式的要求。
此外,我读到.net 4.5具有本地zip支持,因此我的项目可能会迁移到那里,而不是sharpzip,但我仍然没有看到类似于CanDecompressEntry的方法/参数:http://msdn.microsoft.com/en-us/library/3z72378a%28v=vs.110%29 我最后的选择将是使用try catch并尝试对文件进行解压缩。

我的问题最简单的形式是这样的:“在上面的代码中,为什么if语句会返回false?” - Sean Dunford
根据PKWARE规范,魔数是必需的。 - frenchone
6个回答

20

这是一个基类,用于处理未经压缩、PKZIP压缩(使用SharpZipLib)或GZip压缩(.net内置)的数据的组件。也许比你所需的要多一点,但应该可以帮助你开始。这是使用@PhonicUK的建议解析数据流的头文件的示例。你在工厂方法中看到的派生类处理PKZip和GZip解压缩的具体细节。

abstract class Expander
{
    private const int ZIP_LEAD_BYTES = 0x04034b50;
    private const ushort GZIP_LEAD_BYTES = 0x8b1f;

    public abstract MemoryStream Expand(Stream stream); 
    
    internal static bool IsPkZipCompressedData(byte[] data)
    {
        Debug.Assert(data != null && data.Length >= 4);
        // if the first 4 bytes of the array are the ZIP signature then it is compressed data
        return (BitConverter.ToInt32(data, 0) == ZIP_LEAD_BYTES);
    }

    internal static bool IsGZipCompressedData(byte[] data)
    {
        Debug.Assert(data != null && data.Length >= 2);
        // if the first 2 bytes of the array are theG ZIP signature then it is compressed data;
        return (BitConverter.ToUInt16(data, 0) == GZIP_LEAD_BYTES);
    }

    public static bool IsCompressedData(byte[] data)
    {
        return IsPkZipCompressedData(data) || IsGZipCompressedData(data);
    }

    public static Expander GetExpander(Stream stream)
    {
        Debug.Assert(stream != null);
        Debug.Assert(stream.CanSeek);
        stream.Seek(0, 0);

        try
        {
            byte[] bytes = new byte[4];

            stream.Read(bytes, 0, 4);

            if (IsGZipCompressedData(bytes))
                return new GZipExpander();

            if (IsPkZipCompressedData(bytes))
                return new ZipExpander();

            return new NullExpander();
        }
        finally
        {
            stream.Seek(0, 0);  // set the stream back to the begining
        }
    }
}

1
这很有帮助,但根据我所做的研究,PK文件头或魔数并不是确定文件是否为zip格式的可靠方法。不过还是谢谢你。 - Sean Dunford
1
我没有遇到过这个问题,但这是来自一个源头被充分理解和控制的压缩数据系统。祝好运! - dkackman
我将不得不对我们的文件系统进行审计以确保。我相信PK魔数检查、文件扩展名和解压时的try catch混合应该足够了。我们最初想避免使用try catch来确定文件是否为zip,但必须在其中加入。即使我们假设魔数是zip,我们仍然需要try catch来确定zip是否损坏。我希望我能给你点赞,但现在还太菜了。我们还重新制定了上传文件的方式,以消除一些歧义。再次感谢。 - Sean Dunford

13

查看https://stackoverflow.com/a/16587134/206730的参考资料

请检查以下链接:

icsharpcode-sharpziplib-validate-zip-file

How-to-check-if-a-file-is-compressed-in-c#

ZIP文件始终以0x04034b50(4字节)开头
查看更多: http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers

示例用法:

        bool isPKZip = IOHelper.CheckSignature(pkg, 4, IOHelper.SignatureZip);
        Assert.IsTrue(isPKZip, "Not ZIP the package : " + pkg);

// http://blog.somecreativity.com/2008/04/08/how-to-check-if-a-file-is-compressed-in-c/
    public static partial class IOHelper
    {
        public const string SignatureGzip = "1F-8B-08";
        public const string SignatureZip = "50-4B-03-04";

        public static bool CheckSignature(string filepath, int signatureSize, string expectedSignature)
        {
            if (String.IsNullOrEmpty(filepath)) throw new ArgumentException("Must specify a filepath");
            if (String.IsNullOrEmpty(expectedSignature)) throw new ArgumentException("Must specify a value for the expected file signature");
            using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                if (fs.Length < signatureSize)
                    return false;
                byte[] signature = new byte[signatureSize];
                int bytesRequired = signatureSize;
                int index = 0;
                while (bytesRequired > 0)
                {
                    int bytesRead = fs.Read(signature, index, bytesRequired);
                    bytesRequired -= bytesRead;
                    index += bytesRead;
                }
                string actualSignature = BitConverter.ToString(signature);
                if (actualSignature == expectedSignature) return true;
                return false;
            }
        }

    }

8

维基百科和其他来源列出魔法数字并不是一种确定文件是否为zip的好方法,因为它不是zip文件格式所必需的。另外,我们希望避免使用try-catch方法,但这是当前的方法。 - Sean Dunford
1
注意:0x04034B50 是小端序,因此文件的第一个字节是0x50,第二个字节是0x4B,以此类推... - vojta
@Sean Dunford。不,魔数是检查文件是否为zip的好方法。如果魔数存在,则文件很可能是zip。如果魔数消失,则不应将文件视为zip,因为魔数是必需的。如果尽管缺少标头仍然解析为zip,则作者试图欺骗您。 - frenchone

3

我使用了https://en.wikipedia.org/wiki/List_of_file_signatures,并在我的zip文件后添加一个额外字节,以区分我的zip文件和Word文档(它们共用前四个字节)。

这是我的代码:

public class ZipFileUtilities
{
    private static readonly byte[] ZipBytes1 = { 0x50, 0x4b, 0x03, 0x04, 0x0a };
    private static readonly byte[] GzipBytes = { 0x1f, 0x8b };
    private static readonly byte[] TarBytes = { 0x1f, 0x9d };
    private static readonly byte[] LzhBytes = { 0x1f, 0xa0 };
    private static readonly byte[] Bzip2Bytes = { 0x42, 0x5a, 0x68 };
    private static readonly byte[] LzipBytes = { 0x4c, 0x5a, 0x49, 0x50 };
    private static readonly byte[] ZipBytes2 = { 0x50, 0x4b, 0x05, 0x06 };
    private static readonly byte[] ZipBytes3 = { 0x50, 0x4b, 0x07, 0x08 };

    public static byte[] GetFirstBytes(string filepath, int length)
    {
        using (var sr = new StreamReader(filepath))
        {
            sr.BaseStream.Seek(0, 0);
            var bytes = new byte[length];
            sr.BaseStream.Read(bytes, 0, length);

            return bytes;
        }
    }

    public static bool IsZipFile(string filepath)
    {
        return IsCompressedData(GetFirstBytes(filepath, 5));
    }

    public static bool IsCompressedData(byte[] data)
    {
        foreach (var headerBytes in new[] { ZipBytes1, ZipBytes2, ZipBytes3, GzipBytes, TarBytes, LzhBytes, Bzip2Bytes, LzipBytes })
        {
            if (HeaderBytesMatch(headerBytes, data))
                return true;
        }

        return false;
    }

    private static bool HeaderBytesMatch(byte[] headerBytes, byte[] dataBytes)
    {
        if (dataBytes.Length < headerBytes.Length)
            throw new ArgumentOutOfRangeException(nameof(dataBytes), 
                $"Passed databytes length ({dataBytes.Length}) is shorter than the headerbytes ({headerBytes.Length})");

        for (var i = 0; i < headerBytes.Length; i++)
        {
            if (headerBytes[i] == dataBytes[i]) continue;

            return false;
        }

        return true;
    }

 }

可能有更好的编码方式,特别是字节比较方面,但由于它是一个可变长度的字节比较(取决于要检查的签名),我认为至少这段代码是可读的 - 至少对我而言。


2
如果您正在进行Web编程,您可以检查文件的内容类型:application/zip

1
感谢dkackman和Kiquenet提供的答案。为了完整起见,下面的代码使用签名来识别压缩(zip)文件。你还需要额外考虑到较新的MS Office文件格式也会与此签名匹配(如.docx和.xlsx文件等)。正如其他地方所述,这确实是压缩存档,你可以将文件重命名为.zip扩展名并查看其中的XML。
下面的代码首先使用上面使用的签名检查ZIP(压缩),然后我们进行MS Office包的后续检查。请注意,要使用System.IO.Packaging.Package,您需要将项目引用“WindowsBase”(这是.NET程序集引用)。
    private const string SignatureZip = "50-4B-03-04";
    private const string SignatureGzip = "1F-8B-08";

    public static bool IsZip(this Stream stream)
    {
        if (stream.Position > 0)
        {
            stream.Seek(0, SeekOrigin.Begin);
        }

        bool isZip = CheckSignature(stream, 4, SignatureZip);
        bool isGzip = CheckSignature(stream, 3, SignatureGzip);

        bool isSomeKindOfZip = isZip || isGzip;

        if (isSomeKindOfZip && stream.IsPackage()) //Signature matches ZIP, but it's package format (docx etc).
        {
            return false;
        }

        return isSomeKindOfZip;
    }

    /// <summary>
    /// MS .docx, .xslx and other extensions are (correctly) identified as zip files using signature lookup.
    /// This tests if System.IO.Packaging is able to open, and if package has parts, this is not a zip file.
    /// </summary>
    /// <param name="stream"></param>
    /// <returns></returns>
    private static bool IsPackage(this Stream stream)
    {
        Package package = Package.Open(stream, FileMode.Open, FileAccess.Read);
        return package.GetParts().Any();
    }

当一个文件在Package.Open()时被压缩,这个操作会失败吗? - more urgent jest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接