RAR (Roshall ARchiver) archive files: Go parsing library

RAR is a archive format used by popular proprietary RAR archiver, created by Eugene Roshal. There are two major versions of format (v1.5-4.0 and RAR v5+).

File format essentially consists of a linear sequence of blocks. Each block has fixed header and custom body (that depends on block type), so it's possible to skip block even if one doesn't know how to process a certain block type.

Application

RAR archiver

File extension

rar

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.7

References

This page hosts a formal specification of RAR (Roshall ARchiver) archive files using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Go source code to parse RAR (Roshall ARchiver) archive files

rar.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import (
	"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
	"bytes"
)


/**
 * RAR is a archive format used by popular proprietary RAR archiver,
 * created by Eugene Roshal. There are two major versions of format
 * (v1.5-4.0 and RAR v5+).
 * 
 * File format essentially consists of a linear sequence of
 * blocks. Each block has fixed header and custom body (that depends on
 * block type), so it's possible to skip block even if one doesn't know
 * how to process a certain block type.
 * @see <a href="http://acritum.com/winrar/rar-format">Source</a>
 */

type Rar_BlockTypes int
const (
	Rar_BlockTypes__Marker Rar_BlockTypes = 114
	Rar_BlockTypes__ArchiveHeader Rar_BlockTypes = 115
	Rar_BlockTypes__FileHeader Rar_BlockTypes = 116
	Rar_BlockTypes__OldStyleCommentHeader Rar_BlockTypes = 117
	Rar_BlockTypes__OldStyleAuthenticityInfo76 Rar_BlockTypes = 118
	Rar_BlockTypes__OldStyleSubblock Rar_BlockTypes = 119
	Rar_BlockTypes__OldStyleRecoveryRecord Rar_BlockTypes = 120
	Rar_BlockTypes__OldStyleAuthenticityInfo79 Rar_BlockTypes = 121
	Rar_BlockTypes__Subblock Rar_BlockTypes = 122
	Rar_BlockTypes__Terminator Rar_BlockTypes = 123
)
var values_Rar_BlockTypes = map[Rar_BlockTypes]struct{}{114: {}, 115: {}, 116: {}, 117: {}, 118: {}, 119: {}, 120: {}, 121: {}, 122: {}, 123: {}}
func (v Rar_BlockTypes) isDefined() bool {
	_, ok := values_Rar_BlockTypes[v]
	return ok
}

type Rar_Methods int
const (
	Rar_Methods__Store Rar_Methods = 48
	Rar_Methods__Fastest Rar_Methods = 49
	Rar_Methods__Fast Rar_Methods = 50
	Rar_Methods__Normal Rar_Methods = 51
	Rar_Methods__Good Rar_Methods = 52
	Rar_Methods__Best Rar_Methods = 53
)
var values_Rar_Methods = map[Rar_Methods]struct{}{48: {}, 49: {}, 50: {}, 51: {}, 52: {}, 53: {}}
func (v Rar_Methods) isDefined() bool {
	_, ok := values_Rar_Methods[v]
	return ok
}

type Rar_Oses int
const (
	Rar_Oses__MsDos Rar_Oses = 0
	Rar_Oses__Os2 Rar_Oses = 1
	Rar_Oses__Windows Rar_Oses = 2
	Rar_Oses__Unix Rar_Oses = 3
	Rar_Oses__MacOs Rar_Oses = 4
	Rar_Oses__Beos Rar_Oses = 5
)
var values_Rar_Oses = map[Rar_Oses]struct{}{0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}}
func (v Rar_Oses) isDefined() bool {
	_, ok := values_Rar_Oses[v]
	return ok
}
type Rar struct {
	Magic *Rar_MagicSignature
	Blocks []kaitai.Struct
	_io *kaitai.Stream
	_root *Rar
	_parent kaitai.Struct
}
func NewRar() *Rar {
	return &Rar{
	}
}

func (this Rar) IO_() *kaitai.Stream {
	return this._io
}

func (this *Rar) Read(io *kaitai.Stream, parent kaitai.Struct, root *Rar) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp1 := NewRar_MagicSignature()
	err = tmp1.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.Magic = tmp1
	for i := 0;; i++ {
		tmp2, err := this._io.EOF()
		if err != nil {
			return err
		}
		if tmp2 {
			break
		}
		switch (this.Magic.Version) {
		case 0:
			tmp3 := NewRar_Block()
			err = tmp3.Read(this._io, this, this._root)
			if err != nil {
				return err
			}
			this.Blocks = append(this.Blocks, tmp3)
		case 1:
			tmp4 := NewRar_BlockV5()
			err = tmp4.Read(this._io, this, this._root)
			if err != nil {
				return err
			}
			this.Blocks = append(this.Blocks, tmp4)
		}
	}
	return err
}

