How to zip and unzip files in Go

Compressing files into a .zip archive and unzipping it in Go is super easy thanks to its extensive and easy-to-use standard library.

Here is how.

Zip files in Go

// zip.go
package main

import (
	"archive/zip"
	"io"
	"io/fs"
	"log"
	"os"
	"path/filepath"
)

func main() {
	if len(os.Args) < 3 {
		fmt.Println("Usage: go run zip.go archive.zip file1 [file2...]")
		os.Exit(1)
	}

	archive := os.Args[1]
	files := os.Args[2:]

	err := zipFiles(archive, files...)
	if err != nil {
		log.Fatal(err)
	}
}

func zipFiles(archive string, files ...string) (err error) {
	// Create the ZIP file
	zipFile, err := os.Create(archive)
	if err != nil {
		return
	}
	defer zipFile.Close()

	zipWriter := zip.NewWriter(zipFile)
	defer zipWriter.Close()

	for _, filePath := range files {
		err = processFile(zipWriter, filePath)
		if err != nil {
			return
		}
	}

	return
}

func processFile(zipWriter *zip.Writer, filePath string) error {
	var err error
	var fileInfo fs.FileInfo
	var header *zip.FileHeader
	var headerWriter io.Writer
	var file *os.File

	fileInfo, err = os.Stat(filePath)
	if err != nil {
		return err
	}

	header, err = zip.FileInfoHeader(fileInfo)
	if err != nil {
		return err
	}

	header.Method = zip.Deflate

	header.Name, err = filepath.Rel(filepath.Dir("."), filePath)
	if err != nil {
		return err
	}

	if fileInfo.IsDir() {
		header.Name += "/"
	}

	headerWriter, err = zipWriter.CreateHeader(header)
	if err != nil {
		return err
	}

	if fileInfo.IsDir() {
		return nil
	}

	file, err = os.Open(filePath)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = io.Copy(headerWriter, file)
	return err
}

To run this program, save it to a file called "zip.go" and execute the following command in your terminal:

$ go run zip.go your_zipfile.zip file1 file2 file3...

Unzip files in Go

package main

import (
	"archive/zip"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	if len(os.Args) != 3 {
		fmt.Println("Usage: go run unzip.go <zipfile> <destination>")
		os.Exit(1)
	}

	zipFile := os.Args[1]
	destDir := os.Args[2]

	if err := unzip(zipFile, destDir); err != nil {
		fmt.Println("Error unzipping file:", err)
		os.Exit(1)
	}

	fmt.Println("Successfully unzipped:", zipFile)
}

func unzip(zipFile, destDir string) error {
	reader, err := zip.OpenReader(zipFile)
	if err != nil {
		return err
	}
	defer reader.Close()

	destDir = filepath.Clean(destDir)

	for _, file := range reader.File {
		if err := extractFile(file, destDir); err != nil {
			return err
		}
	}

	return nil
}

func extractFile(file *zip.File, destDir string) error {
	destPath := filepath.Join(destDir, file.Name)
	destPath = filepath.Clean(destPath)

	// Check for file traversal attack
	if !strings.HasPrefix(destPath, destDir) {
		return fmt.Errorf("invalid file path: %s", file.Name)
	}

	if file.FileInfo().IsDir() {
		if err := os.MkdirAll(destPath, file.Mode()); err != nil {
			return err
		}
	} else {
		if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
			return err
		}

		destFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			return err
		}
		defer destFile.Close()

		srcFile, err := file.Open()
		if err != nil {
			return err
		}
		defer srcFile.Close()

		if _, err := io.Copy(destFile, srcFile); err != nil {
			return err
		}
	}

	return nil
}

To run this program, save it to a file called "unzip.go" and execute the following command in your terminal:

$ go run unzip.go path/to/your/zipfile.zip path/to/destination/directory
1 email / week to learn how to (ab)use technology for fun & profit: Programming, Hacking & Entrepreneurship.
I hate spam even more than you do. I'll never share your email, and you can unsubscribe at any time.

Tags: go, programming

Want to learn Rust, Cryptography and Security? Get my book Black Hat Rust!