处理嵌套的zip文件与archive/zip

3
我正在尝试在Go语言中处理嵌套的zip文件(即一个zip文件包含另一个zip文件)。我想要遍历zip文件并列出它包含的所有文件,但是archive/zip只提供了两种处理zip文件的方法:zip.NewReaderzip.OpenReaderOpenReader打开磁盘上的文件,而NewReader接受io.ReaderAt和文件大小作为参数。当你使用其中任何一种方法遍历压缩文件时,你会获得一个zip.File,里面包含zip文件中的每个文件,调用f.Open可以得到zip.ReadCloser以获取文件f的内容。要打开嵌套的zip文件,我需要使用NewReader,但是zip.Filezip.ReadCloser都不符合io.ReaderAt接口的要求。 zip.File有一个私有字段zipr,它是一个io.ReaderAtzip.ReadCloser有一个私有字段f,它是一个os.File,应该满足对NewReader的要求。
我的问题是:有没有办法在不先将嵌套的zip文件写入磁盘或将整个文件读入内存的情况下打开嵌套的zip文件?
看起来zip.File提供了所有需要的东西,但是它们没有被导出。我希望我漏掉了什么。

1
我认为最简单的方法是将其复制到bytes.Buffer或磁盘中。嵌套的压缩文件有多大? - captncraig
你可能会发现 https://godoc.org/golang.org/x/tools/godoc/vfs/zipfs 的 API 更加方便。不太确定。 - captncraig
@captncraig 这是一个小型扫描程序,所以我不知道会遇到什么zip文件。我也不确定这有多常见,但我感觉使用流将所有内容连接起来非常接近了。bytes.Buffer的选择很好,我可以在读取整个文件之前检查文件大小,因为我可以访问它们。 - freb
1
只要小心像 https://en.wikipedia.org/wiki/Zip_bomb 这样的东西。 - captncraig
需要从zip归档条目中读取io.ReaderAt,该条目是一个嵌套的xlsx文件的副本。 - user1087001
2个回答

2

如果您决定往回走,从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,并且不会阻塞完整读取,因此可能无法正常工作

0

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

// 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炸弹可能不安全,并且对于大于内存的文件肯定会因OOM而失败。


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