/**
 * File format signature to validate that it is indeed a RAR archive
 */

/**
 * Sequence of blocks that constitute the RAR file
 */

/**
 * Basic block that RAR files consist of. There are several block
 * types (see `block_type`), which have different `body` and
 * `add_body`.
 */
type Rar_Block struct {
	Crc16 uint16
	BlockType Rar_BlockTypes
	Flags uint16
	BlockSize uint16
	AddSize uint32
	Body interface{}
	AddBody []byte
	_io *kaitai.Stream
	_root *Rar
	_parent *Rar
	_raw_Body []byte
	_f_bodySize bool
	bodySize int
	_f_hasAdd bool
	hasAdd bool
	_f_headerSize bool
	headerSize int8
}
func NewRar_Block() *Rar_Block {
	return &Rar_Block{
	}
}

func (this Rar_Block) IO_() *kaitai.Stream {
	return this._io
}

func (this *Rar_Block) Read(io *kaitai.Stream, parent *Rar, root *Rar) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp5, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.Crc16 = uint16(tmp5)
	tmp6, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.BlockType = Rar_BlockTypes(tmp6)
	tmp7, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.Flags = uint16(tmp7)
	tmp8, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.BlockSize = uint16(tmp8)
	tmp9, err := this.HasAdd()
	if err != nil {
		return err
	}
	if (tmp9) {
		tmp10, err := this._io.ReadU4le()
		if err != nil {
			return err
		}
		this.AddSize = uint32(tmp10)
	}
	switch (this.BlockType) {
	case Rar_BlockTypes__FileHeader:
		tmp11, err := this.BodySize()
		if err != nil {
			return err
		}
		tmp12, err := this._io.ReadBytes(int(tmp11))
		if err != nil {
			return err
		}
		tmp12 = tmp12
		this._raw_Body = tmp12
		_io__raw_Body := kaitai.NewStream(bytes.NewReader(this._raw_Body))
		tmp13 := NewRar_BlockFileHeader()
		err = tmp13.Read(_io__raw_Body, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp13
	default:
		tmp14, err := this.BodySize()
		if err != nil {
			return err
		}
		tmp15, err := this._io.ReadBytes(int(tmp14))
		if err != nil {
			return err
		}
		tmp15 = tmp15
		this._raw_Body = tmp15
	}
	tmp16, err := this.HasAdd()
	if err != nil {
		return err
	}
	if (tmp16) {
		tmp17, err := this._io.ReadBytes(int(this.AddSize))
		if err != nil {
			return err
		}
		tmp17 = tmp17
		this.AddBody = tmp17
	}
	return err
}
func (this *Rar_Block) BodySize() (v int, err error) {
	if (this._f_bodySize) {
		return this.bodySize, nil
	}
	this._f_bodySize = true
	tmp18, err := this.HeaderSize()
	if err != nil {
		return 0, err
	}
	this.bodySize = int(this.BlockSize - tmp18)
	return this.bodySize, nil
}

/**
 * True if block has additional content attached to it
 */
func (this *Rar_Block) HasAdd() (v bool, err error) {
	if (this._f_hasAdd) {
		return this.hasAdd, nil
	}
	this._f_hasAdd = true
	this.hasAdd = bool(this.Flags & 32768 != 0)
	return this.hasAdd, nil
}
func (this *Rar_Block) HeaderSize() (v int8, err error) {
	if (this._f_headerSize) {
		return this.headerSize, nil
	}
	this._f_headerSize = true
	var tmp19 int8;
	tmp20, err := this.HasAdd()
	if err != nil {
		return 0, err
	}
	if (tmp20) {
		tmp19 = 11
	} else {
		tmp19 = 7
	}
	this.headerSize = int8(tmp19)
	return this.headerSize, nil
}

/**
 * CRC16 of whole block or some part of it (depends on block type)
 */

/**
 * Size of block (header + body, but without additional content)
 */

/**
 * Size of additional content in this block
 */

/**
 * Additional content in this block
 */
type Rar_BlockFileHeader struct {
	LowUnpSize uint32
	HostOs Rar_Oses
	FileCrc32 uint32
	FileTime *DosDatetime
	RarVersion uint8
	Method Rar_Methods
	NameSize uint16
	Attr uint32
	HighPackSize uint32
	FileName []byte
	Salt uint64
	_io *kaitai.Stream
	_root *Rar
	_parent *Rar_Block
	_raw_FileTime []byte
}
func NewRar_BlockFileHeader() *Rar_BlockFileHeader {
	return &Rar_BlockFileHeader{
	}
}

