PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Shared / OLERead.php

OLERead.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Shared/OLERead.php

334 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TablePress\PhpOffice\PhpSpreadsheet\Shared;
4
5 use TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
6
7 class OLERead
8 {
9 private string $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
18 const PROPERTY_STORAGE_BLOCK_SIZE = 0x80;
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
24 const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2C;
25 const ROOT_START_BLOCK_POS = 0x30;
26 const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3C;
27 const EXTENSION_BLOCK_POS = 0x44;
28 const NUM_EXTENSION_BLOCK_POS = 0x48;
29 const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4C;
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 ?int $wrkbook = null;
38
39 public ?int $summaryInformation = null;
40
41 public ?int $documentSummaryInformation = null;
42
43 private int $numBigBlockDepotBlocks;
44
45 private int $rootStartBlock;
46
47 private int $sbdStartBlock;
48
49 private int $extensionBlock;
50
51 private int $numExtensionBlocks;
52
53 private string $bigBlockChain;
54
55 private string $smallBlockChain;
56
57 private string $entry;
58
59 private int $rootentry;
60
61 /** @var mixed[][] */
62 private array $props = [];
63
64 /** @var int[] */
65 private array $possibleLoop = [];
66
67 /**
68 * Read the file.
69 */
70 public function read(string $filename): void
71 {
72 File::assertFile($filename);
73
74 // Get the file identifier
75 // Don't bother reading the whole file until we know it's a valid OLE file
76 $this->data = (string) file_get_contents($filename, false, null, 0, 8);
77
78 // Check OLE identifier
79 $identifierOle = pack('CCCCCCCC', 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1);
80 if ($this->data != $identifierOle) {
81 throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file');
82 }
83
84 // Get the file data
85 $this->data = (string) file_get_contents($filename);
86
87 // Total number of sectors used for the SAT
88 $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
89
90 // SecID of the first sector of the directory stream
91 $this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
92
93 // SecID of the first sector of the SSAT (or -2 if not extant)
94 $this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
95
96 // SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
97 $this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
98
99 // Total number of sectors used by MSAT
100 $this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
101
102 $bigBlockDepotBlocks = [];
103 $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
104
105 $bbdBlocks = $this->numBigBlockDepotBlocks;
106
107 if ($this->numExtensionBlocks !== 0) {
108 $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
109 }
110
111 for ($i = 0; $i < $bbdBlocks; ++$i) {
112 $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
113 $pos += 4;
114 }
115
116 for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
117 $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
118 $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
119
120 for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
121 $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
122 $pos += 4;
123 }
124
125 $bbdBlocks += $blocksToRead;
126 if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
127 $this->extensionBlock = self::getInt4d($this->data, $pos);
128 }
129 }
130
131 $pos = 0;
132 $this->bigBlockChain = '';
133 $bbs = self::BIG_BLOCK_SIZE / 4;
134 for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
135 $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
136
137 $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs);
138 $pos += 4 * $bbs;
139 }
140
141 $sbdBlock = $this->sbdStartBlock;
142 $this->smallBlockChain = '';
143 $this->possibleLoop = [];
144 while ($sbdBlock != -2) {
145 $this->catchLoop($sbdBlock);
146 $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
147
148 $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
149 $pos += 4 * $bbs;
150
151 $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);
152 }
153
154 // read the directory stream
155 $block = $this->rootStartBlock;
156 $this->entry = $this->readData($block);
157
158 $this->readPropertySets();
159 }
160
161 private function catchLoop(int $sbdBlock): void
162 {
163 if (in_array($sbdBlock, $this->possibleLoop, true)) {
164 throw new ReaderException('Detected loop while iterating blocks');
165 }
166 $this->possibleLoop[] = $sbdBlock;
167 }
168
169 /**
170 * Extract binary stream data.
171 */
172 public function getStream(?int $stream): ?string
173 {
174 if ($stream === null) {
175 return null;
176 }
177
178 $streamData = '';
179
180 if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
181 /** @var int */
182 $temp = $this->props[$this->rootentry]['startBlock'];
183 $rootdata = $this->readData($temp);
184
185 /** @var int */
186 $block = $this->props[$stream]['startBlock'];
187
188 $this->possibleLoop = [];
189 while ($block != -2) {
190 $this->catchLoop($block);
191 $pos = $block * self::SMALL_BLOCK_SIZE;
192 $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
193
194 $block = self::getInt4d($this->smallBlockChain, $block * 4);
195 }
196
197 return $streamData;
198 }
199 /** @var int */
200 $temp = $this->props[$stream]['size'];
201 $numBlocks = $temp / self::BIG_BLOCK_SIZE;
202 if ($temp % self::BIG_BLOCK_SIZE != 0) {
203 ++$numBlocks;
204 }
205
206 if ($numBlocks == 0) {
207 return '';
208 }
209
210 /** @var int */
211 $block = $this->props[$stream]['startBlock'];
212
213 $this->possibleLoop = [];
214 while ($block != -2) {
215 $this->catchLoop($block);
216 $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
217 $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
218 $block = self::getInt4d($this->bigBlockChain, $block * 4);
219 }
220
221 return $streamData;
222 }
223
224 /**
225 * Read a standard stream (by joining sectors using information from SAT).
226 *
227 * @param int $block Sector ID where the stream starts
228 *
229 * @return string Data for standard stream
230 */
231 private function readData(int $block): string
232 {
233 $data = '';
234
235 $this->possibleLoop = [];
236 while ($block != -2) {
237 $this->catchLoop($block);
238 $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
239 $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
240 $block = self::getInt4d($this->bigBlockChain, $block * 4);
241 }
242
243 return $data;
244 }
245
246 /**
247 * Read entries in the directory stream.
248 */
249 private function readPropertySets(): void
250 {
251 $offset = 0;
252
253 // loop through entries, each entry is 128 bytes
254 $entryLen = strlen($this->entry);
255 while ($offset < $entryLen) {
256 // entry data (128 bytes)
257 $d = (string) substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
258
259 // size in bytes of name
260 $nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8);
261
262 // type of entry
263 $type = ord($d[self::TYPE_POS]);
264
265 // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
266 // sectorID of first sector of the short-stream container stream, if this entry is root entry
267 $startBlock = self::getInt4d($d, self::START_BLOCK_POS);
268
269 $size = self::getInt4d($d, self::SIZE_POS);
270
271 $name = str_replace("\x00", '', (string) substr($d, 0, $nameSize));
272
273 $this->props[] = [
274 'name' => $name,
275 'type' => $type,
276 'startBlock' => $startBlock,
277 'size' => $size,
278 ];
279
280 // tmp helper to simplify checks
281 $upName = strtoupper($name);
282
283 // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
284 if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) {
285 $this->wrkbook = count($this->props) - 1;
286 } elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
287 // Root entry
288 $this->rootentry = count($this->props) - 1;
289 }
290
291 // Summary information
292 if ($name == chr(5) . 'SummaryInformation') {
293 $this->summaryInformation = count($this->props) - 1;
294 }
295
296 // Additional Document Summary information
297 if ($name == chr(5) . 'DocumentSummaryInformation') {
298 $this->documentSummaryInformation = count($this->props) - 1;
299 }
300
301 $offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
302 }
303 }
304
305 /**
306 * Read 4 bytes of data at specified position.
307 */
308 private static function getInt4d(string $data, int $pos): int
309 {
310 if ($pos < 0) {
311 // Invalid position
312 throw new ReaderException('Parameter pos=' . $pos . ' is invalid.');
313 }
314
315 $len = strlen($data);
316 if ($len < $pos + 4) {
317 $data .= str_repeat("\0", $pos + 4 - $len);
318 }
319
320 // FIX: represent numbers correctly on 64-bit system
321 // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
322 // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
323 $_or_24 = ord($data[$pos + 3]);
324 if ($_or_24 >= 128) {
325 // negative number
326 $_ord_24 = -abs((256 - $_or_24) << 24);
327 } else {
328 $_ord_24 = ($_or_24 & 127) << 24;
329 }
330
331 return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
332 }
333 }
334