1<?php
2/**
3 * Copyright 2008-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (LGPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
7 *
8 * @author   Michael Cochrane <mike@graftonhall.co.nz>
9 * @author   Michael Slusarz <slusarz@horde.org>
10 * @category Horde
11 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
12 * @package  Compress
13 */
14
15/**
16 * This class allows rar files to be read.
17 *
18 * @author    Michael Cochrane <mike@graftonhall.co.nz>
19 * @author    Michael Slusarz <slusarz@horde.org>
20 * @category  Horde
21 * @copyright 2008-2017 Horde LLC
22 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
23 * @package   Compress
24 */
25class Horde_Compress_Rar extends Horde_Compress_Base
26{
27    const BLOCK_START = "\x52\x61\x72\x21\x1a\x07\x00";
28
29    /**
30     */
31    public $canDecompress = true;
32
33    /**
34     * Rar compression methods
35     *
36     * @var array
37     */
38    protected $_methods = array(
39        0x30 => 'Store',
40        0x31 => 'Fastest',
41        0x32 => 'Fast',
42        0x33 => 'Normal',
43        0x34 => 'Good',
44        0x35 => 'Best'
45    );
46
47    /**
48     * @return array  Info on the compressed file:
49     * <pre>
50     * KEY: Position in RAR archive
51     * VALUES:
52     *   attr - File attributes
53     *   date - File modification time
54     *   csize - Compressed file size
55     *   method - Compression method
56     *   name - Filename
57     *   size - Original file size
58     * </pre>
59     *
60     * @throws Horde_Compress_Exception
61     */
62    public function decompress($data, array $params = array())
63    {
64        $blockStart = strpos($data, self::BLOCK_START);
65        if ($blockStart === false) {
66            throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid RAR data."));
67        }
68
69        $data_len = strlen($data);
70        $position = $blockStart + 7;
71        $return_array = array();
72
73        while ($position < $data_len) {
74            if ($position + 7 > $data_len) {
75                throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid RAR data."));
76            }
77            //$head_crc = substr($data, $position, 2);
78            $head_type = ord(substr($data, $position + 2, 1));
79            $head_flags = unpack('vFlags', substr($data, $position + 3, 2));
80            $head_flags = $head_flags['Flags'];
81            $head_size = unpack('vSize', substr($data, $position + 5, 2));
82            $head_size = $head_size['Size'];
83
84            $position += 7;
85            $head_size -= 7;
86
87            switch ($head_type) {
88            case 0x73:
89                /* Archive header */
90                $position += $head_size;
91                break;
92
93            case 0x74:
94                /* File Header */
95                $info = unpack(
96                    'VPacked/VUnpacked/COS/VCRC32/VTime/CVersion/CMethod/vLength/vAttrib',
97                    substr($data, $position)
98                );
99                $year = (($info['Time'] >> 25) & 0x7f) + 80;
100                $name = substr($data, $position + 25, $info['Length']);
101                if ($unicode = strpos($name, "\0")) {
102                    $name = substr($name, 0, $unicode);
103                }
104
105                $return_array[] = array(
106                    'name' => $name,
107                    'size' => $info['Unpacked'],
108                    'csize' => $info['Packed'],
109                    'date' => mktime(
110                        (($info['Time'] >> 11) & 0x1f),
111                        (($info['Time'] >> 5) & 0x3f),
112                        (($info['Time'] << 1) & 0x3e),
113                        (($info['Time'] >> 21) & 0x07),
114                        (($info['Time'] >> 16) & 0x1f),
115                        $year < 1900 ? $year + 1900 : $year
116                    ),
117                    'method' => $this->_methods[$info['Method']],
118                    'attr' => (($info['Attrib'] & 0x10) ? 'D' : '-') .
119                              (($info['Attrib'] & 0x20) ? 'A' : '-') .
120                              (($info['Attrib'] & 0x03) ? 'S' : '-') .
121                              (($info['Attrib'] & 0x02) ? 'H' : '-') .
122                              (($info['Attrib'] & 0x01) ? 'R' : '-')
123                );
124
125                $position += $head_size + $info['Packed'];
126                break;
127
128            default:
129                if ($head_size == -7) {
130                    /* We've already added 7 bytes above. If we remove those
131                     * same 7 bytes, we will enter an infinite loop. */
132                    throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid RAR data."));
133                }
134                $position += $head_size;
135                break;
136            }
137        }
138
139        return $return_array;
140    }
141}
142