ILIAS  eassessment Revision 61809
 All Data Structures Namespaces Files Functions Variables Groups Pages
OLERead.php
Go to the documentation of this file.
1 <?php
28 define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1));
29 
31  private $data = '';
32 
33  // OLE identifier
35 
36  // Size of a sector = 512 bytes
37  const BIG_BLOCK_SIZE = 0x200;
38 
39  // Size of a short sector = 64 bytes
40  const SMALL_BLOCK_SIZE = 0x40;
41 
42  // Size of a directory entry always = 128 bytes
44 
45  // Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
46  const SMALL_BLOCK_THRESHOLD = 0x1000;
47 
48  // header offsets
50  const ROOT_START_BLOCK_POS = 0x30;
52  const EXTENSION_BLOCK_POS = 0x44;
55 
56  // property storage offsets (directory offsets)
57  const SIZE_OF_NAME_POS = 0x40;
58  const TYPE_POS = 0x42;
59  const START_BLOCK_POS = 0x74;
60  const SIZE_POS = 0x78;
61 
62 
63 
64  public $wrkbook = null;
65  public $summaryInformation = null;
67 
68 
75  public function read($sFileName)
76  {
77  // Check if file exists and is readable
78  if(!is_readable($sFileName)) {
79  throw new Exception("Could not open " . $sFileName . " for reading! File does not exist, or it is not readable.");
80  }
81 
82  // Get the file data
83  $this->data = file_get_contents($sFileName);
84 
85  // Check OLE identifier
86  if (substr($this->data, 0, 8) != self::IDENTIFIER_OLE) {
87  throw new Exception('The filename ' . $sFileName . ' is not recognised as an OLE file');
88  }
89 
90  // Total number of sectors used for the SAT
91  $this->numBigBlockDepotBlocks = self::_GetInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
92 
93  // SecID of the first sector of the directory stream
94  $this->rootStartBlock = self::_GetInt4d($this->data, self::ROOT_START_BLOCK_POS);
95 
96  // SecID of the first sector of the SSAT (or -2 if not extant)
97  $this->sbdStartBlock = self::_GetInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
98 
99  // SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
100  $this->extensionBlock = self::_GetInt4d($this->data, self::EXTENSION_BLOCK_POS);
101 
102  // Total number of sectors used by MSAT
103  $this->numExtensionBlocks = self::_GetInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
104 
105  $bigBlockDepotBlocks = array();
107 
108  $bbdBlocks = $this->numBigBlockDepotBlocks;
109 
110  if ($this->numExtensionBlocks != 0) {
111  $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4;
112  }
113 
114  for ($i = 0; $i < $bbdBlocks; ++$i) {
115  $bigBlockDepotBlocks[$i] = self::_GetInt4d($this->data, $pos);
116  $pos += 4;
117  }
118 
119  for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
120  $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
121  $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
122 
123  for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
124  $bigBlockDepotBlocks[$i] = self::_GetInt4d($this->data, $pos);
125  $pos += 4;
126  }
127 
128  $bbdBlocks += $blocksToRead;
129  if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
130  $this->extensionBlock = self::_GetInt4d($this->data, $pos);
131  }
132  }
133 
134  $pos = $index = 0;
135  $this->bigBlockChain = array();
136 
137  $bbs = self::BIG_BLOCK_SIZE / 4;
138  for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
139  $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
140 
141  for ($j = 0 ; $j < $bbs; ++$j) {
142  $this->bigBlockChain[$index] = self::_GetInt4d($this->data, $pos);
143  $pos += 4 ;
144  ++$index;
145  }
146  }
147 
148  $pos = $index = 0;
149  $sbdBlock = $this->sbdStartBlock;
150  $this->smallBlockChain = array();
151 
152  while ($sbdBlock != -2) {
153  $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
154 
155  for ($j = 0; $j < $bbs; ++$j) {
156  $this->smallBlockChain[$index] = self::_GetInt4d($this->data, $pos);
157  $pos += 4;
158  ++$index;
159  }
160 
161  $sbdBlock = $this->bigBlockChain[$sbdBlock];
162  }
163 
164  // read the directory stream
165  $block = $this->rootStartBlock;
166  $this->entry = $this->_readData($block);
167 
168  $this->_readPropertySets();
169  }
170 
176  public function getStream($stream)
177  {
178  if (is_null($stream)) {
179  return null;
180  }
181 
182  $streamData = '';
183 
184  if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
185  $rootdata = $this->_readData($this->props[$this->rootentry]['startBlock']);
186 
187  $block = $this->props[$stream]['startBlock'];
188 
189  while ($block != -2) {
190  $pos = $block * self::SMALL_BLOCK_SIZE;
191  $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
192 
193  $block = $this->smallBlockChain[$block];
194  }
195 
196  return $streamData;
197  } else {
198  $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
199  if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
200  ++$numBlocks;
201  }
202 
203  if ($numBlocks == 0) return '';
204 
205  $block = $this->props[$stream]['startBlock'];
206 
207  while ($block != -2) {
208  $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
209  $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
210  $block = $this->bigBlockChain[$block];
211  }
212 
213  return $streamData;
214  }
215  }
216 
223  private function _readData($bl)
224  {
225  $block = $bl;
226  $data = '';
227 
228  while ($block != -2) {
229  $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
230  $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
231  $block = $this->bigBlockChain[$block];
232  }
233  return $data;
234  }
235 
239  private function _readPropertySets() {
240  $offset = 0;
241 
242  // loop through entires, each entry is 128 bytes
243  $entryLen = strlen($this->entry);
244  while ($offset < $entryLen) {
245  // entry data (128 bytes)
246  $d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
247 
248  // size in bytes of name
249  $nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS+1]) << 8);
250 
251  // type of entry
252  $type = ord($d[self::TYPE_POS]);
253 
254  // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
255  // sectorID of first sector of the short-stream container stream, if this entry is root entry
256  $startBlock = self::_GetInt4d($d, self::START_BLOCK_POS);
257 
258  $size = self::_GetInt4d($d, self::SIZE_POS);
259 
260  $name = str_replace("\x00", "", substr($d,0,$nameSize));
261 
262  $this->props[] = array (
263  'name' => $name,
264  'type' => $type,
265  'startBlock' => $startBlock,
266  'size' => $size);
267 
268  // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
269  if (($name == 'Workbook') || ($name == 'Book') || ($name == 'WORKBOOK') || ($name == 'BOOK')) {
270  $this->wrkbook = count($this->props) - 1;
271  }
272 
273  // Root entry
274  if ($name == 'Root Entry' || $name == 'ROOT ENTRY' || $name == 'R') {
275  $this->rootentry = count($this->props) - 1;
276  }
277 
278  // Summary information
279  if ($name == chr(5) . 'SummaryInformation') {
280 // echo 'Summary Information<br />';
281  $this->summaryInformation = count($this->props) - 1;
282  }
283 
284  // Additional Document Summary information
285  if ($name == chr(5) . 'DocumentSummaryInformation') {
286 // echo 'Document Summary Information<br />';
287  $this->documentSummaryInformation = count($this->props) - 1;
288  }
289 
291  }
292 
293  }
294 
302  private static function _GetInt4d($data, $pos)
303  {
304  // FIX: represent numbers correctly on 64-bit system
305  // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
306  // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
307  $_or_24 = ord($data[$pos + 3]);
308  if ($_or_24 >= 128) {
309  // negative number
310  $_ord_24 = -abs((256 - $_or_24) << 24);
311  } else {
312  $_ord_24 = ($_or_24 & 127) << 24;
313  }
314  return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
315  }
316 
317 }