Source File
reader.go
Belonging Package
archive/zip
package zip
import (
)
var (
ErrFormat = errors.New("zip: not a valid zip file")
ErrAlgorithm = errors.New("zip: unsupported compression algorithm")
ErrChecksum = errors.New("zip: checksum error")
)
type ReadCloser struct {
f *os.File
Reader
}
func ( io.ReaderAt, int64) (*Reader, error) {
if < 0 {
return nil, errors.New("zip: size cannot be negative")
}
:= new(Reader)
if := .init(, ); != nil {
return nil,
}
return , nil
}
func ( *Reader) ( io.ReaderAt, int64) error {
, := readDirectoryEnd(, )
if != nil {
return
}
.r =
.File = make([]*File, 0, .directoryRecords)
.Comment = .comment
:= io.NewSectionReader(, 0, )
if _, = .Seek(int64(.directoryOffset), io.SeekStart); != nil {
return
}
:= bufio.NewReader()
return
}
return nil
}
func ( *Reader) ( uint16, Decompressor) {
if .decompressors == nil {
.decompressors = make(map[uint16]Decompressor)
}
.decompressors[] =
}
func ( *Reader) ( uint16) Decompressor {
:= .decompressors[]
if == nil {
= decompressor()
}
return
}
func ( *ReadCloser) () error {
return .f.Close()
}
func ( *File) () ( int64, error) {
, := .findBodyOffset()
if != nil {
return
}
return .headerOffset + , nil
}
func ( *File) () (io.ReadCloser, error) {
, := .findBodyOffset()
if != nil {
return nil,
}
:= int64(.CompressedSize64)
:= io.NewSectionReader(.zipr, .headerOffset+, )
:= .zip.decompressor(.Method)
if == nil {
return nil, ErrAlgorithm
}
var io.ReadCloser = ()
var io.Reader
if .hasDataDescriptor() {
= io.NewSectionReader(.zipr, .headerOffset++, dataDescriptorLen)
}
= &checksumReader{
rc: ,
hash: crc32.NewIEEE(),
f: ,
desr: ,
}
return , nil
}
type checksumReader struct {
rc io.ReadCloser
hash hash.Hash32
nread uint64 // number of bytes read so far
f *File
desr io.Reader // if non-nil, where to read the data descriptor
err error // sticky error
}
func ( *checksumReader) () (fs.FileInfo, error) {
return headerFileInfo{&.f.FileHeader}, nil
}
func ( *checksumReader) ( []byte) ( int, error) {
if .err != nil {
return 0, .err
}
, = .rc.Read()
.hash.Write([:])
.nread += uint64()
if == nil {
return
}
if == io.EOF {
if .nread != .f.UncompressedSize64 {
return 0, io.ErrUnexpectedEOF
}
if .desr != nil {
if := readDataDescriptor(.desr, .f); != nil {
if == io.EOF {
= io.ErrUnexpectedEOF
} else {
=
}
} else if .hash.Sum32() != .f.CRC32 {
= ErrChecksum
}
func ( *File) () (int64, error) {
var [fileHeaderLen]byte
if , := .zipr.ReadAt([:], .headerOffset); != nil {
return 0,
}
:= readBuf([:])
if := .uint32(); != fileHeaderSignature {
return 0, ErrFormat
}
= [22:] // skip over most of the header
:= int(.uint16())
:= int(.uint16())
return int64(fileHeaderLen + + ), nil
}
func ( *File, io.Reader) error {
var [directoryHeaderLen]byte
if , := io.ReadFull(, [:]); != nil {
return
}
:= readBuf([:])
if := .uint32(); != directoryHeaderSignature {
return ErrFormat
}
.CreatorVersion = .uint16()
.ReaderVersion = .uint16()
.Flags = .uint16()
.Method = .uint16()
.ModifiedTime = .uint16()
.ModifiedDate = .uint16()
.CRC32 = .uint32()
.CompressedSize = .uint32()
.UncompressedSize = .uint32()
.CompressedSize64 = uint64(.CompressedSize)
.UncompressedSize64 = uint64(.UncompressedSize)
:= int(.uint16())
:= int(.uint16())
:= int(.uint16())
= [4:] // skipped start disk number and internal attributes (2x uint16)
.ExternalAttrs = .uint32()
.headerOffset = int64(.uint32())
:= make([]byte, ++)
if , := io.ReadFull(, ); != nil {
return
}
.Name = string([:])
.Extra = [ : +]
.Comment = string([+:])
, := detectUTF8(.Name)
, := detectUTF8(.Comment)
switch {
.NonUTF8 = .Flags&0x800 == 0
}
:= .UncompressedSize == ^uint32(0)
:= .CompressedSize == ^uint32(0)
:= .headerOffset == int64(^uint32(0))
if {
= false
if len() < 8 {
return ErrFormat
}
.UncompressedSize64 = .uint64()
}
if {
= false
if len() < 8 {
return ErrFormat
}
.CompressedSize64 = .uint64()
}
if {
= false
if len() < 8 {
return ErrFormat
}
.headerOffset = int64(.uint64())
}
case ntfsExtraID:
if len() < 4 {
continue
}
.uint32() // reserved (ignored)
for len() >= 4 { // need at least tag and size
:= .uint16()
:= int(.uint16())
if len() < {
continue
}
:= .sub()
if != 1 || != 24 {
continue // Ignore irrelevant attributes
}
const = 1e7 // Windows timestamp resolution
:= int64(.uint64()) // ModTime since Windows epoch
:= int64( / )
:= (1e9 / ) * int64(%)
:= time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC)
= time.Unix(.Unix()+, )
}
case unixExtraID, infoZipUnixExtraID:
if len() < 8 {
continue
}
.uint32() // AcTime (ignored)
:= int64(.uint32()) // ModTime since Unix epoch
= time.Unix(, 0)
case extTimeExtraID:
if len() < 5 || .uint8()&1 == 0 {
continue
}
:= int64(.uint32()) // ModTime since Unix epoch
= time.Unix(, 0)
}
}
:= msDosTimeToTime(.ModifiedDate, .ModifiedTime)
.Modified =
if !.IsZero() {
.Modified = .UTC()
if .ModifiedTime != 0 || .ModifiedDate != 0 {
.Modified = .In(timeZone(.Sub()))
}
}
return nil
}
:= readBuf([4:]) // skip signature
:= &directoryEnd{
diskNbr: uint32(.uint16()),
dirDiskNbr: uint32(.uint16()),
dirRecordsThisDisk: uint64(.uint16()),
directoryRecords: uint64(.uint16()),
directorySize: uint64(.uint32()),
directoryOffset: uint64(.uint32()),
commentLen: .uint16(),
}
:= int(.commentLen)
if > len() {
return nil, errors.New("zip: invalid comment length")
}
.comment = string([:])
if .directoryRecords == 0xffff || .directorySize == 0xffff || .directoryOffset == 0xffffffff {
, := findDirectory64End(, )
if == nil && >= 0 {
= readDirectory64End(, , )
}
if != nil {
return nil,
}
if := int64(.directoryOffset); < 0 || >= {
return nil, ErrFormat
}
return , nil
}
func ( io.ReaderAt, int64) (int64, error) {
:= - directory64LocLen
if < 0 {
return -1, nil // no need to look for a header outside the file
}
:= make([]byte, directory64LocLen)
if , := .ReadAt(, ); != nil {
return -1,
}
:= readBuf()
if := .uint32(); != directory64LocSignature {
return -1, nil
}
if .uint32() != 0 { // number of the disk with the start of the zip64 end of central directory
return -1, nil // the file is not a valid zip64-file
}
:= .uint64() // relative offset of the zip64 end of central directory record
if .uint32() != 1 { // total number of disks
return -1, nil // the file is not a valid zip64-file
}
return int64(), nil
}
func ( io.ReaderAt, int64, *directoryEnd) ( error) {
:= make([]byte, directory64EndLen)
if , := .ReadAt(, ); != nil {
return
}
:= readBuf()
if := .uint32(); != directory64EndSignature {
return ErrFormat
}
= [12:] // skip dir size, version and version needed (uint64 + 2x uint16)
.diskNbr = .uint32() // number of this disk
.dirDiskNbr = .uint32() // number of the disk with the start of the central directory
.dirRecordsThisDisk = .uint64() // total number of entries in the central directory on this disk
.directoryRecords = .uint64() // total number of entries in the central directory
.directorySize = .uint64() // size of the central directory
.directoryOffset = .uint64() // offset of start of central directory with respect to the starting disk number
return nil
}
func ( []byte) int {
:= int([+directoryEndLen-2]) | int([+directoryEndLen-1])<<8
if +directoryEndLen+ <= len() {
return
}
}
}
return -1
}
type readBuf []byte
func ( *readBuf) () uint8 {
:= (*)[0]
* = (*)[1:]
return
}
func ( *readBuf) () uint16 {
:= binary.LittleEndian.Uint16(*)
* = (*)[2:]
return
}
func ( *readBuf) () uint32 {
:= binary.LittleEndian.Uint32(*)
* = (*)[4:]
return
}
func ( *readBuf) () uint64 {
:= binary.LittleEndian.Uint64(*)
* = (*)[8:]
return
}
func ( *readBuf) ( int) readBuf {
:= (*)[:]
* = (*)[:]
return
}
type fileListEntry struct {
name string
file *File // nil for directories
}
type fileInfoDirEntry interface {
fs.FileInfo
fs.DirEntry
}
func ( *fileListEntry) () fileInfoDirEntry {
if .file != nil {
return headerFileInfo{&.file.FileHeader}
}
return
}
func ( *fileListEntry) () string { , , := split(.name); return }
func ( *fileListEntry) () int64 { return 0 }
func ( *fileListEntry) () time.Time { return time.Time{} }
func ( *fileListEntry) () fs.FileMode { return fs.ModeDir | 0555 }
func ( *fileListEntry) () fs.FileMode { return fs.ModeDir }
func ( *fileListEntry) () bool { return true }
func ( *fileListEntry) () interface{} { return nil }
func ( *fileListEntry) () (fs.FileInfo, error) { return , nil }
func ( string) string {
= strings.ReplaceAll(, `\`, `/`)
:= path.Clean()
if strings.HasPrefix(, "/") {
= [len("/"):]
}
for strings.HasPrefix(, "../") {
= [len("../"):]
}
return
}
func ( *Reader) () {
.fileListOnce.Do(func() {
:= make(map[string]bool)
for , := range .File {
:= toValidName(.Name)
for := path.Dir(); != "."; = path.Dir() {
[] = true
}
.fileList = append(.fileList, fileListEntry{, })
}
for := range {
.fileList = append(.fileList, fileListEntry{ + "/", nil})
}
sort.Slice(.fileList, func(, int) bool { return fileEntryLess(.fileList[].name, .fileList[].name) })
})
}
func (, string) bool {
, , := split()
, , := split()
return < || == && <
}
func ( *Reader) ( string) (fs.File, error) {
.initFileList()
:= .openLookup()
if == nil || !fs.ValidPath() {
return nil, &fs.PathError{Op: "open", Path: , Err: fs.ErrNotExist}
}
if .file == nil || strings.HasSuffix(.file.Name, "/") {
return &openDir{, .openReadDir(), 0}, nil
}
, := .file.Open()
if != nil {
return nil,
}
return .(fs.File), nil
}
func ( string) (, string, bool) {
if [len()-1] == '/' {
= true
= [:len()-1]
}
:= len() - 1
for >= 0 && [] != '/' {
--
}
if < 0 {
return ".", ,
}
return [:], [+1:],
}
var dotFile = &fileListEntry{name: "./"}
func ( *Reader) ( string) *fileListEntry {
if == "." {
return dotFile
}
, , := split()
:= .fileList
:= sort.Search(len(), func( int) bool {
, , := split([].name)
return > || == && >=
})
if < len() {
:= [].name
if == || len() == len()+1 && [len()] == '/' && [:len()] == {
return &[]
}
}
return nil
}
func ( *Reader) ( string) []fileListEntry {
:= .fileList
:= sort.Search(len(), func( int) bool {
, , := split([].name)
return >=
})
:= sort.Search(len(), func( int) bool {
, , := split([].name)
return >
})
return [:]
}
type openDir struct {
e *fileListEntry
files []fileListEntry
offset int
}
func ( *openDir) () error { return nil }
func ( *openDir) () (fs.FileInfo, error) { return .e.stat(), nil }
func ( *openDir) ([]byte) (int, error) {
return 0, &fs.PathError{Op: "read", Path: .e.name, Err: errors.New("is a directory")}
}
func ( *openDir) ( int) ([]fs.DirEntry, error) {
:= len(.files) - .offset
if > 0 && > {
=
}
if == 0 {
if <= 0 {
return nil, nil
}
return nil, io.EOF
}
:= make([]fs.DirEntry, )
for := range {
[] = .files[.offset+].stat()
}
.offset +=
return , nil
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |