XAR (eXtensible ARchive): PHP parsing library

From Wikipedia:

"XAR (short for eXtensible ARchive format) is an open source file archiver and the archiver's file format. It was created within the OpenDarwin project and is used in macOS X 10.5 and up for software installation routines, as well as browser extensions in Safari 5.0 and up."

File extension

["xar", "pkg", "xip"]

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.9

References

This page hosts a formal specification of XAR (eXtensible ARchive) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

PHP source code to parse XAR (eXtensible ARchive)

Xar.php

<?php
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

/**
 * From [Wikipedia](https://en.wikipedia.org/wiki/Xar_(archiver)):
 * 
 * "XAR (short for eXtensible ARchive format) is an open source file archiver
 * and the archiver's file format. It was created within the OpenDarwin project
 * and is used in macOS X 10.5 and up for software installation routines, as
 * well as browser extensions in Safari 5.0 and up."
 */

namespace {
    class Xar extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Kaitai\Struct\Struct $_parent = null, \Xar $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_headerPrefix = new \Xar\FileHeaderPrefix($this->_io, $this, $this->_root);
            $this->_m__raw_header = $this->_io->readBytes(($this->headerPrefix()->lenHeader() - 6));
            $_io__raw_header = new \Kaitai\Struct\Stream($this->_m__raw_header);
            $this->_m_header = new \Xar\FileHeader($_io__raw_header, $this, $this->_root);
            $this->_m__raw__raw_toc = $this->_io->readBytes($this->header()->lenTocCompressed());
            $this->_m__raw_toc = \Kaitai\Struct\Stream::processZlib($this->_m__raw__raw_toc);
            $_io__raw_toc = new \Kaitai\Struct\Stream($this->_m__raw_toc);
            $this->_m_toc = new \Xar\TocType($_io__raw_toc, $this, $this->_root);
        }
        protected $_m_checksumAlgorithmOther;
        public function checksumAlgorithmOther() {
            if ($this->_m_checksumAlgorithmOther !== null)
                return $this->_m_checksumAlgorithmOther;
            $this->_m_checksumAlgorithmOther = 3;
            return $this->_m_checksumAlgorithmOther;
        }
        protected $_m_headerPrefix;
        protected $_m_header;
        protected $_m_toc;
        protected $_m__raw_header;
        protected $_m__raw_toc;
        protected $_m__raw__raw_toc;

        /**
         * internal; access `_root.header` instead
         */
        public function headerPrefix() { return $this->_m_headerPrefix; }
        public function header() { return $this->_m_header; }

        /**
         * zlib compressed XML further describing the content of the archive
         */
        public function toc() { return $this->_m_toc; }
        public function _raw_header() { return $this->_m__raw_header; }
        public function _raw_toc() { return $this->_m__raw_toc; }
        public function _raw__raw_toc() { return $this->_m__raw__raw_toc; }
    }
}

namespace Xar {
    class FileHeaderPrefix extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Xar $_parent = null, \Xar $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_magic = $this->_io->readBytes(4);
            if (!($this->magic() == "\x78\x61\x72\x21")) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError("\x78\x61\x72\x21", $this->magic(), $this->_io(), "/types/file_header_prefix/seq/0");
            }
            $this->_m_lenHeader = $this->_io->readU2be();
        }
        protected $_m_magic;
        protected $_m_lenHeader;
        public function magic() { return $this->_m_magic; }

        /**
         * internal; access `_root.header.len_header` instead
         */
        public function lenHeader() { return $this->_m_lenHeader; }
    }
}

namespace Xar {
    class FileHeader extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Xar $_parent = null, \Xar $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_version = $this->_io->readU2be();
            if (!($this->version() == 1)) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError(1, $this->version(), $this->_io(), "/types/file_header/seq/0");
            }
            $this->_m_lenTocCompressed = $this->_io->readU8be();
            $this->_m_tocLengthUncompressed = $this->_io->readU8be();
            $this->_m_checksumAlgorithmInt = $this->_io->readU4be();
            if ($this->hasChecksumAlgName()) {
                $this->_m_checksumAlgName = \Kaitai\Struct\Stream::bytesToStr(\Kaitai\Struct\Stream::bytesTerminate($this->_io->readBytesFull(), 0, false), "UTF-8");
                $_ = $this->checksumAlgName();
                if (!( (($_ != "") && ($_ != "none")) )) {
                    throw new \Kaitai\Struct\Error\ValidationExprError($this->checksumAlgName(), $this->_io(), "/types/file_header/seq/4");
                }
            }
        }
        protected $_m_checksumAlgorithmName;

        /**
         * If it is not
         * 
         * * `""` (empty string), indicating an unknown integer value (access
         *   `checksum_algorithm_int` for debugging purposes to find out
         *   what that value is), or
         * * `"none"`, indicating that the TOC checksum is not provided (in that
         *   case, the `<checksum>` property or its `style` attribute should be
         *   missing, or the `style` attribute must be set to `"none"`),
         * 
         * it must exactly match the `style` attribute value of the
         * `<checksum>` property in the root node `<toc>`. See
         * <https://github.com/mackyle/xar/blob/66d451d/xar/lib/archive.c#L345-L371>
         * for reference.
         * 
         * The `xar` (eXtensible ARchiver) program [uses OpenSSL's function
         * `EVP_get_digestbyname`](
         *   https://github.com/mackyle/xar/blob/66d451d/xar/lib/archive.c#L328
         * ) to verify this value (if it's not `""` or `"none"`, of course).
         * So it's reasonable to assume that this can only have one of the values
         * that OpenSSL recognizes.
         */
        public function checksumAlgorithmName() {
            if ($this->_m_checksumAlgorithmName !== null)
                return $this->_m_checksumAlgorithmName;
            $this->_m_checksumAlgorithmName = ($this->hasChecksumAlgName() ? $this->checksumAlgName() : ($this->checksumAlgorithmInt() == \Xar\ChecksumAlgorithmsApple::NONE ? "none" : ($this->checksumAlgorithmInt() == \Xar\ChecksumAlgorithmsApple::SHA1 ? "sha1" : ($this->checksumAlgorithmInt() == \Xar\ChecksumAlgorithmsApple::MD5 ? "md5" : ($this->checksumAlgorithmInt() == \Xar\ChecksumAlgorithmsApple::SHA256 ? "sha256" : ($this->checksumAlgorithmInt() == \Xar\ChecksumAlgorithmsApple::SHA512 ? "sha512" : ""))))));
            return $this->_m_checksumAlgorithmName;
        }
        protected $_m_hasChecksumAlgName;
        public function hasChecksumAlgName() {
            if ($this->_m_hasChecksumAlgName !== null)
                return $this->_m_hasChecksumAlgName;
            $this->_m_hasChecksumAlgName =  (($this->checksumAlgorithmInt() == $this->_root()->checksumAlgorithmOther()) && ($this->lenHeader() >= 32) && (\Kaitai\Struct\Stream::mod($this->lenHeader(), 4) == 0)) ;
            return $this->_m_hasChecksumAlgName;
        }
        protected $_m_lenHeader;
        public function lenHeader() {
            if ($this->_m_lenHeader !== null)
                return $this->_m_lenHeader;
            $this->_m_lenHeader = $this->_root()->headerPrefix()->lenHeader();
            return $this->_m_lenHeader;
        }
        protected $_m_version;
        protected $_m_lenTocCompressed;
        protected $_m_tocLengthUncompressed;
        protected $_m_checksumAlgorithmInt;
        protected $_m_checksumAlgName;
        public function version() { return $this->_m_version; }
        public function lenTocCompressed() { return $this->_m_lenTocCompressed; }
        public function tocLengthUncompressed() { return $this->_m_tocLengthUncompressed; }

        /**
         * internal; access `checksum_algorithm_name` instead
         */
        public function checksumAlgorithmInt() { return $this->_m_checksumAlgorithmInt; }

        /**
         * internal; access `checksum_algorithm_name` instead
         */
        public function checksumAlgName() { return $this->_m_checksumAlgName; }
    }
}

namespace Xar {
    class TocType extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Xar $_parent = null, \Xar $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_xmlString = \Kaitai\Struct\Stream::bytesToStr($this->_io->readBytesFull(), "UTF-8");
        }
        protected $_m_xmlString;
        public function xmlString() { return $this->_m_xmlString; }
    }
}

namespace Xar {
    class ChecksumAlgorithmsApple {
        const NONE = 0;
        const SHA1 = 1;
        const MD5 = 2;
        const SHA256 = 3;
        const SHA512 = 4;
    }
}