For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

526 lines
17 KiB

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2002 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | [email protected] so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Author: Xavier Noguer <[email protected]> |
  17. // | Based on OLE::Storage_Lite by Kawai, Takanori |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id: OLE.php,v 1.13 2007/03/07 14:38:25 schmidt Exp $
  21. /**
  22. * Array for storing OLE instances that are accessed from
  23. * OLE_ChainedBlockStream::stream_open().
  24. * @var array
  25. */
  26. $GLOBALS['_OLE_INSTANCES'] = array();
  27. /**
  28. * OLE package base class.
  29. *
  30. * @author Xavier Noguer <xnoguer@php.net>
  31. * @author Christian Schmidt <schmidt@php.net>
  32. * @category PHPExcel
  33. * @package PHPExcel_Shared_OLE
  34. */
  35. class PHPExcel_Shared_OLE
  36. {
  37. const OLE_PPS_TYPE_ROOT = 5;
  38. const OLE_PPS_TYPE_DIR = 1;
  39. const OLE_PPS_TYPE_FILE = 2;
  40. const OLE_DATA_SIZE_SMALL = 0x1000;
  41. const OLE_LONG_INT_SIZE = 4;
  42. const OLE_PPS_SIZE = 0x80;
  43. /**
  44. * The file handle for reading an OLE container
  45. * @var resource
  46. */
  47. public $_file_handle;
  48. /**
  49. * Array of PPS's found on the OLE container
  50. * @var array
  51. */
  52. public $_list = array();
  53. /**
  54. * Root directory of OLE container
  55. * @var OLE_PPS_Root
  56. */
  57. public $root;
  58. /**
  59. * Big Block Allocation Table
  60. * @var array (blockId => nextBlockId)
  61. */
  62. public $bbat;
  63. /**
  64. * Short Block Allocation Table
  65. * @var array (blockId => nextBlockId)
  66. */
  67. public $sbat;
  68. /**
  69. * Size of big blocks. This is usually 512.
  70. * @var int number of octets per block.
  71. */
  72. public $bigBlockSize;
  73. /**
  74. * Size of small blocks. This is usually 64.
  75. * @var int number of octets per block
  76. */
  77. public $smallBlockSize;
  78. /**
  79. * Reads an OLE container from the contents of the file given.
  80. *
  81. * @acces public
  82. * @param string $file
  83. * @return mixed true on success, PEAR_Error on failure
  84. */
  85. public function read($file)
  86. {
  87. $fh = fopen($file, "r");
  88. if (!$fh) {
  89. throw new PHPExcel_Reader_Exception("Can't open file $file");
  90. }
  91. $this->_file_handle = $fh;
  92. $signature = fread($fh, 8);
  93. if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
  94. throw new PHPExcel_Reader_Exception("File doesn't seem to be an OLE container.");
  95. }
  96. fseek($fh, 28);
  97. if (fread($fh, 2) != "\xFE\xFF") {
  98. // This shouldn't be a problem in practice
  99. throw new PHPExcel_Reader_Exception("Only Little-Endian encoding is supported.");
  100. }
  101. // Size of blocks and short blocks in bytes
  102. $this->bigBlockSize = pow(2, self::_readInt2($fh));
  103. $this->smallBlockSize = pow(2, self::_readInt2($fh));
  104. // Skip UID, revision number and version number
  105. fseek($fh, 44);
  106. // Number of blocks in Big Block Allocation Table
  107. $bbatBlockCount = self::_readInt4($fh);
  108. // Root chain 1st block
  109. $directoryFirstBlockId = self::_readInt4($fh);
  110. // Skip unused bytes
  111. fseek($fh, 56);
  112. // Streams shorter than this are stored using small blocks
  113. $this->bigBlockThreshold = self::_readInt4($fh);
  114. // Block id of first sector in Short Block Allocation Table
  115. $sbatFirstBlockId = self::_readInt4($fh);
  116. // Number of blocks in Short Block Allocation Table
  117. $sbbatBlockCount = self::_readInt4($fh);
  118. // Block id of first sector in Master Block Allocation Table
  119. $mbatFirstBlockId = self::_readInt4($fh);
  120. // Number of blocks in Master Block Allocation Table
  121. $mbbatBlockCount = self::_readInt4($fh);
  122. $this->bbat = array();
  123. // Remaining 4 * 109 bytes of current block is beginning of Master
  124. // Block Allocation Table
  125. $mbatBlocks = array();
  126. for ($i = 0; $i < 109; ++$i) {
  127. $mbatBlocks[] = self::_readInt4($fh);
  128. }
  129. // Read rest of Master Block Allocation Table (if any is left)
  130. $pos = $this->_getBlockOffset($mbatFirstBlockId);
  131. for ($i = 0; $i < $mbbatBlockCount; ++$i) {
  132. fseek($fh, $pos);
  133. for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
  134. $mbatBlocks[] = self::_readInt4($fh);
  135. }
  136. // Last block id in each block points to next block
  137. $pos = $this->_getBlockOffset(self::_readInt4($fh));
  138. }
  139. // Read Big Block Allocation Table according to chain specified by
  140. // $mbatBlocks
  141. for ($i = 0; $i < $bbatBlockCount; ++$i) {
  142. $pos = $this->_getBlockOffset($mbatBlocks[$i]);
  143. fseek($fh, $pos);
  144. for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
  145. $this->bbat[] = self::_readInt4($fh);
  146. }
  147. }
  148. // Read short block allocation table (SBAT)
  149. $this->sbat = array();
  150. $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
  151. $sbatFh = $this->getStream($sbatFirstBlockId);
  152. for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
  153. $this->sbat[$blockId] = self::_readInt4($sbatFh);
  154. }
  155. fclose($sbatFh);
  156. $this->_readPpsWks($directoryFirstBlockId);
  157. return true;
  158. }
  159. /**
  160. * @param int block id
  161. * @param int byte offset from beginning of file
  162. * @access public
  163. */
  164. public function _getBlockOffset($blockId)
  165. {
  166. return 512 + $blockId * $this->bigBlockSize;
  167. }
  168. /**
  169. * Returns a stream for use with fread() etc. External callers should
  170. * use PHPExcel_Shared_OLE_PPS_File::getStream().
  171. * @param int|PPS block id or PPS
  172. * @return resource read-only stream
  173. */
  174. public function getStream($blockIdOrPps)
  175. {
  176. static $isRegistered = false;
  177. if (!$isRegistered) {
  178. stream_wrapper_register('ole-chainedblockstream', 'PHPExcel_Shared_OLE_ChainedBlockStream');
  179. $isRegistered = true;
  180. }
  181. // Store current instance in global array, so that it can be accessed
  182. // in OLE_ChainedBlockStream::stream_open().
  183. // Object is removed from self::$instances in OLE_Stream::close().
  184. $GLOBALS['_OLE_INSTANCES'][] = $this;
  185. $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES']));
  186. $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
  187. if ($blockIdOrPps instanceof PHPExcel_Shared_OLE_PPS) {
  188. $path .= '&blockId=' . $blockIdOrPps->_StartBlock;
  189. $path .= '&size=' . $blockIdOrPps->Size;
  190. } else {
  191. $path .= '&blockId=' . $blockIdOrPps;
  192. }
  193. return fopen($path, 'r');
  194. }
  195. /**
  196. * Reads a signed char.
  197. * @param resource file handle
  198. * @return int
  199. * @access public
  200. */
  201. private static function _readInt1($fh)
  202. {
  203. list(, $tmp) = unpack("c", fread($fh, 1));
  204. return $tmp;
  205. }
  206. /**
  207. * Reads an unsigned short (2 octets).
  208. * @param resource file handle
  209. * @return int
  210. * @access public
  211. */
  212. private static function _readInt2($fh)
  213. {
  214. list(, $tmp) = unpack("v", fread($fh, 2));
  215. return $tmp;
  216. }
  217. /**
  218. * Reads an unsigned long (4 octets).
  219. * @param resource file handle
  220. * @return int
  221. * @access public
  222. */
  223. private static function _readInt4($fh)
  224. {
  225. list(, $tmp) = unpack("V", fread($fh, 4));
  226. return $tmp;
  227. }
  228. /**
  229. * Gets information about all PPS's on the OLE container from the PPS WK's
  230. * creates an OLE_PPS object for each one.
  231. *
  232. * @access public
  233. * @param integer the block id of the first block
  234. * @return mixed true on success, PEAR_Error on failure
  235. */
  236. public function _readPpsWks($blockId)
  237. {
  238. $fh = $this->getStream($blockId);
  239. for ($pos = 0;; $pos += 128) {
  240. fseek($fh, $pos, SEEK_SET);
  241. $nameUtf16 = fread($fh, 64);
  242. $nameLength = self::_readInt2($fh);
  243. $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
  244. // Simple conversion from UTF-16LE to ISO-8859-1
  245. $name = str_replace("\x00", "", $nameUtf16);
  246. $type = self::_readInt1($fh);
  247. switch ($type) {
  248. case self::OLE_PPS_TYPE_ROOT:
  249. $pps = new PHPExcel_Shared_OLE_PPS_Root(null, null, array());
  250. $this->root = $pps;
  251. break;
  252. case self::OLE_PPS_TYPE_DIR:
  253. $pps = new PHPExcel_Shared_OLE_PPS(null, null, null, null, null, null, null, null, null, array());
  254. break;
  255. case self::OLE_PPS_TYPE_FILE:
  256. $pps = new PHPExcel_Shared_OLE_PPS_File($name);
  257. break;
  258. default:
  259. continue 2;
  260. }
  261. fseek($fh, 1, SEEK_CUR);
  262. $pps->Type = $type;
  263. $pps->Name = $name;
  264. $pps->PrevPps = self::_readInt4($fh);
  265. $pps->NextPps = self::_readInt4($fh);
  266. $pps->DirPps = self::_readInt4($fh);
  267. fseek($fh, 20, SEEK_CUR);
  268. $pps->Time1st = self::OLE2LocalDate(fread($fh, 8));
  269. $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8));
  270. $pps->_StartBlock = self::_readInt4($fh);
  271. $pps->Size = self::_readInt4($fh);
  272. $pps->No = count($this->_list);
  273. $this->_list[] = $pps;
  274. // check if the PPS tree (starting from root) is complete
  275. if (isset($this->root) && $this->_ppsTreeComplete($this->root->No)) {
  276. break;
  277. }
  278. }
  279. fclose($fh);
  280. // Initialize $pps->children on directories
  281. foreach ($this->_list as $pps) {
  282. if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
  283. $nos = array($pps->DirPps);
  284. $pps->children = array();
  285. while ($nos) {
  286. $no = array_pop($nos);
  287. if ($no != -1) {
  288. $childPps = $this->_list[$no];
  289. $nos[] = $childPps->PrevPps;
  290. $nos[] = $childPps->NextPps;
  291. $pps->children[] = $childPps;
  292. }
  293. }
  294. }
  295. }
  296. return true;
  297. }
  298. /**
  299. * It checks whether the PPS tree is complete (all PPS's read)
  300. * starting with the given PPS (not necessarily root)
  301. *
  302. * @access public
  303. * @param integer $index The index of the PPS from which we are checking
  304. * @return boolean Whether the PPS tree for the given PPS is complete
  305. */
  306. public function _ppsTreeComplete($index)
  307. {
  308. return isset($this->_list[$index]) &&
  309. ($pps = $this->_list[$index]) &&
  310. ($pps->PrevPps == -1 ||
  311. $this->_ppsTreeComplete($pps->PrevPps)) &&
  312. ($pps->NextPps == -1 ||
  313. $this->_ppsTreeComplete($pps->NextPps)) &&
  314. ($pps->DirPps == -1 ||
  315. $this->_ppsTreeComplete($pps->DirPps));
  316. }
  317. /**
  318. * Checks whether a PPS is a File PPS or not.
  319. * If there is no PPS for the index given, it will return false.
  320. *
  321. * @access public
  322. * @param integer $index The index for the PPS
  323. * @return bool true if it's a File PPS, false otherwise
  324. */
  325. public function isFile($index)
  326. {
  327. if (isset($this->_list[$index])) {
  328. return ($this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE);
  329. }
  330. return false;
  331. }
  332. /**
  333. * Checks whether a PPS is a Root PPS or not.
  334. * If there is no PPS for the index given, it will return false.
  335. *
  336. * @access public
  337. * @param integer $index The index for the PPS.
  338. * @return bool true if it's a Root PPS, false otherwise
  339. */
  340. public function isRoot($index)
  341. {
  342. if (isset($this->_list[$index])) {
  343. return ($this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT);
  344. }
  345. return false;
  346. }
  347. /**
  348. * Gives the total number of PPS's found in the OLE container.
  349. *
  350. * @access public
  351. * @return integer The total number of PPS's found in the OLE container
  352. */
  353. public function ppsTotal()
  354. {
  355. return count($this->_list);
  356. }
  357. /**
  358. * Gets data from a PPS
  359. * If there is no PPS for the index given, it will return an empty string.
  360. *
  361. * @access public
  362. * @param integer $index The index for the PPS
  363. * @param integer $position The position from which to start reading
  364. * (relative to the PPS)
  365. * @param integer $length The amount of bytes to read (at most)
  366. * @return string The binary string containing the data requested
  367. * @see OLE_PPS_File::getStream()
  368. */
  369. public function getData($index, $position, $length)
  370. {
  371. // if position is not valid return empty string
  372. if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
  373. return '';
  374. }
  375. $fh = $this->getStream($this->_list[$index]);
  376. $data = stream_get_contents($fh, $length, $position);
  377. fclose($fh);
  378. return $data;
  379. }
  380. /**
  381. * Gets the data length from a PPS
  382. * If there is no PPS for the index given, it will return 0.
  383. *
  384. * @access public
  385. * @param integer $index The index for the PPS
  386. * @return integer The amount of bytes in data the PPS has
  387. */
  388. public function getDataLength($index)
  389. {
  390. if (isset($this->_list[$index])) {
  391. return $this->_list[$index]->Size;
  392. }
  393. return 0;
  394. }
  395. /**
  396. * Utility function to transform ASCII text to Unicode
  397. *
  398. * @access public
  399. * @static
  400. * @param string $ascii The ASCII string to transform
  401. * @return string The string in Unicode
  402. */
  403. public static function Asc2Ucs($ascii)
  404. {
  405. $rawname = '';
  406. for ($i = 0; $i < strlen($ascii); ++$i) {
  407. $rawname .= $ascii[$i] . "\x00";
  408. }
  409. return $rawname;
  410. }
  411. /**
  412. * Utility function
  413. * Returns a string for the OLE container with the date given
  414. *
  415. * @access public
  416. * @static
  417. * @param integer $date A timestamp
  418. * @return string The string for the OLE container
  419. */
  420. public static function LocalDate2OLE($date = null)
  421. {
  422. if (!isset($date)) {
  423. return "\x00\x00\x00\x00\x00\x00\x00\x00";
  424. }
  425. // factor used for separating numbers into 4 bytes parts
  426. $factor = pow(2, 32);
  427. // days from 1-1-1601 until the beggining of UNIX era
  428. $days = 134774;
  429. // calculate seconds
  430. $big_date = $days*24*3600 + gmmktime(date("H", $date), date("i", $date), date("s", $date), date("m", $date), date("d", $date), date("Y", $date));
  431. // multiply just to make MS happy
  432. $big_date *= 10000000;
  433. $high_part = floor($big_date / $factor);
  434. // lower 4 bytes
  435. $low_part = floor((($big_date / $factor) - $high_part) * $factor);
  436. // Make HEX string
  437. $res = '';
  438. for ($i = 0; $i < 4; ++$i) {
  439. $hex = $low_part % 0x100;
  440. $res .= pack('c', $hex);
  441. $low_part /= 0x100;
  442. }
  443. for ($i = 0; $i < 4; ++$i) {
  444. $hex = $high_part % 0x100;
  445. $res .= pack('c', $hex);
  446. $high_part /= 0x100;
  447. }
  448. return $res;
  449. }
  450. /**
  451. * Returns a timestamp from an OLE container's date
  452. *
  453. * @access public
  454. * @static
  455. * @param integer $string A binary string with the encoded date
  456. * @return string The timestamp corresponding to the string
  457. */
  458. public static function OLE2LocalDate($string)
  459. {
  460. if (strlen($string) != 8) {
  461. return new PEAR_Error("Expecting 8 byte string");
  462. }
  463. // factor used for separating numbers into 4 bytes parts
  464. $factor = pow(2, 32);
  465. list(, $high_part) = unpack('V', substr($string, 4, 4));
  466. list(, $low_part) = unpack('V', substr($string, 0, 4));
  467. $big_date = ($high_part * $factor) + $low_part;
  468. // translate to seconds
  469. $big_date /= 10000000;
  470. // days from 1-1-1601 until the beggining of UNIX era
  471. $days = 134774;
  472. // translate to seconds from beggining of UNIX era
  473. $big_date -= $days * 24 * 3600;
  474. return floor($big_date);
  475. }
  476. }