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