mirror of
https://github.com/krolaw/zipstream.git
synced 2025-04-01 19:39:12 +00:00
125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
// Package zipstream provides support for reading ZIP archives through an io.Reader.
|
|
//
|
|
// Zip64 archives are not yet supported.
|
|
package zipstream
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"hash/crc32"
|
|
"io"
|
|
)
|
|
|
|
const (
|
|
readAhead = 28
|
|
maxRead = 4096
|
|
bufferSize = maxRead + readAhead
|
|
)
|
|
|
|
// A Reader provides sequential access to the contents of a zip archive.
|
|
// A zip archive consists of a sequence of files,
|
|
// The Next method advances to the next file in the archive (including the first),
|
|
// and then it can be treated as an io.Reader to access the file's data.
|
|
// The Buffered method recovers any bytes read beyond the end of the zip file,
|
|
// necessary if you plan to process anything after it that is not another zip file.
|
|
type Reader struct {
|
|
io.Reader
|
|
br *bufio.Reader
|
|
}
|
|
|
|
// NewReader creates a new Reader reading from r.
|
|
func NewReader(r io.Reader) *Reader {
|
|
return &Reader{br: bufio.NewReaderSize(r, bufferSize)}
|
|
}
|
|
|
|
// Next advances to the next entry in the zip archive.
|
|
//
|
|
// io.EOF is returned when the end of the zip file has been reached.
|
|
// If Next is called again, it will presume another zip file immediately follows
|
|
// and it will advance into it.
|
|
func (r *Reader) Next() (*zip.FileHeader, error) {
|
|
if r.Reader != nil {
|
|
if _, err := io.Copy(io.Discard, r.Reader); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for {
|
|
sigData, err := r.br.Peek(4096)
|
|
if err != nil {
|
|
if err == io.EOF && len(sigData) < 46+22 { // Min length of Central directory + End of central directory
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
switch sig := binary.LittleEndian.Uint32(sigData); sig {
|
|
case fileHeaderSignature:
|
|
break
|
|
case directoryHeaderSignature: // Directory appears at end of file so we are finished
|
|
return nil, discardCentralDirectory(r.br)
|
|
default:
|
|
index := bytes.Index(sigData[1:], sigBytes)
|
|
if index == -1 {
|
|
r.br.Discard(len(sigData) - len(sigBytes) + 1)
|
|
continue
|
|
} else {
|
|
r.br.Discard(index + 1)
|
|
}
|
|
}
|
|
break
|
|
}
|
|
|
|
headBuf := make([]byte, fileHeaderLen)
|
|
if _, err := io.ReadFull(r.br, headBuf); err != nil {
|
|
return nil, err
|
|
}
|
|
b := readBuf(headBuf[4:])
|
|
|
|
f := &zip.FileHeader{
|
|
ReaderVersion: b.uint16(),
|
|
Flags: b.uint16(),
|
|
Method: b.uint16(),
|
|
ModifiedTime: b.uint16(),
|
|
ModifiedDate: b.uint16(),
|
|
CRC32: b.uint32(),
|
|
CompressedSize: b.uint32(), // TODO handle zip64
|
|
UncompressedSize: b.uint32(), // TODO handle zip64
|
|
}
|
|
|
|
filenameLen := b.uint16()
|
|
extraLen := b.uint16()
|
|
|
|
d := make([]byte, filenameLen+extraLen)
|
|
if _, err := io.ReadFull(r.br, d); err != nil {
|
|
return nil, err
|
|
}
|
|
f.Name = string(d[:filenameLen])
|
|
f.Extra = d[filenameLen : filenameLen+extraLen]
|
|
|
|
dcomp := decompressor(f.Method)
|
|
if dcomp == nil {
|
|
return nil, zip.ErrAlgorithm
|
|
}
|
|
|
|
// TODO handle encryption here
|
|
crc := &crcReader{
|
|
hash: crc32.NewIEEE(),
|
|
crc: &f.CRC32,
|
|
}
|
|
if f.Flags&0x8 != 0 { // If has dataDescriptor
|
|
crc.Reader = dcomp(&descriptorReader{br: r.br, fileHeader: f})
|
|
} else {
|
|
crc.Reader = dcomp(io.LimitReader(r.br, int64(f.CompressedSize)))
|
|
crc.crc = &f.CRC32
|
|
}
|
|
r.Reader = crc
|
|
return f, nil
|
|
}
|
|
|
|
// Buffered returns any bytes beyond the end of the zip file that it may have
|
|
// read. These are necessary if you plan to process anything after it,
|
|
// that isn't another zip file.
|
|
func (r *Reader) Buffered() io.Reader { return r.br }
|