ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
All Data Structures Namespaces Files Functions Variables Typedefs Modules Pages
OLE.php
Go to the documentation of this file.
1 <?php
2 
4 
5 // vim: set expandtab tabstop=4 shiftwidth=4:
6 // +----------------------------------------------------------------------+
7 // | PHP Version 4 |
8 // +----------------------------------------------------------------------+
9 // | Copyright (c) 1997-2002 The PHP Group |
10 // +----------------------------------------------------------------------+
11 // | This source file is subject to version 2.02 of the PHP license, |
12 // | that is bundled with this package in the file LICENSE, and is |
13 // | available at through the world-wide-web at |
14 // | http://www.php.net/license/2_02.txt. |
15 // | If you did not receive a copy of the PHP license and are unable to |
16 // | obtain it through the world-wide-web, please send a note to |
17 // | license@php.net so we can mail you a copy immediately. |
18 // +----------------------------------------------------------------------+
19 // | Author: Xavier Noguer <xnoguer@php.net> |
20 // | Based on OLE::Storage_Lite by Kawai, Takanori |
21 // +----------------------------------------------------------------------+
22 //
23 
25 use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
28 
29 /*
30  * Array for storing OLE instances that are accessed from
31  * OLE_ChainedBlockStream::stream_open().
32  *
33  * @var array
34  */
35 $GLOBALS['_OLE_INSTANCES'] = [];
36 
43 class OLE
44 {
45  const OLE_PPS_TYPE_ROOT = 5;
46  const OLE_PPS_TYPE_DIR = 1;
47  const OLE_PPS_TYPE_FILE = 2;
48  const OLE_DATA_SIZE_SMALL = 0x1000;
49  const OLE_LONG_INT_SIZE = 4;
50  const OLE_PPS_SIZE = 0x80;
51 
57  public $_file_handle;
58 
64  public $_list = [];
65 
71  public $root;
72 
78  public $bbat;
79 
85  public $sbat;
86 
92  public $bigBlockSize;
93 
100 
107 
117  public function read($file)
118  {
119  $fh = fopen($file, 'rb');
120  if (!$fh) {
121  throw new ReaderException("Can't open file $file");
122  }
123  $this->_file_handle = $fh;
124 
125  $signature = fread($fh, 8);
126  if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
127  throw new ReaderException("File doesn't seem to be an OLE container.");
128  }
129  fseek($fh, 28);
130  if (fread($fh, 2) != "\xFE\xFF") {
131  // This shouldn't be a problem in practice
132  throw new ReaderException('Only Little-Endian encoding is supported.');
133  }
134  // Size of blocks and short blocks in bytes
135  $this->bigBlockSize = 2 ** self::readInt2($fh);
136  $this->smallBlockSize = 2 ** self::readInt2($fh);
137 
138  // Skip UID, revision number and version number
139  fseek($fh, 44);
140  // Number of blocks in Big Block Allocation Table
141  $bbatBlockCount = self::readInt4($fh);
142 
143  // Root chain 1st block
144  $directoryFirstBlockId = self::readInt4($fh);
145 
146  // Skip unused bytes
147  fseek($fh, 56);
148  // Streams shorter than this are stored using small blocks
149  $this->bigBlockThreshold = self::readInt4($fh);
150  // Block id of first sector in Short Block Allocation Table
151  $sbatFirstBlockId = self::readInt4($fh);
152  // Number of blocks in Short Block Allocation Table
153  $sbbatBlockCount = self::readInt4($fh);
154  // Block id of first sector in Master Block Allocation Table
155  $mbatFirstBlockId = self::readInt4($fh);
156  // Number of blocks in Master Block Allocation Table
157  $mbbatBlockCount = self::readInt4($fh);
158  $this->bbat = [];
159 
160  // Remaining 4 * 109 bytes of current block is beginning of Master
161  // Block Allocation Table
162  $mbatBlocks = [];
163  for ($i = 0; $i < 109; ++$i) {
164  $mbatBlocks[] = self::readInt4($fh);
165  }
166 
167  // Read rest of Master Block Allocation Table (if any is left)
168  $pos = $this->getBlockOffset($mbatFirstBlockId);
169  for ($i = 0; $i < $mbbatBlockCount; ++$i) {
170  fseek($fh, $pos);
171  for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
172  $mbatBlocks[] = self::readInt4($fh);
173  }
174  // Last block id in each block points to next block
175  $pos = $this->getBlockOffset(self::readInt4($fh));
176  }
177 
178  // Read Big Block Allocation Table according to chain specified by $mbatBlocks
179  for ($i = 0; $i < $bbatBlockCount; ++$i) {
180  $pos = $this->getBlockOffset($mbatBlocks[$i]);
181  fseek($fh, $pos);
182  for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
183  $this->bbat[] = self::readInt4($fh);
184  }
185  }
186 
187  // Read short block allocation table (SBAT)
188  $this->sbat = [];
189  $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
190  $sbatFh = $this->getStream($sbatFirstBlockId);
191  for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
192  $this->sbat[$blockId] = self::readInt4($sbatFh);
193  }
194  fclose($sbatFh);
195 
196  $this->readPpsWks($directoryFirstBlockId);
197 
198  return true;
199  }
200 
206  public function getBlockOffset($blockId)
207  {
208  return 512 + $blockId * $this->bigBlockSize;
209  }
210 
219  public function getStream($blockIdOrPps)
220  {
221  static $isRegistered = false;
222  if (!$isRegistered) {
223  stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class);
224  $isRegistered = true;
225  }
226 
227  // Store current instance in global array, so that it can be accessed
228  // in OLE_ChainedBlockStream::stream_open().
229  // Object is removed from self::$instances in OLE_Stream::close().
230  $GLOBALS['_OLE_INSTANCES'][] = $this;
231  $keys = array_keys($GLOBALS['_OLE_INSTANCES']);
232  $instanceId = end($keys);
233 
234  $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
235  if ($blockIdOrPps instanceof OLE\PPS) {
236  $path .= '&blockId=' . $blockIdOrPps->startBlock;
237  $path .= '&size=' . $blockIdOrPps->Size;
238  } else {
239  $path .= '&blockId=' . $blockIdOrPps;
240  }
241 
242  return fopen($path, 'rb');
243  }
244 
252  private static function readInt1($fh)
253  {
254  [, $tmp] = unpack('c', fread($fh, 1));
255 
256  return $tmp;
257  }
258 
266  private static function readInt2($fh)
267  {
268  [, $tmp] = unpack('v', fread($fh, 2));
269 
270  return $tmp;
271  }
272 
280  private static function readInt4($fh)
281  {
282  [, $tmp] = unpack('V', fread($fh, 4));
283 
284  return $tmp;
285  }
286 
295  public function readPpsWks($blockId)
296  {
297  $fh = $this->getStream($blockId);
298  for ($pos = 0; true; $pos += 128) {
299  fseek($fh, $pos, SEEK_SET);
300  $nameUtf16 = fread($fh, 64);
301  $nameLength = self::readInt2($fh);
302  $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
303  // Simple conversion from UTF-16LE to ISO-8859-1
304  $name = str_replace("\x00", '', $nameUtf16);
305  $type = self::readInt1($fh);
306  switch ($type) {
307  case self::OLE_PPS_TYPE_ROOT:
308  $pps = new OLE\PPS\Root(null, null, []);
309  $this->root = $pps;
310 
311  break;
312  case self::OLE_PPS_TYPE_DIR:
313  $pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
314 
315  break;
316  case self::OLE_PPS_TYPE_FILE:
317  $pps = new OLE\PPS\File($name);
318 
319  break;
320  default:
321  throw new Exception('Unsupported PPS type');
322  }
323  fseek($fh, 1, SEEK_CUR);
324  $pps->Type = $type;
325  $pps->Name = $name;
326  $pps->PrevPps = self::readInt4($fh);
327  $pps->NextPps = self::readInt4($fh);
328  $pps->DirPps = self::readInt4($fh);
329  fseek($fh, 20, SEEK_CUR);
330  $pps->Time1st = self::OLE2LocalDate(fread($fh, 8));
331  $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8));
332  $pps->startBlock = self::readInt4($fh);
333  $pps->Size = self::readInt4($fh);
334  $pps->No = count($this->_list);
335  $this->_list[] = $pps;
336 
337  // check if the PPS tree (starting from root) is complete
338  if (isset($this->root) && $this->ppsTreeComplete($this->root->No)) {
339  break;
340  }
341  }
342  fclose($fh);
343 
344  // Initialize $pps->children on directories
345  foreach ($this->_list as $pps) {
346  if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
347  $nos = [$pps->DirPps];
348  $pps->children = [];
349  while ($nos) {
350  $no = array_pop($nos);
351  if ($no != -1) {
352  $childPps = $this->_list[$no];
353  $nos[] = $childPps->PrevPps;
354  $nos[] = $childPps->NextPps;
355  $pps->children[] = $childPps;
356  }
357  }
358  }
359  }
360 
361  return true;
362  }
363 
372  private function ppsTreeComplete($index)
373  {
374  return isset($this->_list[$index]) &&
375  ($pps = $this->_list[$index]) &&
376  ($pps->PrevPps == -1 ||
377  $this->ppsTreeComplete($pps->PrevPps)) &&
378  ($pps->NextPps == -1 ||
379  $this->ppsTreeComplete($pps->NextPps)) &&
380  ($pps->DirPps == -1 ||
381  $this->ppsTreeComplete($pps->DirPps));
382  }
383 
392  public function isFile($index)
393  {
394  if (isset($this->_list[$index])) {
395  return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE;
396  }
397 
398  return false;
399  }
400 
409  public function isRoot($index)
410  {
411  if (isset($this->_list[$index])) {
412  return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT;
413  }
414 
415  return false;
416  }
417 
423  public function ppsTotal()
424  {
425  return count($this->_list);
426  }
427 
441  public function getData($index, $position, $length)
442  {
443  // if position is not valid return empty string
444  if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
445  return '';
446  }
447  $fh = $this->getStream($this->_list[$index]);
448  $data = stream_get_contents($fh, $length, $position);
449  fclose($fh);
450 
451  return $data;
452  }
453 
462  public function getDataLength($index)
463  {
464  if (isset($this->_list[$index])) {
465  return $this->_list[$index]->Size;
466  }
467 
468  return 0;
469  }
470 
478  public static function ascToUcs($ascii)
479  {
480  $rawname = '';
481  $iMax = strlen($ascii);
482  for ($i = 0; $i < $iMax; ++$i) {
483  $rawname .= $ascii[$i]
484  . "\x00";
485  }
486 
487  return $rawname;
488  }
489 
498  public static function localDateToOLE($date)
499  {
500  if (!$date) {
501  return "\x00\x00\x00\x00\x00\x00\x00\x00";
502  }
503  $dateTime = Date::dateTimeFromTimestamp("$date");
504 
505  // factor used for separating numbers into 4 bytes parts
506  $factor = 2 ** 32;
507 
508  // days from 1-1-1601 until the beggining of UNIX era
509  $days = 134774;
510  // calculate seconds
511  $big_date = $days * 24 * 3600 + (float) $dateTime->format('U');
512  // multiply just to make MS happy
513  $big_date *= 10000000;
514 
515  $high_part = floor($big_date / $factor);
516  // lower 4 bytes
517  $low_part = floor((($big_date / $factor) - $high_part) * $factor);
518 
519  // Make HEX string
520  $res = '';
521 
522  for ($i = 0; $i < 4; ++$i) {
523  $hex = $low_part % 0x100;
524  $res .= pack('c', $hex);
525  $low_part /= 0x100;
526  }
527  for ($i = 0; $i < 4; ++$i) {
528  $hex = $high_part % 0x100;
529  $res .= pack('c', $hex);
530  $high_part /= 0x100;
531  }
532 
533  return $res;
534  }
535 
543  public static function OLE2LocalDate($oleTimestamp)
544  {
545  if (strlen($oleTimestamp) != 8) {
546  throw new ReaderException('Expecting 8 byte string');
547  }
548 
549  // convert to units of 100 ns since 1601:
550  $unpackedTimestamp = unpack('v4', $oleTimestamp);
551  $timestampHigh = (float) $unpackedTimestamp[4] * 65536 + (float) $unpackedTimestamp[3];
552  $timestampLow = (float) $unpackedTimestamp[2] * 65536 + (float) $unpackedTimestamp[1];
553 
554  // translate to seconds since 1601:
555  $timestampHigh /= 10000000;
556  $timestampLow /= 10000000;
557 
558  // days from 1601 to 1970:
559  $days = 134774;
560 
561  // translate to seconds since 1970:
562  $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
563 
564  return IntOrFloat::evaluate($unixTimestamp);
565  }
566 }
$path
Definition: aliased.php:25
$GLOBALS['_OLE_INSTANCES']
Definition: OLE.php:35
isFile($index)
Checks whether a PPS is a File PPS or not.
Definition: OLE.php:392
static evaluate($value)
Help some functions with large results operate correctly on 32-bit, by returning result as int when p...
Definition: IntOrFloat.php:15
$type
static readInt1($fh)
Reads a signed char.
Definition: OLE.php:252
static ascToUcs($ascii)
Utility function to transform ASCII text to Unicode.
Definition: OLE.php:478
ppsTotal()
Gives the total number of PPS&#39;s found in the OLE container.
Definition: OLE.php:423
getStream($blockIdOrPps)
Returns a stream for use with fread() etc.
Definition: OLE.php:219
static readInt2($fh)
Reads an unsigned short (2 octets).
Definition: OLE.php:266
$index
Definition: metadata.php:60
$keys
read($file)
Reads an OLE container from the contents of the file given.
Definition: OLE.php:117
getDataLength($index)
Gets the data length from a PPS If there is no PPS for the index given, it will return 0...
Definition: OLE.php:462
foreach($_POST as $key=> $value) $res
isRoot($index)
Checks whether a PPS is a Root PPS or not.
Definition: OLE.php:409
static localDateToOLE($date)
Utility function Returns a string for the OLE container with the date given.
Definition: OLE.php:498
readPpsWks($blockId)
Gets information about all PPS&#39;s on the OLE container from the PPS WK&#39;s creates an OLE_PPS object for...
Definition: OLE.php:295
ppsTreeComplete($index)
It checks whether the PPS tree is complete (all PPS&#39;s read) starting with the given PPS (not necessar...
Definition: OLE.php:372
$i
Definition: disco.tpl.php:19
static readInt4($fh)
Reads an unsigned long (4 octets).
Definition: OLE.php:280
static OLE2LocalDate($oleTimestamp)
Returns a timestamp from an OLE container&#39;s date.
Definition: OLE.php:543
getData($index, $position, $length)
Gets data from a PPS If there is no PPS for the index given, it will return an empty string...
Definition: OLE.php:441
$data
Definition: bench.php:6
OLE package base class.
Definition: OLE.php:43