├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yaml │ └── cs.yaml ├── .gitignore ├── .php_cs.dist ├── OLE.php ├── OLE ├── ChainedBlockStream.php ├── PPS.php └── PPS │ ├── File.php │ └── Root.php ├── README.md ├── composer.json ├── package.xml └── phpunit.xml.dist /.gitattributes: -------------------------------------------------------------------------------- 1 | # Debug with: git archive --format=tar HEAD | tar t 2 | 3 | /tests export-ignore 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "composer" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # yamllint disable rule:line-length 2 | # yamllint disable rule:braces 3 | 4 | name: Continuous Integration 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | - master 12 | 13 | jobs: 14 | tests: 15 | runs-on: ${{ matrix.operating-system }} 16 | 17 | strategy: 18 | matrix: 19 | operating-system: ['ubuntu-latest'] 20 | php-version: 21 | - '5.6' 22 | - '7.0' 23 | - '7.1' 24 | - '7.2' 25 | - '7.3' 26 | - '7.4' 27 | - '8.0' 28 | - '8.1' 29 | - '8.2' 30 | - '8.3' 31 | - '8.4' 32 | 33 | name: CI on ${{ matrix.operating-system }} with PHP ${{ matrix.php-version }} 34 | 35 | steps: 36 | - name: Checkout code 37 | uses: actions/checkout@v4 38 | 39 | - name: Setup PHP 40 | uses: shivammathur/setup-php@v2 41 | with: 42 | php-version: ${{ matrix.php-version }} 43 | ini-values: error_reporting=E_ALL 44 | tools: composer:v2 45 | coverage: none 46 | 47 | - name: Get composer cache directory 48 | id: composer-cache 49 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 50 | 51 | - name: Cache dependencies 52 | uses: actions/cache@v3 53 | with: 54 | path: ${{ steps.composer-cache.outputs.dir }} 55 | key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('composer.*') }}-${{ matrix.composer-flags }} 56 | restore-keys: | 57 | composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('composer.*') }}- 58 | composer-${{ runner.os }}-${{ matrix.php-version }}- 59 | composer-${{ runner.os }}- 60 | composer- 61 | 62 | - name: Install dependencies 63 | run: | 64 | composer update --no-interaction --prefer-dist --no-progress ${{ matrix.composer-flags }} 65 | 66 | - name: Run tests 67 | run: | 68 | vendor/bin/phpunit 69 | 70 | - name: Lint code 71 | run: | 72 | find OLE* -type f -name \*.php | xargs -n1 php -l 73 | -------------------------------------------------------------------------------- /.github/workflows/cs.yaml: -------------------------------------------------------------------------------- 1 | name: Coding Standards 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | 10 | jobs: 11 | coding-standards: 12 | name: Coding Standards 13 | runs-on: ubuntu-latest 14 | 15 | env: 16 | PHP_CS_FIXER_VERSION: v2.17.3 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup PHP 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: 7.4 26 | coverage: none 27 | tools: php-cs-fixer:${{ env.PHP_CS_FIXER_VERSION }} 28 | 29 | - name: Restore PHP-CS-Fixer cache 30 | uses: actions/cache@v3 31 | with: 32 | path: .php_cs.cache 33 | key: "php-cs-fixer" 34 | restore-keys: "php-cs-fixer" 35 | 36 | - name: Run PHP-CS-Fixer, version ${{ env.PHP_CS_FIXER_VERSION }} 37 | run: | 38 | php-cs-fixer fix --diff --diff-format=udiff --dry-run --verbose 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # composer related 2 | composer.lock 3 | composer.phar 4 | vendor 5 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | setRules([ 5 | '@PSR1' => true, 6 | ]) 7 | ->setFinder( 8 | PhpCsFixer\Finder::create() 9 | ->in(__DIR__) 10 | ) 11 | ; 12 | -------------------------------------------------------------------------------- /OLE.php: -------------------------------------------------------------------------------- 1 | | 17 | // | Based on OLE::Storage_Lite by Kawai, Takanori | 18 | // +----------------------------------------------------------------------+ 19 | // 20 | // $Id$ 21 | 22 | 23 | /** 24 | * Constants for OLE package 25 | */ 26 | define('OLE_PPS_TYPE_ROOT', 0x05); 27 | define('OLE_PPS_TYPE_DIR', 0x01); 28 | define('OLE_PPS_TYPE_FILE', 0x02); 29 | define('OLE_DATA_SIZE_SMALL', 0x1000); 30 | define('OLE_LONG_INT_SIZE', 4); 31 | define('OLE_PPS_SIZE', 0x80); 32 | define('OLE_DIFSECT', 0xFFFFFFFC); 33 | define('OLE_FATSECT', 0xFFFFFFFD); 34 | define('OLE_ENDOFCHAIN', 0xFFFFFFFE); 35 | define('OLE_FREESECT', 0xFFFFFFFF); 36 | define('OLE_LITTLE_ENDIAN', 0xFFFE); 37 | define('OLE_VERSION_MAJOR_3', 0x0003); 38 | define('OLE_VERSION_MINOR', 0x003E); 39 | define('OLE_SECTOR_SHIFT_3', 0x0009); 40 | define('OLE_MINI_SECTOR_SHIFT', 0x0006); 41 | define('OLE_CFB_SIGNATURE', "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"); 42 | 43 | if (!class_exists('PEAR')) { 44 | require_once 'PEAR.php'; 45 | } 46 | 47 | /** 48 | * Array for storing OLE instances that are accessed from 49 | * OLE_ChainedBlockStream::stream_open(). 50 | * @var array 51 | */ 52 | $GLOBALS['_OLE_INSTANCES'] = array(); 53 | 54 | /** 55 | * OLE package base class. 56 | * 57 | * @category Structures 58 | * @package OLE 59 | * @author Xavier Noguer 60 | * @author Christian Schmidt 61 | */ 62 | class OLE extends PEAR 63 | { 64 | 65 | /** 66 | * The file handle for reading an OLE container 67 | * @var resource 68 | */ 69 | var $_file_handle; 70 | 71 | /** 72 | * Reference to the sbat stream 73 | * @var resource 74 | */ 75 | var $_small_handle; 76 | 77 | /** 78 | * Array of PPS's found on the OLE container 79 | * @var array 80 | */ 81 | var $_list; 82 | 83 | /** 84 | * Root directory of OLE container 85 | * @var OLE_PPS_Root 86 | */ 87 | var $root; 88 | 89 | /** 90 | * Big Block Allocation Table 91 | * @var array (blockId => nextBlockId) 92 | */ 93 | var $bbat; 94 | 95 | /** 96 | * Short Block Allocation Table 97 | * @var array (blockId => nextBlockId) 98 | */ 99 | var $sbat; 100 | 101 | /** 102 | * Size of big blocks. This is usually 512. 103 | * @var int number of octets per block. 104 | */ 105 | var $bigBlockSize; 106 | 107 | /** 108 | * The big block threshhold. 109 | * @var int 110 | */ 111 | var $bigBlockThreshold; 112 | 113 | /** 114 | * Size of small blocks. This is usually 64. 115 | * @var int number of octets per block 116 | */ 117 | var $smallBlockSize; 118 | 119 | /** 120 | * Creates a new OLE object 121 | * @access public 122 | */ 123 | function __construct() 124 | { 125 | $this->_list = array(); 126 | } 127 | 128 | /** 129 | * Destructor (using PEAR) 130 | * Just closes the file handle on the OLE file. 131 | * 132 | * @access private 133 | */ 134 | function _OLE() 135 | { 136 | fclose($this->_file_handle); 137 | } 138 | 139 | /** 140 | * Reads an OLE container from the contents of the file given. 141 | * 142 | * @access public 143 | * @param string $file 144 | * @return mixed true on success, PEAR_Error on failure 145 | */ 146 | function read($file) 147 | { 148 | $fh = @fopen($file, "r"); 149 | if (!$fh) { 150 | return $this->raiseError("Can't open file $file"); 151 | } 152 | 153 | return $this->readStream($fh); 154 | } 155 | 156 | /** 157 | * Reads an OLE container from the contents of the stream given. 158 | * 159 | * @access public 160 | * @param resource $fh 161 | * @return mixed true on success, PEAR_Error on failure 162 | */ 163 | function readStream($fh) 164 | { 165 | $this->_file_handle = $fh; 166 | 167 | $signature = fread($fh, 8); 168 | if (OLE_CFB_SIGNATURE != $signature) { 169 | return $this->raiseError("File doesn't seem to be an OLE container."); 170 | } 171 | fseek($fh, 28); 172 | if ($this->_readInt2($fh) != OLE_LITTLE_ENDIAN) { 173 | // This shouldn't be a problem in practice 174 | return $this->raiseError("Only Little-Endian encoding is supported."); 175 | } 176 | // Size of blocks and short blocks in bytes 177 | $this->bigBlockSize = pow(2, $this->_readInt2($fh)); 178 | $this->smallBlockSize = pow(2, $this->_readInt2($fh)); 179 | 180 | // Skip UID, revision number and version number 181 | fseek($fh, 44); 182 | // Number of blocks in Big Block Allocation Table 183 | $bbatBlockCount = $this->_readInt4($fh); 184 | 185 | // Root chain 1st block 186 | $directoryFirstBlockId = $this->_readInt4($fh); 187 | 188 | // Skip unused bytes 189 | fseek($fh, 56); 190 | // Streams shorter than this are stored using small blocks 191 | $this->bigBlockThreshold = $this->_readInt4($fh); 192 | // Block id of first sector in Short Block Allocation Table 193 | $sbatFirstBlockId = $this->_readInt4($fh); 194 | // Number of blocks in Short Block Allocation Table 195 | $sbbatBlockCount = $this->_readInt4($fh); 196 | // Block id of first sector in Master Block Allocation Table 197 | $mbatFirstBlockId = $this->_readInt4($fh); 198 | // Number of blocks in Master Block Allocation Table 199 | $mbbatBlockCount = $this->_readInt4($fh); 200 | $this->bbat = array(); 201 | 202 | // Remaining 4 * 109 bytes of current block is beginning of Master 203 | // Block Allocation Table 204 | $mbatBlocks = array(); 205 | for ($i = 0; $i < 109; $i++) { 206 | $mbatBlocks[] = $this->_readInt4($fh); 207 | } 208 | 209 | // Read rest of Master Block Allocation Table (if any is left) 210 | $pos = $this->_getBlockOffset($mbatFirstBlockId); 211 | for ($i = 0; $i < $mbbatBlockCount; $i++) { 212 | fseek($fh, $pos); 213 | for ($j = 0; $j < $this->bigBlockSize / 4 - 1; $j++) { 214 | $mbatBlocks[] = $this->_readInt4($fh); // ffix - invalid block address check 215 | } 216 | // Last block id in each block points to next block 217 | $chainBlock = $this->_readInt4($fh); 218 | if ($chainBlock === OLE_ENDOFCHAIN) { // ENDOFCHAIN 219 | break; 220 | } 221 | $pos = $this->_getBlockOffset($chainBlock); 222 | } 223 | 224 | 225 | // Read Big Block Allocation Table according to chain specified by 226 | // $mbatBlocks 227 | for ($i = 0; $i < $bbatBlockCount; $i++) { 228 | $pos = $this->_getBlockOffset($mbatBlocks[$i]); 229 | fseek($fh, $pos); 230 | for ($j = 0 ; $j < $this->bigBlockSize / 4; $j++) { 231 | $this->bbat[] = $this->_readInt4($fh); 232 | } 233 | } 234 | 235 | // Read short block allocation table (SBAT) 236 | $this->sbat = array(); 237 | $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4; 238 | $sbatFh = $this->getStream($sbatFirstBlockId); 239 | if (!$sbatFh) { 240 | // Avoid an infinite loop if ChainedBlockStream.php somehow is 241 | // missing 242 | return false; 243 | } 244 | 245 | for ($blockId = 0; $blockId < $shortBlockCount; $blockId++) { 246 | $this->sbat[$blockId] = $this->_readInt4($sbatFh); 247 | } 248 | fclose($sbatFh); 249 | 250 | $this->_readPpsWks($directoryFirstBlockId); 251 | 252 | return true; 253 | } 254 | 255 | /** 256 | * @param int $blockId block id 257 | * @return int byte offset from beginning of file 258 | * @access private 259 | */ 260 | function _getBlockOffset($blockId) 261 | { 262 | return 512 + $blockId * $this->bigBlockSize; 263 | } 264 | 265 | /** 266 | * Returns a stream for use with fread() etc. External callers should 267 | * use OLE_PPS_File::getStream(). 268 | * @param int|PPS $blockIdOrPps block id or PPS 269 | * @return resource read-only stream 270 | */ 271 | function getStream($blockIdOrPps) 272 | { 273 | include_once 'OLE/ChainedBlockStream.php'; 274 | static $isRegistered = false; 275 | if (!$isRegistered) { 276 | stream_wrapper_register('ole-chainedblockstream', 277 | 'OLE_ChainedBlockStream'); 278 | $isRegistered = true; 279 | } 280 | 281 | // Store current instance in global array, so that it can be accessed 282 | // in OLE_ChainedBlockStream::stream_open(). 283 | // Object is removed from self::$instances in OLE_Stream::close(). 284 | $GLOBALS['_OLE_INSTANCES'][] = $this; 285 | $keys = array_keys($GLOBALS['_OLE_INSTANCES']); 286 | $instanceId = end($keys); 287 | 288 | $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId; 289 | if (is_a($blockIdOrPps, 'OLE_PPS')) { 290 | $path .= '&blockId=' . $blockIdOrPps->_StartBlock; 291 | $path .= '&size=' . $blockIdOrPps->Size; 292 | } else { 293 | $path .= '&blockId=' . $blockIdOrPps; 294 | } 295 | return fopen($path, 'r'); 296 | } 297 | 298 | /** 299 | * Reads a signed char. 300 | * @param resource $fh file handle 301 | * @return int 302 | * @access private 303 | */ 304 | function _readInt1($fh) 305 | { 306 | list(, $tmp) = unpack("c", fread($fh, 1)); 307 | return $tmp; 308 | } 309 | 310 | /** 311 | * Reads an unsigned short (2 octets). 312 | * @param resource $fh file handle 313 | * @return int 314 | * @access private 315 | */ 316 | function _readInt2($fh) 317 | { 318 | list(, $tmp) = unpack("v", fread($fh, 2)); 319 | return $tmp; 320 | } 321 | 322 | /** 323 | * Reads an unsigned long (4 octets). 324 | * @param resource file handle 325 | * @return int 326 | * @access private 327 | */ 328 | function _readInt4($fh) 329 | { 330 | list(, $tmp) = unpack("V", fread($fh, 4)); 331 | return $tmp; 332 | } 333 | 334 | /** 335 | * Gets information about all PPS's on the OLE container from the PPS WK's 336 | * creates an OLE_PPS object for each one. 337 | * 338 | * @access private 339 | * @param integer $blockId the block id of the first block 340 | * @return mixed true on success, PEAR_Error on failure 341 | */ 342 | function _readPpsWks($blockId) 343 | { 344 | $fh = $this->getStream($blockId); 345 | for ($pos = 0; ; $pos += 128) { 346 | fseek($fh, $pos, SEEK_SET); 347 | $nameUtf16 = fread($fh, 64); 348 | $nameLength = $this->_readInt2($fh); 349 | $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2); 350 | // Simple conversion from UTF-16LE to ISO-8859-1 351 | $name = str_replace("\x00", "", $nameUtf16); 352 | $type = $this->_readInt1($fh); 353 | switch ($type) { 354 | case OLE_PPS_TYPE_ROOT: 355 | require_once 'OLE/PPS/Root.php'; 356 | $pps = new OLE_PPS_Root(null, null, array()); 357 | $this->root = $pps; 358 | break; 359 | case OLE_PPS_TYPE_DIR: 360 | $pps = new OLE_PPS(null, null, null, null, null, 361 | null, null, null, null, array()); 362 | break; 363 | case OLE_PPS_TYPE_FILE: 364 | require_once 'OLE/PPS/File.php'; 365 | $pps = new OLE_PPS_File($name); 366 | break; 367 | default: 368 | continue 2; 369 | } 370 | fseek($fh, 1, SEEK_CUR); // skip Color Flag 371 | $pps->Type = $type; 372 | $pps->Name = $name; 373 | $pps->PrevPps = $this->_readInt4($fh); // Left Sibling ID 374 | $pps->NextPps = $this->_readInt4($fh); // Right Sibling ID 375 | $pps->DirPps = $this->_readInt4($fh); // Child ID 376 | fseek($fh, 20, SEEK_CUR); // skip CLSID (16 bytes) + State Bits 377 | $pps->Time1st = OLE::OLE2LocalDate(fread($fh, 8)); 378 | $pps->Time2nd = OLE::OLE2LocalDate(fread($fh, 8)); 379 | $pps->_StartBlock = $this->_readInt4($fh); 380 | $pps->Size = $this->_readInt4($fh); 381 | $pps->No = count($this->_list); 382 | $this->_list[] = $pps; 383 | 384 | if ($type == OLE_PPS_TYPE_ROOT) { 385 | $this->_small_handle = $this->getStream($pps->_StartBlock); 386 | } 387 | 388 | // check if the PPS tree (starting from root) is complete 389 | if (isset($this->root) && 390 | $this->_ppsTreeComplete($this->root->No)) { 391 | 392 | break; 393 | } 394 | } 395 | fclose($fh); 396 | 397 | // Initialize $pps->children on directories 398 | foreach ($this->_list as $pps) { 399 | if ($pps->Type == OLE_PPS_TYPE_DIR || $pps->Type == OLE_PPS_TYPE_ROOT) { 400 | $nos = array($pps->DirPps); 401 | $pps->children = array(); 402 | while ($nos) { 403 | $no = array_pop($nos); 404 | if ($no != OLE_FREESECT) { 405 | $childPps = $this->_list[$no]; 406 | $nos[] = $childPps->PrevPps; 407 | $nos[] = $childPps->NextPps; 408 | $pps->children[] = $childPps; 409 | } 410 | } 411 | } 412 | } 413 | 414 | return true; 415 | } 416 | 417 | /** 418 | * It checks whether the PPS tree is complete (all PPS's read) 419 | * starting with the given PPS (not necessarily root) 420 | * 421 | * @access private 422 | * @param integer $index The index of the PPS from which we are checking 423 | * @return boolean Whether the PPS tree for the given PPS is complete 424 | */ 425 | function _ppsTreeComplete($index) 426 | { 427 | return isset($this->_list[$index]) && 428 | ($pps = $this->_list[$index]) && 429 | ($pps->PrevPps == OLE_FREESECT || 430 | $this->_ppsTreeComplete($pps->PrevPps)) && 431 | ($pps->NextPps == OLE_FREESECT || 432 | $this->_ppsTreeComplete($pps->NextPps)) && 433 | ($pps->DirPps == OLE_FREESECT || 434 | $this->_ppsTreeComplete($pps->DirPps)); 435 | } 436 | 437 | /** 438 | * Checks whether a PPS is a File PPS or not. 439 | * If there is no PPS for the index given, it will return false. 440 | * @param integer $index The index for the PPS 441 | * @return bool true if it's a File PPS, false otherwise 442 | * @access public 443 | */ 444 | function isFile($index) 445 | { 446 | if (isset($this->_list[$index])) { 447 | return ($this->_list[$index]->Type == OLE_PPS_TYPE_FILE); 448 | } 449 | return false; 450 | } 451 | 452 | /** 453 | * Checks whether a PPS is a Root PPS or not. 454 | * If there is no PPS for the index given, it will return false. 455 | * @param integer $index The index for the PPS. 456 | * @return bool true if it's a Root PPS, false otherwise 457 | * @access public 458 | */ 459 | function isRoot($index) 460 | { 461 | if (isset($this->_list[$index])) { 462 | return ($this->_list[$index]->Type == OLE_PPS_TYPE_ROOT); 463 | } 464 | return false; 465 | } 466 | 467 | /** 468 | * Gives the total number of PPS's found in the OLE container. 469 | * @return integer The total number of PPS's found in the OLE container 470 | * @access public 471 | */ 472 | function ppsTotal() 473 | { 474 | return count($this->_list); 475 | } 476 | 477 | /** 478 | * Gets data from a PPS 479 | * If there is no PPS for the index given, it will return an empty string. 480 | * @param integer $index The index for the PPS 481 | * @param integer $position The position from which to start reading 482 | * (relative to the PPS) 483 | * @param integer $length The amount of bytes to read (at most) 484 | * @return string The binary string containing the data requested 485 | * @access public 486 | * @see OLE_PPS_File::getStream() 487 | */ 488 | function getData($index, $position, $length) 489 | { 490 | // if position is not valid return empty string 491 | if (!isset($this->_list[$index]) || 492 | $position >= $this->_list[$index]->Size || 493 | $position < 0) { 494 | 495 | return ''; 496 | } 497 | $fh = $this->getStream($this->_list[$index]); 498 | $data = stream_get_contents($fh, $length, $position); 499 | fclose($fh); 500 | return $data; 501 | } 502 | 503 | /** 504 | * Gets the data length from a PPS 505 | * If there is no PPS for the index given, it will return 0. 506 | * @param integer $index The index for the PPS 507 | * @return integer The amount of bytes in data the PPS has 508 | * @access public 509 | */ 510 | function getDataLength($index) 511 | { 512 | if (isset($this->_list[$index])) { 513 | return $this->_list[$index]->Size; 514 | } 515 | return 0; 516 | } 517 | 518 | /** 519 | * Utility function to transform ASCII text to Unicode 520 | * 521 | * @access public 522 | * @static 523 | * @param string $ascii The ASCII string to transform 524 | * @return string The string in Unicode 525 | */ 526 | static function Asc2Ucs($ascii) 527 | { 528 | $rawname = ''; 529 | for ($i = 0; $i < strlen($ascii); $i++) { 530 | $rawname .= $ascii[$i] . "\x00"; 531 | } 532 | return $rawname; 533 | } 534 | 535 | /** 536 | * Utility function 537 | * Returns a string for the OLE container with the date given 538 | * 539 | * @access public 540 | * @static 541 | * @param integer $date A timestamp 542 | * @return string The string for the OLE container 543 | */ 544 | static function LocalDate2OLE($date = null) 545 | { 546 | if (!isset($date)) { 547 | return "\x00\x00\x00\x00\x00\x00\x00\x00"; 548 | } 549 | 550 | // factor used for separating numbers into 4 bytes parts 551 | $factor = pow(2, 32); 552 | 553 | // days from 1-1-1601 until the beggining of UNIX era 554 | $days = 134774; 555 | // calculate seconds 556 | $big_date = $days * 24 * 3600 + 557 | gmmktime(date("H",$date),date("i",$date),date("s",$date), 558 | date("m",$date),date("d",$date),date("Y",$date)); 559 | // multiply just to make MS happy 560 | $big_date *= 10000000; 561 | 562 | $high_part = floor($big_date / $factor); 563 | // lower 4 bytes 564 | $low_part = floor((($big_date / $factor) - $high_part) * $factor); 565 | 566 | // Make HEX string 567 | $res = ''; 568 | 569 | for ($i = 0; $i < 4; $i++) { 570 | $hex = (int) $low_part % 0x100; 571 | $res .= pack('c', $hex); 572 | $low_part /= 0x100; 573 | } 574 | for ($i = 0; $i < 4; $i++) { 575 | $hex = (int) $high_part % 0x100; 576 | $res .= pack('c', $hex); 577 | $high_part /= 0x100; 578 | } 579 | return $res; 580 | } 581 | 582 | /** 583 | * Returns a timestamp from an OLE container's date 584 | * @param integer $string A binary string with the encoded date 585 | * @return string The timestamp corresponding to the string 586 | * @access public 587 | * @static 588 | */ 589 | static function OLE2LocalDate($string) 590 | { 591 | if (strlen($string) != 8) { 592 | return new PEAR_Error("Expecting 8 byte string"); 593 | } 594 | 595 | // factor used for separating numbers into 4 bytes parts 596 | $factor = pow(2,32); 597 | $high_part = 0; 598 | for ($i = 0; $i < 4; $i++) { 599 | list(, $high_part) = unpack('C', $string[(7 - $i)]); 600 | if ($i < 3) { 601 | $high_part *= 0x100; 602 | } 603 | } 604 | $low_part = 0; 605 | for ($i = 4; $i < 8; $i++) { 606 | list(, $low_part) = unpack('C', $string[(7 - $i)]); 607 | if ($i < 7) { 608 | $low_part *= 0x100; 609 | } 610 | } 611 | $big_date = ($high_part * $factor) + $low_part; 612 | // translate to seconds 613 | $big_date /= 10000000; 614 | 615 | // days from 1-1-1601 until the beggining of UNIX era 616 | $days = 134774; 617 | 618 | // translate to seconds from beggining of UNIX era 619 | $big_date -= $days * 24 * 3600; 620 | return floor($big_date); 621 | } 622 | } 623 | -------------------------------------------------------------------------------- /OLE/ChainedBlockStream.php: -------------------------------------------------------------------------------- 1 | 19 | * @license http://www.php.net/license/3_0.txt PHP License 3.0 20 | * @version CVS: $Id$ 21 | * @link http://pear.php.net/package/OLE 22 | * @since File available since Release 0.6.0 23 | */ 24 | 25 | if (!class_exists('PEAR')) { 26 | require_once 'PEAR.php'; 27 | } 28 | 29 | if (!class_exists('OLE')) { 30 | require_once 'OLE.php'; 31 | } 32 | 33 | 34 | /** 35 | * Stream wrapper for reading data stored in an OLE file. Implements methods 36 | * for PHP's stream_wrapper_register(). For creating streams using this 37 | * wrapper, use OLE_PPS_File::getStream(). 38 | * 39 | * @category Structures 40 | * @package OLE 41 | * @author Christian Schmidt 42 | * @license http://www.php.net/license/3_0.txt PHP License 3.0 43 | * @version Release: @package_version@ 44 | * @link http://pear.php.net/package/OLE 45 | * @since Class available since Release 0.6.0 46 | */ 47 | class OLE_ChainedBlockStream extends PEAR 48 | { 49 | /** 50 | * The OLE container of the file that is being read. 51 | * @var OLE 52 | */ 53 | var $ole; 54 | 55 | /** 56 | * Parameters specified by fopen(). 57 | * @var array 58 | */ 59 | var $params; 60 | 61 | /** 62 | * The binary data of the file. 63 | * @var string 64 | */ 65 | var $data; 66 | 67 | /** 68 | * The file pointer. 69 | * @var int byte offset 70 | */ 71 | var $pos; 72 | 73 | var $context; 74 | 75 | /** 76 | * Implements support for fopen(). 77 | * For creating streams using this wrapper, use OLE_PPS_File::getStream(). 78 | * @param string resource name including scheme, e.g. 79 | * ole-chainedblockstream://oleInstanceId=1 80 | * @param string only "r" is supported 81 | * @param int mask of STREAM_REPORT_ERRORS and STREAM_USE_PATH 82 | * @param string absolute path of the opened stream (out parameter) 83 | * @return bool true on success 84 | */ 85 | function stream_open($path, $mode, $options, &$openedPath) 86 | { 87 | if ($mode != 'r') { 88 | if ($options & STREAM_REPORT_ERRORS) { 89 | trigger_error('Only reading is supported', E_USER_WARNING); 90 | } 91 | return false; 92 | } 93 | 94 | // 25 is length of "ole-chainedblockstream://" 95 | parse_str(substr($path, 25), $this->params); 96 | if (!isset($this->params['oleInstanceId'], 97 | $this->params['blockId'], 98 | $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']])) { 99 | 100 | if ($options & STREAM_REPORT_ERRORS) { 101 | trigger_error('OLE stream not found', E_USER_WARNING); 102 | } 103 | return false; 104 | } 105 | $this->ole = $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']]; 106 | 107 | $blockId = $this->params['blockId']; 108 | $this->data = ''; 109 | if (isset($this->params['size']) && 110 | $this->params['size'] < $this->ole->bigBlockThreshold && 111 | $blockId != $this->ole->root->_StartBlock) { 112 | 113 | // Block id refers to small blocks 114 | $rootPos = 0; 115 | while ($blockId != OLE_ENDOFCHAIN) { 116 | $pos = $rootPos + $blockId * $this->ole->smallBlockSize; 117 | 118 | $blockId = $this->ole->sbat[$blockId]; 119 | fseek($this->ole->_small_handle, $pos); 120 | $this->data .= fread($this->ole->_small_handle, $this->ole->smallBlockSize); 121 | } 122 | } else { 123 | // Block id refers to big blocks 124 | while ($blockId != OLE_ENDOFCHAIN) { 125 | $pos = $this->ole->_getBlockOffset($blockId); 126 | fseek($this->ole->_file_handle, $pos); 127 | $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize); 128 | $blockId = $this->ole->bbat[$blockId]; 129 | } 130 | } 131 | if (isset($this->params['size'])) { 132 | $this->data = substr($this->data, 0, $this->params['size']); 133 | } 134 | 135 | if ($options & STREAM_USE_PATH) { 136 | $openedPath = $path; 137 | } 138 | 139 | return true; 140 | } 141 | 142 | /** 143 | * Implements support for fclose(). 144 | */ 145 | function stream_close() 146 | { 147 | $this->ole = null; 148 | 149 | // $GLOBALS is not always defined in stream_close 150 | if (isset($GLOBALS['_OLE_INSTANCES'])) { 151 | unset($GLOBALS['_OLE_INSTANCES']); 152 | } 153 | } 154 | 155 | /** 156 | * Implements support for fread(), fgets() etc. 157 | * @param int maximum number of bytes to read 158 | * @return string 159 | */ 160 | function stream_read($count) 161 | { 162 | if ($this->stream_eof()) { 163 | return false; 164 | } 165 | 166 | $pos = isset($this->pos) ? $this->pos : 0; 167 | 168 | $s = substr($this->data, $pos, $count); 169 | $this->pos += $count; 170 | return $s; 171 | } 172 | 173 | /** 174 | * Implements support for feof(). 175 | * @return bool TRUE if the file pointer is at EOF; otherwise FALSE 176 | */ 177 | function stream_eof() 178 | { 179 | $eof = $this->pos >= strlen($this->data); 180 | // Workaround for bug in PHP 5.0.x: http://bugs.php.net/27508 181 | if (version_compare(PHP_VERSION, '5.0', '>=') && 182 | version_compare(PHP_VERSION, '5.1', '<')) { 183 | 184 | $eof = !$eof; 185 | } 186 | return $eof; 187 | } 188 | 189 | /** 190 | * Returns the position of the file pointer, i.e. its offset into the file 191 | * stream. Implements support for ftell(). 192 | * @return int 193 | */ 194 | function stream_tell() 195 | { 196 | return $this->pos; 197 | } 198 | 199 | /** 200 | * Implements support for fseek(). 201 | * @param int byte offset 202 | * @param int SEEK_SET, SEEK_CUR or SEEK_END 203 | * @return bool 204 | */ 205 | function stream_seek($offset, $whence) 206 | { 207 | if ($whence == SEEK_SET && $offset >= 0) { 208 | $this->pos = $offset; 209 | } elseif ($whence == SEEK_CUR && -$offset <= $this->pos) { 210 | $this->pos += $offset; 211 | } elseif ($whence == SEEK_END && -$offset <= strlen($this->data)) { 212 | $this->pos = strlen($this->data) + $offset; 213 | } else { 214 | return false; 215 | } 216 | return true; 217 | } 218 | 219 | /** 220 | * Implements support for fstat(). Currently the only supported field is 221 | * "size". 222 | * @return array 223 | */ 224 | function stream_stat() 225 | { 226 | return array( 227 | 'size' => strlen($this->data), 228 | ); 229 | } 230 | 231 | /** 232 | * PHP 5.6 for some reason wants this to be implemented. Currently returning false as if it wasn't implemented. 233 | * @return boolean 234 | */ 235 | function stream_flush() 236 | { 237 | // If not implemented, FALSE is assumed as the return value. 238 | return false; 239 | } 240 | 241 | // Methods used by stream_wrapper_register() that are not implemented: 242 | // int stream_write ( string data ) 243 | // bool rename ( string path_from, string path_to ) 244 | // bool mkdir ( string path, int mode, int options ) 245 | // bool rmdir ( string path, int options ) 246 | // bool dir_opendir ( string path, int options ) 247 | // array url_stat ( string path, int flags ) 248 | // string dir_readdir ( void ) 249 | // bool dir_rewinddir ( void ) 250 | // bool dir_closedir ( void ) 251 | } 252 | -------------------------------------------------------------------------------- /OLE/PPS.php: -------------------------------------------------------------------------------- 1 | | 17 | // | Based on OLE::Storage_Lite by Kawai, Takanori | 18 | // +----------------------------------------------------------------------+ 19 | // 20 | // $Id$ 21 | 22 | 23 | if (!class_exists('PEAR')) { 24 | require_once 'PEAR.php'; 25 | } 26 | 27 | if (!class_exists('OLE')) { 28 | require_once 'OLE.php'; 29 | } 30 | 31 | /** 32 | * Class for creating PPS's for OLE containers 33 | * 34 | * @author Xavier Noguer 35 | * @category Structures 36 | * @package OLE 37 | */ 38 | class OLE_PPS extends PEAR 39 | { 40 | /** 41 | * The PPS index 42 | * @var integer 43 | */ 44 | var $No; 45 | 46 | /** 47 | * The PPS name (in Unicode) 48 | * @var string 49 | */ 50 | var $Name; 51 | 52 | /** 53 | * The PPS type. Dir, Root or File 54 | * @var integer 55 | */ 56 | var $Type; 57 | 58 | /** 59 | * The index of the previous PPS 60 | * @var integer 61 | */ 62 | var $PrevPps; 63 | 64 | /** 65 | * The index of the next PPS 66 | * @var integer 67 | */ 68 | var $NextPps; 69 | 70 | /** 71 | * The index of it's first child if this is a Dir or Root PPS 72 | * @var integer 73 | */ 74 | var $DirPps; 75 | 76 | /** 77 | * A timestamp 78 | * @var integer 79 | */ 80 | var $Time1st; 81 | 82 | /** 83 | * A timestamp 84 | * @var integer 85 | */ 86 | var $Time2nd; 87 | 88 | /** 89 | * Starting block (small or big) for this PPS's data inside the container 90 | * @var integer 91 | */ 92 | var $_StartBlock; 93 | 94 | /** 95 | * The size of the PPS's data (in bytes) 96 | * @var integer 97 | */ 98 | var $Size; 99 | 100 | /** 101 | * The PPS's data (only used if it's not using a temporary file) 102 | * @var string 103 | */ 104 | var $_data; 105 | 106 | /** 107 | * Array of child PPS's (only used by Root and Dir PPS's) 108 | * @var array 109 | */ 110 | var $children = array(); 111 | 112 | /** 113 | * Pointer to OLE container 114 | * @var OLE 115 | */ 116 | var $ole; 117 | 118 | /** 119 | * The constructor 120 | * 121 | * @access public 122 | * @param integer $No The PPS index 123 | * @param string $name The PPS name 124 | * @param integer $type The PPS type. Dir, Root or File 125 | * @param integer $prev The index of the previous PPS 126 | * @param integer $next The index of the next PPS 127 | * @param integer $dir The index of it's first child if this is a Dir or Root PPS 128 | * @param integer $time_1st A timestamp 129 | * @param integer $time_2nd A timestamp 130 | * @param string $data The (usually binary) source data of the PPS 131 | * @param array $children Array containing children PPS for this PPS 132 | */ 133 | function __construct($No, $name, $type, $prev, $next, $dir, $time_1st, $time_2nd, $data, $children) 134 | { 135 | $this->No = $No; 136 | $this->Name = $name; 137 | $this->Type = $type; 138 | $this->PrevPps = $prev; 139 | $this->NextPps = $next; 140 | $this->DirPps = $dir; 141 | $this->Time1st = $time_1st; 142 | $this->Time2nd = $time_2nd; 143 | $this->_data = $data; 144 | $this->children = $children; 145 | if ($data != '') { 146 | $this->Size = strlen($data); 147 | } else { 148 | $this->Size = 0; 149 | } 150 | } 151 | 152 | /** 153 | * Returns the amount of data saved for this PPS 154 | * 155 | * @access private 156 | * @return integer The amount of data (in bytes) 157 | */ 158 | function _DataLen() 159 | { 160 | if (!isset($this->_data)) { 161 | return 0; 162 | } 163 | if (isset($this->_PPS_FILE)) { 164 | fseek($this->_PPS_FILE, 0); 165 | $stats = fstat($this->_PPS_FILE); 166 | return $stats[7]; 167 | } else { 168 | return strlen($this->_data); 169 | } 170 | } 171 | 172 | /** 173 | * Returns a string with the PPS's WK (What is a WK?) 174 | * 175 | * @access private 176 | * @return string The binary string 177 | */ 178 | function _getPpsWk() 179 | { 180 | $ret = $this->Name; 181 | for ($i = 0; $i < (64 - strlen($this->Name)); $i++) { 182 | $ret .= "\x00"; 183 | } 184 | $ret .= pack("v", strlen($this->Name) + 2) // 66 185 | . pack("c", $this->Type) // 67 186 | . pack("c", 0x00) //UK // 68 187 | . pack("V", $this->PrevPps) //Prev // 72 188 | . pack("V", $this->NextPps) //Next // 76 189 | . pack("V", $this->DirPps) //Dir // 80 190 | . "\x00\x09\x02\x00" // 84 191 | . "\x00\x00\x00\x00" // 88 192 | . "\xc0\x00\x00\x00" // 92 193 | . "\x00\x00\x00\x46" // 96 // Seems to be ok only for Root 194 | . "\x00\x00\x00\x00" // 100 195 | . OLE::LocalDate2OLE($this->Time1st) // 108 196 | . OLE::LocalDate2OLE($this->Time2nd) // 116 197 | . pack("V", isset($this->_StartBlock)? 198 | $this->_StartBlock:0) // 120 199 | . pack("V", $this->Size) // 124 200 | . pack("V", 0); // 128 201 | return $ret; 202 | } 203 | 204 | /** 205 | * Updates index and pointers to previous, next and children PPS's for this 206 | * PPS. I don't think it'll work with Dir PPS's. 207 | * 208 | * @access private 209 | * @param array &$pps_array Reference to the array of PPS's for the whole OLE 210 | * container 211 | * @return integer The index for this PPS 212 | */ 213 | static function _savePpsSetPnt(&$raList, $to_save, $depth = 0) 214 | { 215 | if ( !is_array($to_save) || (count($to_save) == 0) ) { 216 | return OLE_FREESECT; 217 | } 218 | elseif( count($to_save) == 1 ) { 219 | $cnt = count($raList); 220 | // If the first entry, it's the root... Don't clone it! 221 | $raList[$cnt] = ( $depth == 0 ) ? $to_save[0] : clone $to_save[0]; 222 | $raList[$cnt]->No = $cnt; 223 | $raList[$cnt]->PrevPps = OLE_FREESECT; 224 | $raList[$cnt]->NextPps = OLE_FREESECT; 225 | $raList[$cnt]->DirPps = self::_savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++); 226 | return $cnt; 227 | } 228 | else { 229 | $iPos = floor(count($to_save) / 2); 230 | $aPrev = array_slice($to_save, 0, $iPos); 231 | $aNext = array_slice($to_save, $iPos + 1); 232 | 233 | $cnt = count($raList); 234 | // If the first entry, it's the root... Don't clone it! 235 | $raList[$cnt] = ( $depth == 0 ) ? $to_save[$iPos] : clone $to_save[$iPos]; 236 | $raList[$cnt]->No = $cnt; 237 | $raList[$cnt]->PrevPps = self::_savePpsSetPnt($raList, $aPrev, $depth++); 238 | $raList[$cnt]->NextPps = self::_savePpsSetPnt($raList, $aNext, $depth++); 239 | $raList[$cnt]->DirPps = self::_savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++); 240 | 241 | return $cnt; 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /OLE/PPS/File.php: -------------------------------------------------------------------------------- 1 | | 17 | // | Based on OLE::Storage_Lite by Kawai, Takanori | 18 | // +----------------------------------------------------------------------+ 19 | // 20 | // $Id$ 21 | 22 | 23 | if (!class_exists('OLE_PPS')) { 24 | require_once 'OLE/PPS.php'; 25 | } 26 | 27 | if (!class_exists('System')) { 28 | require_once 'System.php'; 29 | } 30 | 31 | /** 32 | * Class for creating File PPS's for OLE containers 33 | * 34 | * @author Xavier Noguer 35 | * @category Structures 36 | * @package OLE 37 | */ 38 | class OLE_PPS_File extends OLE_PPS 39 | { 40 | /** 41 | * The temporary dir for storing the OLE file 42 | * @var string 43 | */ 44 | var $_tmp_dir; 45 | 46 | /** 47 | * The temporary file handle 48 | * @var resource 49 | */ 50 | var $_PPS_FILE; 51 | 52 | var $_tmp_filename; 53 | 54 | /** 55 | * The constructor 56 | * 57 | * @access public 58 | * @param string $name The name of the file (in Unicode) 59 | * @see OLE::Asc2Ucs() 60 | */ 61 | function __construct($name) 62 | { 63 | $system = new System(); 64 | $this->_tmp_dir = $system->tmpdir(); 65 | parent::__construct( 66 | null, 67 | $name, 68 | OLE_PPS_TYPE_FILE, 69 | null, 70 | null, 71 | null, 72 | null, 73 | null, 74 | '', 75 | array()); 76 | } 77 | 78 | /** 79 | * Sets the temp dir used for storing the OLE file 80 | * 81 | * @access public 82 | * @param string $dir The dir to be used as temp dir 83 | * @return boolean true if given dir is valid, false otherwise 84 | */ 85 | function setTempDir($dir) 86 | { 87 | if (is_dir($dir)) { 88 | $this->_tmp_dir = $dir; 89 | return true; 90 | } 91 | return false; 92 | } 93 | 94 | /** 95 | * Initialization method. Has to be called right after OLE_PPS_File(). 96 | * 97 | * @access public 98 | * @return mixed true on success. PEAR_Error on failure 99 | */ 100 | function init() 101 | { 102 | $this->_tmp_filename = tempnam($this->_tmp_dir, "OLE_PPS_File"); 103 | $fh = @fopen($this->_tmp_filename, "w+b"); 104 | if ($fh == false) { 105 | return $this->raiseError("Can't create temporary file"); 106 | } 107 | $this->_PPS_FILE = $fh; 108 | if ($this->_PPS_FILE) { 109 | fseek($this->_PPS_FILE, 0); 110 | } 111 | 112 | return true; 113 | } 114 | 115 | /** 116 | * Append data to PPS 117 | * 118 | * @access public 119 | * @param string $data The data to append 120 | */ 121 | function append($data) 122 | { 123 | if ($this->_PPS_FILE) { 124 | fwrite($this->_PPS_FILE, $data); 125 | } else { 126 | $this->_data .= $data; 127 | } 128 | } 129 | 130 | /** 131 | * Returns a stream for reading this file using fread() etc. 132 | * @return resource a read-only stream 133 | */ 134 | function getStream() 135 | { 136 | $this->ole->getStream($this); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /OLE/PPS/Root.php: -------------------------------------------------------------------------------- 1 | | 17 | // | Based on OLE::Storage_Lite by Kawai, Takanori | 18 | // +----------------------------------------------------------------------+ 19 | // 20 | // $Id$ 21 | 22 | if (!class_exists('OLE_PPS')) { 23 | require_once 'OLE/PPS.php'; 24 | } 25 | 26 | if (!class_exists('System')) { 27 | require_once 'System.php'; 28 | } 29 | 30 | /** 31 | * Class for creating Root PPS's for OLE containers 32 | * 33 | * @author Xavier Noguer 34 | * @category Structures 35 | * @package OLE 36 | */ 37 | class OLE_PPS_Root extends OLE_PPS 38 | { 39 | /** 40 | * Flag to enable new logic 41 | * @var bool 42 | */ 43 | var $new_func = true; 44 | 45 | /** 46 | * The temporary dir for storing the OLE file 47 | * @var string 48 | */ 49 | var $_tmp_dir; 50 | 51 | var $_BIG_BLOCK_SIZE; 52 | 53 | var $_SMALL_BLOCK_SIZE; 54 | 55 | var $_tmp_filename; 56 | 57 | var $_FILEH_; 58 | 59 | /** 60 | * Constructor 61 | * 62 | * @access public 63 | * @param integer $time_1st A timestamp 64 | * @param integer $time_2nd A timestamp 65 | */ 66 | function __construct($time_1st, $time_2nd, $raChild) 67 | { 68 | $system = new System(); 69 | $this->_tmp_dir = $system->tmpdir(); 70 | parent::__construct( 71 | null, 72 | OLE::Asc2Ucs('Root Entry'), 73 | OLE_PPS_TYPE_ROOT, 74 | null, 75 | null, 76 | null, 77 | $time_1st, 78 | $time_2nd, 79 | null, 80 | $raChild); 81 | } 82 | 83 | /** 84 | * Sets the temp dir used for storing the OLE file 85 | * 86 | * @access public 87 | * @param string $dir The dir to be used as temp dir 88 | * @return true if given dir is valid, false otherwise 89 | */ 90 | function setTempDir($dir) 91 | { 92 | if (is_dir($dir)) { 93 | $this->_tmp_dir = $dir; 94 | return true; 95 | } 96 | return false; 97 | } 98 | 99 | /** 100 | * Method for saving the whole OLE container (including files). 101 | * In fact, if called with an empty argument (or '-'), it saves to a 102 | * temporary file and then outputs it's contents to stdout. 103 | * 104 | * @param string $filename The name of the file where to save the OLE container 105 | * @access public 106 | * @return mixed true on success, PEAR_Error on failure 107 | */ 108 | function save($filename) 109 | { 110 | // Initial Setting for saving 111 | $this->_BIG_BLOCK_SIZE = pow(2, 112 | ((isset($this->_BIG_BLOCK_SIZE))? $this->_adjust2($this->_BIG_BLOCK_SIZE) : 9)); 113 | $this->_SMALL_BLOCK_SIZE= pow(2, 114 | ((isset($this->_SMALL_BLOCK_SIZE))? $this->_adjust2($this->_SMALL_BLOCK_SIZE): 6)); 115 | 116 | // Open temp file if we are sending output to stdout 117 | if (($filename == '-') || ($filename == '')) { 118 | $this->_tmp_filename = tempnam($this->_tmp_dir, "OLE_PPS_Root"); 119 | $this->_FILEH_ = @fopen($this->_tmp_filename,"w+b"); 120 | if ($this->_FILEH_ == false) { 121 | return $this->raiseError("Can't create temporary file."); 122 | } 123 | } else { 124 | $this->_FILEH_ = @fopen($filename, "wb"); 125 | if ($this->_FILEH_ == false) { 126 | return $this->raiseError("Can't open $filename. It may be in use or protected."); 127 | } 128 | } 129 | // Make an array of PPS's (for Save) 130 | $aList = array(); 131 | OLE_PPS_Root::_savePpsSetPnt($aList, array($this)); 132 | // calculate values for header 133 | list($iSBDcnt, $iBBcnt, $iPPScnt) = $this->_calcSize($aList); //, $rhInfo); 134 | // Save Header 135 | $this->_saveHeader($iSBDcnt, $iBBcnt, $iPPScnt); 136 | 137 | // Make Small Data string (write SBD) 138 | $this->_data = $this->_makeSmallData($aList); 139 | 140 | // Write BB 141 | $this->_saveBigData($iSBDcnt, $aList); 142 | // Write PPS 143 | $this->_savePps($aList); 144 | // Write Big Block Depot and BDList and Adding Header informations 145 | $this->_saveBbd($iSBDcnt, $iBBcnt, $iPPScnt); 146 | // Close File, send it to stdout if necessary 147 | if (($filename == '-') || ($filename == '')) { 148 | fseek($this->_FILEH_, 0); 149 | fpassthru($this->_FILEH_); 150 | @fclose($this->_FILEH_); 151 | // Delete the temporary file. 152 | @unlink($this->_tmp_filename); 153 | } else { 154 | @fclose($this->_FILEH_); 155 | } 156 | 157 | return true; 158 | } 159 | 160 | /** 161 | * Calculate some numbers 162 | * 163 | * @access private 164 | * @param array $raList Reference to an array of PPS's 165 | * @return array The array of numbers 166 | */ 167 | function _calcSize(&$raList) 168 | { 169 | // Calculate Basic Setting 170 | $iBBcnt = 0; 171 | $iSBcnt = 0; 172 | for ($i = 0; $i < count($raList); $i++) { 173 | if ($raList[$i]->Type == OLE_PPS_TYPE_FILE) { 174 | $raList[$i]->Size = $raList[$i]->_DataLen(); 175 | if ($raList[$i]->Size < OLE_DATA_SIZE_SMALL) { 176 | $iSBcnt += floor($raList[$i]->Size / $this->_SMALL_BLOCK_SIZE) 177 | + (($raList[$i]->Size % $this->_SMALL_BLOCK_SIZE)? 1: 0); 178 | } else { 179 | $iBBcnt += (floor($raList[$i]->Size / $this->_BIG_BLOCK_SIZE) + 180 | (($raList[$i]->Size % $this->_BIG_BLOCK_SIZE)? 1: 0)); 181 | } 182 | } 183 | } 184 | $iSmallLen = $iSBcnt * $this->_SMALL_BLOCK_SIZE; 185 | $iSlCnt = floor($this->_BIG_BLOCK_SIZE / OLE_LONG_INT_SIZE); 186 | $iSBDcnt = floor($iSBcnt / $iSlCnt) + (($iSBcnt % $iSlCnt)? 1:0); 187 | $iBBcnt += (floor($iSmallLen / $this->_BIG_BLOCK_SIZE) + 188 | (( $iSmallLen % $this->_BIG_BLOCK_SIZE)? 1: 0)); 189 | $iCnt = count($raList); 190 | $iBdCnt = $this->_BIG_BLOCK_SIZE / OLE_PPS_SIZE; 191 | $iPPScnt = (floor($iCnt/$iBdCnt) + (($iCnt % $iBdCnt)? 1: 0)); 192 | 193 | return array($iSBDcnt, $iBBcnt, $iPPScnt); 194 | } 195 | 196 | /** 197 | * Helper function for caculating a magic value for block sizes 198 | * 199 | * @access private 200 | * @param integer $i2 The argument 201 | * @see save() 202 | * @return integer 203 | */ 204 | function _adjust2($i2) 205 | { 206 | $iWk = log($i2)/log(2); 207 | return ($iWk > floor($iWk))? floor($iWk)+1:$iWk; 208 | } 209 | 210 | /** 211 | * Save OLE header 212 | * 213 | * @access private 214 | * @param integer $iSBDcnt 215 | * @param integer $iBBcnt 216 | * @param integer $iPPScnt 217 | */ 218 | function _saveHeader($iSBDcnt, $iBBcnt, $iPPScnt) 219 | { 220 | $FILE = $this->_FILEH_; 221 | 222 | if($this->new_func) 223 | return $this->_create_header($iSBDcnt, $iBBcnt, $iPPScnt); 224 | 225 | // Calculate Basic Setting 226 | $iBlCnt = $this->_BIG_BLOCK_SIZE / OLE_LONG_INT_SIZE; 227 | $i1stBdL = ($this->_BIG_BLOCK_SIZE - 0x4C) / OLE_LONG_INT_SIZE; 228 | 229 | $iBdExL = 0; 230 | $iAll = $iBBcnt + $iPPScnt + $iSBDcnt; 231 | $iAllW = $iAll; 232 | $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt)? 1: 0); 233 | $iBdCnt = floor(($iAll + $iBdCntW) / $iBlCnt) + ((($iAllW+$iBdCntW) % $iBlCnt)? 1: 0); 234 | 235 | // Calculate BD count 236 | if ($iBdCnt > $i1stBdL) { 237 | while (1) { 238 | $iBdExL++; 239 | $iAllW++; 240 | $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt)? 1: 0); 241 | $iBdCnt = floor(($iAllW + $iBdCntW) / $iBlCnt) + ((($iAllW+$iBdCntW) % $iBlCnt)? 1: 0); 242 | if ($iBdCnt <= ($iBdExL*$iBlCnt+ $i1stBdL)) { 243 | break; 244 | } 245 | } 246 | } 247 | 248 | // Save Header 249 | fwrite($FILE, 250 | OLE_CFB_SIGNATURE // Header Signature (8 bytes) 251 | . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // Header CLSID (16 bytes) 252 | . pack("v", OLE_VERSION_MINOR) // Minor Version (2 bytes) 253 | . pack("v", OLE_VERSION_MAJOR_3) // Major Version (2 bytes) 254 | . pack("v", OLE_LITTLE_ENDIAN) // Byte Order (2 bytes) 255 | . pack("v", OLE_SECTOR_SHIFT_3) // Sector Shift (2 bytes) 256 | . pack("v", OLE_MINI_SECTOR_SHIFT) // Mini Sector Shift (2 bytes) 257 | . "\x00\x00\x00\x00\x00\x00" // Reserved (6 bytes) 258 | . "\x00\x00\x00\x00" // Number of Directory Sectors (4 bytes) 259 | . pack("V", $iBdCnt) // Number of FAT Sectors (4 bytes) 260 | . pack("V", $iBBcnt+$iSBDcnt) //ROOT START, First Directory Sector Location (4 bytes) 261 | . pack("V", 0) // Transaction Signature Number (4 bytes) 262 | . pack("V", 0x00001000) // Mini Stream Cutoff Size (4 bytes) 263 | . pack("V", $iSBDcnt ? 0 : OLE_ENDOFCHAIN) // First Mini FAT Sector Location (4 bytes) 264 | . pack("V", $iSBDcnt) // Number of Mini FAT Sectors (4 bytes) 265 | ); 266 | // Extra BDList Start, Count 267 | if ($iBdCnt < $i1stBdL) { 268 | fwrite($FILE, 269 | pack("V", OLE_ENDOFCHAIN). // Extra BDList Start 270 | pack("V", 0) // Extra BDList Count 271 | ); 272 | } else { 273 | fwrite($FILE, pack("V", $iAll+$iBdCnt) . pack("V", $iBdExL)); 274 | } 275 | 276 | // BDList 277 | for ($i = 0; $i < $i1stBdL && $i < $iBdCnt; $i++) { 278 | fwrite($FILE, pack("V", $iAll+$i)); 279 | } 280 | if ($i < $i1stBdL) { // free sectors 281 | for ($j = 0; $j < ($i1stBdL-$i); $j++) { 282 | fwrite($FILE, (pack("V", OLE_FREESECT))); 283 | } 284 | } 285 | } 286 | 287 | /** 288 | * Saving big data (PPS's with data bigger than OLE_DATA_SIZE_SMALL) 289 | * 290 | * @access private 291 | * @param integer $iStBlk 292 | * @param array &$raList Reference to array of PPS's 293 | */ 294 | function _saveBigData($iStBlk, &$raList) 295 | { 296 | $FILE = $this->_FILEH_; 297 | 298 | // cycle through PPS's 299 | for ($i = 0; $i < count($raList); $i++) { 300 | if ($raList[$i]->Type != OLE_PPS_TYPE_DIR) { 301 | $raList[$i]->Size = $raList[$i]->_DataLen(); 302 | if (($raList[$i]->Size >= OLE_DATA_SIZE_SMALL) || 303 | (($raList[$i]->Type == OLE_PPS_TYPE_ROOT) && isset($raList[$i]->_data))) 304 | { 305 | // Write Data 306 | if (isset($raList[$i]->_PPS_FILE)) { 307 | $iLen = 0; 308 | fseek($raList[$i]->_PPS_FILE, 0); // To The Top 309 | while($sBuff = fread($raList[$i]->_PPS_FILE, 4096)) { 310 | $iLen += strlen($sBuff); 311 | fwrite($FILE, $sBuff); 312 | } 313 | } else { 314 | fwrite($FILE, $raList[$i]->_data); 315 | } 316 | 317 | if ($raList[$i]->Size % $this->_BIG_BLOCK_SIZE) { 318 | for ($j = 0; $j < ($this->_BIG_BLOCK_SIZE - ($raList[$i]->Size % $this->_BIG_BLOCK_SIZE)); $j++) { 319 | fwrite($FILE, "\x00"); 320 | } 321 | } 322 | // Set For PPS 323 | $raList[$i]->_StartBlock = $iStBlk; 324 | $iStBlk += 325 | (floor($raList[$i]->Size / $this->_BIG_BLOCK_SIZE) + 326 | (($raList[$i]->Size % $this->_BIG_BLOCK_SIZE)? 1: 0)); 327 | } 328 | // Close file for each PPS, and unlink it 329 | if (isset($raList[$i]->_PPS_FILE)) { 330 | @fclose($raList[$i]->_PPS_FILE); 331 | $raList[$i]->_PPS_FILE = null; 332 | @unlink($raList[$i]->_tmp_filename); 333 | } 334 | } 335 | } 336 | } 337 | 338 | /** 339 | * get small data (PPS's with data smaller than OLE_DATA_SIZE_SMALL) 340 | * 341 | * @access private 342 | * @param array &$raList Reference to array of PPS's 343 | */ 344 | function _makeSmallData(&$raList) 345 | { 346 | $sRes = ''; 347 | $FILE = $this->_FILEH_; 348 | $iSmBlk = 0; 349 | 350 | for ($i = 0; $i < count($raList); $i++) { 351 | // Make SBD, small data string 352 | if ($raList[$i]->Type == OLE_PPS_TYPE_FILE) { 353 | if ($raList[$i]->Size <= 0) { 354 | continue; 355 | } 356 | if ($raList[$i]->Size < OLE_DATA_SIZE_SMALL) { 357 | $iSmbCnt = floor($raList[$i]->Size / $this->_SMALL_BLOCK_SIZE) 358 | + (($raList[$i]->Size % $this->_SMALL_BLOCK_SIZE)? 1: 0); 359 | // Add to SBD 360 | for ($j = 0; $j < ($iSmbCnt-1); $j++) { 361 | fwrite($FILE, pack("V", $j+$iSmBlk+1)); 362 | } 363 | fwrite($FILE, pack("V", OLE_ENDOFCHAIN)); 364 | 365 | // Add to Data String(this will be written for RootEntry) 366 | if ($raList[$i]->_PPS_FILE) { 367 | fseek($raList[$i]->_PPS_FILE, 0); // To The Top 368 | while ($sBuff = fread($raList[$i]->_PPS_FILE, 4096)) { 369 | $sRes .= $sBuff; 370 | } 371 | } else { 372 | $sRes .= $raList[$i]->_data; 373 | } 374 | if ($raList[$i]->Size % $this->_SMALL_BLOCK_SIZE) { 375 | for ($j = 0; $j < ($this->_SMALL_BLOCK_SIZE - ($raList[$i]->Size % $this->_SMALL_BLOCK_SIZE)); $j++) { 376 | $sRes .= "\x00"; 377 | } 378 | } 379 | // Set for PPS 380 | $raList[$i]->_StartBlock = $iSmBlk; 381 | $iSmBlk += $iSmbCnt; 382 | } 383 | } 384 | } 385 | $iSbCnt = floor($this->_BIG_BLOCK_SIZE / OLE_LONG_INT_SIZE); 386 | if ($iSmBlk % $iSbCnt) { 387 | for ($i = 0; $i < ($iSbCnt - ($iSmBlk % $iSbCnt)); $i++) { 388 | fwrite($FILE, pack("V", OLE_FREESECT)); 389 | } 390 | } 391 | return $sRes; 392 | } 393 | 394 | /** 395 | * Saves all the PPS's WKs 396 | * 397 | * @access private 398 | * @param array $raList Reference to an array with all PPS's 399 | */ 400 | function _savePps(&$raList) 401 | { 402 | // Save each PPS WK 403 | for ($i = 0; $i < count($raList); $i++) { 404 | fwrite($this->_FILEH_, $raList[$i]->_getPpsWk()); 405 | } 406 | // Adjust for Block 407 | $iCnt = count($raList); 408 | $iBCnt = $this->_BIG_BLOCK_SIZE / OLE_PPS_SIZE; 409 | if ($iCnt % $iBCnt) { 410 | for ($i = 0; $i < (($iBCnt - ($iCnt % $iBCnt)) * OLE_PPS_SIZE); $i++) { 411 | fwrite($this->_FILEH_, "\x00"); 412 | } 413 | } 414 | } 415 | 416 | /** 417 | * Saving Big Block Depot 418 | * 419 | * @access private 420 | * @param integer $iSbdSize 421 | * @param integer $iBsize 422 | * @param integer $iPpsCnt 423 | */ 424 | function _saveBbd($iSbdSize, $iBsize, $iPpsCnt) 425 | { 426 | if($this->new_func) 427 | return $this->_create_big_block_chain($iSbdSize, $iBsize, $iPpsCnt); 428 | 429 | $FILE = $this->_FILEH_; 430 | // Calculate Basic Setting 431 | $iBbCnt = $this->_BIG_BLOCK_SIZE / OLE_LONG_INT_SIZE; 432 | $i1stBdL = ($this->_BIG_BLOCK_SIZE - 0x4C) / OLE_LONG_INT_SIZE; 433 | 434 | $iBdExL = 0; 435 | $iAll = $iBsize + $iPpsCnt + $iSbdSize; 436 | $iAllW = $iAll; 437 | $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt)? 1: 0); 438 | $iBdCnt = floor(($iAll + $iBdCntW) / $iBbCnt) + ((($iAllW+$iBdCntW) % $iBbCnt)? 1: 0); 439 | // Calculate BD count 440 | if ($iBdCnt >$i1stBdL) { 441 | while (1) { 442 | $iBdExL++; 443 | $iAllW++; 444 | $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt)? 1: 0); 445 | $iBdCnt = floor(($iAllW + $iBdCntW) / $iBbCnt) + ((($iAllW+$iBdCntW) % $iBbCnt)? 1: 0); 446 | if ($iBdCnt <= ($iBdExL*$iBbCnt+ $i1stBdL)) { 447 | break; 448 | } 449 | } 450 | } 451 | 452 | // Making BD 453 | // Set for SBD 454 | if ($iSbdSize > 0) { 455 | for ($i = 0; $i < ($iSbdSize - 1); $i++) { 456 | fwrite($FILE, pack("V", $i+1)); 457 | } 458 | fwrite($FILE, pack("V", OLE_ENDOFCHAIN)); 459 | } 460 | // Set for B 461 | for ($i = 0; $i < ($iBsize - 1); $i++) { 462 | fwrite($FILE, pack("V", $i+$iSbdSize+1)); 463 | } 464 | fwrite($FILE, pack("V", OLE_ENDOFCHAIN)); 465 | 466 | // Set for PPS 467 | for ($i = 0; $i < ($iPpsCnt - 1); $i++) { 468 | fwrite($FILE, pack("V", $i+$iSbdSize+$iBsize+1)); 469 | } 470 | fwrite($FILE, pack("V", OLE_ENDOFCHAIN)); 471 | // Set for BBD itself ( 0xFFFFFFFD : BBD) 472 | for ($i = 0; $i < $iBdCnt; $i++) { 473 | fwrite($FILE, pack("V", OLE_FATSECT)); 474 | } 475 | // Set for ExtraBDList 476 | for ($i = 0; $i < $iBdExL; $i++) { 477 | fwrite($FILE, pack("V", OLE_DIFSECT)); 478 | } 479 | // Adjust for Block 480 | if (($iAllW + $iBdCnt) % $iBbCnt) { 481 | for ($i = 0; $i < ($iBbCnt - (($iAllW + $iBdCnt) % $iBbCnt)); $i++) { 482 | fwrite($FILE, pack("V", OLE_FREESECT)); 483 | } 484 | } 485 | // Extra BDList 486 | if ($iBdCnt > $i1stBdL) { 487 | $iN=0; 488 | $iNb=0; 489 | for ($i = $i1stBdL;$i < $iBdCnt; $i++, $iN++) { 490 | if ($iN >= ($iBbCnt - 1)) { 491 | $iN = 0; 492 | $iNb++; 493 | fwrite($FILE, pack("V", $iAll+$iBdCnt+$iNb)); 494 | } 495 | fwrite($FILE, pack("V", $iBsize+$iSbdSize+$iPpsCnt+$i)); 496 | } 497 | if (($iBdCnt-$i1stBdL) % ($iBbCnt-1)) { 498 | for ($i = 0; $i < (($iBbCnt - 1) - (($iBdCnt - $i1stBdL) % ($iBbCnt - 1))); $i++) { 499 | fwrite($FILE, pack("V", OLE_FREESECT)); 500 | } 501 | } 502 | fwrite($FILE, pack("V", OLE_ENDOFCHAIN)); 503 | } 504 | } 505 | 506 | 507 | 508 | /** 509 | * New method to store Bigblock chain 510 | * 511 | * @access private 512 | * @param integer $num_sb_blocks - number of Smallblock depot blocks 513 | * @param integer $num_bb_blocks - number of Bigblock depot blocks 514 | * @param integer $num_pps_blocks - number of PropertySetStorage blocks 515 | */ 516 | function _create_big_block_chain($num_sb_blocks, $num_bb_blocks, $num_pps_blocks) 517 | { 518 | $FILE = $this->_FILEH_; 519 | 520 | $bbd_info = $this->_calculate_big_block_chain($num_sb_blocks, $num_bb_blocks, $num_pps_blocks); 521 | 522 | $data = ""; 523 | 524 | if($num_sb_blocks > 0) 525 | { 526 | for($i = 0; $i<($num_sb_blocks-1); $i++) 527 | $data .= pack("V", $i+1); 528 | $data .= pack("V", OLE_ENDOFCHAIN); 529 | } 530 | 531 | for($i = 0; $i<($num_bb_blocks-1); $i++) 532 | $data .= pack("V", $i + $num_sb_blocks + 1); 533 | $data .= pack("V", OLE_ENDOFCHAIN); 534 | 535 | for($i = 0; $i<($num_pps_blocks-1); $i++) 536 | $data .= pack("V", $i + $num_sb_blocks + $num_bb_blocks + 1); 537 | $data .= pack("V", OLE_ENDOFCHAIN); 538 | 539 | for($i = 0; $i < $bbd_info["0xFFFFFFFD_blockchain_entries"]; $i++) 540 | $data .= pack("V", OLE_FATSECT); 541 | 542 | for($i = 0; $i < $bbd_info["0xFFFFFFFC_blockchain_entries"]; $i++) 543 | $data .= pack("V", OLE_DIFSECT); 544 | 545 | // Adjust for Block 546 | $all_entries = $num_sb_blocks + $num_bb_blocks + $num_pps_blocks + $bbd_info["0xFFFFFFFD_blockchain_entries"] + $bbd_info["0xFFFFFFFC_blockchain_entries"]; 547 | if($all_entries % $bbd_info["entries_per_block"]) 548 | { 549 | $rest = $bbd_info["entries_per_block"] - ($all_entries % $bbd_info["entries_per_block"]); 550 | for($i = 0; $i < $rest; $i++) 551 | $data .= pack("V", OLE_FREESECT); 552 | } 553 | 554 | // Extra BDList 555 | if($bbd_info["blockchain_list_entries"] > $bbd_info["header_blockchain_list_entries"]) 556 | { 557 | $iN=0; 558 | $iNb=0; 559 | for($i = $bbd_info["header_blockchain_list_entries"]; $i < $bbd_info["blockchain_list_entries"]; $i++, $iN++) 560 | { 561 | if($iN >= ($bbd_info["entries_per_block"]-1)) 562 | { 563 | $iN = 0; 564 | $iNb++; 565 | $data .= pack("V", $num_sb_blocks + $num_bb_blocks + $num_pps_blocks + $bbd_info["0xFFFFFFFD_blockchain_entries"] + $iNb); 566 | } 567 | 568 | $data .= pack("V", $num_bb_blocks + $num_sb_blocks + $num_pps_blocks + $i); 569 | } 570 | 571 | $all_entries = $bbd_info["blockchain_list_entries"] - $bbd_info["header_blockchain_list_entries"]; 572 | if(($all_entries % ($bbd_info["entries_per_block"] - 1))) 573 | { 574 | $rest = ($bbd_info["entries_per_block"] - 1) - ($all_entries % ($bbd_info["entries_per_block"] - 1)); 575 | for($i = 0; $i < $rest; $i++) 576 | $data .= pack("V", OLE_FREESECT); 577 | } 578 | 579 | $data .= pack("V", OLE_ENDOFCHAIN); 580 | } 581 | 582 | /* 583 | $this->dump($data, 0, strlen($data)); 584 | die; 585 | */ 586 | 587 | fwrite($FILE, $data); 588 | } 589 | 590 | /** 591 | * New method to store Header 592 | * 593 | * @access private 594 | * @param integer $num_sb_blocks - number of Smallblock depot blocks 595 | * @param integer $num_bb_blocks - number of Bigblock depot blocks 596 | * @param integer $num_pps_blocks - number of PropertySetStorage blocks 597 | */ 598 | function _create_header($num_sb_blocks, $num_bb_blocks, $num_pps_blocks) 599 | { 600 | $FILE = $this->_FILEH_; 601 | 602 | $bbd_info = $this->_calculate_big_block_chain($num_sb_blocks, $num_bb_blocks, $num_pps_blocks); 603 | 604 | // Save Header 605 | fwrite($FILE, 606 | OLE_CFB_SIGNATURE // Header Signature (8 bytes) 607 | . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // Header CLSID (16 bytes) 608 | . pack("v", OLE_VERSION_MINOR) // Minor Version (2 bytes) 609 | . pack("v", OLE_VERSION_MAJOR_3) // Major Version (2 bytes) 610 | . pack("v", OLE_LITTLE_ENDIAN) // Byte Order (2 bytes) 611 | . pack("v", OLE_SECTOR_SHIFT_3) // Sector Shift (2 bytes) 612 | . pack("v", OLE_MINI_SECTOR_SHIFT) // Mini Sector Shift (2 bytes) 613 | . "\x00\x00\x00\x00\x00\x00" // Reserved (6 bytes) 614 | . "\x00\x00\x00\x00" // Number of Directory Sectors (4 bytes) 615 | . pack("V", $bbd_info["blockchain_list_entries"]) // Number of FAT Sectors (4 bytes) 616 | . pack("V", $num_sb_blocks + $num_bb_blocks) //ROOT START, First Directory Sector Location (4 bytes) 617 | . pack("V", 0) // Transaction Signature Number (4 bytes) 618 | . pack("V", 0x00001000) // Mini Stream Cutoff Size (4 bytes) 619 | . pack("V", $num_sb_blocks > 0 ? 0 : OLE_ENDOFCHAIN) // First Mini FAT Sector Location (4 bytes) 620 | . pack("V", $num_sb_blocks) // Number of Mini FAT Sectors (4 bytes) 621 | ); 622 | 623 | // Extra BDList Start, Count 624 | if($bbd_info["blockchain_list_entries"] < $bbd_info["header_blockchain_list_entries"]) 625 | { 626 | fwrite($FILE, 627 | pack("V", OLE_ENDOFCHAIN). // Extra BDList Start 628 | pack("V", 0) // Extra BDList Count 629 | ); 630 | } 631 | else 632 | { 633 | fwrite($FILE, pack("V", $num_sb_blocks + $num_bb_blocks + $num_pps_blocks + $bbd_info["0xFFFFFFFD_blockchain_entries"]) . pack("V", $bbd_info["0xFFFFFFFC_blockchain_entries"])); 634 | } 635 | 636 | // BDList 637 | for ($i=0; $i < $bbd_info["header_blockchain_list_entries"] and $i < $bbd_info["blockchain_list_entries"]; $i++) 638 | { 639 | fwrite($FILE, pack("V", $num_bb_blocks + $num_sb_blocks + $num_pps_blocks + $i)); 640 | } 641 | 642 | if($i < $bbd_info["header_blockchain_list_entries"]) 643 | { 644 | for($j = 0; $j < ($bbd_info["header_blockchain_list_entries"]-$i); $j++) 645 | { 646 | fwrite($FILE, (pack("V", OLE_FREESECT))); 647 | } 648 | } 649 | } 650 | 651 | /** 652 | * New method to calculate Bigblock chain 653 | * 654 | * @access private 655 | * @param integer $num_sb_blocks - number of Smallblock depot blocks 656 | * @param integer $num_bb_blocks - number of Bigblock depot blocks 657 | * @param integer $num_pps_blocks - number of PropertySetStorage blocks 658 | */ 659 | function _calculate_big_block_chain($num_sb_blocks, $num_bb_blocks, $num_pps_blocks) 660 | { 661 | $bbd_info["entries_per_block"] = $this->_BIG_BLOCK_SIZE / OLE_LONG_INT_SIZE; 662 | $bbd_info["header_blockchain_list_entries"] = ($this->_BIG_BLOCK_SIZE - 0x4C) / OLE_LONG_INT_SIZE; 663 | $bbd_info["blockchain_entries"] = $num_sb_blocks + $num_bb_blocks + $num_pps_blocks; 664 | $bbd_info["0xFFFFFFFD_blockchain_entries"] = $this->get_number_of_pointer_blocks($bbd_info["blockchain_entries"]); 665 | $bbd_info["blockchain_list_entries"] = $this->get_number_of_pointer_blocks($bbd_info["blockchain_entries"] + $bbd_info["0xFFFFFFFD_blockchain_entries"]); 666 | 667 | // do some magic 668 | $bbd_info["ext_blockchain_list_entries"] = 0; 669 | $bbd_info["0xFFFFFFFC_blockchain_entries"] = 0; 670 | if($bbd_info["blockchain_list_entries"] > $bbd_info["header_blockchain_list_entries"]) 671 | { 672 | do 673 | { 674 | $bbd_info["blockchain_list_entries"] = $this->get_number_of_pointer_blocks($bbd_info["blockchain_entries"] + $bbd_info["0xFFFFFFFD_blockchain_entries"] + $bbd_info["0xFFFFFFFC_blockchain_entries"]); 675 | $bbd_info["ext_blockchain_list_entries"] = $bbd_info["blockchain_list_entries"] - $bbd_info["header_blockchain_list_entries"]; 676 | $bbd_info["0xFFFFFFFC_blockchain_entries"] = $this->get_number_of_pointer_blocks($bbd_info["ext_blockchain_list_entries"]); 677 | $bbd_info["0xFFFFFFFD_blockchain_entries"] = $this->get_number_of_pointer_blocks($num_sb_blocks + $num_bb_blocks + $num_pps_blocks + $bbd_info["0xFFFFFFFD_blockchain_entries"] + $bbd_info["0xFFFFFFFC_blockchain_entries"]); 678 | } 679 | while($bbd_info["blockchain_list_entries"] < $this->get_number_of_pointer_blocks($bbd_info["blockchain_entries"] + $bbd_info["0xFFFFFFFD_blockchain_entries"] + $bbd_info["0xFFFFFFFC_blockchain_entries"])); 680 | } 681 | 682 | return $bbd_info; 683 | } 684 | 685 | /** 686 | * Calculates number of pointer blocks 687 | * 688 | * @access public 689 | * @param integer $num_pointers - number of pointers 690 | */ 691 | function get_number_of_pointer_blocks($num_pointers) 692 | { 693 | $pointers_per_block = $this->_BIG_BLOCK_SIZE / OLE_LONG_INT_SIZE; 694 | 695 | return floor($num_pointers / $pointers_per_block) + (($num_pointers % $pointers_per_block)? 1: 0); 696 | } 697 | 698 | /** 699 | * Support method for some hexdumping 700 | * 701 | * @access public 702 | * @param string $data - Binary data 703 | * @param integer $from - Start offset of data to dump 704 | * @param integer $to - Target offset of data to dump 705 | */ 706 | function dump($data, $from, $to) 707 | { 708 | $chars = array(); 709 | for($i = $from; $i < $to; $i++) 710 | { 711 | if(sizeof($chars) == 16) 712 | { 713 | printf("%08X (% 12d) |", $i-16, $i-16); 714 | foreach($chars as $char) 715 | printf(" %02X", $char); 716 | print " |\n"; 717 | 718 | $chars = array(); 719 | } 720 | 721 | $chars[] = ord($data[$i]); 722 | } 723 | 724 | if(sizeof($chars)) 725 | { 726 | printf("%08X (% 12d) |", $i-sizeof($chars), $i-sizeof($chars)); 727 | foreach($chars as $char) 728 | printf(" %02X", $char); 729 | print " |\n"; 730 | } 731 | } 732 | } 733 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/pear/OLE.svg?branch=master)](https://travis-ci.org/pear/OLE) 2 | [![Latest Stable Version](https://poser.pugx.org/pear/ole/v/stable)](https://packagist.org/packages/pear/ole) 3 | 4 | This package is http://pear.php.net/package/OLE and has been migrated from https://svn.php.net/repository/pear/packages/OLE 5 | 6 | Please report all new issues via the PEAR bug tracker. 7 | 8 | If this package is marked as unmaintained and you have fixes, please submit your pull requests and start discussion on the pear-qa mailing list. 9 | 10 | To test, run 11 | 12 | $ composer install 13 | $ vendor/bin/phpunit 14 | 15 | To build, simply 16 | 17 | $ pear package 18 | 19 | To install from scratch 20 | 21 | $ pear install package.xml 22 | 23 | To upgrade 24 | 25 | $ pear upgrade -f package.xml 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pear/ole", 3 | "type": "library", 4 | "description": "This package allows reading and writing of OLE (Object Linking and Embedding) compound documents. This format is used as container for Excel (.xls), Word (.doc) and other Microsoft file formats.", 5 | "license": "PHP-3.01", 6 | "authors": [ 7 | { 8 | "name": "Christian Schmidt", 9 | "email": "schmidt@php.net", 10 | "role": "Lead" 11 | }, 12 | { 13 | "name": "Xavier Noguer", 14 | "email": "xnoguer@php.net", 15 | "role": "Lead" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=5.6", 20 | "pear/pear_exception": "^1.0" 21 | }, 22 | "require-dev": { 23 | "friendsofphp/php-cs-fixer": "^2", 24 | "pear/pear-core-minimal": "^1.10", 25 | "phpunit/phpunit": ">=5 <10", 26 | "sanmai/phpunit-legacy-adapter": "^6 || ^8" 27 | }, 28 | "autoload": { 29 | "psr-0": { 30 | "OLE": "./" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-0": { 35 | "OLE": "tests/" 36 | } 37 | }, 38 | "include-path": [ 39 | "./" 40 | ], 41 | "support": { 42 | "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=OLE", 43 | "source": "https://github.com/pear/OLE" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | OLE 4 | pear.php.net 5 | Package for reading and writing OLE containers 6 | This package allows reading and writing of OLE (Object Linking and Embedding) compound documents. This format is used as container for Excel (.xls), Word (.doc) and other Microsoft file formats. 7 | 8 | Christian Schmidt 9 | schmidt 10 | schmidt@php.net 11 | yes 12 | 13 | 14 | Xavier Noguer 15 | xnoguer 16 | xnoguer@php.net 17 | no 18 | 19 | 2017-06-20 20 | 21 | 1.0.0RC3 22 | 1.0.0RC3 23 | 24 | 25 | beta 26 | beta 27 | 28 | PHP 29 | 30 | Bug #19284: RC2 breaks header in excel files from Spreadsheet_Excel_Writer 31 | Bug #21216: Call to undefined method PEAR::OLE_PPS() 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 5.0.0 46 | 47 | 48 | 1.4.0b1 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 0.2.1 57 | 0.2.1 58 | 59 | 60 | alpha 61 | alpha 62 | 63 | 2003-05-12 64 | PHP 65 | 66 | Fixing install dir 67 | 68 | 69 | 70 | 71 | 0.3 72 | 0.3 73 | 74 | 75 | beta 76 | beta 77 | 78 | 2003-08-21 79 | PHP 80 | 81 | -added OLE_PPS_File::init() initialization method. 82 | -better error handling. 83 | 84 | 85 | 86 | 87 | 0.4 88 | 0.4 89 | 90 | 91 | beta 92 | beta 93 | 94 | 2003-09-25 95 | PHP 96 | 97 | -deleting tmp files (Herman Kuiper). 98 | -fixed hardcoded tmp dir (Herman Kuiper). 99 | -fixed pass by reference warning (Herman Kuiper). 100 | 101 | 102 | 103 | 104 | 0.5 105 | 0.5 106 | 107 | 108 | beta 109 | beta 110 | 111 | 2003-12-14 112 | PHP 113 | 114 | - BC break!!! OLE/OLE.php file moved to OLE.php to comply with PEAR 115 | standards. You will have to change your require('OLE/OLE.php')'s 116 | for require('OLE.php')'s 117 | - If you are using Spreadsheet_Excel_Writer, do not upgrade to this 118 | version yet. A new version of Spreadsheet_Excel_Writer will be 119 | released soon so the BC break won't affect you. 120 | - allowing setting of temp dir for OLE_PPS_File and OLE_PPS_Root objects 121 | - fixed problem when reading files (not reading the whole OLE tree) 122 | 123 | 124 | 125 | 126 | 0.6.0 127 | 0.6.0 128 | 129 | 130 | beta 131 | beta 132 | 133 | 2007-12-09 134 | PHP 135 | 136 | Rewrite of parser (no change to writer): 137 | - Files inside OLE container are now saved in directory structure. 138 | - Parser now properly uses Big Block, Small Block and Master Block Allocation Tables. 139 | - Added stream interface for reading files inside OLE container. 140 | 141 | - Bug #6516. Fix "PPS at 1 has unknown type" errors. (Christian Schmidt) 142 | - Coding Standard cleanups (by helgi) 143 | - Bug #3951 OLE_PPS_File::init() does not return true on success (by helgi) 144 | - Bug #3955 OLE::_readPpsWks() does not return true on success (by helgi) 145 | 146 | 147 | 148 | 149 | 0.6.1 150 | 0.6.1 151 | 152 | 153 | beta 154 | beta 155 | 156 | 2007-12-18 157 | PHP 158 | 159 | - fixed bug #12693: wrong order of require_once 160 | - added missing file to package: ChainedBlockStream.php 161 | 162 | 163 | 164 | 165 | 0.6.2 166 | 0.6.2 167 | 168 | 169 | beta 170 | beta 171 | 172 | 2012-01-26 173 | PHP 174 | 175 | - fixed bug #12944: Incompatibility open_basedir restriction. 176 | 177 | 178 | 179 | 180 | 1.0.0RC2 181 | 1.0.0RC2 182 | 183 | 184 | beta 185 | beta 186 | 187 | 2012-01-26 188 | PHP 189 | 190 | QA release 191 | Bug #15904 Invalid Bigblock chain with files > 200MB 192 | Bug #17685 OLE doesn't save multistreams 193 | 194 | 195 | 196 | 197 | 1.0.0RC3 198 | 1.0.0RC3 199 | 200 | 201 | beta 202 | beta 203 | 204 | 2017-06-20 205 | PHP 206 | 207 | Bug #19284: RC2 breaks header in excel files from Spreadsheet_Excel_Writer 208 | Bug #21216: Call to undefined method PEAR::OLE_PPS() 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | tests/ 9 | 10 | 11 | 12 | 13 | 14 | OLE/ 15 | OLE.php 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------