【问题标题】:Handling nested zip files with archive/zip使用 archive/zip 处理嵌套的 zip 文件
【发布时间】:2016-10-25 16:46:27
【问题描述】:

我正在努力处理 Go 中的嵌套 zip 文件(其中一个 zip 文件包含另一个 zip 文件)。我正在尝试递归一个 zip 文件并列出它包含的所有文件。

archive/zip 为您提供了两种处理 zip 文件的方法:

  • zip.NewReader
  • zip.OpenReader

OpenReader 打开磁盘上的文件。 NewReader 接受 io.ReaderAt 和文件大小。当您使用其中任何一个遍历压缩文件时,您会为 zip 中的每个文件得到一个 zip.File。要获取文件 f 的文件内容,请调用 f.Open,它会为您提供 zip.ReadCloser。要打开嵌套的 zip 文件,我需要使用 NewReader,但 zip.Filezip.ReadCloser 不满足 io.ReaderAt 接口。

zip.File 有一个私有字段 zipr,这是一个 io.ReaderAtzip.ReadCloser 有一个私有字段 f,这是一个 os.File,它应该满足 NewReader 的要求。

我的问题:有什么方法可以打开嵌套的 zip 文件,而无需先将内容写入磁盘上的文件,或者将整个内容读入内存。

看起来所需的一切都在 zip.File 中可用,但没有导出。我希望我错过了什么。

【问题讨论】:

  • 我担心最简单的做法是将其复制到bytes.Buffer 或磁盘中。嵌套拉链有多大?
  • 您可能会发现godoc.org/golang.org/x/tools/godoc/vfs/zipfs 的 api 也更适用。不确定。
  • @captncraig 这是一个小型扫描程序,所以我不知道我会遇到什么 zip 文件。我也不确定这会有多普遍,我只是觉得能够使用流将所有东西连接在一起非常接近。 bytes.Buffer 上的电话很好,我想我可以在阅读整个内容之前检查文件大小,因为我可以访问它。
  • 小心en.wikipedia.org/wiki/Zip_bomb之类的东西

标签: go zip


【解决方案1】:

如果您决定倒退,从io.Reader 重新初始化的io.ReaderAt 怎么样:(此代码基本上未经测试,但希望您明白)

package main

import (
    "io"
    "io/ioutil"
    "os"
    "strings"
)

type inefficientReaderAt struct {
    rdr    io.ReadCloser
    cur    int64
    initer func() (io.ReadCloser, error)
}

func newInefficentReaderAt(initer func() (io.ReadCloser, error)) *inefficientReaderAt {
    return &inefficientReaderAt{
        initer: initer,
    }
}

func (r *inefficientReaderAt) Read(p []byte) (n int, err error) {
    n, err = r.rdr.Read(p)
    r.cur += int64(n)
    return n, err
}

func (r *inefficientReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
    // reset on rewind
    if off < r.cur || r.rdr == nil {
        r.cur = 0
        r.rdr, err = r.initer()
        if err != nil {
            return 0, err
        }
    }

    if off > r.cur {
        sz, err := io.CopyN(ioutil.Discard, r.rdr, off-r.cur)
        n = int(sz)
        if err != nil {
            return n, err
        }
    }

    return r.Read(p)
}

func main() {
    r := newInefficentReaderAt(func() (io.ReadCloser, error) {
        return ioutil.NopCloser(strings.NewReader("ABCDEFG")), nil
    })

    io.Copy(os.Stdout, io.NewSectionReader(r, 0, 3))
    io.Copy(os.Stdout, io.NewSectionReader(r, 1, 3))
}

如果您大部分时间都在前进,这可能没问题。特别是如果您使用缓冲阅读器。

  • 我应该注意到这违反了io.ReaderAt 保证:https://godoc.org/io#ReaderFrom,即它不允许并行调用ReadAt,并且不会阻塞完整读取,因此这甚至可能无法正常工作李>

【讨论】:

    【解决方案2】:

    我遇到了完全相同的需求并想出了以下方法,不确定它是否对您有帮助:

    // NewZipFromReader ...
    func NewZipFromReader(file io.ReadCloser, size int64) (*zip.Reader, error) {
        in := file.(io.Reader)
    
        if _, ok := in.(io.ReaderAt); ok != true {
            buffer, err := ioutil.ReadAll(in)
    
            if err != nil {
                return nil, err
            }
    
            in = bytes.NewReader(buffer)
            size = int64(len(buffer))
        }
    
        reader, err := zip.NewReader(in.(io.ReaderAt), size)
    
        if err != nil {
            return nil, err
        }
    
        return reader, nil
    }
    

    所以如果file 没有实现io.ReaderAt,它会将整个内容读入缓冲区。

    处理 ZIP 炸弹可能不安全,并且对于大于 RAM 的文件肯定会因 OOM 而失败。

    【讨论】:

      猜你喜欢
      • 2016-05-22
      • 2020-11-30
      • 1970-01-01
      • 2021-05-29
      • 2022-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多