diff --git a/proto_apng/proto_apng b/proto_apng/proto_apng new file mode 100755 index 0000000..70d7a52 Binary files /dev/null and b/proto_apng/proto_apng differ diff --git a/proto_apng/zip/reader.go b/proto_apng/zip/reader.go index 4b7f1ff..3d38c67 100644 --- a/proto_apng/zip/reader.go +++ b/proto_apng/zip/reader.go @@ -5,20 +5,9 @@ package zip import ( - "bufio" "encoding/binary" "errors" - _ "fmt" - "hash" - "hash/crc32" "io" - "io/fs" - "os" - "path" - "sort" - "strings" - "sync" - "time" ) var ( @@ -28,300 +17,6 @@ var ( ErrInsecurePath = errors.New("zip: insecure file path") ) -// A Reader serves content from a ZIP archive. -type Reader struct { - r io.ReaderAt - File []*File - Comment string - decompressors map[uint16]Decompressor - - // Some JAR files are zip files with a prefix that is a bash script. - // The baseOffset field is the start of the zip file proper. - baseOffset int64 - - // fileList is a list of files sorted by ename, - // for use by the Open method. - fileListOnce sync.Once - fileList []fileListEntry -} - -// A ReadCloser is a [Reader] that must be closed when no longer needed. -type ReadCloser struct { - f *os.File - Reader -} - -// A File is a single file in a ZIP archive. -// The file information is in the embedded [FileHeader]. -// The file content can be accessed by calling [File.Open]. -type File struct { - FileHeader - zip *Reader - zipr io.ReaderAt - headerOffset int64 // includes overall ZIP archive baseOffset - zip64 bool // zip64 extended information extra field presence -} - -// OpenReader will open the Zip file specified by name and return a ReadCloser. -// -// If any file inside the archive uses a non-local name -// (as defined by [filepath.IsLocal]) or a name containing backslashes -// and the GODEBUG environment variable contains `zipinsecurepath=0`, -// OpenReader returns the reader with an ErrInsecurePath error. -// A future version of Go may introduce this behavior by default. -// Programs that want to accept non-local names can ignore -// the ErrInsecurePath error and use the returned reader. -func OpenReader(name string) (*ReadCloser, error) { - f, err := os.Open(name) - if err != nil { - return nil, err - } - fi, err := f.Stat() - if err != nil { - f.Close() - return nil, err - } - r := new(ReadCloser) - if err = r.init(f, fi.Size()); err != nil && err != ErrInsecurePath { - f.Close() - return nil, err - } - r.f = f - return r, err -} - -// NewReader returns a new [Reader] reading from r, which is assumed to -// have the given size in bytes. -// -// If any file inside the archive uses a non-local name -// (as defined by [filepath.IsLocal]) or a name containing backslashes -// and the GODEBUG environment variable contains `zipinsecurepath=0`, -// NewReader returns the reader with an [ErrInsecurePath] error. -// A future version of Go may introduce this behavior by default. -// Programs that want to accept non-local names can ignore -// the [ErrInsecurePath] error and use the returned reader. -func NewReader(r io.ReaderAt, size int64) (*Reader, error) { - if size < 0 { - return nil, errors.New("zip: size cannot be negative") - } - zr := new(Reader) - var err error - if err = zr.init(r, size); err != nil && err != ErrInsecurePath { - return nil, err - } - return zr, err -} - -func (r *Reader) init(rdr io.ReaderAt, size int64) error { - r.r = rdr - r.baseOffset = 0 - // Since the number of directory records is not validated, it is not - // safe to preallocate r.File without first checking that the specified - // number of files is reasonable, since a malformed archive may - // indicate it contains up to 1 << 128 - 1 files. Since each file has a - // header which will be _at least_ 30 bytes we can safely preallocate - // if (data size / 30) >= end.directoryRecords. - r.File = []*File{} - rs := io.NewSectionReader(rdr, 0, size) - var err error - if _, err = rs.Seek(r.baseOffset+0, io.SeekStart); err != nil { - return err - } - buf := bufio.NewReader(rs) - - // The count of files inside a zip is truncated to fit in a uint16. - // Gloss over this by reading headers until we encounter - // a bad one, and then only report an ErrFormat or UnexpectedEOF if - // the file count modulo 65536 is incorrect. - for { - f := &File{zip: r, zipr: rdr} - err = readDirectoryHeader(f, buf) - if err == ErrFormat || err == io.ErrUnexpectedEOF { - break - } - if err != nil { - return err - } - f.headerOffset += r.baseOffset - r.File = append(r.File, f) - } - return nil -} - -// RegisterDecompressor registers or overrides a custom decompressor for a -// specific method ID. If a decompressor for a given method is not found, -// [Reader] will default to looking up the decompressor at the package level. -func (r *Reader) RegisterDecompressor(method uint16, dcomp Decompressor) { - if r.decompressors == nil { - r.decompressors = make(map[uint16]Decompressor) - } - r.decompressors[method] = dcomp -} - -func (r *Reader) decompressor(method uint16) Decompressor { - dcomp := r.decompressors[method] - if dcomp == nil { - dcomp = decompressor(method) - } - return dcomp -} - -// Close closes the Zip file, rendering it unusable for I/O. -func (rc *ReadCloser) Close() error { - return rc.f.Close() -} - -// DataOffset returns the offset of the file's possibly-compressed -// data, relative to the beginning of the zip file. -// -// Most callers should instead use [File.Open], which transparently -// decompresses data and verifies checksums. -func (f *File) DataOffset() (offset int64, err error) { - bodyOffset, err := f.findBodyOffset() - if err != nil { - return - } - return f.headerOffset + bodyOffset, nil -} - -// Open returns a [ReadCloser] that provides access to the [File]'s contents. -// Multiple files may be read concurrently. -func (f *File) Open() (io.ReadCloser, error) { - bodyOffset, err := f.findBodyOffset() - if err != nil { - return nil, err - } - if strings.HasSuffix(f.Name, "/") { - // The ZIP specification (APPNOTE.TXT) specifies that directories, which - // are technically zero-byte files, must not have any associated file - // data. We previously tried failing here if f.CompressedSize64 != 0, - // but it turns out that a number of implementations (namely, the Java - // jar tool) don't properly set the storage method on directories - // resulting in a file with compressed size > 0 but uncompressed size == - // 0. We still want to fail when a directory has associated uncompressed - // data, but we are tolerant of cases where the uncompressed size is - // zero but compressed size is not. - if f.UncompressedSize64 != 0 { - return &dirReader{ErrFormat}, nil - } else { - return &dirReader{io.EOF}, nil - } - } - size := int64(f.CompressedSize64) - r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size) - dcomp := f.zip.decompressor(f.Method) - if dcomp == nil { - return nil, ErrAlgorithm - } - var rc io.ReadCloser = dcomp(r) - var desr io.Reader - if f.hasDataDescriptor() { - desr = io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset+size, dataDescriptorLen) - } - rc = &checksumReader{ - rc: rc, - hash: crc32.NewIEEE(), - f: f, - desr: desr, - } - return rc, nil -} - -// OpenRaw returns a [Reader] that provides access to the [File]'s contents without -// decompression. -func (f *File) OpenRaw() (io.Reader, error) { - bodyOffset, err := f.findBodyOffset() - if err != nil { - return nil, err - } - r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, int64(f.CompressedSize64)) - return r, nil -} - -type dirReader struct { - err error -} - -func (r *dirReader) Read([]byte) (int, error) { - return 0, r.err -} - -func (r *dirReader) Close() error { - 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 (r *checksumReader) Stat() (fs.FileInfo, error) { - return headerFileInfo{&r.f.FileHeader}, nil -} - -func (r *checksumReader) Read(b []byte) (n int, err error) { - if r.err != nil { - return 0, r.err - } - n, err = r.rc.Read(b) - r.hash.Write(b[:n]) - r.nread += uint64(n) - if r.nread > r.f.UncompressedSize64 { - return 0, ErrFormat - } - if err == nil { - return - } - if err == io.EOF { - if r.nread != r.f.UncompressedSize64 { - return 0, io.ErrUnexpectedEOF - } - if r.desr != nil { - if err1 := readDataDescriptor(r.desr, r.f); err1 != nil { - if err1 == io.EOF { - err = io.ErrUnexpectedEOF - } else { - err = err1 - } - } else if r.hash.Sum32() != r.f.CRC32 { - err = ErrChecksum - } - } else { - // If there's not a data descriptor, we still compare - // the CRC32 of what we've read against the file header - // or TOC's CRC32, if it seems like it was set. - if r.f.CRC32 != 0 && r.hash.Sum32() != r.f.CRC32 { - err = ErrChecksum - } - } - } - r.err = err - return -} - -func (r *checksumReader) Close() error { return r.rc.Close() } - -// findBodyOffset does the minimum work to verify the file has a header -// and returns the file body offset. -func (f *File) findBodyOffset() (int64, error) { - var buf [fileHeaderLen]byte - if _, err := f.zipr.ReadAt(buf[:], f.headerOffset); err != nil { - return 0, err - } - b := readBuf(buf[:]) - if sig := b.uint32(); sig != fileHeaderSignature { - return 0, ErrFormat - } - b = b[22:] // skip over most of the header - filenameLen := int(b.uint16()) - extraLen := int(b.uint16()) - return int64(fileHeaderLen + filenameLen + extraLen), nil -} - type FileContent struct { Filename []byte Content []byte @@ -367,221 +62,6 @@ func ReadFile(r io.ReadSeeker) (*FileContent, error) { }, nil } -// readDirectoryHeader attempts to read a directory header from r. -// It returns io.ErrUnexpectedEOF if it cannot read a complete header, -// and ErrFormat if it doesn't find a valid header signature. -func readDirectoryHeader(f *File, r io.Reader) error { - var buf [directoryHeaderLen]byte - if _, err := io.ReadFull(r, buf[:]); err != nil { - return err - } - b := readBuf(buf[:]) - if sig := b.uint32(); sig != fileHeaderSignature { - // fmt.Printf("read dir start %x", sig) - - return ErrFormat - } - f.CreatorVersion = b.uint16() - f.ReaderVersion = b.uint16() - f.Flags = b.uint16() - f.Method = b.uint16() - f.ModifiedTime = b.uint16() - f.ModifiedDate = b.uint16() - f.CRC32 = b.uint32() - f.CompressedSize = b.uint32() - f.UncompressedSize = b.uint32() - f.CompressedSize64 = uint64(f.CompressedSize) - f.UncompressedSize64 = uint64(f.UncompressedSize) - filenameLen := int(b.uint16()) - extraLen := int(b.uint16()) - commentLen := int(b.uint16()) - b = b[4:] // skipped start disk number and internal attributes (2x uint16) - f.ExternalAttrs = b.uint32() - f.headerOffset = int64(b.uint32()) - d := make([]byte, filenameLen+extraLen+commentLen) - if _, err := io.ReadFull(r, d); err != nil { - return err - } - f.Name = string(d[:filenameLen]) - f.Extra = d[filenameLen : filenameLen+extraLen] - f.Comment = string(d[filenameLen+extraLen:]) - - // Determine the character encoding. - utf8Valid1, utf8Require1 := detectUTF8(f.Name) - utf8Valid2, utf8Require2 := detectUTF8(f.Comment) - switch { - case !utf8Valid1 || !utf8Valid2: - // Name and Comment definitely not UTF-8. - f.NonUTF8 = true - case !utf8Require1 && !utf8Require2: - // Name and Comment use only single-byte runes that overlap with UTF-8. - f.NonUTF8 = false - default: - // Might be UTF-8, might be some other encoding; preserve existing flag. - // Some ZIP writers use UTF-8 encoding without setting the UTF-8 flag. - // Since it is impossible to always distinguish valid UTF-8 from some - // other encoding (e.g., GBK or Shift-JIS), we trust the flag. - f.NonUTF8 = f.Flags&0x800 == 0 - } - - needUSize := f.UncompressedSize == ^uint32(0) - needCSize := f.CompressedSize == ^uint32(0) - needHeaderOffset := f.headerOffset == int64(^uint32(0)) - - // Best effort to find what we need. - // Other zip authors might not even follow the basic format, - // and we'll just ignore the Extra content in that case. - var modified time.Time -parseExtras: - for extra := readBuf(f.Extra); len(extra) >= 4; { // need at least tag and size - fieldTag := extra.uint16() - fieldSize := int(extra.uint16()) - if len(extra) < fieldSize { - break - } - fieldBuf := extra.sub(fieldSize) - - switch fieldTag { - case zip64ExtraID: - f.zip64 = true - - // update directory values from the zip64 extra block. - // They should only be consulted if the sizes read earlier - // are maxed out. - // See golang.org/issue/13367. - if needUSize { - needUSize = false - if len(fieldBuf) < 8 { - return ErrFormat - } - f.UncompressedSize64 = fieldBuf.uint64() - } - if needCSize { - needCSize = false - if len(fieldBuf) < 8 { - return ErrFormat - } - f.CompressedSize64 = fieldBuf.uint64() - } - if needHeaderOffset { - needHeaderOffset = false - if len(fieldBuf) < 8 { - return ErrFormat - } - f.headerOffset = int64(fieldBuf.uint64()) - } - case ntfsExtraID: - if len(fieldBuf) < 4 { - continue parseExtras - } - fieldBuf.uint32() // reserved (ignored) - for len(fieldBuf) >= 4 { // need at least tag and size - attrTag := fieldBuf.uint16() - attrSize := int(fieldBuf.uint16()) - if len(fieldBuf) < attrSize { - continue parseExtras - } - attrBuf := fieldBuf.sub(attrSize) - if attrTag != 1 || attrSize != 24 { - continue // Ignore irrelevant attributes - } - - const ticksPerSecond = 1e7 // Windows timestamp resolution - ts := int64(attrBuf.uint64()) // ModTime since Windows epoch - secs := ts / ticksPerSecond - nsecs := (1e9 / ticksPerSecond) * (ts % ticksPerSecond) - epoch := time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC) - modified = time.Unix(epoch.Unix()+secs, nsecs) - } - case unixExtraID, infoZipUnixExtraID: - if len(fieldBuf) < 8 { - continue parseExtras - } - fieldBuf.uint32() // AcTime (ignored) - ts := int64(fieldBuf.uint32()) // ModTime since Unix epoch - modified = time.Unix(ts, 0) - case extTimeExtraID: - if len(fieldBuf) < 5 || fieldBuf.uint8()&1 == 0 { - continue parseExtras - } - ts := int64(fieldBuf.uint32()) // ModTime since Unix epoch - modified = time.Unix(ts, 0) - } - } - - msdosModified := msDosTimeToTime(f.ModifiedDate, f.ModifiedTime) - f.Modified = msdosModified - if !modified.IsZero() { - f.Modified = modified.UTC() - - // If legacy MS-DOS timestamps are set, we can use the delta between - // the legacy and extended versions to estimate timezone offset. - // - // A non-UTC timezone is always used (even if offset is zero). - // Thus, FileHeader.Modified.Location() == time.UTC is useful for - // determining whether extended timestamps are present. - // This is necessary for users that need to do additional time - // calculations when dealing with legacy ZIP formats. - if f.ModifiedTime != 0 || f.ModifiedDate != 0 { - f.Modified = modified.In(timeZone(msdosModified.Sub(modified))) - } - } - - // Assume that uncompressed size 2³²-1 could plausibly happen in - // an old zip32 file that was sharding inputs into the largest chunks - // possible (or is just malicious; search the web for 42.zip). - // If needUSize is true still, it means we didn't see a zip64 extension. - // As long as the compressed size is not also 2³²-1 (implausible) - // and the header is not also 2³²-1 (equally implausible), - // accept the uncompressed size 2³²-1 as valid. - // If nothing else, this keeps archive/zip working with 42.zip. - _ = needUSize - - if needCSize || needHeaderOffset { - return ErrFormat - } - - return nil -} - -func readDataDescriptor(r io.Reader, f *File) error { - var buf [dataDescriptorLen]byte - // The spec says: "Although not originally assigned a - // signature, the value 0x08074b50 has commonly been adopted - // as a signature value for the data descriptor record. - // Implementers should be aware that ZIP files may be - // encountered with or without this signature marking data - // descriptors and should account for either case when reading - // ZIP files to ensure compatibility." - // - // dataDescriptorLen includes the size of the signature but - // first read just those 4 bytes to see if it exists. - if _, err := io.ReadFull(r, buf[:4]); err != nil { - return err - } - off := 0 - maybeSig := readBuf(buf[:4]) - if maybeSig.uint32() != dataDescriptorSignature { - // No data descriptor signature. Keep these four - // bytes. - off += 4 - } - if _, err := io.ReadFull(r, buf[off:12]); err != nil { - return err - } - b := readBuf(buf[:12]) - if b.uint32() != f.CRC32 { - return ErrChecksum - } - - // The two sizes that follow here can be either 32 bits or 64 bits - // but the spec is not very clear on this and different - // interpretations has been made causing incompatibilities. We - // already have the sizes from the central directory so we can - // just ignore these. - - return nil -} type readBuf []byte @@ -614,242 +94,3 @@ func (b *readBuf) sub(n int) readBuf { *b = (*b)[n:] return b2 } - -// A fileListEntry is a File and its ename. -// If file == nil, the fileListEntry describes a directory without metadata. -type fileListEntry struct { - name string - file *File - isDir bool - isDup bool -} - -type fileInfoDirEntry interface { - fs.FileInfo - fs.DirEntry -} - -func (f *fileListEntry) stat() (fileInfoDirEntry, error) { - if f.isDup { - return nil, errors.New(f.name + ": duplicate entries in zip file") - } - if !f.isDir { - return headerFileInfo{&f.file.FileHeader}, nil - } - return f, nil -} - -// Only used for directories. -func (f *fileListEntry) Name() string { _, elem, _ := split(f.name); return elem } -func (f *fileListEntry) Size() int64 { return 0 } -func (f *fileListEntry) Mode() fs.FileMode { return fs.ModeDir | 0555 } -func (f *fileListEntry) Type() fs.FileMode { return fs.ModeDir } -func (f *fileListEntry) IsDir() bool { return true } -func (f *fileListEntry) Sys() any { return nil } - -func (f *fileListEntry) ModTime() time.Time { - if f.file == nil { - return time.Time{} - } - return f.file.FileHeader.Modified.UTC() -} - -func (f *fileListEntry) Info() (fs.FileInfo, error) { return f, nil } - -func (f *fileListEntry) String() string { - return fs.FormatDirEntry(f) -} - -// toValidName coerces name to be a valid name for fs.FS.Open. -func toValidName(name string) string { - name = strings.ReplaceAll(name, `\`, `/`) - p := path.Clean(name) - - p = strings.TrimPrefix(p, "/") - - for strings.HasPrefix(p, "../") { - p = p[len("../"):] - } - - return p -} - -func (r *Reader) initFileList() { - r.fileListOnce.Do(func() { - // files and knownDirs map from a file/directory name - // to an index into the r.fileList entry that we are - // building. They are used to mark duplicate entries. - files := make(map[string]int) - knownDirs := make(map[string]int) - - // dirs[name] is true if name is known to be a directory, - // because it appears as a prefix in a path. - dirs := make(map[string]bool) - - for _, file := range r.File { - isDir := len(file.Name) > 0 && file.Name[len(file.Name)-1] == '/' - name := toValidName(file.Name) - if name == "" { - continue - } - - if idx, ok := files[name]; ok { - r.fileList[idx].isDup = true - continue - } - if idx, ok := knownDirs[name]; ok { - r.fileList[idx].isDup = true - continue - } - - for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) { - dirs[dir] = true - } - - idx := len(r.fileList) - entry := fileListEntry{ - name: name, - file: file, - isDir: isDir, - } - r.fileList = append(r.fileList, entry) - if isDir { - knownDirs[name] = idx - } else { - files[name] = idx - } - } - for dir := range dirs { - if _, ok := knownDirs[dir]; !ok { - if idx, ok := files[dir]; ok { - r.fileList[idx].isDup = true - } else { - entry := fileListEntry{ - name: dir, - file: nil, - isDir: true, - } - r.fileList = append(r.fileList, entry) - } - } - } - - sort.Slice(r.fileList, func(i, j int) bool { return fileEntryLess(r.fileList[i].name, r.fileList[j].name) }) - }) -} - -func fileEntryLess(x, y string) bool { - xdir, xelem, _ := split(x) - ydir, yelem, _ := split(y) - return xdir < ydir || xdir == ydir && xelem < yelem -} - -// Open opens the named file in the ZIP archive, -// using the semantics of fs.FS.Open: -// paths are always slash separated, with no -// leading / or ../ elements. -func (r *Reader) Open(name string) (fs.File, error) { - r.initFileList() - - if !fs.ValidPath(name) { - return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} - } - e := r.openLookup(name) - if e == nil { - return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} - } - if e.isDir { - return &openDir{e, r.openReadDir(name), 0}, nil - } - rc, err := e.file.Open() - if err != nil { - return nil, err - } - return rc.(fs.File), nil -} - -func split(name string) (dir, elem string, isDir bool) { - if len(name) > 0 && name[len(name)-1] == '/' { - isDir = true - name = name[:len(name)-1] - } - i := len(name) - 1 - for i >= 0 && name[i] != '/' { - i-- - } - if i < 0 { - return ".", name, isDir - } - return name[:i], name[i+1:], isDir -} - -var dotFile = &fileListEntry{name: "./", isDir: true} - -func (r *Reader) openLookup(name string) *fileListEntry { - if name == "." { - return dotFile - } - - dir, elem, _ := split(name) - files := r.fileList - i := sort.Search(len(files), func(i int) bool { - idir, ielem, _ := split(files[i].name) - return idir > dir || idir == dir && ielem >= elem - }) - if i < len(files) { - fname := files[i].name - if fname == name || len(fname) == len(name)+1 && fname[len(name)] == '/' && fname[:len(name)] == name { - return &files[i] - } - } - return nil -} - -func (r *Reader) openReadDir(dir string) []fileListEntry { - files := r.fileList - i := sort.Search(len(files), func(i int) bool { - idir, _, _ := split(files[i].name) - return idir >= dir - }) - j := sort.Search(len(files), func(j int) bool { - jdir, _, _ := split(files[j].name) - return jdir > dir - }) - return files[i:j] -} - -type openDir struct { - e *fileListEntry - files []fileListEntry - offset int -} - -func (d *openDir) Close() error { return nil } -func (d *openDir) Stat() (fs.FileInfo, error) { return d.e.stat() } - -func (d *openDir) Read([]byte) (int, error) { - return 0, &fs.PathError{Op: "read", Path: d.e.name, Err: errors.New("is a directory")} -} - -func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) { - n := len(d.files) - d.offset - if count > 0 && n > count { - n = count - } - if n == 0 { - if count <= 0 { - return nil, nil - } - return nil, io.EOF - } - list := make([]fs.DirEntry, n) - for i := range list { - s, err := d.files[d.offset+i].stat() - if err != nil { - return nil, err - } - list[i] = s - } - d.offset += n - return list, nil -} diff --git a/proto_apng/zip/register.go b/proto_apng/zip/register.go deleted file mode 100644 index 18e620d..0000000 --- a/proto_apng/zip/register.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package zip - -import ( - "compress/flate" - "errors" - "io" - "sync" -) - -// A Compressor returns a new compressing writer, writing to w. -// The WriteCloser's Close method must be used to flush pending data to w. -// The Compressor itself must be safe to invoke from multiple goroutines -// simultaneously, but each returned writer will be used only by -// one goroutine at a time. -type Compressor func(w io.Writer) (io.WriteCloser, error) - -// A Decompressor returns a new decompressing reader, reading from r. -// The [io.ReadCloser]'s Close method must be used to release associated resources. -// The Decompressor itself must be safe to invoke from multiple goroutines -// simultaneously, but each returned reader will be used only by -// one goroutine at a time. -type Decompressor func(r io.Reader) io.ReadCloser - -var flateWriterPool sync.Pool - -func newFlateWriter(w io.Writer) io.WriteCloser { - fw, ok := flateWriterPool.Get().(*flate.Writer) - if ok { - fw.Reset(w) - } else { - fw, _ = flate.NewWriter(w, 5) - } - return &pooledFlateWriter{fw: fw} -} - -type pooledFlateWriter struct { - mu sync.Mutex // guards Close and Write - fw *flate.Writer -} - -func (w *pooledFlateWriter) Write(p []byte) (n int, err error) { - w.mu.Lock() - defer w.mu.Unlock() - if w.fw == nil { - return 0, errors.New("Write after Close") - } - return w.fw.Write(p) -} - -func (w *pooledFlateWriter) Close() error { - w.mu.Lock() - defer w.mu.Unlock() - var err error - if w.fw != nil { - err = w.fw.Close() - flateWriterPool.Put(w.fw) - w.fw = nil - } - return err -} - -var flateReaderPool sync.Pool - -func newFlateReader(r io.Reader) io.ReadCloser { - fr, ok := flateReaderPool.Get().(io.ReadCloser) - if ok { - fr.(flate.Resetter).Reset(r, nil) - } else { - fr = flate.NewReader(r) - } - return &pooledFlateReader{fr: fr} -} - -type pooledFlateReader struct { - mu sync.Mutex // guards Close and Read - fr io.ReadCloser -} - -func (r *pooledFlateReader) Read(p []byte) (n int, err error) { - r.mu.Lock() - defer r.mu.Unlock() - if r.fr == nil { - return 0, errors.New("Read after Close") - } - return r.fr.Read(p) -} - -func (r *pooledFlateReader) Close() error { - r.mu.Lock() - defer r.mu.Unlock() - var err error - if r.fr != nil { - err = r.fr.Close() - flateReaderPool.Put(r.fr) - r.fr = nil - } - return err -} - -var ( - compressors sync.Map // map[uint16]Compressor - decompressors sync.Map // map[uint16]Decompressor -) - -func init() { - compressors.Store(Store, Compressor(func(w io.Writer) (io.WriteCloser, error) { return &nopCloser{w}, nil })) - compressors.Store(Deflate, Compressor(func(w io.Writer) (io.WriteCloser, error) { return newFlateWriter(w), nil })) - - decompressors.Store(Store, Decompressor(io.NopCloser)) - decompressors.Store(Deflate, Decompressor(newFlateReader)) -} - -// RegisterDecompressor allows custom decompressors for a specified method ID. -// The common methods [Store] and [Deflate] are built in. -func RegisterDecompressor(method uint16, dcomp Decompressor) { - if _, dup := decompressors.LoadOrStore(method, dcomp); dup { - panic("decompressor already registered") - } -} - -// RegisterCompressor registers custom compressors for a specified method ID. -// The common methods [Store] and [Deflate] are built in. -func RegisterCompressor(method uint16, comp Compressor) { - if _, dup := compressors.LoadOrStore(method, comp); dup { - panic("compressor already registered") - } -} - -func compressor(method uint16) Compressor { - ci, ok := compressors.Load(method) - if !ok { - return nil - } - return ci.(Compressor) -} - -func decompressor(method uint16) Decompressor { - di, ok := decompressors.Load(method) - if !ok { - return nil - } - return di.(Decompressor) -} diff --git a/proto_apng/zip/struct.go b/proto_apng/zip/struct.go index 73fed7b..82f6ef2 100644 --- a/proto_apng/zip/struct.go +++ b/proto_apng/zip/struct.go @@ -21,12 +21,6 @@ fields must be used instead. */ package zip -import ( - "io/fs" - "path" - "time" -) - // Compression methods. const ( Store uint16 = 0 // no compression @@ -47,362 +41,4 @@ const ( dataDescriptor64Len = 24 // two uint32: signature, crc32 | two uint64: compressed size, size directory64LocLen = 20 // directory64EndLen = 56 // + extra - - // Constants for the first byte in CreatorVersion. - creatorFAT = 0 - creatorUnix = 3 - creatorNTFS = 11 - creatorVFAT = 14 - creatorMacOSX = 19 - - // Version numbers. - zipVersion20 = 20 // 2.0 - zipVersion45 = 45 // 4.5 (reads and writes zip64 archives) - - // Limits for non zip64 files. - uint16max = (1 << 16) - 1 - uint32max = (1 << 32) - 1 - - // Extra header IDs. - // - // IDs 0..31 are reserved for official use by PKWARE. - // IDs above that range are defined by third-party vendors. - // Since ZIP lacked high precision timestamps (nor an official specification - // of the timezone used for the date fields), many competing extra fields - // have been invented. Pervasive use effectively makes them "official". - // - // See http://mdfs.net/Docs/Comp/Archiving/Zip/ExtraField - zip64ExtraID = 0x0001 // Zip64 extended information - ntfsExtraID = 0x000a // NTFS - unixExtraID = 0x000d // UNIX - extTimeExtraID = 0x5455 // Extended timestamp - infoZipUnixExtraID = 0x5855 // Info-ZIP Unix extension ) - -// FileHeader describes a file within a ZIP file. -// See the [ZIP specification] for details. -// -// [ZIP specification]: https://support.pkware.com/pkzip/appnote -type FileHeader struct { - // Name is the name of the file. - // - // It must be a relative path, not start with a drive letter (such as "C:"), - // and must use forward slashes instead of back slashes. A trailing slash - // indicates that this file is a directory and should have no data. - Name string - - // Comment is any arbitrary user-defined string shorter than 64KiB. - Comment string - - // NonUTF8 indicates that Name and Comment are not encoded in UTF-8. - // - // By specification, the only other encoding permitted should be CP-437, - // but historically many ZIP readers interpret Name and Comment as whatever - // the system's local character encoding happens to be. - // - // This flag should only be set if the user intends to encode a non-portable - // ZIP file for a specific localized region. Otherwise, the Writer - // automatically sets the ZIP format's UTF-8 flag for valid UTF-8 strings. - NonUTF8 bool - - CreatorVersion uint16 - ReaderVersion uint16 - Flags uint16 - - // Method is the compression method. If zero, Store is used. - Method uint16 - - // Modified is the modified time of the file. - // - // When reading, an extended timestamp is preferred over the legacy MS-DOS - // date field, and the offset between the times is used as the timezone. - // If only the MS-DOS date is present, the timezone is assumed to be UTC. - // - // When writing, an extended timestamp (which is timezone-agnostic) is - // always emitted. The legacy MS-DOS date field is encoded according to the - // location of the Modified time. - Modified time.Time - - // ModifiedTime is an MS-DOS-encoded time. - // - // Deprecated: Use Modified instead. - ModifiedTime uint16 - - // ModifiedDate is an MS-DOS-encoded date. - // - // Deprecated: Use Modified instead. - ModifiedDate uint16 - - // CRC32 is the CRC32 checksum of the file content. - CRC32 uint32 - - // CompressedSize is the compressed size of the file in bytes. - // If either the uncompressed or compressed size of the file - // does not fit in 32 bits, CompressedSize is set to ^uint32(0). - // - // Deprecated: Use CompressedSize64 instead. - CompressedSize uint32 - - // UncompressedSize is the compressed size of the file in bytes. - // If either the uncompressed or compressed size of the file - // does not fit in 32 bits, CompressedSize is set to ^uint32(0). - // - // Deprecated: Use UncompressedSize64 instead. - UncompressedSize uint32 - - // CompressedSize64 is the compressed size of the file in bytes. - CompressedSize64 uint64 - - // UncompressedSize64 is the uncompressed size of the file in bytes. - UncompressedSize64 uint64 - - Extra []byte - ExternalAttrs uint32 // Meaning depends on CreatorVersion -} - -// FileInfo returns an fs.FileInfo for the [FileHeader]. -func (h *FileHeader) FileInfo() fs.FileInfo { - return headerFileInfo{h} -} - -// headerFileInfo implements [fs.FileInfo]. -type headerFileInfo struct { - fh *FileHeader -} - -func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) } -func (fi headerFileInfo) Size() int64 { - if fi.fh.UncompressedSize64 > 0 { - return int64(fi.fh.UncompressedSize64) - } - return int64(fi.fh.UncompressedSize) -} -func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() } -func (fi headerFileInfo) ModTime() time.Time { - if fi.fh.Modified.IsZero() { - return fi.fh.ModTime() - } - return fi.fh.Modified.UTC() -} -func (fi headerFileInfo) Mode() fs.FileMode { return fi.fh.Mode() } -func (fi headerFileInfo) Type() fs.FileMode { return fi.fh.Mode().Type() } -func (fi headerFileInfo) Sys() any { return fi.fh } - -func (fi headerFileInfo) Info() (fs.FileInfo, error) { return fi, nil } - -func (fi headerFileInfo) String() string { - return fs.FormatFileInfo(fi) -} - -// FileInfoHeader creates a partially-populated [FileHeader] from an -// fs.FileInfo. -// Because fs.FileInfo's Name method returns only the base name of -// the file it describes, it may be necessary to modify the Name field -// of the returned header to provide the full path name of the file. -// If compression is desired, callers should set the FileHeader.Method -// field; it is unset by default. -func FileInfoHeader(fi fs.FileInfo) (*FileHeader, error) { - size := fi.Size() - fh := &FileHeader{ - Name: fi.Name(), - UncompressedSize64: uint64(size), - } - fh.SetModTime(fi.ModTime()) - fh.SetMode(fi.Mode()) - if fh.UncompressedSize64 > uint32max { - fh.UncompressedSize = uint32max - } else { - fh.UncompressedSize = uint32(fh.UncompressedSize64) - } - return fh, nil -} - -// timeZone returns a *time.Location based on the provided offset. -// If the offset is non-sensible, then this uses an offset of zero. -func timeZone(offset time.Duration) *time.Location { - const ( - minOffset = -12 * time.Hour // E.g., Baker island at -12:00 - maxOffset = +14 * time.Hour // E.g., Line island at +14:00 - offsetAlias = 15 * time.Minute // E.g., Nepal at +5:45 - ) - offset = offset.Round(offsetAlias) - if offset < minOffset || maxOffset < offset { - offset = 0 - } - return time.FixedZone("", int(offset/time.Second)) -} - -// msDosTimeToTime converts an MS-DOS date and time into a time.Time. -// The resolution is 2s. -// See: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-dosdatetimetofiletime -func msDosTimeToTime(dosDate, dosTime uint16) time.Time { - return time.Date( - // date bits 0-4: day of month; 5-8: month; 9-15: years since 1980 - int(dosDate>>9+1980), - time.Month(dosDate>>5&0xf), - int(dosDate&0x1f), - - // time bits 0-4: second/2; 5-10: minute; 11-15: hour - int(dosTime>>11), - int(dosTime>>5&0x3f), - int(dosTime&0x1f*2), - 0, // nanoseconds - - time.UTC, - ) -} - -// timeToMsDosTime converts a time.Time to an MS-DOS date and time. -// The resolution is 2s. -// See: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-filetimetodosdatetime -func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) { - fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9) - fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11) - return -} - -// ModTime returns the modification time in UTC using the legacy -// [ModifiedDate] and [ModifiedTime] fields. -// -// Deprecated: Use [Modified] instead. -func (h *FileHeader) ModTime() time.Time { - return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime) -} - -// SetModTime sets the [Modified], [ModifiedTime], and [ModifiedDate] fields -// to the given time in UTC. -// -// Deprecated: Use [Modified] instead. -func (h *FileHeader) SetModTime(t time.Time) { - t = t.UTC() // Convert to UTC for compatibility - h.Modified = t - h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t) -} - -const ( - // Unix constants. The specification doesn't mention them, - // but these seem to be the values agreed on by tools. - s_IFMT = 0xf000 - s_IFSOCK = 0xc000 - s_IFLNK = 0xa000 - s_IFREG = 0x8000 - s_IFBLK = 0x6000 - s_IFDIR = 0x4000 - s_IFCHR = 0x2000 - s_IFIFO = 0x1000 - s_ISUID = 0x800 - s_ISGID = 0x400 - s_ISVTX = 0x200 - - msdosDir = 0x10 - msdosReadOnly = 0x01 -) - -// Mode returns the permission and mode bits for the [FileHeader]. -func (h *FileHeader) Mode() (mode fs.FileMode) { - switch h.CreatorVersion >> 8 { - case creatorUnix, creatorMacOSX: - mode = unixModeToFileMode(h.ExternalAttrs >> 16) - case creatorNTFS, creatorVFAT, creatorFAT: - mode = msdosModeToFileMode(h.ExternalAttrs) - } - if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' { - mode |= fs.ModeDir - } - return mode -} - -// SetMode changes the permission and mode bits for the [FileHeader]. -func (h *FileHeader) SetMode(mode fs.FileMode) { - h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8 - h.ExternalAttrs = fileModeToUnixMode(mode) << 16 - - // set MSDOS attributes too, as the original zip does. - if mode&fs.ModeDir != 0 { - h.ExternalAttrs |= msdosDir - } - if mode&0200 == 0 { - h.ExternalAttrs |= msdosReadOnly - } -} - -// isZip64 reports whether the file size exceeds the 32 bit limit -func (h *FileHeader) isZip64() bool { - return h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max -} - -func (h *FileHeader) hasDataDescriptor() bool { - return h.Flags&0x8 != 0 -} - -func msdosModeToFileMode(m uint32) (mode fs.FileMode) { - if m&msdosDir != 0 { - mode = fs.ModeDir | 0777 - } else { - mode = 0666 - } - if m&msdosReadOnly != 0 { - mode &^= 0222 - } - return mode -} - -func fileModeToUnixMode(mode fs.FileMode) uint32 { - var m uint32 - switch mode & fs.ModeType { - default: - m = s_IFREG - case fs.ModeDir: - m = s_IFDIR - case fs.ModeSymlink: - m = s_IFLNK - case fs.ModeNamedPipe: - m = s_IFIFO - case fs.ModeSocket: - m = s_IFSOCK - case fs.ModeDevice: - m = s_IFBLK - case fs.ModeDevice | fs.ModeCharDevice: - m = s_IFCHR - } - if mode&fs.ModeSetuid != 0 { - m |= s_ISUID - } - if mode&fs.ModeSetgid != 0 { - m |= s_ISGID - } - if mode&fs.ModeSticky != 0 { - m |= s_ISVTX - } - return m | uint32(mode&0777) -} - -func unixModeToFileMode(m uint32) fs.FileMode { - mode := fs.FileMode(m & 0777) - switch m & s_IFMT { - case s_IFBLK: - mode |= fs.ModeDevice - case s_IFCHR: - mode |= fs.ModeDevice | fs.ModeCharDevice - case s_IFDIR: - mode |= fs.ModeDir - case s_IFIFO: - mode |= fs.ModeNamedPipe - case s_IFLNK: - mode |= fs.ModeSymlink - case s_IFREG: - // nothing to do - case s_IFSOCK: - mode |= fs.ModeSocket - } - if m&s_ISGID != 0 { - mode |= fs.ModeSetgid - } - if m&s_ISUID != 0 { - mode |= fs.ModeSetuid - } - if m&s_ISVTX != 0 { - mode |= fs.ModeSticky - } - return mode -} diff --git a/proto_apng/zip/testdata/comment-truncated.zip b/proto_apng/zip/testdata/comment-truncated.zip deleted file mode 100644 index 1bc19a8..0000000 Binary files a/proto_apng/zip/testdata/comment-truncated.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/crc32-not-streamed.zip b/proto_apng/zip/testdata/crc32-not-streamed.zip deleted file mode 100644 index f268d88..0000000 Binary files a/proto_apng/zip/testdata/crc32-not-streamed.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/dd.zip b/proto_apng/zip/testdata/dd.zip deleted file mode 100644 index e53378b..0000000 Binary files a/proto_apng/zip/testdata/dd.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/dupdir.zip b/proto_apng/zip/testdata/dupdir.zip deleted file mode 100644 index 292720b..0000000 Binary files a/proto_apng/zip/testdata/dupdir.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/go-no-datadesc-sig.zip.base64 b/proto_apng/zip/testdata/go-no-datadesc-sig.zip.base64 deleted file mode 100644 index 1c2c071..0000000 --- a/proto_apng/zip/testdata/go-no-datadesc-sig.zip.base64 +++ /dev/null @@ -1 +0,0 @@ -UEsDBBQACAAAAGWHaECoZTJ+BAAAAAQAAAAHABgAZm9vLnR4dFVUBQAD3lVZT3V4CwABBPUBAAAEFAAAAGZvbwqoZTJ+BAAAAAQAAABQSwMEFAAIAAAAZodoQOmzogQEAAAABAAAAAcAGABiYXIudHh0VVQFAAPgVVlPdXgLAAEE9QEAAAQUAAAAYmFyCumzogQEAAAABAAAAFBLAQIUAxQACAAAAGWHaECoZTJ+BAAAAAQAAAAHABgAAAAAAAAAAACkgQAAAABmb28udHh0VVQFAAPeVVlPdXgLAAEE9QEAAAQUAAAAUEsBAhQDFAAIAAAAZodoQOmzogQEAAAABAAAAAcAGAAAAAAAAAAAAKSBTQAAAGJhci50eHRVVAUAA+BVWU91eAsAAQT1AQAABBQAAABQSwUGAAAAAAIAAgCaAAAAmgAAAAAA diff --git a/proto_apng/zip/testdata/go-with-datadesc-sig.zip b/proto_apng/zip/testdata/go-with-datadesc-sig.zip deleted file mode 100644 index bcfe121..0000000 Binary files a/proto_apng/zip/testdata/go-with-datadesc-sig.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/gophercolor16x16.png b/proto_apng/zip/testdata/gophercolor16x16.png deleted file mode 100644 index 48854ff..0000000 Binary files a/proto_apng/zip/testdata/gophercolor16x16.png and /dev/null differ diff --git a/proto_apng/zip/testdata/readme.notzip b/proto_apng/zip/testdata/readme.notzip deleted file mode 100644 index 79b1cb6..0000000 Binary files a/proto_apng/zip/testdata/readme.notzip and /dev/null differ diff --git a/proto_apng/zip/testdata/readme.zip b/proto_apng/zip/testdata/readme.zip deleted file mode 100644 index 5642a67..0000000 Binary files a/proto_apng/zip/testdata/readme.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/subdir.zip b/proto_apng/zip/testdata/subdir.zip deleted file mode 100644 index 324d06b..0000000 Binary files a/proto_apng/zip/testdata/subdir.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/symlink.zip b/proto_apng/zip/testdata/symlink.zip deleted file mode 100644 index af84693..0000000 Binary files a/proto_apng/zip/testdata/symlink.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/test-badbase.zip b/proto_apng/zip/testdata/test-badbase.zip deleted file mode 100644 index 245a62c..0000000 Binary files a/proto_apng/zip/testdata/test-badbase.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/test-baddirsz.zip b/proto_apng/zip/testdata/test-baddirsz.zip deleted file mode 100644 index 45b3314..0000000 Binary files a/proto_apng/zip/testdata/test-baddirsz.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/test-prefix.zip b/proto_apng/zip/testdata/test-prefix.zip deleted file mode 100644 index 1eabb48..0000000 Binary files a/proto_apng/zip/testdata/test-prefix.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/test-trailing-junk.zip b/proto_apng/zip/testdata/test-trailing-junk.zip deleted file mode 100644 index 42281b4..0000000 Binary files a/proto_apng/zip/testdata/test-trailing-junk.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/test.zip b/proto_apng/zip/testdata/test.zip deleted file mode 100644 index 03890c0..0000000 Binary files a/proto_apng/zip/testdata/test.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/time-22738.zip b/proto_apng/zip/testdata/time-22738.zip deleted file mode 100644 index eb85b57..0000000 Binary files a/proto_apng/zip/testdata/time-22738.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/time-7zip.zip b/proto_apng/zip/testdata/time-7zip.zip deleted file mode 100644 index 4f74819..0000000 Binary files a/proto_apng/zip/testdata/time-7zip.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/time-go.zip b/proto_apng/zip/testdata/time-go.zip deleted file mode 100644 index f008805..0000000 Binary files a/proto_apng/zip/testdata/time-go.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/time-infozip.zip b/proto_apng/zip/testdata/time-infozip.zip deleted file mode 100644 index 8e63948..0000000 Binary files a/proto_apng/zip/testdata/time-infozip.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/time-osx.zip b/proto_apng/zip/testdata/time-osx.zip deleted file mode 100644 index e82c5c2..0000000 Binary files a/proto_apng/zip/testdata/time-osx.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/time-win7.zip b/proto_apng/zip/testdata/time-win7.zip deleted file mode 100644 index 8ba222b..0000000 Binary files a/proto_apng/zip/testdata/time-win7.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/time-winrar.zip b/proto_apng/zip/testdata/time-winrar.zip deleted file mode 100644 index a8a19b0..0000000 Binary files a/proto_apng/zip/testdata/time-winrar.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/time-winzip.zip b/proto_apng/zip/testdata/time-winzip.zip deleted file mode 100644 index f6e8f8b..0000000 Binary files a/proto_apng/zip/testdata/time-winzip.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/unix.zip b/proto_apng/zip/testdata/unix.zip deleted file mode 100644 index ce1a981..0000000 Binary files a/proto_apng/zip/testdata/unix.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/utf8-7zip.zip b/proto_apng/zip/testdata/utf8-7zip.zip deleted file mode 100644 index 0e97884..0000000 Binary files a/proto_apng/zip/testdata/utf8-7zip.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/utf8-infozip.zip b/proto_apng/zip/testdata/utf8-infozip.zip deleted file mode 100644 index 25a8926..0000000 Binary files a/proto_apng/zip/testdata/utf8-infozip.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/utf8-osx.zip b/proto_apng/zip/testdata/utf8-osx.zip deleted file mode 100644 index 9b0c058..0000000 Binary files a/proto_apng/zip/testdata/utf8-osx.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/utf8-winrar.zip b/proto_apng/zip/testdata/utf8-winrar.zip deleted file mode 100644 index 4bad6c3..0000000 Binary files a/proto_apng/zip/testdata/utf8-winrar.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/utf8-winzip.zip b/proto_apng/zip/testdata/utf8-winzip.zip deleted file mode 100644 index 909d52e..0000000 Binary files a/proto_apng/zip/testdata/utf8-winzip.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/winxp.zip b/proto_apng/zip/testdata/winxp.zip deleted file mode 100644 index 3919322..0000000 Binary files a/proto_apng/zip/testdata/winxp.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/zip64-2.zip b/proto_apng/zip/testdata/zip64-2.zip deleted file mode 100644 index f844e35..0000000 Binary files a/proto_apng/zip/testdata/zip64-2.zip and /dev/null differ diff --git a/proto_apng/zip/testdata/zip64.zip b/proto_apng/zip/testdata/zip64.zip deleted file mode 100644 index a2ee1fa..0000000 Binary files a/proto_apng/zip/testdata/zip64.zip and /dev/null differ diff --git a/proto_apng/zip/writer.go b/proto_apng/zip/writer.go deleted file mode 100644 index e33df24..0000000 --- a/proto_apng/zip/writer.go +++ /dev/null @@ -1,666 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package zip - -import ( - "bufio" - "encoding/binary" - "errors" - "hash" - "hash/crc32" - "io" - "io/fs" - "strings" - "unicode/utf8" -) - -var ( - errLongName = errors.New("zip: FileHeader.Name too long") - errLongExtra = errors.New("zip: FileHeader.Extra too long") -) - -// Writer implements a zip file writer. -type Writer struct { - cw *countWriter - dir []*header - last *fileWriter - closed bool - compressors map[uint16]Compressor - comment string - - // testHookCloseSizeOffset if non-nil is called with the size - // of offset of the central directory at Close. - testHookCloseSizeOffset func(size, offset uint64) -} - -type header struct { - *FileHeader - offset uint64 - raw bool -} - -// NewWriter returns a new [Writer] writing a zip file to w. -func NewWriter(w io.Writer) *Writer { - return &Writer{cw: &countWriter{w: bufio.NewWriter(w)}} -} - -// SetOffset sets the offset of the beginning of the zip data within the -// underlying writer. It should be used when the zip data is appended to an -// existing file, such as a binary executable. -// It must be called before any data is written. -func (w *Writer) SetOffset(n int64) { - if w.cw.count != 0 { - panic("zip: SetOffset called after data was written") - } - w.cw.count = n -} - -// Flush flushes any buffered data to the underlying writer. -// Calling Flush is not normally necessary; calling Close is sufficient. -func (w *Writer) Flush() error { - return w.cw.w.(*bufio.Writer).Flush() -} - -// SetComment sets the end-of-central-directory comment field. -// It can only be called before [Writer.Close]. -func (w *Writer) SetComment(comment string) error { - if len(comment) > uint16max { - return errors.New("zip: Writer.Comment too long") - } - w.comment = comment - return nil -} - -// Close finishes writing the zip file by writing the central directory. -// It does not close the underlying writer. -func (w *Writer) Close() error { - if w.last != nil && !w.last.closed { - if err := w.last.close(); err != nil { - return err - } - w.last = nil - } - if w.closed { - return errors.New("zip: writer closed twice") - } - w.closed = true - - // write central directory - start := w.cw.count - for _, h := range w.dir { - var buf [directoryHeaderLen]byte - b := writeBuf(buf[:]) - b.uint32(uint32(directoryHeaderSignature)) - b.uint16(h.CreatorVersion) - b.uint16(h.ReaderVersion) - b.uint16(h.Flags) - b.uint16(h.Method) - b.uint16(h.ModifiedTime) - b.uint16(h.ModifiedDate) - b.uint32(h.CRC32) - if h.isZip64() || h.offset >= uint32max { - // the file needs a zip64 header. store maxint in both - // 32 bit size fields (and offset later) to signal that the - // zip64 extra header should be used. - b.uint32(uint32max) // compressed size - b.uint32(uint32max) // uncompressed size - - // append a zip64 extra block to Extra - var buf [28]byte // 2x uint16 + 3x uint64 - eb := writeBuf(buf[:]) - eb.uint16(zip64ExtraID) - eb.uint16(24) // size = 3x uint64 - eb.uint64(h.UncompressedSize64) - eb.uint64(h.CompressedSize64) - eb.uint64(h.offset) - h.Extra = append(h.Extra, buf[:]...) - } else { - b.uint32(h.CompressedSize) - b.uint32(h.UncompressedSize) - } - - b.uint16(uint16(len(h.Name))) - b.uint16(uint16(len(h.Extra))) - b.uint16(uint16(len(h.Comment))) - b = b[4:] // skip disk number start and internal file attr (2x uint16) - b.uint32(h.ExternalAttrs) - if h.offset > uint32max { - b.uint32(uint32max) - } else { - b.uint32(uint32(h.offset)) - } - if _, err := w.cw.Write(buf[:]); err != nil { - return err - } - if _, err := io.WriteString(w.cw, h.Name); err != nil { - return err - } - if _, err := w.cw.Write(h.Extra); err != nil { - return err - } - if _, err := io.WriteString(w.cw, h.Comment); err != nil { - return err - } - } - end := w.cw.count - - records := uint64(len(w.dir)) - size := uint64(end - start) - offset := uint64(start) - - if f := w.testHookCloseSizeOffset; f != nil { - f(size, offset) - } - - if records >= uint16max || size >= uint32max || offset >= uint32max { - var buf [directory64EndLen + directory64LocLen]byte - b := writeBuf(buf[:]) - - // zip64 end of central directory record - b.uint32(directory64EndSignature) - b.uint64(directory64EndLen - 12) // length minus signature (uint32) and length fields (uint64) - b.uint16(zipVersion45) // version made by - b.uint16(zipVersion45) // version needed to extract - b.uint32(0) // number of this disk - b.uint32(0) // number of the disk with the start of the central directory - b.uint64(records) // total number of entries in the central directory on this disk - b.uint64(records) // total number of entries in the central directory - b.uint64(size) // size of the central directory - b.uint64(offset) // offset of start of central directory with respect to the starting disk number - - // zip64 end of central directory locator - b.uint32(directory64LocSignature) - b.uint32(0) // number of the disk with the start of the zip64 end of central directory - b.uint64(uint64(end)) // relative offset of the zip64 end of central directory record - b.uint32(1) // total number of disks - - if _, err := w.cw.Write(buf[:]); err != nil { - return err - } - - // store max values in the regular end record to signal - // that the zip64 values should be used instead - records = uint16max - size = uint32max - offset = uint32max - } - - // write end record - var buf [directoryEndLen]byte - b := writeBuf(buf[:]) - b.uint32(uint32(directoryEndSignature)) - b = b[4:] // skip over disk number and first disk number (2x uint16) - b.uint16(uint16(records)) // number of entries this disk - b.uint16(uint16(records)) // number of entries total - b.uint32(uint32(size)) // size of directory - b.uint32(uint32(offset)) // start of directory - b.uint16(uint16(len(w.comment))) // byte size of EOCD comment - if _, err := w.cw.Write(buf[:]); err != nil { - return err - } - if _, err := io.WriteString(w.cw, w.comment); err != nil { - return err - } - - return w.cw.w.(*bufio.Writer).Flush() -} - -// Create adds a file to the zip file using the provided name. -// It returns a [Writer] to which the file contents should be written. -// The file contents will be compressed using the [Deflate] method. -// The name must be a relative path: it must not start with a drive -// letter (e.g. C:) or leading slash, and only forward slashes are -// allowed. To create a directory instead of a file, add a trailing -// slash to the name. -// The file's contents must be written to the [io.Writer] before the next -// call to [Writer.Create], [Writer.CreateHeader], or [Writer.Close]. -func (w *Writer) Create(name string) (io.Writer, error) { - header := &FileHeader{ - Name: name, - Method: Deflate, - } - return w.CreateHeader(header) -} - -// detectUTF8 reports whether s is a valid UTF-8 string, and whether the string -// must be considered UTF-8 encoding (i.e., not compatible with CP-437, ASCII, -// or any other common encoding). -func detectUTF8(s string) (valid, require bool) { - for i := 0; i < len(s); { - r, size := utf8.DecodeRuneInString(s[i:]) - i += size - // Officially, ZIP uses CP-437, but many readers use the system's - // local character encoding. Most encoding are compatible with a large - // subset of CP-437, which itself is ASCII-like. - // - // Forbid 0x7e and 0x5c since EUC-KR and Shift-JIS replace those - // characters with localized currency and overline characters. - if r < 0x20 || r > 0x7d || r == 0x5c { - if !utf8.ValidRune(r) || (r == utf8.RuneError && size == 1) { - return false, false - } - require = true - } - } - return true, require -} - -// prepare performs the bookkeeping operations required at the start of -// CreateHeader and CreateRaw. -func (w *Writer) prepare(fh *FileHeader) error { - if w.last != nil && !w.last.closed { - if err := w.last.close(); err != nil { - return err - } - } - if len(w.dir) > 0 && w.dir[len(w.dir)-1].FileHeader == fh { - // See https://golang.org/issue/11144 confusion. - return errors.New("archive/zip: invalid duplicate FileHeader") - } - return nil -} - -// CreateHeader adds a file to the zip archive using the provided [FileHeader] -// for the file metadata. [Writer] takes ownership of fh and may mutate -// its fields. The caller must not modify fh after calling [Writer.CreateHeader]. -// -// This returns a [Writer] to which the file contents should be written. -// The file's contents must be written to the io.Writer before the next -// call to [Writer.Create], [Writer.CreateHeader], [Writer.CreateRaw], or [Writer.Close]. -func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { - if err := w.prepare(fh); err != nil { - return nil, err - } - - // The ZIP format has a sad state of affairs regarding character encoding. - // Officially, the name and comment fields are supposed to be encoded - // in CP-437 (which is mostly compatible with ASCII), unless the UTF-8 - // flag bit is set. However, there are several problems: - // - // * Many ZIP readers still do not support UTF-8. - // * If the UTF-8 flag is cleared, several readers simply interpret the - // name and comment fields as whatever the local system encoding is. - // - // In order to avoid breaking readers without UTF-8 support, - // we avoid setting the UTF-8 flag if the strings are CP-437 compatible. - // However, if the strings require multibyte UTF-8 encoding and is a - // valid UTF-8 string, then we set the UTF-8 bit. - // - // For the case, where the user explicitly wants to specify the encoding - // as UTF-8, they will need to set the flag bit themselves. - utf8Valid1, utf8Require1 := detectUTF8(fh.Name) - utf8Valid2, utf8Require2 := detectUTF8(fh.Comment) - switch { - case fh.NonUTF8: - fh.Flags &^= 0x800 - case (utf8Require1 || utf8Require2) && (utf8Valid1 && utf8Valid2): - fh.Flags |= 0x800 - } - - fh.CreatorVersion = fh.CreatorVersion&0xff00 | zipVersion20 // preserve compatibility byte - fh.ReaderVersion = zipVersion20 - - // If Modified is set, this takes precedence over MS-DOS timestamp fields. - if !fh.Modified.IsZero() { - // Contrary to the FileHeader.SetModTime method, we intentionally - // do not convert to UTC, because we assume the user intends to encode - // the date using the specified timezone. A user may want this control - // because many legacy ZIP readers interpret the timestamp according - // to the local timezone. - // - // The timezone is only non-UTC if a user directly sets the Modified - // field directly themselves. All other approaches sets UTC. - fh.ModifiedDate, fh.ModifiedTime = timeToMsDosTime(fh.Modified) - - // Use "extended timestamp" format since this is what Info-ZIP uses. - // Nearly every major ZIP implementation uses a different format, - // but at least most seem to be able to understand the other formats. - // - // This format happens to be identical for both local and central header - // if modification time is the only timestamp being encoded. - var mbuf [9]byte // 2*SizeOf(uint16) + SizeOf(uint8) + SizeOf(uint32) - mt := uint32(fh.Modified.Unix()) - eb := writeBuf(mbuf[:]) - eb.uint16(extTimeExtraID) - eb.uint16(5) // Size: SizeOf(uint8) + SizeOf(uint32) - eb.uint8(1) // Flags: ModTime - eb.uint32(mt) // ModTime - fh.Extra = append(fh.Extra, mbuf[:]...) - } - - var ( - ow io.Writer - fw *fileWriter - ) - h := &header{ - FileHeader: fh, - offset: uint64(w.cw.count), - } - - if strings.HasSuffix(fh.Name, "/") { - // Set the compression method to Store to ensure data length is truly zero, - // which the writeHeader method always encodes for the size fields. - // This is necessary as most compression formats have non-zero lengths - // even when compressing an empty string. - fh.Method = Store - fh.Flags &^= 0x8 // we will not write a data descriptor - - // Explicitly clear sizes as they have no meaning for directories. - fh.CompressedSize = 0 - fh.CompressedSize64 = 0 - fh.UncompressedSize = 0 - fh.UncompressedSize64 = 0 - - ow = dirWriter{} - } else { - fh.Flags |= 0x8 // we will write a data descriptor - - fw = &fileWriter{ - zipw: w.cw, - compCount: &countWriter{w: w.cw}, - crc32: crc32.NewIEEE(), - } - comp := w.compressor(fh.Method) - if comp == nil { - return nil, ErrAlgorithm - } - var err error - fw.comp, err = comp(fw.compCount) - if err != nil { - return nil, err - } - fw.rawCount = &countWriter{w: fw.comp} - fw.header = h - ow = fw - } - w.dir = append(w.dir, h) - if err := writeHeader(w.cw, h); err != nil { - return nil, err - } - // If we're creating a directory, fw is nil. - w.last = fw - return ow, nil -} - -func writeHeader(w io.Writer, h *header) error { - const maxUint16 = 1<<16 - 1 - if len(h.Name) > maxUint16 { - return errLongName - } - if len(h.Extra) > maxUint16 { - return errLongExtra - } - - var buf [fileHeaderLen]byte - b := writeBuf(buf[:]) - b.uint32(uint32(fileHeaderSignature)) - b.uint16(h.ReaderVersion) - b.uint16(h.Flags) - b.uint16(h.Method) - b.uint16(h.ModifiedTime) - b.uint16(h.ModifiedDate) - // In raw mode (caller does the compression), the values are either - // written here or in the trailing data descriptor based on the header - // flags. - if h.raw && !h.hasDataDescriptor() { - b.uint32(h.CRC32) - b.uint32(uint32(min(h.CompressedSize64, uint32max))) - b.uint32(uint32(min(h.UncompressedSize64, uint32max))) - } else { - // When this package handle the compression, these values are - // always written to the trailing data descriptor. - b.uint32(0) // crc32 - b.uint32(0) // compressed size - b.uint32(0) // uncompressed size - } - b.uint16(uint16(len(h.Name))) - b.uint16(uint16(len(h.Extra))) - if _, err := w.Write(buf[:]); err != nil { - return err - } - if _, err := io.WriteString(w, h.Name); err != nil { - return err - } - _, err := w.Write(h.Extra) - return err -} - -// CreateRaw adds a file to the zip archive using the provided [FileHeader] and -// returns a [Writer] to which the file contents should be written. The file's -// contents must be written to the io.Writer before the next call to [Writer.Create], -// [Writer.CreateHeader], [Writer.CreateRaw], or [Writer.Close]. -// -// In contrast to [Writer.CreateHeader], the bytes passed to Writer are not compressed. -func (w *Writer) CreateRaw(fh *FileHeader) (io.Writer, error) { - if err := w.prepare(fh); err != nil { - return nil, err - } - - fh.CompressedSize = uint32(min(fh.CompressedSize64, uint32max)) - fh.UncompressedSize = uint32(min(fh.UncompressedSize64, uint32max)) - - h := &header{ - FileHeader: fh, - offset: uint64(w.cw.count), - raw: true, - } - w.dir = append(w.dir, h) - if err := writeHeader(w.cw, h); err != nil { - return nil, err - } - - if strings.HasSuffix(fh.Name, "/") { - w.last = nil - return dirWriter{}, nil - } - - fw := &fileWriter{ - header: h, - zipw: w.cw, - } - w.last = fw - return fw, nil -} - -// Copy copies the file f (obtained from a [Reader]) into w. It copies the raw -// form directly bypassing decompression, compression, and validation. -func (w *Writer) Copy(f *File) error { - r, err := f.OpenRaw() - if err != nil { - return err - } - fw, err := w.CreateRaw(&f.FileHeader) - if err != nil { - return err - } - _, err = io.Copy(fw, r) - return err -} - -// RegisterCompressor registers or overrides a custom compressor for a specific -// method ID. If a compressor for a given method is not found, [Writer] will -// default to looking up the compressor at the package level. -func (w *Writer) RegisterCompressor(method uint16, comp Compressor) { - if w.compressors == nil { - w.compressors = make(map[uint16]Compressor) - } - w.compressors[method] = comp -} - -// AddFS adds the files from fs.FS to the archive. -// It walks the directory tree starting at the root of the filesystem -// adding each file to the zip using deflate while maintaining the directory structure. -func (w *Writer) AddFS(fsys fs.FS) error { - return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - info, err := d.Info() - if err != nil { - return err - } - if !info.Mode().IsRegular() { - return errors.New("zip: cannot add non-regular file") - } - h, err := FileInfoHeader(info) - if err != nil { - return err - } - h.Name = name - h.Method = Deflate - fw, err := w.CreateHeader(h) - if err != nil { - return err - } - f, err := fsys.Open(name) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(fw, f) - return err - }) -} - -func (w *Writer) compressor(method uint16) Compressor { - comp := w.compressors[method] - if comp == nil { - comp = compressor(method) - } - return comp -} - -type dirWriter struct{} - -func (dirWriter) Write(b []byte) (int, error) { - if len(b) == 0 { - return 0, nil - } - return 0, errors.New("zip: write to directory") -} - -type fileWriter struct { - *header - zipw io.Writer - rawCount *countWriter - comp io.WriteCloser - compCount *countWriter - crc32 hash.Hash32 - closed bool -} - -func (w *fileWriter) Write(p []byte) (int, error) { - if w.closed { - return 0, errors.New("zip: write to closed file") - } - if w.raw { - return w.zipw.Write(p) - } - w.crc32.Write(p) - return w.rawCount.Write(p) -} - -func (w *fileWriter) close() error { - if w.closed { - return errors.New("zip: file closed twice") - } - w.closed = true - if w.raw { - return w.writeDataDescriptor() - } - if err := w.comp.Close(); err != nil { - return err - } - - // update FileHeader - fh := w.header.FileHeader - fh.CRC32 = w.crc32.Sum32() - fh.CompressedSize64 = uint64(w.compCount.count) - fh.UncompressedSize64 = uint64(w.rawCount.count) - - if fh.isZip64() { - fh.CompressedSize = uint32max - fh.UncompressedSize = uint32max - fh.ReaderVersion = zipVersion45 // requires 4.5 - File uses ZIP64 format extensions - } else { - fh.CompressedSize = uint32(fh.CompressedSize64) - fh.UncompressedSize = uint32(fh.UncompressedSize64) - } - - return w.writeDataDescriptor() -} - -func (w *fileWriter) writeDataDescriptor() error { - if !w.hasDataDescriptor() { - return nil - } - // Write data descriptor. This is more complicated than one would - // think, see e.g. comments in zipfile.c:putextended() and - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588. - // The approach here is to write 8 byte sizes if needed without - // adding a zip64 extra in the local header (too late anyway). - var buf []byte - if w.isZip64() { - buf = make([]byte, dataDescriptor64Len) - } else { - buf = make([]byte, dataDescriptorLen) - } - b := writeBuf(buf) - b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X - b.uint32(w.CRC32) - if w.isZip64() { - b.uint64(w.CompressedSize64) - b.uint64(w.UncompressedSize64) - } else { - b.uint32(w.CompressedSize) - b.uint32(w.UncompressedSize) - } - _, err := w.zipw.Write(buf) - return err -} - -type countWriter struct { - w io.Writer - count int64 -} - -func (w *countWriter) Write(p []byte) (int, error) { - n, err := w.w.Write(p) - w.count += int64(n) - return n, err -} - -type nopCloser struct { - io.Writer -} - -func (w nopCloser) Close() error { - return nil -} - -type writeBuf []byte - -func (b *writeBuf) uint8(v uint8) { - (*b)[0] = v - *b = (*b)[1:] -} - -func (b *writeBuf) uint16(v uint16) { - binary.LittleEndian.PutUint16(*b, v) - *b = (*b)[2:] -} - -func (b *writeBuf) uint32(v uint32) { - binary.LittleEndian.PutUint32(*b, v) - *b = (*b)[4:] -} - -func (b *writeBuf) uint64(v uint64) { - binary.LittleEndian.PutUint64(*b, v) - *b = (*b)[8:] -}