Skip to content

archive/zip: feature: support streamable archives #74147

@caarlos0

Description

@caarlos0

Go version

go version go1.24.4 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='c++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/carlos/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/carlos/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/qj/36_tzh_54kj9mm0lhlhy3f900000gn/T/go-build3621002674=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/carlos/Developer/goreleaser/goreleaser/go.mod'
GOMODCACHE='/Users/carlos/Developer/Go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/carlos/Developer/Go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.24.4/libexec'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/carlos/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.24.4/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.4'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Creating a zip archive using archive/zip like so:

package main

import (
	"archive/zip"
	"compress/flate"
	"io"
	"os"
)

func main() {
	fz, err := os.OpenFile("foo.zip", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0o644)
	if err != nil {
		panic(err)
	}
	z := zip.NewWriter(fz)
	z.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
		return flate.NewWriter(out, flate.BestCompression)
	})

	f, err := os.Open("main.go")
	if err != nil {
		panic(err)
	}
	fd, err := f.Stat()
	if err != nil {
		panic(err)
	}
	fh, err := zip.FileInfoHeader(fd)
	if err != nil {
		panic(err)
	}
	w, err := z.CreateHeader(fh)
	if err != nil {
		panic(err)
	}
	_, err = io.Copy(w, f)
	if err != nil {
		panic(err)
	}
	if err := z.Close(); err != nil {
		panic(err)
	}
}

Then, you run it, it should create a foo.zip file.

From there, we can do things like:

$ busybox unzip -l foo.zip
Archive:  foo.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
      715  06-13-2025 13:17   main.go
 --------                     -------
      715                     1 files

But, if you try to unzip it from a pipe:

$ cat foo.zip | busybox unzip -
Archive:  -
unzip: zip flag 8 (streaming) is not supported

If we do the same with unzip shipped by Apple on macOS, it only prints its own help.

My guess is that this line might be the culprit: https://cs.opensource.google/go/go/+/master:src/archive/zip/writer.go;l=359

It seems we're always setting the stream bit if the file isn't a directory...

What did you see happen?

unzip: zip flag 8 (streaming) is not supported

What did you expect to see?

Not to see that error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FeatureRequestIssues asking for a new feature that does not need a proposal.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.WaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      close