func (this Rar_BlockFileHeader) IO_() *kaitai.Stream {
	return this._io
}

func (this *Rar_BlockFileHeader) Read(io *kaitai.Stream, parent *Rar_Block, root *Rar) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp21, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LowUnpSize = uint32(tmp21)
	tmp22, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.HostOs = Rar_Oses(tmp22)
	tmp23, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.FileCrc32 = uint32(tmp23)
	tmp24, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp24 = tmp24
	this._raw_FileTime = tmp24
	_io__raw_FileTime := kaitai.NewStream(bytes.NewReader(this._raw_FileTime))
	tmp25 := NewDosDatetime()
	err = tmp25.Read(_io__raw_FileTime, nil, nil)
	if err != nil {
		return err
	}
	this.FileTime = tmp25
	tmp26, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.RarVersion = tmp26
	tmp27, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Method = Rar_Methods(tmp27)
	tmp28, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.NameSize = uint16(tmp28)
	tmp29, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.Attr = uint32(tmp29)
	if (this._parent.Flags & 256 != 0) {
		tmp30, err := this._io.ReadU4le()
		if err != nil {
			return err
		}
		this.HighPackSize = uint32(tmp30)
	}
	tmp31, err := this._io.ReadBytes(int(this.NameSize))
	if err != nil {
		return err
	}
	tmp31 = tmp31
	this.FileName = tmp31
	if (this._parent.Flags & 1024 != 0) {
		tmp32, err := this._io.ReadU8le()
		if err != nil {
			return err
		}
		this.Salt = uint64(tmp32)
	}
	return err
}

/**
 * Uncompressed file size (lower 32 bits, if 64-bit header flag is present)
 */

/**
 * Operating system used for archiving
 */

/**
 * Date and time in standard MS DOS format
 */

/**
 * RAR version needed to extract file (Version number is encoded as 10 * Major version + minor version.)
 */

/**
 * Compression method
 */

/**
 * File name size
 */

/**
 * File attributes
 */

/**
 * Compressed file size, high 32 bits, only if 64-bit header flag is present
 */
type Rar_BlockV5 struct {
	_io *kaitai.Stream
	_root *Rar
	_parent *Rar
}
func NewRar_BlockV5() *Rar_BlockV5 {
	return &Rar_BlockV5{
	}
}

func (this Rar_BlockV5) IO_() *kaitai.Stream {
	return this._io
}

func (this *Rar_BlockV5) Read(io *kaitai.Stream, parent *Rar, root *Rar) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	return err
}

/**
 * RAR uses either 7-byte magic for RAR versions 1.5 to 4.0, and
 * 8-byte magic (and pretty different block format) for v5+. This
 * type would parse and validate both versions of signature. Note
 * that actually this signature is a valid RAR "block": in theory,
 * one can omit signature reading at all, and read this normally,
 * as a block, if exact RAR version is known (and thus it's
 * possible to choose correct block format).
 */
type Rar_MagicSignature struct {
	Magic1 []byte
	Version uint8
	Magic3 []byte
	_io *kaitai.Stream
	_root *Rar
	_parent *Rar
}
func NewRar_MagicSignature() *Rar_MagicSignature {
	return &Rar_MagicSignature{
	}
}

func (this Rar_MagicSignature) IO_() *kaitai.Stream {
	return this._io
}

func (this *Rar_MagicSignature) Read(io *kaitai.Stream, parent *Rar, root *Rar) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp33, err := this._io.ReadBytes(int(6))
	if err != nil {
		return err
	}
	tmp33 = tmp33
	this.Magic1 = tmp33
	if !(bytes.Equal(this.Magic1, []uint8{82, 97, 114, 33, 26, 7})) {
		return kaitai.NewValidationNotEqualError([]uint8{82, 97, 114, 33, 26, 7}, this.Magic1, this._io, "/types/magic_signature/seq/0")
	}
	tmp34, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Version = tmp34
	if (this.Version == 1) {
		tmp35, err := this._io.ReadBytes(int(1))
		if err != nil {
			return err
		}
		tmp35 = tmp35
		this.Magic3 = tmp35
		if !(bytes.Equal(this.Magic3, []uint8{0})) {
			return kaitai.NewValidationNotEqualError([]uint8{0}, this.Magic3, this._io, "/types/magic_signature/seq/2")
		}
	}
	return err
}

/**
 * Fixed part of file's magic signature that doesn't change with RAR version
 */

/**
 * Variable part of magic signature: 0 means old (RAR 1.5-4.0)
 * format, 1 means new (RAR 5+) format
 */

/**
 * New format (RAR 5+) magic contains extra byte
 */