ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
OLERead.php
Go to the documentation of this file.
1 <?php
2 
4 
5 use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
6 
7 class OLERead
8 {
9  private $data = '';
10 
11  // Size of a sector = 512 bytes
12  const BIG_BLOCK_SIZE = 0x200;
13 
14  // Size of a short sector = 64 bytes
15  const SMALL_BLOCK_SIZE = 0x40;
16 
17  // Size of a directory entry always = 128 bytes
19 
20  // Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
21  const SMALL_BLOCK_THRESHOLD = 0x1000;
22 
23  // header offsets
25  const ROOT_START_BLOCK_POS = 0x30;
27  const EXTENSION_BLOCK_POS = 0x44;
30 
31  // property storage offsets (directory offsets)
32  const SIZE_OF_NAME_POS = 0x40;
33  const TYPE_POS = 0x42;
34  const START_BLOCK_POS = 0x74;
35  const SIZE_POS = 0x78;
36 
37  public $wrkbook;
38 
40 
42 
47 
51  private $rootStartBlock;
52 
56  private $sbdStartBlock;
57 
61  private $extensionBlock;
62 
67 
71  private $bigBlockChain;
72 
77 
81  private $entry;
82 
86  private $rootentry;
87 
91  private $props = [];
92 
96  public function read(string $pFilename): void
97  {
98  File::assertFile($pFilename);
99 
100  // Get the file identifier
101  // Don't bother reading the whole file until we know it's a valid OLE file
102  $this->data = file_get_contents($pFilename, false, null, 0, 8);
103 
104  // Check OLE identifier
105  $identifierOle = pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1);
106  if ($this->data != $identifierOle) {
107  throw new ReaderException('The filename ' . $pFilename . ' is not recognised as an OLE file');
108  }
109 
110  // Get the file data
111  $this->data = file_get_contents($pFilename);
112 
113  // Total number of sectors used for the SAT
114  $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
115 
116  // SecID of the first sector of the directory stream
117  $this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
118 
119  // SecID of the first sector of the SSAT (or -2 if not extant)
120  $this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
121 
122  // SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
123  $this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
124 
125  // Total number of sectors used by MSAT
126  $this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
127 
128  $bigBlockDepotBlocks = [];
129  $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
130 
131  $bbdBlocks = $this->numBigBlockDepotBlocks;
132 
133  if ($this->numExtensionBlocks != 0) {
134  $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
135  }
136 
137  for ($i = 0; $i < $bbdBlocks; ++$i) {
138  $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
139  $pos += 4;
140  }
141 
142  for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
143  $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
144  $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
145 
146  for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
147  $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
148  $pos += 4;
149  }
150 
151  $bbdBlocks += $blocksToRead;
152  if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
153  $this->extensionBlock = self::getInt4d($this->data, $pos);
154  }
155  }
156 
157  $pos = 0;
158  $this->bigBlockChain = '';
159  $bbs = self::BIG_BLOCK_SIZE / 4;
160  for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
161  $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
162 
163  $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs);
164  $pos += 4 * $bbs;
165  }
166 
167  $pos = 0;
168  $sbdBlock = $this->sbdStartBlock;
169  $this->smallBlockChain = '';
170  while ($sbdBlock != -2) {
171  $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
172 
173  $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
174  $pos += 4 * $bbs;
175 
176  $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);
177  }
178 
179  // read the directory stream
180  $block = $this->rootStartBlock;
181  $this->entry = $this->readData($block);
182 
183  $this->readPropertySets();
184  }
185 
193  public function getStream($stream)
194  {
195  if ($stream === null) {
196  return null;
197  }
198 
199  $streamData = '';
200 
201  if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
202  $rootdata = $this->readData($this->props[$this->rootentry]['startBlock']);
203 
204  $block = $this->props[$stream]['startBlock'];
205 
206  while ($block != -2) {
207  $pos = $block * self::SMALL_BLOCK_SIZE;
208  $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
209 
210  $block = self::getInt4d($this->smallBlockChain, $block * 4);
211  }
212 
213  return $streamData;
214  }
215  $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
216  if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
217  ++$numBlocks;
218  }
219 
220  if ($numBlocks == 0) {
221  return '';
222  }
223 
224  $block = $this->props[$stream]['startBlock'];
225 
226  while ($block != -2) {
227  $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
228  $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
229  $block = self::getInt4d($this->bigBlockChain, $block * 4);
230  }
231 
232  return $streamData;
233  }
234 
242  private function readData($bl)
243  {
244  $block = $bl;
245  $data = '';
246 
247  while ($block != -2) {
248  $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
249  $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
250  $block = self::getInt4d($this->bigBlockChain, $block * 4);
251  }
252 
253  return $data;
254  }
255 
259  private function readPropertySets(): void
260  {
261  $offset = 0;
262 
263  // loop through entires, each entry is 128 bytes
264  $entryLen = strlen($this->entry);
265  while ($offset < $entryLen) {
266  // entry data (128 bytes)
267  $d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
268 
269  // size in bytes of name
270  $nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8);
271 
272  // type of entry
273  $type = ord($d[self::TYPE_POS]);
274 
275  // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
276  // sectorID of first sector of the short-stream container stream, if this entry is root entry
277  $startBlock = self::getInt4d($d, self::START_BLOCK_POS);
278 
279  $size = self::getInt4d($d, self::SIZE_POS);
280 
281  $name = str_replace("\x00", '', substr($d, 0, $nameSize));
282 
283  $this->props[] = [
284  'name' => $name,
285  'type' => $type,
286  'startBlock' => $startBlock,
287  'size' => $size,
288  ];
289 
290  // tmp helper to simplify checks
291  $upName = strtoupper($name);
292 
293  // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
294  if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) {
295  $this->wrkbook = count($this->props) - 1;
296  } elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
297  // Root entry
298  $this->rootentry = count($this->props) - 1;
299  }
300 
301  // Summary information
302  if ($name == chr(5) . 'SummaryInformation') {
303  $this->summaryInformation = count($this->props) - 1;
304  }
305 
306  // Additional Document Summary information
307  if ($name == chr(5) . 'DocumentSummaryInformation') {
308  $this->documentSummaryInformation = count($this->props) - 1;
309  }
310 
311  $offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
312  }
313  }
314 
323  private static function getInt4d($data, $pos)
324  {
325  if ($pos < 0) {
326  // Invalid position
327  throw new ReaderException('Parameter pos=' . $pos . ' is invalid.');
328  }
329 
330  $len = strlen($data);
331  if ($len < $pos + 4) {
332  $data .= str_repeat("\0", $pos + 4 - $len);
333  }
334 
335  // FIX: represent numbers correctly on 64-bit system
336  // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
337  // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
338  $_or_24 = ord($data[$pos + 3]);
339  if ($_or_24 >= 128) {
340  // negative number
341  $_ord_24 = -abs((256 - $_or_24) << 24);
342  } else {
343  $_ord_24 = ($_or_24 & 127) << 24;
344  }
345 
346  return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
347  }
348 }
$size
Definition: RandomTest.php:84
readData($bl)
Read a standard stream (by joining sectors using information from SAT).
Definition: OLERead.php:242
$type
$stream
PHP stream implementation.
read(string $pFilename)
Read the file.
Definition: OLERead.php:96
readPropertySets()
Read entries in the directory stream.
Definition: OLERead.php:259
$this data['403_header']
$i
Definition: disco.tpl.php:19
static assertFile($filename)
Assert that given path is an existing file and is readable, otherwise throw exception.
Definition: File.php:143
static getInt4d($data, $pos)
Read 4 bytes of data at specified position.
Definition: OLERead.php:323
for($i=6; $i< 13; $i++) for($i=1; $i< 13; $i++) $d
Definition: date.php:296
getStream($stream)
Extract binary stream data.
Definition: OLERead.php:193