PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.4.7
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.4.7
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Shared / OLE.php

OLE.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.4.7, at vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php

574 lines 17.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace PhpOffice\PhpSpreadsheet\Shared;
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
24 use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
25 use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
26 use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
27
28 /*
29 * Array for storing OLE instances that are accessed from
30 * OLE_ChainedBlockStream::stream_open().
31 *
32 * @var array
33 */
34 $GLOBALS['_OLE_INSTANCES'] = [];
35
36 /**
37 * OLE package base class.
38 *
39 * @author Xavier Noguer <xnoguer@php.net>
40 * @author Christian Schmidt <schmidt@php.net>
41 *
42 * @category PhpSpreadsheet
43 */
44 class OLE
45 {
46 const OLE_PPS_TYPE_ROOT = 5;
47 const OLE_PPS_TYPE_DIR = 1;
48 const OLE_PPS_TYPE_FILE = 2;
49 const OLE_DATA_SIZE_SMALL = 0x1000;
50 const OLE_LONG_INT_SIZE = 4;
51 const OLE_PPS_SIZE = 0x80;
52
53 /**
54 * The file handle for reading an OLE container.
55 *
56 * @var resource
57 */
58 public $_file_handle;
59
60 /**
61 * Array of PPS's found on the OLE container.
62 *
63 * @var array
64 */
65 public $_list = [];
66
67 /**
68 * Root directory of OLE container.
69 *
70 * @var Root
71 */
72 public $root;
73
74 /**
75 * Big Block Allocation Table.
76 *
77 * @var array (blockId => nextBlockId)
78 */
79 public $bbat;
80
81 /**
82 * Short Block Allocation Table.
83 *
84 * @var array (blockId => nextBlockId)
85 */
86 public $sbat;
87
88 /**
89 * Size of big blocks. This is usually 512.
90 *
91 * @var int number of octets per block
92 */
93 public $bigBlockSize;
94
95 /**
96 * Size of small blocks. This is usually 64.
97 *
98 * @var int number of octets per block
99 */
100 public $smallBlockSize;
101
102 /**
103 * Threshold for big blocks.
104 *
105 * @var int
106 */
107 public $bigBlockThreshold;
108
109 /**
110 * Reads an OLE container from the contents of the file given.
111 *
112 * @acces public
113 *
114 * @param string $file
115 *
116 * @throws ReaderException
117 *
118 * @return bool true on success, PEAR_Error on failure
119 */
120 public function read($file)
121 {
122 $fh = fopen($file, 'r');
123 if (!$fh) {
124 throw new ReaderException("Can't open file $file");
125 }
126 $this->_file_handle = $fh;
127
128 $signature = fread($fh, 8);
129 if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
130 throw new ReaderException("File doesn't seem to be an OLE container.");
131 }
132 fseek($fh, 28);
133 if (fread($fh, 2) != "\xFE\xFF") {
134 // This shouldn't be a problem in practice
135 throw new ReaderException('Only Little-Endian encoding is supported.');
136 }
137 // Size of blocks and short blocks in bytes
138 $this->bigBlockSize = pow(2, self::_readInt2($fh));
139 $this->smallBlockSize = pow(2, self::_readInt2($fh));
140
141 // Skip UID, revision number and version number
142 fseek($fh, 44);
143 // Number of blocks in Big Block Allocation Table
144 $bbatBlockCount = self::_readInt4($fh);
145
146 // Root chain 1st block
147 $directoryFirstBlockId = self::_readInt4($fh);
148
149 // Skip unused bytes
150 fseek($fh, 56);
151 // Streams shorter than this are stored using small blocks
152 $this->bigBlockThreshold = self::_readInt4($fh);
153 // Block id of first sector in Short Block Allocation Table
154 $sbatFirstBlockId = self::_readInt4($fh);
155 // Number of blocks in Short Block Allocation Table
156 $sbbatBlockCount = self::_readInt4($fh);
157 // Block id of first sector in Master Block Allocation Table
158 $mbatFirstBlockId = self::_readInt4($fh);
159 // Number of blocks in Master Block Allocation Table
160 $mbbatBlockCount = self::_readInt4($fh);
161 $this->bbat = [];
162
163 // Remaining 4 * 109 bytes of current block is beginning of Master
164 // Block Allocation Table
165 $mbatBlocks = [];
166 for ($i = 0; $i < 109; ++$i) {
167 $mbatBlocks[] = self::_readInt4($fh);
168 }
169
170 // Read rest of Master Block Allocation Table (if any is left)
171 $pos = $this->_getBlockOffset($mbatFirstBlockId);
172 for ($i = 0; $i < $mbbatBlockCount; ++$i) {
173 fseek($fh, $pos);
174 for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
175 $mbatBlocks[] = self::_readInt4($fh);
176 }
177 // Last block id in each block points to next block
178 $pos = $this->_getBlockOffset(self::_readInt4($fh));
179 }
180
181 // Read Big Block Allocation Table according to chain specified by $mbatBlocks
182 for ($i = 0; $i < $bbatBlockCount; ++$i) {
183 $pos = $this->_getBlockOffset($mbatBlocks[$i]);
184 fseek($fh, $pos);
185 for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
186 $this->bbat[] = self::_readInt4($fh);
187 }
188 }
189
190 // Read short block allocation table (SBAT)
191 $this->sbat = [];
192 $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
193 $sbatFh = $this->getStream($sbatFirstBlockId);
194 for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
195 $this->sbat[$blockId] = self::_readInt4($sbatFh);
196 }
197 fclose($sbatFh);
198
199 $this->_readPpsWks($directoryFirstBlockId);
200
201 return true;
202 }
203
204 /**
205 * @param int $blockId byte offset from beginning of file
206 *
207 * @return int
208 */
209 public function _getBlockOffset($blockId)
210 {
211 return 512 + $blockId * $this->bigBlockSize;
212 }
213
214 /**
215 * Returns a stream for use with fread() etc. External callers should
216 * use \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::getStream().
217 *
218 * @param int|OLE\PPS $blockIdOrPps block id or PPS
219 *
220 * @return resource read-only stream
221 */
222 public function getStream($blockIdOrPps)
223 {
224 static $isRegistered = false;
225 if (!$isRegistered) {
226 stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class);
227 $isRegistered = true;
228 }
229
230 // Store current instance in global array, so that it can be accessed
231 // in OLE_ChainedBlockStream::stream_open().
232 // Object is removed from self::$instances in OLE_Stream::close().
233 $GLOBALS['_OLE_INSTANCES'][] = $this;
234 $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES']));
235
236 $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
237 if ($blockIdOrPps instanceof OLE\PPS) {
238 $path .= '&blockId=' . $blockIdOrPps->startBlock;
239 $path .= '&size=' . $blockIdOrPps->Size;
240 } else {
241 $path .= '&blockId=' . $blockIdOrPps;
242 }
243
244 return fopen($path, 'r');
245 }
246
247 /**
248 * Reads a signed char.
249 *
250 * @param resource $fh file handle
251 *
252 * @return int
253 */
254 private static function _readInt1($fh)
255 {
256 list(, $tmp) = unpack('c', fread($fh, 1));
257
258 return $tmp;
259 }
260
261 /**
262 * Reads an unsigned short (2 octets).
263 *
264 * @param resource $fh file handle
265 *
266 * @return int
267 */
268 private static function _readInt2($fh)
269 {
270 list(, $tmp) = unpack('v', fread($fh, 2));
271
272 return $tmp;
273 }
274
275 /**
276 * Reads an unsigned long (4 octets).
277 *
278 * @param resource $fh file handle
279 *
280 * @return int
281 */
282 private static function _readInt4($fh)
283 {
284 list(, $tmp) = unpack('V', fread($fh, 4));
285
286 return $tmp;
287 }
288
289 /**
290 * Gets information about all PPS's on the OLE container from the PPS WK's
291 * creates an OLE_PPS object for each one.
292 *
293 * @param int $blockId the block id of the first block
294 *
295 * @return bool true on success, PEAR_Error on failure
296 */
297 public function _readPpsWks($blockId)
298 {
299 $fh = $this->getStream($blockId);
300 for ($pos = 0; true; $pos += 128) {
301 fseek($fh, $pos, SEEK_SET);
302 $nameUtf16 = fread($fh, 64);
303 $nameLength = self::_readInt2($fh);
304 $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
305 // Simple conversion from UTF-16LE to ISO-8859-1
306 $name = str_replace("\x00", '', $nameUtf16);
307 $type = self::_readInt1($fh);
308 switch ($type) {
309 case self::OLE_PPS_TYPE_ROOT:
310 $pps = new OLE\PPS\Root(null, null, []);
311 $this->root = $pps;
312
313 break;
314 case self::OLE_PPS_TYPE_DIR:
315 $pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
316
317 break;
318 case self::OLE_PPS_TYPE_FILE:
319 $pps = new OLE\PPS\File($name);
320
321 break;
322 default:
323 break;
324 }
325 fseek($fh, 1, SEEK_CUR);
326 $pps->Type = $type;
327 $pps->Name = $name;
328 $pps->PrevPps = self::_readInt4($fh);
329 $pps->NextPps = self::_readInt4($fh);
330 $pps->DirPps = self::_readInt4($fh);
331 fseek($fh, 20, SEEK_CUR);
332 $pps->Time1st = self::OLE2LocalDate(fread($fh, 8));
333 $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8));
334 $pps->startBlock = self::_readInt4($fh);
335 $pps->Size = self::_readInt4($fh);
336 $pps->No = count($this->_list);
337 $this->_list[] = $pps;
338
339 // check if the PPS tree (starting from root) is complete
340 if (isset($this->root) && $this->_ppsTreeComplete($this->root->No)) {
341 break;
342 }
343 }
344 fclose($fh);
345
346 // Initialize $pps->children on directories
347 foreach ($this->_list as $pps) {
348 if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
349 $nos = [$pps->DirPps];
350 $pps->children = [];
351 while ($nos) {
352 $no = array_pop($nos);
353 if ($no != -1) {
354 $childPps = $this->_list[$no];
355 $nos[] = $childPps->PrevPps;
356 $nos[] = $childPps->NextPps;
357 $pps->children[] = $childPps;
358 }
359 }
360 }
361 }
362
363 return true;
364 }
365
366 /**
367 * It checks whether the PPS tree is complete (all PPS's read)
368 * starting with the given PPS (not necessarily root).
369 *
370 * @param int $index The index of the PPS from which we are checking
371 *
372 * @return bool Whether the PPS tree for the given PPS is complete
373 */
374 public function _ppsTreeComplete($index)
375 {
376 return isset($this->_list[$index]) &&
377 ($pps = $this->_list[$index]) &&
378 ($pps->PrevPps == -1 ||
379 $this->_ppsTreeComplete($pps->PrevPps)) &&
380 ($pps->NextPps == -1 ||
381 $this->_ppsTreeComplete($pps->NextPps)) &&
382 ($pps->DirPps == -1 ||
383 $this->_ppsTreeComplete($pps->DirPps));
384 }
385
386 /**
387 * Checks whether a PPS is a File PPS or not.
388 * If there is no PPS for the index given, it will return false.
389 *
390 * @param int $index The index for the PPS
391 *
392 * @return bool true if it's a File PPS, false otherwise
393 */
394 public function isFile($index)
395 {
396 if (isset($this->_list[$index])) {
397 return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE;
398 }
399
400 return false;
401 }
402
403 /**
404 * Checks whether a PPS is a Root PPS or not.
405 * If there is no PPS for the index given, it will return false.
406 *
407 * @param int $index the index for the PPS
408 *
409 * @return bool true if it's a Root PPS, false otherwise
410 */
411 public function isRoot($index)
412 {
413 if (isset($this->_list[$index])) {
414 return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT;
415 }
416
417 return false;
418 }
419
420 /**
421 * Gives the total number of PPS's found in the OLE container.
422 *
423 * @return int The total number of PPS's found in the OLE container
424 */
425 public function ppsTotal()
426 {
427 return count($this->_list);
428 }
429
430 /**
431 * Gets data from a PPS
432 * If there is no PPS for the index given, it will return an empty string.
433 *
434 * @param int $index The index for the PPS
435 * @param int $position The position from which to start reading
436 * (relative to the PPS)
437 * @param int $length The amount of bytes to read (at most)
438 *
439 * @return string The binary string containing the data requested
440 *
441 * @see OLE_PPS_File::getStream()
442 */
443 public function getData($index, $position, $length)
444 {
445 // if position is not valid return empty string
446 if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
447 return '';
448 }
449 $fh = $this->getStream($this->_list[$index]);
450 $data = stream_get_contents($fh, $length, $position);
451 fclose($fh);
452
453 return $data;
454 }
455
456 /**
457 * Gets the data length from a PPS
458 * If there is no PPS for the index given, it will return 0.
459 *
460 * @param int $index The index for the PPS
461 *
462 * @return int The amount of bytes in data the PPS has
463 */
464 public function getDataLength($index)
465 {
466 if (isset($this->_list[$index])) {
467 return $this->_list[$index]->Size;
468 }
469
470 return 0;
471 }
472
473 /**
474 * Utility function to transform ASCII text to Unicode.
475 *
476 * @param string $ascii The ASCII string to transform
477 *
478 * @return string The string in Unicode
479 */
480 public static function ascToUcs($ascii)
481 {
482 $rawname = '';
483 $iMax = strlen($ascii);
484 for ($i = 0; $i < $iMax; ++$i) {
485 $rawname .= $ascii[$i]
486 . "\x00";
487 }
488
489 return $rawname;
490 }
491
492 /**
493 * Utility function
494 * Returns a string for the OLE container with the date given.
495 *
496 * @param int $date A timestamp
497 *
498 * @return string The string for the OLE container
499 */
500 public static function localDateToOLE($date)
501 {
502 if (!isset($date)) {
503 return "\x00\x00\x00\x00\x00\x00\x00\x00";
504 }
505
506 // factor used for separating numbers into 4 bytes parts
507 $factor = pow(2, 32);
508
509 // days from 1-1-1601 until the beggining of UNIX era
510 $days = 134774;
511 // calculate seconds
512 $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date));
513 // multiply just to make MS happy
514 $big_date *= 10000000;
515
516 $high_part = floor($big_date / $factor);
517 // lower 4 bytes
518 $low_part = floor((($big_date / $factor) - $high_part) * $factor);
519
520 // Make HEX string
521 $res = '';
522
523 for ($i = 0; $i < 4; ++$i) {
524 $hex = $low_part % 0x100;
525 $res .= pack('c', $hex);
526 $low_part /= 0x100;
527 }
528 for ($i = 0; $i < 4; ++$i) {
529 $hex = $high_part % 0x100;
530 $res .= pack('c', $hex);
531 $high_part /= 0x100;
532 }
533
534 return $res;
535 }
536
537 /**
538 * Returns a timestamp from an OLE container's date.
539 *
540 * @param string $oleTimestamp A binary string with the encoded date
541 *
542 * @throws ReaderException
543 *
544 * @return int The Unix timestamp corresponding to the string
545 */
546 public static function OLE2LocalDate($oleTimestamp)
547 {
548 if (strlen($oleTimestamp) != 8) {
549 throw new ReaderException('Expecting 8 byte string');
550 }
551
552 // convert to units of 100 ns since 1601:
553 $unpackedTimestamp = unpack('v4', $oleTimestamp);
554 $timestampHigh = (float) $unpackedTimestamp[4] * 65536 + (float) $unpackedTimestamp[3];
555 $timestampLow = (float) $unpackedTimestamp[2] * 65536 + (float) $unpackedTimestamp[1];
556
557 // translate to seconds since 1601:
558 $timestampHigh /= 10000000;
559 $timestampLow /= 10000000;
560
561 // days from 1601 to 1970:
562 $days = 134774;
563
564 // translate to seconds since 1970:
565 $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
566
567 if ((int) $unixTimestamp == $unixTimestamp) {
568 return (int) $unixTimestamp;
569 }
570
571 return $unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN;
572 }
573 }
574