All formats

rardecode

LanguageGo
LicenseBSD 2-Clause
Repositorygithub.com/nwaples/rardecode
Documentationpkg.go.dev/github.com/nwaples/rardecode/v2
Depends onstandard library only
Read / writeyes / no; RAR4 and RAR5 (RAR 2.0 and newer compression), passwords and encrypted headers, solid, multi-volume, BLAKE2 checksums

rardecode is an independent RAR decoder written in Go: no cgo, no UnRAR code, no external tools. It decodes all RAR compression methods (LZ, PPMd, RarVM filters for RAR4; RAR5 filters), decrypts AES-encrypted archives, follows volumes and verifies CRC-32 and BLAKE2 checksums.

The API mirrors archive/tar: OpenReader opens an archive on disk (and its volumes), NewReader reads a single-volume archive from any io.Reader, and Next moves to the next entry, after which the reader returns that entry’s data. OpenFS returns an fs.FS for random access, and List returns all entries with an Open method. Use the module path with /v2; the old v1 path is unmaintained.

Installation#

Shell
go get github.com/nwaples/rardecode/v2

Basic usage: extract a RAR file#

Go
package main

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/nwaples/rardecode/v2"
)

func main() {
	r, err := rardecode.OpenReader("archive.rar")
	if err != nil {
		log.Fatal(err)
	}
	defer r.Close()

	dest := "output"
	for {
		hdr, err := r.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		path := filepath.Join(dest, filepath.FromSlash(hdr.Name))
		if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
			log.Fatalf("unsafe path %q", hdr.Name)
		}
		fmt.Println(hdr.Name, hdr.UnPackedSize)
		if hdr.IsDir {
			os.MkdirAll(path, 0o755)
			continue
		}
		if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
			log.Fatal(err)
		}
		f, err := os.Create(path)
		if err != nil {
			log.Fatal(err)
		}
		_, err = io.Copy(f, r) // reads the current entry; checks its checksum at the end
		f.Close()
		if err != nil {
			log.Fatal(err)
		}
	}
}

How to list the files in a RAR#

List reads only the headers:

Go
files, err := rardecode.List("archive.rar")
if err != nil {
	log.Fatal(err)
}
for _, f := range files {
	fmt.Printf("%-40s %10d %s solid=%v encrypted=%v\n",
		f.Name, f.UnPackedSize, f.ModificationTime.Format("2006-01-02 15:04"), f.Solid, f.Encrypted)
}

How to read one file from a RAR with io/fs#

OpenFS gives an fs.FS, so fs.ReadFile, fs.WalkDir and http.FS work on the archive:

Go
fsys, err := rardecode.OpenFS("archive.rar")
if err != nil {
	log.Fatal(err)
}
data, err := fs.ReadFile(fsys, "docs/readme.txt")
if err != nil {
	log.Fatal(err)
}
fmt.Println(len(data))

fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
	fmt.Println(path)
	return err
})

Random access doesn’t work for files in a solid archive: opening one returns rardecode.ErrSolidOpen. Read solid archives in order with OpenReader and Next.

How to extract a password-protected RAR#

Go
r, err := rardecode.OpenReader("secret.rar", rardecode.Password("password"))
if err != nil {
	log.Fatal(err) // with encrypted headers, a wrong password fails here
}
defer r.Close()
hdr, err := r.Next()
if errors.Is(err, rardecode.ErrBadPassword) {
	log.Fatal("wrong password")
}

ErrBadPassword needs the password check value of RAR5 archives; with RAR4, a wrong password surfaces as ErrBadFileChecksum when the entry is read. Without a password, encrypted archives return ErrArchiveEncrypted (encrypted headers) or ErrArchivedFileEncrypted (encrypted file data). hdr.Encrypted and hdr.HeaderEncrypted tell which is the case.

How to read a RAR from a stream#

NewReader takes any io.Reader, for example an HTTP response body. It handles single-volume archives only:

Go
resp, err := http.Get("https://example.com/archive.rar")
if err != nil {
	log.Fatal(err)
}
defer resp.Body.Close()
r, err := rardecode.NewReader(resp.Body)
if err != nil {
	log.Fatal(err)
}
for {
	hdr, err := r.Next()
	if err == io.EOF {
		break
	}
	if err != nil {
		log.Fatal(err)
	}
	n, _ := io.Copy(io.Discard, r)
	fmt.Println(hdr.Name, n)
}

Tips#