ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
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
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
43class OLE
44{
48 const OLE_DATA_SIZE_SMALL = 0x1000;
50 const OLE_PPS_SIZE = 0x80;
51
58
64 public $_list = [];
65
71 public $root;
72
78 public $bbat;
79
85 public $sbat;
86
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) {
308 $pps = new OLE\PPS\Root(null, null, []);
309 $this->root = $pps;
310
311 break;
313 $pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
314
315 break;
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
An exception for terminatinating execution or to throw for unit testing.
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
Class for creating File PPS's for OLE containers.
Definition: File.php:32
Class for creating Root PPS's for OLE containers.
Definition: Root.php:32
Class for creating PPS's for OLE containers.
Definition: PPS.php:31
OLE package base class.
Definition: OLE.php:44
static OLE2LocalDate($oleTimestamp)
Returns a timestamp from an OLE container's date.
Definition: OLE.php:543
static localDateToOLE($date)
Utility function Returns a string for the OLE container with the date given.
Definition: OLE.php:498
static readInt4($fh)
Reads an unsigned long (4 octets).
Definition: OLE.php:280
isRoot($index)
Checks whether a PPS is a Root PPS or not.
Definition: OLE.php:409
isFile($index)
Checks whether a PPS is a File PPS or not.
Definition: OLE.php:392
read($file)
Reads an OLE container from the contents of the file given.
Definition: OLE.php:117
getStream($blockIdOrPps)
Returns a stream for use with fread() etc.
Definition: OLE.php:219
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
static readInt1($fh)
Reads a signed char.
Definition: OLE.php:252
ppsTreeComplete($index)
It checks whether the PPS tree is complete (all PPS's read) starting with the given PPS (not necessar...
Definition: OLE.php:372
readPpsWks($blockId)
Gets information about all PPS's on the OLE container from the PPS WK's creates an OLE_PPS object for...
Definition: OLE.php:295
static readInt2($fh)
Reads an unsigned short (2 octets).
Definition: OLE.php:266
static ascToUcs($ascii)
Utility function to transform ASCII text to Unicode.
Definition: OLE.php:478
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
ppsTotal()
Gives the total number of PPS's found in the OLE container.
Definition: OLE.php:423
$i
Definition: disco.tpl.php:19
$index
Definition: metadata.php:60
$keys
$GLOBALS['_OLE_INSTANCES']
Definition: OLE.php:35
$type
foreach($_POST as $key=> $value) $res
$data
Definition: bench.php:6