nfTlWNl rn Ѻq$³*AӪ}# Q&VY{ V~PQ?ltjdReԈ h$)wM_$$}=xF'bޑXm3߂MkO%3d'D:IhS^lT?QknVVY ]{RMXzm8_d袶OA Sv 6d&x_|wAih`L`Q+B6pyqXzmq ی/洓͚osݻU٠s0%soE9'| =e?P4 p)&</dqڶGHaלD9G~иSoKR$k2@$HeѦRR&Wmds12L@&5_cVB '7cqCOIKO(I΋>HގXQ;d3tXPVaz7Dl] #ѶÛ(k՝㬖T Qb}Ԩ:]D[vҕZ!:Ms:XJ V{h`seqɢ4|@ѕi);2*R ,8YZ%r0]=~E39bgMV,-PS;16;$ȼl@03o+0iqj2-!0]#!/ޒRyi7ԙ´N8* 7 ˋ3!c ϒ.%,]z$wwp_{ZzvjhYᰨWoȷEg͗;_KT5FTC&DX7"^R"mZ:tJvv$}JwXE ^A{US&-GXa4fŵEjZ4sAAWK TcIÞ~SqM[c/Ԯ qeۅr.0K&Kv rC$MJ%x_<8K_-Q2ihgJeާ؜m%"zޑ:h6V7S Q34ֱD;VC%ᦔD ;3圹X ,_386<֕蠄4^U}tjJ(mYvn4z8MW\VϜ|X(|4ϔrU= ^6;*ҫjFJ,M>Pc)ZA?7kQnw@M`DEPĐ-8O^scGWK9a[eJ;j*%w*] {6"4Z#?U4ltT`s<|$E#)J/o.pXfsFoQ TJ4-cGWK9llPQܥ%թ>3 @lJ(!] cGWK9llPQܥ%}G ׫.!۠Py-@*{5ܑ5JPysҋ" H-0|TLd?!ԳHK-՟Ex@Sv=;fOi3$K^o\%d|,l%MՒ)$| \݊س@?لFε;ё l8W ~}C2{glI_RN)X$U"Ӡ勘&1ď:Hd mF6+Ai,w߼;zw%E˨tuEt7JR XRe&j?ZV: Y6JC륌6'wrDi j]ykIYe/*ɹˎԚI* l&X:G9]k9Ms1`72w.SISY>:|*.2n]Uy?%O#%*,%:X{RE$ SD7[Iڝ!ﳡ?vv'TЂh?2L$:II^3. quV`]v?X deSG-]/Ra WL v[cil׹_tK빉I  $'\K+@Ce#{Tp^?湼LXOehq_wӡHA% W h;ΊLuOэO e xS=p:}ح_50Gbf̓g^%ٶ(L<=N "z}+[\aehq_wӡHA% W h;MMŻ7LF(AlRUdIJ!i(CK=ٙH*9'Q#%i`~?q|mLAvtA՟nft|tO!n4]K>FKBJսېbe QD䑎Я1aQ?!/9CrO 8Hsxúop9/NU-[?;JIAa*T(gH$#v@0y+~~e.u#T:a[Q=vQK젡JD(Ȉ E']ƁP/hSFGk2V SMu y4p"__Si/KlU^!g^WYz;˵gTo=c+Z uɝnim6d+~^5 Y23^ yEkisaW\3(Cpoߨ΍~)Bn6 Spk7wjQenabU|)q͵67`]NvGΚsD8k^ Zstpv:$Z"q?vˍwJF2w[;*-!\xfaٳeΏV $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); $pos += 4; } $bbdBlocks += $blocksToRead; if ($bbdBlocks < $this->numBigBlockDepotBlocks) { $this->extensionBlock = self::getInt4d($this->data, $pos); } } $pos = 0; $this->bigBlockChain = ''; $bbs = self::BIG_BLOCK_SIZE / 4; for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) { $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE; $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs); $pos += 4 * $bbs; } $sbdBlock = $this->sbdStartBlock; $this->smallBlockChain = ''; while ($sbdBlock != -2) { $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE; $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs); $pos += 4 * $bbs; $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4); } // read the directory stream $block = $this->rootStartBlock; $this->entry = $this->readData($block); $this->readPropertySets(); } /** * Extract binary stream data. * * @param ?int $stream * * @return null|string */ public function getStream($stream) { if ($stream === null) { return null; } $streamData = ''; if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) { $rootdata = $this->readData($this->props[$this->rootentry]['startBlock']); $block = $this->props[$stream]['startBlock']; while ($block != -2) { $pos = $block * self::SMALL_BLOCK_SIZE; $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE); $block = self::getInt4d($this->smallBlockChain, $block * 4); } return $streamData; } $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE; if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) { ++$numBlocks; } if ($numBlocks == 0) { return ''; } $block = $this->props[$stream]['startBlock']; while ($block != -2) { $pos = ($block + 1) * self::BIG_BLOCK_SIZE; $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); $block = self::getInt4d($this->bigBlockChain, $block * 4); } return $streamData; } /** * Read a standard stream (by joining sectors using information from SAT). * * @param int $block Sector ID where the stream starts * * @return string Data for standard stream */ private function readData($block) { $data = ''; while ($block != -2) { $pos = ($block + 1) * self::BIG_BLOCK_SIZE; $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); $block = self::getInt4d($this->bigBlockChain, $block * 4); } return $data; } /** * Read entries in the directory stream. */ private function readPropertySets(): void { $offset = 0; // loop through entires, each entry is 128 bytes $entryLen = strlen($this->entry); while ($offset < $entryLen) { // entry data (128 bytes) $d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE); // size in bytes of name $nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8); // type of entry $type = ord($d[self::TYPE_POS]); // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook) // sectorID of first sector of the short-stream container stream, if this entry is root entry $startBlock = self::getInt4d($d, self::START_BLOCK_POS); $size = self::getInt4d($d, self::SIZE_POS); $name = str_replace("\x00", '', substr($d, 0, $nameSize)); $this->props[] = [ 'name' => $name, 'type' => $type, 'startBlock' => $startBlock, 'size' => $size, ]; // tmp helper to simplify checks $upName = strtoupper($name); // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook) if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) { $this->wrkbook = count($this->props) - 1; } elseif ($upName === 'ROOT ENTRY' || $upName === 'R') { // Root entry $this->rootentry = count($this->props) - 1; } // Summary information if ($name == chr(5) . 'SummaryInformation') { $this->summaryInformation = count($this->props) - 1; } // Additional Document Summary information if ($name == chr(5) . 'DocumentSummaryInformation') { $this->documentSummaryInformation = count($this->props) - 1; } $offset += self::PROPERTY_STORAGE_BLOCK_SIZE; } } /** * Read 4 bytes of data at specified position. * * @param string $data * @param int $pos * * @return int */ private static function getInt4d($data, $pos) { if ($pos < 0) { // Invalid position throw new ReaderException('Parameter pos=' . $pos . ' is invalid.'); } $len = strlen($data); if ($len < $pos + 4) { $data .= str_repeat("\0", $pos + 4 - $len); } // FIX: represent numbers correctly on 64-bit system // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems $_or_24 = ord($data[$pos + 3]); if ($_or_24 >= 128) { // negative number $_ord_24 = -abs((256 - $_or_24) << 24); } else { $_ord_24 = ($_or_24 & 127) << 24; } return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24; } }