← All changes
|
libraries/vendor/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
+55
-16
3.3.2
→
3.4
View file →
| @@ -57,9 +57,9 @@ | ||
| 57 | 57 | } |
| 58 | 58 | |
| 59 | 59 | // 25 is length of "ole-chainedblockstream://" |
| 60 | 60 | parse_str((string) substr($path, 25), $this->params); |
| 61 | - if (!isset($this->params['oleInstanceId'], $this->params['blockId'], $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']])) { //* @phpstan-ignore-line | |
| 61 | + if (!isset($this->params['oleInstanceId'], $this->params['blockId'], $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']])) { //* @phpstan-ignore offsetAccess.nonOffsetAccessible (I don't know how to fix this) | |
| 62 | 62 | if ($options & STREAM_REPORT_ERRORS) { |
| 63 | 63 | trigger_error('OLE stream not found', E_USER_WARNING); |
| 64 | 64 | } |
| 65 | 65 | |
| @@ -64,37 +64,61 @@ | ||
| 64 | 64 | } |
| 65 | 65 | |
| 66 | 66 | return false; |
| 67 | 67 | } |
| 68 | - $this->ole = $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']]; //* @phpstan-ignore-line | |
| 69 | - if (!($this->ole instanceof OLE)) { //* @phpstan-ignore-line | |
| 68 | + $this->ole = $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']]; //* @phpstan-ignore assign.propertyType (I don't know how to fix this) | |
| 69 | + if (!($this->ole instanceof OLE)) { //* @phpstan-ignore instanceof.alwaysTrue (I don't know how to fix this) | |
| 70 | 70 | throw new Exception('class is not OLE'); |
| 71 | 71 | } |
| 72 | 72 | |
| 73 | - $blockId = $this->params['blockId']; | |
| 73 | + $blockId = (int) $this->params['blockId']; | |
| 74 | + $size = isset($this->params['size']) ? (int) $this->params['size'] : null; | |
| 75 | + $isRoot = isset($this->params['isRoot']) && $this->params['isRoot'] === '1'; | |
| 74 | 76 | $this->data = ''; |
| 75 | - if (isset($this->params['size']) && $this->params['size'] < $this->ole->bigBlockThreshold && $blockId != $this->ole->root->startBlock) { | |
| 77 | + if ($size !== null && $size < $this->ole->bigBlockThreshold && !$isRoot) { | |
| 76 | 78 | // Block id refers to small blocks |
| 77 | - $rootPos = $this->ole->getBlockOffset((int) $this->ole->root->startBlock); | |
| 79 | + $rootData = ''; | |
| 80 | + if ($this->ole->root->startBlock === null) { | |
| 81 | + throw new Exception('Invalid OLE root mini-stream chain.'); | |
| 82 | + } | |
| 83 | + $rootBlockId = (int) $this->ole->root->startBlock; | |
| 84 | + $rootBlocks = []; | |
| 85 | + while ($rootBlockId !== -2) { | |
| 86 | + if (isset($rootBlocks[$rootBlockId], $this->ole->bbat[$rootBlockId])) { | |
| 87 | + throw new Exception('Invalid OLE root mini-stream chain.'); | |
| 88 | + } | |
| 89 | + $rootBlocks[$rootBlockId] = true; | |
| 90 | + fseek($this->ole->_file_handle, $this->ole->getBlockOffset($rootBlockId)); | |
| 91 | + $rootData .= fread($this->ole->_file_handle, $this->ole->bigBlockSize); | |
| 92 | + $rootBlockId = self::nextBlock($this->ole->bbat, $rootBlockId, 'Invalid OLE root mini-stream chain.'); | |
| 93 | + } | |
| 94 | + | |
| 95 | + $smallBlocks = []; | |
| 78 | 96 | while ($blockId != -2) { |
| 79 | - /** @var int $blockId */ | |
| 80 | - $pos = $rootPos + $blockId * $this->ole->bigBlockSize; | |
| 81 | - $blockId = $this->ole->sbat[$blockId]; | |
| 82 | - fseek($this->ole->_file_handle, $pos); | |
| 83 | - $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize); | |
| 97 | + if (isset($smallBlocks[$blockId], $this->ole->sbat[$blockId])) { | |
| 98 | + throw new Exception('Invalid OLE mini-stream chain.'); | |
| 99 | + } | |
| 100 | + $smallBlocks[$blockId] = true; | |
| 101 | + $pos = $blockId * $this->ole->smallBlockSize; | |
| 102 | + $this->data .= substr($rootData, $pos, $this->ole->smallBlockSize); | |
| 103 | + $blockId = self::nextBlock($this->ole->sbat, $blockId, 'Invalid OLE mini-stream chain.'); | |
| 84 | 104 | } |
| 85 | 105 | } else { |
| 86 | 106 | // Block id refers to big blocks |
| 107 | + $bigBlocks = []; | |
| 87 | 108 | while ($blockId != -2) { |
| 88 | - /** @var int $blockId */ | |
| 109 | + if (isset($bigBlocks[$blockId], $this->ole->bbat[$blockId])) { | |
| 110 | + throw new Exception('Invalid OLE stream chain.'); | |
| 111 | + } | |
| 112 | + $bigBlocks[$blockId] = true; | |
| 89 | 113 | $pos = $this->ole->getBlockOffset($blockId); |
| 90 | 114 | fseek($this->ole->_file_handle, $pos); |
| 91 | 115 | $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize); |
| 92 | - $blockId = $this->ole->bbat[$blockId]; | |
| 116 | + $blockId = self::nextBlock($this->ole->bbat, $blockId, 'Invalid OLE stream chain.'); | |
| 93 | 117 | } |
| 94 | 118 | } |
| 95 | - if (isset($this->params['size'])) { | |
| 96 | - $this->data = (string) substr($this->data, 0, $this->params['size']); //* @phpstan-ignore-line | |
| 119 | + if ($size !== null) { | |
| 120 | + $this->data = (string) substr($this->data, 0, $size); | |
| 97 | 121 | } |
| 98 | 122 | |
| 99 | 123 | if ($options & STREAM_USE_PATH) { |
| 100 | 124 | $openedPath = $path; |
| @@ -102,8 +126,19 @@ | ||
| 102 | 126 | |
| 103 | 127 | return true; |
| 104 | 128 | } |
| 105 | 129 | |
| 130 | + /** @param mixed[] $allocationTable */ | |
| 131 | + private static function nextBlock(array $allocationTable, int $blockId, string $message): int | |
| 132 | + { | |
| 133 | + $nextBlockId = $allocationTable[$blockId] ?? null; | |
| 134 | + if (!is_int($nextBlockId)) { | |
| 135 | + throw new Exception($message); | |
| 136 | + } | |
| 137 | + | |
| 138 | + return $nextBlockId; | |
| 139 | + } | |
| 140 | + | |
| 106 | 141 | /** |
| 107 | 142 | * Implements support for fclose(). |
| 108 | 143 | */ |
| 109 | 144 | public function stream_close(): void // @codingStandardsIgnoreLine |
| @@ -150,8 +185,12 @@ | ||
| 150 | 185 | } |
| 151 | 186 | |
| 152 | 187 | /** |
| 153 | 188 | * Implements support for fseek(). |
| 189 | + * Note that the first condition is always true, at least in | |
| 190 | + * the unit test suite. One consequence is that Phpstan's | |
| 191 | + * correct flagging of count($this->data) below is never | |
| 192 | + * executed, and would fail should it be executed. | |
| 154 | 193 | * |
| 155 | 194 | * @param int $offset byte offset |
| 156 | 195 | * @param int $whence SEEK_SET, SEEK_CUR or SEEK_END |
| 157 | 196 | */ |
| @@ -160,9 +199,9 @@ | ||
| 160 | 199 | if ($whence == SEEK_SET && $offset >= 0) { |
| 161 | 200 | $this->pos = $offset; |
| 162 | 201 | } elseif ($whence == SEEK_CUR && -$offset <= $this->pos) { |
| 163 | 202 | $this->pos += $offset; |
| 164 | - } elseif ($whence == SEEK_END && -$offset <= count($this->data)) { // @phpstan-ignore-line | |
| 203 | + } elseif ($whence == SEEK_END && -$offset <= count($this->data)) { // @phpstan-ignore argument.type (phpstan is correct - see docBlock above) | |
| 165 | 204 | $this->pos = strlen($this->data) + $offset; |
| 166 | 205 | } else { |
| 167 | 206 | return false; |
| 168 | 207 | } |