【问题标题】:Prevent GZipStream/DeflateStream from trying to consume more than the compressed data防止 GZipStream/DeflateStream 尝试消耗超过压缩数据
【发布时间】:2015-05-12 17:37:54
【问题描述】:

我有一个文件可以像这样创建:

stream.Write(headerBytes, 0, headerBytes.Count);

using (var gz = new GZipStream(stream, Compress, leaveOpen: true);
{
    gz.Write(otherBytes, 0, otherBytes.Count);
}

stream.Write(moreBytes, 0, moreBytes.Count);

现在读取文件时像

stream.Read(headerBytes, 0, headerBytes.Count);
// in reality I make sure that indeed headerBytes.Count get read,
// something the above line omits

using (var gz = new GZipStream(stream, Decompress, leaveOpen: true)
{
  do { /* use buffer... */}
  while ((bytesRead = gz.Read(buffer, 0, buffer.Length)) != 0);
}

while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
  // use buffer...

事实证明,GZipStreamDeflateStream 也是如此)从 stream 读取了 16384 个字节,而不是在我检查的情况下实际的 13293 个压缩字节。

假设我事先不知道文件压缩部分的大小,也不知道压缩数据后面的字节数,有没有办法使用 GzipStream/DeflateStream

  1. 所以它只从stream读取压缩数据
  2. 或者至少弄清楚压缩数据部分的大小,这样我就可以手动stream.Position -= actuallyRead - compressedSize

【问题讨论】:

    标签: .net stream compression gzipstream deflatestream


    【解决方案1】:

    这个答案相当于一个丑陋的解决方法。我不是特别喜欢它,但它确实有效(除非它无效),即使仅适用于 GZipStream

    1. 或者至少弄清楚压缩数据部分的大小,这样我就可以stream.Position -= actuallyRead - compressedSize 手动?

    作为每个 gzip 文件(实际上是每个 gzip 成员ends with

         +---+---+---+---+---+---+---+---+
         |     CRC32     |     ISIZE     |
         +---+---+---+---+---+---+---+---+
    
         CRC32
            This contains a Cyclic Redundancy Check value of the
            uncompressed data
    
         ISIZE
            This contains the size of the original (uncompressed) input
            data modulo 2^32.
    

    我可以只使用我在关闭GzipStream 后知道的未压缩大小(模块 2^32),然后在流中向后搜索,直到找到与它匹配的那 4 个字节。

    为了使其更健壮,我还应该在解压缩时计算 CRC32,并在流中向后搜索到形成正确 CRC32 和 ISIZE 的 8 个字节之后。

    丑陋,但我确实警告过你。

    我多么喜欢封装。封装所有有用的东西,给我们留下一个解压流,它可以在无所不知的 API 设计者预见的一个用例中工作。

    这是一个快速的SeekBack 实现,目前有效:

    /// <returns>the number of bytes sought back (including bytes.Length)
    ///          or 0 in case of failure</returns>
    static int SeekBack(Stream s, byte[] bytes, int maxSeekBack)
    {
        if (maxSeekBack != -1 && maxSeekBack < bytes.Length)
            throw new ArgumentException("maxSeekBack must be >= bytes.Length");
    
        int soughtBack = 0;
        for (int i = bytes.Length - 1; i >= 0; i--)
        {
            while ((maxSeekBack == -1 || soughtBack < maxSeekBack)
                   && s.Position > i)
            {
                s.Position -= 1;
                // as we are seeking back, the following will never become
                // -1 (EOS), so coercing to byte is OK
                byte b = (byte)s.ReadByte();
                s.Position -= 1;
                soughtBack++;
                if (b == bytes[i])
                {
                    if (i == 0)
                        return soughtBack;
                    break;
                }
                else
                {
                    var bytesIn = (bytes.Length - 1) - i;
                    if (bytesIn > 0) // back to square one
                    {
                        soughtBack -= bytesIn;
                        s.Position += bytesIn;
                        i = bytes.Length - 1;
                    }
                }
            }
        }
        // no luck? return to original position
        s.Position += soughtBack;
        return 0;
    }
    

    【讨论】:

      【解决方案2】:

      该接口似乎没有提供一种方法来做你想做的事,即one of many reasons 不使用 .NET 的 GZipStream 或 DeflateStream。

      您应该改用DotNetZip

      【讨论】:

        【解决方案3】:

        按照 Mark Adler 的建议,我尝试了 DotNetZip,你瞧,它的 GZipStream.Position 属性不仅不抛出,它甚至返回读入的实际 gzip 字节数(加上 8,出于某种原因,我还是得搞清楚)。

        所以它确实比严格要求的要多,但它可以让我计算回溯多少。

        以下对我有用:

        var posBefore = fileStream.Position;
        long compressedBytesRead;
        using (var gz = new GZipStream(fileStream, CompressionMode.Decompress, true))
        {
            while (gz.Read(buffer, 0, buffer.Length) != 0)
                ; // use it!
            compressedBytesRead = gz.Position;
        }
        var gzipStreamAdvance = fileStream.Position - posBefore;
        var seekBack = gzipStreamAdvance - compressedBytesRead - 8; // but why "- 8"?
        fileStream.Position -= seekBack;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-02-05
          • 2010-09-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多