| 1 |
<?php |
| 2 |
/** |
| 3 |
* PHPExcel |
| 4 |
* |
| 5 |
* Copyright (c) 2006 - 2014 PHPExcel |
| 6 |
* |
| 7 |
* This library is free software; you can redistribute it and/or |
| 8 |
* modify it under the terms of the GNU Lesser General Public |
| 9 |
* License as published by the Free Software Foundation; either |
| 10 |
* version 2.1 of the License, or (at your option) any later version. |
| 11 |
* |
| 12 |
* This library is distributed in the hope that it will be useful, |
| 13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 |
* Lesser General Public License for more details. |
| 16 |
* |
| 17 |
* You should have received a copy of the GNU Lesser General Public |
| 18 |
* License along with this library; if not, write to the Free Software |
| 19 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 20 |
* |
| 21 |
* @category PHPExcel |
| 22 |
* @package PHPExcel_Shared |
| 23 |
* @copyright Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel) |
| 24 |
* @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL |
| 25 |
* @version ##VERSION##, ##DATE## |
| 26 |
*/ |
| 27 |
|
| 28 |
defined('IDENTIFIER_OLE') || |
| 29 |
define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1)); |
| 30 |
|
| 31 |
class PHPExcel_Shared_OLERead { |
| 32 |
private $data = ''; |
| 33 |
|
| 34 |
// OLE identifier |
| 35 |
const IDENTIFIER_OLE = IDENTIFIER_OLE; |
| 36 |
|
| 37 |
// Size of a sector = 512 bytes |
| 38 |
const BIG_BLOCK_SIZE = 0x200; |
| 39 |
|
| 40 |
// Size of a short sector = 64 bytes |
| 41 |
const SMALL_BLOCK_SIZE = 0x40; |
| 42 |
|
| 43 |
// Size of a directory entry always = 128 bytes |
| 44 |
const PROPERTY_STORAGE_BLOCK_SIZE = 0x80; |
| 45 |
|
| 46 |
// Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams |
| 47 |
const SMALL_BLOCK_THRESHOLD = 0x1000; |
| 48 |
|
| 49 |
// header offsets |
| 50 |
const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c; |
| 51 |
const ROOT_START_BLOCK_POS = 0x30; |
| 52 |
const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c; |
| 53 |
const EXTENSION_BLOCK_POS = 0x44; |
| 54 |
const NUM_EXTENSION_BLOCK_POS = 0x48; |
| 55 |
const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c; |
| 56 |
|
| 57 |
// property storage offsets (directory offsets) |
| 58 |
const SIZE_OF_NAME_POS = 0x40; |
| 59 |
const TYPE_POS = 0x42; |
| 60 |
const START_BLOCK_POS = 0x74; |
| 61 |
const SIZE_POS = 0x78; |
| 62 |
|
| 63 |
|
| 64 |
|
| 65 |
public $wrkbook = null; |
| 66 |
public $summaryInformation = null; |
| 67 |
public $documentSummaryInformation = null; |
| 68 |
|
| 69 |
|
| 70 |
/** |
| 71 |
* Read the file |
| 72 |
* |
| 73 |
* @param $sFileName string Filename |
| 74 |
* @throws PHPExcel_Reader_Exception |
| 75 |
*/ |
| 76 |
public function read($sFileName) |
| 77 |
{ |
| 78 |
// Check if file exists and is readable |
| 79 |
if(!is_readable($sFileName)) { |
| 80 |
throw new PHPExcel_Reader_Exception("Could not open " . $sFileName . " for reading! File does not exist, or it is not readable."); |
| 81 |
} |
| 82 |
|
| 83 |
// Get the file identifier |
| 84 |
// Don't bother reading the whole file until we know it's a valid OLE file |
| 85 |
$this->data = file_get_contents($sFileName, FALSE, NULL, 0, 8); |
| 86 |
|
| 87 |
// Check OLE identifier |
| 88 |
if ($this->data != self::IDENTIFIER_OLE) { |
| 89 |
throw new PHPExcel_Reader_Exception('The filename ' . $sFileName . ' is not recognised as an OLE file'); |
| 90 |
} |
| 91 |
|
| 92 |
// Get the file data |
| 93 |
$this->data = file_get_contents($sFileName); |
| 94 |
|
| 95 |
// Total number of sectors used for the SAT |
| 96 |
$this->numBigBlockDepotBlocks = self::_GetInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); |
| 97 |
|
| 98 |
// SecID of the first sector of the directory stream |
| 99 |
$this->rootStartBlock = self::_GetInt4d($this->data, self::ROOT_START_BLOCK_POS); |
| 100 |
|
| 101 |
// SecID of the first sector of the SSAT (or -2 if not extant) |
| 102 |
$this->sbdStartBlock = self::_GetInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS); |
| 103 |
|
| 104 |
// SecID of the first sector of the MSAT (or -2 if no additional sectors are used) |
| 105 |
$this->extensionBlock = self::_GetInt4d($this->data, self::EXTENSION_BLOCK_POS); |
| 106 |
|
| 107 |
// Total number of sectors used by MSAT |
| 108 |
$this->numExtensionBlocks = self::_GetInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS); |
| 109 |
|
| 110 |
$bigBlockDepotBlocks = array(); |
| 111 |
$pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS; |
| 112 |
|
| 113 |
$bbdBlocks = $this->numBigBlockDepotBlocks; |
| 114 |
|
| 115 |
if ($this->numExtensionBlocks != 0) { |
| 116 |
$bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4; |
| 117 |
} |
| 118 |
|
| 119 |
for ($i = 0; $i < $bbdBlocks; ++$i) { |
| 120 |
$bigBlockDepotBlocks[$i] = self::_GetInt4d($this->data, $pos); |
| 121 |
$pos += 4; |
| 122 |
} |
| 123 |
|
| 124 |
for ($j = 0; $j < $this->numExtensionBlocks; ++$j) { |
| 125 |
$pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE; |
| 126 |
$blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1); |
| 127 |
|
| 128 |
for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) { |
| 129 |
$bigBlockDepotBlocks[$i] = self::_GetInt4d($this->data, $pos); |
| 130 |
$pos += 4; |
| 131 |
} |
| 132 |
|
| 133 |
$bbdBlocks += $blocksToRead; |
| 134 |
if ($bbdBlocks < $this->numBigBlockDepotBlocks) { |
| 135 |
$this->extensionBlock = self::_GetInt4d($this->data, $pos); |
| 136 |
} |
| 137 |
} |
| 138 |
|
| 139 |
$pos = 0; |
| 140 |
$this->bigBlockChain = ''; |
| 141 |
$bbs = self::BIG_BLOCK_SIZE / 4; |
| 142 |
for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) { |
| 143 |
$pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE; |
| 144 |
|
| 145 |
$this->bigBlockChain .= substr($this->data, $pos, 4*$bbs); |
| 146 |
$pos += 4*$bbs; |
| 147 |
} |
| 148 |
|
| 149 |
$pos = 0; |
| 150 |
$sbdBlock = $this->sbdStartBlock; |
| 151 |
$this->smallBlockChain = ''; |
| 152 |
while ($sbdBlock != -2) { |
| 153 |
$pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE; |
| 154 |
|
| 155 |
$this->smallBlockChain .= substr($this->data, $pos, 4*$bbs); |
| 156 |
$pos += 4*$bbs; |
| 157 |
|
| 158 |
$sbdBlock = self::_GetInt4d($this->bigBlockChain, $sbdBlock*4); |
| 159 |
} |
| 160 |
|
| 161 |
// read the directory stream |
| 162 |
$block = $this->rootStartBlock; |
| 163 |
$this->entry = $this->_readData($block); |
| 164 |
|
| 165 |
$this->_readPropertySets(); |
| 166 |
} |
| 167 |
|
| 168 |
/** |
| 169 |
* Extract binary stream data |
| 170 |
* |
| 171 |
* @return string |
| 172 |
*/ |
| 173 |
public function getStream($stream) |
| 174 |
{ |
| 175 |
if ($stream === NULL) { |
| 176 |
return null; |
| 177 |
} |
| 178 |
|
| 179 |
$streamData = ''; |
| 180 |
|
| 181 |
if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) { |
| 182 |
$rootdata = $this->_readData($this->props[$this->rootentry]['startBlock']); |
| 183 |
|
| 184 |
$block = $this->props[$stream]['startBlock']; |
| 185 |
|
| 186 |
while ($block != -2) { |
| 187 |
$pos = $block * self::SMALL_BLOCK_SIZE; |
| 188 |
$streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE); |
| 189 |
|
| 190 |
$block = self::_GetInt4d($this->smallBlockChain, $block*4); |
| 191 |
} |
| 192 |
|
| 193 |
return $streamData; |
| 194 |
} else { |
| 195 |
$numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE; |
| 196 |
if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) { |
| 197 |
++$numBlocks; |
| 198 |
} |
| 199 |
|
| 200 |
if ($numBlocks == 0) return ''; |
| 201 |
|
| 202 |
$block = $this->props[$stream]['startBlock']; |
| 203 |
|
| 204 |
while ($block != -2) { |
| 205 |
$pos = ($block + 1) * self::BIG_BLOCK_SIZE; |
| 206 |
$streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); |
| 207 |
$block = self::_GetInt4d($this->bigBlockChain, $block*4); |
| 208 |
} |
| 209 |
|
| 210 |
return $streamData; |
| 211 |
} |
| 212 |
} |
| 213 |
|
| 214 |
/** |
| 215 |
* Read a standard stream (by joining sectors using information from SAT) |
| 216 |
* |
| 217 |
* @param int $bl Sector ID where the stream starts |
| 218 |
* @return string Data for standard stream |
| 219 |
*/ |
| 220 |
private function _readData($bl) |
| 221 |
{ |
| 222 |
$block = $bl; |
| 223 |
$data = ''; |
| 224 |
|
| 225 |
while ($block != -2) { |
| 226 |
$pos = ($block + 1) * self::BIG_BLOCK_SIZE; |
| 227 |
$data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); |
| 228 |
$block = self::_GetInt4d($this->bigBlockChain, $block*4); |
| 229 |
} |
| 230 |
return $data; |
| 231 |
} |
| 232 |
|
| 233 |
/** |
| 234 |
* Read entries in the directory stream. |
| 235 |
*/ |
| 236 |
private function _readPropertySets() { |
| 237 |
$offset = 0; |
| 238 |
|
| 239 |
// loop through entires, each entry is 128 bytes |
| 240 |
$entryLen = strlen($this->entry); |
| 241 |
while ($offset < $entryLen) { |
| 242 |
// entry data (128 bytes) |
| 243 |
$d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE); |
| 244 |
|
| 245 |
// size in bytes of name |
| 246 |
$nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS+1]) << 8); |
| 247 |
|
| 248 |
// type of entry |
| 249 |
$type = ord($d[self::TYPE_POS]); |
| 250 |
|
| 251 |
// sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook) |
| 252 |
// sectorID of first sector of the short-stream container stream, if this entry is root entry |
| 253 |
$startBlock = self::_GetInt4d($d, self::START_BLOCK_POS); |
| 254 |
|
| 255 |
$size = self::_GetInt4d($d, self::SIZE_POS); |
| 256 |
|
| 257 |
$name = str_replace("\x00", "", substr($d,0,$nameSize)); |
| 258 |
|
| 259 |
|
| 260 |
$this->props[] = array ( |
| 261 |
'name' => $name, |
| 262 |
'type' => $type, |
| 263 |
'startBlock' => $startBlock, |
| 264 |
'size' => $size); |
| 265 |
|
| 266 |
// tmp helper to simplify checks |
| 267 |
$upName = strtoupper($name); |
| 268 |
|
| 269 |
// Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook) |
| 270 |
if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) { |
| 271 |
$this->wrkbook = count($this->props) - 1; |
| 272 |
} |
| 273 |
else if ( $upName === 'ROOT ENTRY' || $upName === 'R') { |
| 274 |
// Root entry |
| 275 |
$this->rootentry = count($this->props) - 1; |
| 276 |
} |
| 277 |
|
| 278 |
// Summary information |
| 279 |
if ($name == chr(5) . 'SummaryInformation') { |
| 280 |
// echo 'Summary Information<br />'; |
| 281 |
$this->summaryInformation = count($this->props) - 1; |
| 282 |
} |
| 283 |
|
| 284 |
// Additional Document Summary information |
| 285 |
if ($name == chr(5) . 'DocumentSummaryInformation') { |
| 286 |
// echo 'Document Summary Information<br />'; |
| 287 |
$this->documentSummaryInformation = count($this->props) - 1; |
| 288 |
} |
| 289 |
|
| 290 |
$offset += self::PROPERTY_STORAGE_BLOCK_SIZE; |
| 291 |
} |
| 292 |
|
| 293 |
} |
| 294 |
|
| 295 |
/** |
| 296 |
* Read 4 bytes of data at specified position |
| 297 |
* |
| 298 |
* @param string $data |
| 299 |
* @param int $pos |
| 300 |
* @return int |
| 301 |
*/ |
| 302 |
private static function _GetInt4d($data, $pos) |
| 303 |
{ |
| 304 |
// FIX: represent numbers correctly on 64-bit system |
| 305 |
// http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 |
| 306 |
// Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems |
| 307 |
$_or_24 = ord($data[$pos + 3]); |
| 308 |
if ($_or_24 >= 128) { |
| 309 |
// negative number |
| 310 |
$_ord_24 = -abs((256 - $_or_24) << 24); |
| 311 |
} else { |
| 312 |
$_ord_24 = ($_or_24 & 127) << 24; |
| 313 |
} |
| 314 |
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24; |
| 315 |
} |
| 316 |
|
| 317 |
} |
| 318 |
|