1<?php 2/** 3 * Copyright 2003-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 * This class is based on code by: 9 * Antony Raijekov <dev@strategma.bg> 10 * http://uruds.gateway.bg/zeos/ 11 * 12 * @author Jan Schneider <jan@horde.org> 13 * @category Horde 14 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 15 * @package Compress 16 */ 17 18/** 19 * This class allows dbx files (e.g. from Outlook Express) to be read. 20 * 21 * @author Jan Schneider <jan@horde.org> 22 * @category Horde 23 * @copyright 2003-2017 Horde LLC 24 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 25 * @package Compress 26 */ 27class Horde_Compress_Dbx extends Horde_Compress_Base 28{ 29 /** 30 */ 31 public $canDecompress = true; 32 33 /** 34 * TODO 35 * 36 * @var array 37 */ 38 protected $_flagArray = array( 39 0x1 => 'MsgFlags', 40 0x2 => 'Sent', 41 0x4 => 'position', 42 0x7 => 'MessageID', 43 0x8 => 'Subject', 44 0x9 => 'From_reply', 45 0xA => 'References', 46 0xB => 'Newsgroup', 47 0xD => 'From', 48 0xE => 'Reply_To', 49 0x12 => 'Received', 50 0x13 => 'Receipt', 51 0x1A => 'Account', 52 0x1B => 'AccountID', 53 0x80 => 'Msg', 54 0x81 => 'MsgFlags', 55 0x84 => 'position', 56 0x91 => 'size', 57 ); 58 59 /** 60 * TODO 61 * 62 * @var array 63 */ 64 protected $_mails = array(); 65 66 /** 67 * TODO 68 * 69 * @var array 70 */ 71 protected $_tmp = array(); 72 73 /** 74 * @return array List of messages. 75 */ 76 public function decompress($data, array $params = array()) 77 { 78 $this->_mails = $this->_tmp = array(); 79 80 $position = 0xC4; 81 $header_info = unpack('Lposition/LDataLength/nHeaderLength/nFlagCount', substr($data, $position, 12)); 82 $position += 12; 83 84 // Go to the first table offest and process it. 85 if ($header_info['position'] > 0) { 86 $position = 0x30; 87 $buf = unpack('Lposition', substr($data, $position, 4)); 88 $position = $buf['position']; 89 $this->_readIndex($data, $position); 90 } 91 92 return $this->_mails; 93 } 94 95 /** 96 * Returns a null-terminated string from the specified data. 97 * 98 * @param string $buf TODO 99 * @param integer $pos TODO 100 * 101 * @return string TODO 102 */ 103 protected function _readString($buf, $pos) 104 { 105 return ($len = strpos(substr($buf, $pos), chr(0))) 106 ? substr($buf, $pos, $len) 107 : ''; 108 } 109 110 /** 111 * TODO 112 * 113 * @param string $data TODO 114 * @param integer $position TODO 115 * 116 * @return string TODO 117 * @throws Horde_Compress_Exception 118 */ 119 protected function _readMessage($data, $position) 120 { 121 $msg = ''; 122 $part = 0; 123 124 if ($position > 0) { 125 $IndexItemsCount = array_pop(unpack('S', substr($data, 0xC4, 4))); 126 if ($IndexItemsCount > 0) { 127 while ($position < strlen($data)) { 128 $part++; 129 $s = substr($data, $position, 528); 130 if (strlen($s) == 0) { 131 break; 132 } 133 if (version_compare(PHP_VERSION, '5.5', '>=')) { 134 $msg_item = unpack('LFilePos/LUnknown/LItemSize/LNextItem/Z512Content', $s); 135 } else { 136 $msg_item = unpack('LFilePos/LUnknown/LItemSize/LNextItem/a512Content', $s); 137 } 138 if ($msg_item['FilePos'] != $position) { 139 throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid file format")); 140 } 141 $position += 528; 142 $msg .= substr($msg_item['Content'], 0, $msg_item['ItemSize']); 143 $position = $msg_item['NextItem']; 144 if ($position == 0) { 145 break; 146 } 147 } 148 } 149 } 150 151 return $msg; 152 } 153 154 /** 155 * TODO 156 * 157 * @param string $data TODO 158 * @param integer $position TODO 159 * 160 * @return array TODO 161 * @throws Horde_Compress_Exception 162 */ 163 protected function _readMessageInfo($data, $position) 164 { 165 $message_info = array(); 166 $msg_header = unpack('Lposition/LDataLength/SHeaderLength/SFlagCount', substr($data, $position, 12)); 167 if ($msg_header['position'] != $position) { 168 throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid file format")); 169 } 170 $position += 12; 171 $message_info['HeaderPosition'] = $msg_header['position']; 172 $flags = $msg_header['FlagCount'] & 0xFF; 173 $DataSize = $msg_header['DataLength'] - $flags * 4; 174 $size = 4 * $flags; 175 $FlagsBuffer = substr($data, $position, $size); 176 $position += $size; 177 $size = $DataSize; 178 $DataBuffer = substr($data, $position, $size); 179 $position += $size; 180 $message_info = array(); 181 182 /* Process flags */ 183 for ($i = 0; $i < $flags; ++$i) { 184 $pos = 0; 185 $f = array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))); 186 187 $mask = $f & 0xFF; 188 switch ($mask) { 189 case 0x1: 190 $pos = $pos + ($f >> 8); 191 $message_info['MsgFlags'] = array_pop(unpack('C', substr($DataBuffer, $pos++, 1))); 192 $message_info['MsgFlags'] += array_pop(unpack('C', substr($DataBuffer, $pos++, 1))) * 256; 193 $message_info['MsgFlags'] += array_pop(unpack('C', substr($DataBuffer, $pos, 1))) * 65536; 194 break; 195 196 case 0x2: 197 case 0x4: 198 $pos += array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; 199 $message_info[$this->_flagArray[$mask]] = array_pop(unpack('L', substr($DataBuffer, $pos, 4))); 200 break; 201 202 case 0x7: 203 case 0x8: 204 case 0x9: 205 case 0xA: 206 case 0xB: 207 case 0xD: 208 case 0xE: 209 case 0x13: 210 case 0x1A: 211 $pos += array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; 212 $message_info[$this->_flagArray[$mask]] = $this->_readString($DataBuffer, $pos); 213 break; 214 215 case 0x12: 216 $pos += array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; 217 $message_info['Received'] = array_pop(unpack('L', substr($DataBuffer, $pos, 4))); 218 break; 219 220 case 0x1B: 221 $pos += array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; 222 $message_info['AccountID'] = intval($this->_readString($DataBuffer, $pos)); 223 break; 224 225 case 0x80: 226 case 0x81: 227 case 0x84: 228 case 0x91: 229 $message_info[$this->_flagArray[$mask]] = array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; 230 break; 231 } 232 } 233 234 return $message_info; 235 } 236 237 /** 238 * TODO 239 * 240 * @param string $data TODO 241 * @param integer $position TODO 242 * 243 * @throws Horde_Compress_Exception 244 */ 245 protected function _readIndex($data, $position) 246 { 247 $index_header = unpack('LFilePos/LUnknown1/LPrevIndex/LNextIndex/LCount/LUnknown', substr($data, $position, 24)); 248 if ($index_header['FilePos'] != $position) { 249 throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid file format")); 250 } 251 252 // Push it into list of processed items. 253 $this->_tmp[$position] = true; 254 if (($index_header['NextIndex'] > 0) && 255 empty($this->_tmp[$index_header['NextIndex']])) { 256 $this->_readIndex($data, $index_header['NextIndex']); 257 } 258 if (($index_header['PrevIndex'] > 0) && 259 empty($this->_tmp[$index_header['PrevIndex']])) { 260 $this->_readIndex($data, $index_header['PrevIndex']); 261 } 262 $position += 24; 263 $icount = $index_header['Count'] >> 8; 264 if ($icount > 0) { 265 $buf = substr($data, $position, 12 * $icount); 266 for ($i = 0; $i < $icount; $i++) { 267 $hdr_buf = substr($buf, $i * 12, 12); 268 $IndexItem = unpack('LHeaderPos/LChildIndex/LUnknown', $hdr_buf); 269 if ($IndexItem['HeaderPos'] > 0) { 270 $mail['info'] = $this->_readMessageInfo($data, $IndexItem['HeaderPos']); 271 $mail['content'] = $this->_readMessage($data, $mail['info']['position']); 272 $this->_mails[] = $mail; 273 } 274 if (($IndexItem['ChildIndex'] > 0) && 275 empty($this->_tmp[$IndexItem['ChildIndex']])) { 276 $this->_readIndex($data, $IndexItem['ChildIndex']); 277 } 278 } 279 } 280 } 281 282} 283