OLERead::readPropertySets()   C
last analyzed

Complexity

Conditions 12
Paths 12

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
dl 0
loc 65
ccs 0
cts 49
cp 0
rs 6.3369
c 0
b 0
f 0
cc 12
nc 12
nop 0
crap 156

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of PHPOffice Common
4
 *
5
 * PHPOffice Common is free software distributed under the terms of the GNU Lesser
6
 * General Public License version 3 as published by the Free Software Foundation.
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code. For the full list of
10
 * contributors, visit https://github.com/PHPOffice/Common/contributors.
11
 *
12
 * @link        https://github.com/PHPOffice/Common
13
 * @copyright   2009-2016 PHPOffice Common contributors
14
 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
15
 */
16
17
namespace PhpOffice\Common\Microsoft;
18
19
if (!defined('IDENTIFIER_OLE')) {
20
    define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1));
21
}
22
23
class OLERead
24
{
25
    private $data = '';
26
27
    // OLE identifier
28
    const IDENTIFIER_OLE = IDENTIFIER_OLE;
29
30
    // Size of a sector = 512 bytes
31
    const BIG_BLOCK_SIZE                    = 0x200;
32
33
    // Size of a short sector = 64 bytes
34
    const SMALL_BLOCK_SIZE                  = 0x40;
35
36
    // Size of a directory entry always = 128 bytes
37
    const PROPERTY_STORAGE_BLOCK_SIZE       = 0x80;
38
39
    // Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
40
    const SMALL_BLOCK_THRESHOLD             = 0x1000;
41
42
    // header offsets
43
    const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS    = 0x2c;
44
    const ROOT_START_BLOCK_POS              = 0x30;
45
    const SMALL_BLOCK_DEPOT_BLOCK_POS       = 0x3c;
46
    const EXTENSION_BLOCK_POS               = 0x44;
47
    const NUM_EXTENSION_BLOCK_POS           = 0x48;
48
    const BIG_BLOCK_DEPOT_BLOCKS_POS        = 0x4c;
49
50
    // property storage offsets (directory offsets)
51
    const SIZE_OF_NAME_POS                  = 0x40;
52
    const TYPE_POS                          = 0x42;
53
    const START_BLOCK_POS                   = 0x74;
54
    const SIZE_POS                          = 0x78;
55
56
    public $summaryInformation              = null;
57
    public $docSummaryInfos                 = null;
58
    public $powerpointDocument              = null;
59
    public $currentUser                     = null;
60
    public $pictures                        = null;
61
    public $rootEntry                       = null;
62
    public $props                           = array();
63
    public $smallBlockChain                 = null;
64
    public $bigBlockChain                   = null;
65
    public $entry                           = null;
66
67
    /**
68
     * Read the file
69
     *
70
     * @param $sFileName string Filename
71
     * @throws \Exception
72
     */
73
    public function read($sFileName)
74
    {
75
        // Check if file exists and is readable
76
        if (!is_readable($sFileName)) {
77
            throw new \Exception("Could not open " . $sFileName . " for reading! File does not exist, or it is not readable.");
78
        }
79
80
        // Get the file identifier
81
        // Don't bother reading the whole file until we know it's a valid OLE file
82
        $this->data = file_get_contents($sFileName, false, null, 0, 8);
83
84
        // Check OLE identifier
85
        if ($this->data != self::IDENTIFIER_OLE) {
86
            throw new \Exception('The filename ' . $sFileName . ' is not recognised as an OLE file');
87
        }
88
89
        // Get the file data
90
        $this->data = file_get_contents($sFileName);
91
92
        // Total number of sectors used for the SAT
93
        $numBigBlkDepotBlks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
94
95
        // SecID of the first sector of the directory stream
96
        $rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
97
98
        // SecID of the first sector of the SSAT (or -2 if not extant)
99
        $sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
100
101
        // SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
102
        $extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
103
104
        // Total number of sectors used by MSAT
105
        $numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
106
107
        $bigBlockDepotBlocks = array();
108
        $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
109
110
        $bbdBlocks = $numBigBlkDepotBlks;
111
112
        if ($numExtensionBlocks != 0) {
113
            $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4;
114
        }
115
116
        for ($i = 0; $i < $bbdBlocks; ++$i) {
117
              $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
118
              $pos += 4;
119
        }
120
121
        for ($j = 0; $j < $numExtensionBlocks; ++$j) {
122
            $pos = ($extensionBlock + 1) * self::BIG_BLOCK_SIZE;
123
            $blocksToRead = min($numBigBlkDepotBlks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
124
125
            for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
126
                $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
127
                $pos += 4;
128
            }
129
130
            $bbdBlocks += $blocksToRead;
131
            if ($bbdBlocks < $numBigBlkDepotBlks) {
132
                $extensionBlock = self::getInt4d($this->data, $pos);
133
            }
134
        }
135
136
        $this->bigBlockChain = '';
137
        $bbs = self::BIG_BLOCK_SIZE / 4;
138
        for ($i = 0; $i < $numBigBlkDepotBlks; ++$i) {
139
            $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
140
141
            $this->bigBlockChain .= substr($this->data, $pos, 4*$bbs);
142
            $pos += 4*$bbs;
143
        }
144
145
        $sbdBlock = $sbdStartBlock;
146
        $this->smallBlockChain = '';
147
        while ($sbdBlock != -2) {
148
            $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
149
150
            $this->smallBlockChain .= substr($this->data, $pos, 4*$bbs);
151
            $pos += 4*$bbs;
152
153
            $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock*4);
154
        }
155
156
        // read the directory stream
157
        $block = $rootStartBlock;
158
        $this->entry = $this->readData($block);
159
160
        $this->readPropertySets();
161
    }
162
163
    /**
164
     * Extract binary stream data
165
     *
166
     * @return string
167
     */
168
    public function getStream($stream)
169
    {
170
        if ($stream === null) {
171
            return null;
172
        }
173
174
        $streamData = '';
175
176
        if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
177
            $rootdata = $this->readData($this->props[$this->rootEntry]['startBlock']);
178
179
            $block = $this->props[$stream]['startBlock'];
180
181
            while ($block != -2) {
182
                  $pos = $block * self::SMALL_BLOCK_SIZE;
183
                $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
184
185
                $block = self::getInt4d($this->smallBlockChain, $block*4);
186
            }
187
188
            return $streamData;
189
        }
190
191
        $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
192
        if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
193
            ++$numBlocks;
194
        }
195
196
        if ($numBlocks == 0) {
197
            return '';
198
        }
199
200
        $block = $this->props[$stream]['startBlock'];
201
202
        while ($block != -2) {
203
            $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
204
            $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
205
            $block = self::getInt4d($this->bigBlockChain, $block*4);
206
        }
207
208
        return $streamData;
209
    }
210
211
    /**
212
     * Read a standard stream (by joining sectors using information from SAT)
213
     *
214
     * @param int $blID Sector ID where the stream starts
215
     * @return string Data for standard stream
216
     */
217
    private function readData($blID)
218
    {
219
        $block = $blID;
220
        $data = '';
221
222
        while ($block != -2) {
223
            $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
224
            $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
225
            $block = self::getInt4d($this->bigBlockChain, $block*4);
226
        }
227
        return $data;
228
    }
229
230
    /**
231
     * Read entries in the directory stream.
232
     */
233
    private function readPropertySets()
234
    {
235
        $offset = 0;
236
237
        // loop through entires, each entry is 128 bytes
238
        $entryLen = strlen($this->entry);
239
        while ($offset < $entryLen) {
240
            // entry data (128 bytes)
241
            $data = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
242
243
            // size in bytes of name
244
            $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS+1]) << 8);
245
246
            // type of entry
247
            $type = ord($data[self::TYPE_POS]);
248
249
            // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
250
            // sectorID of first sector of the short-stream container stream, if this entry is root entry
251
            $startBlock = self::getInt4d($data, self::START_BLOCK_POS);
252
253
            $size = self::getInt4d($data, self::SIZE_POS);
254
255
            $name = str_replace("\x00", "", substr($data, 0, $nameSize));
256
            if ($size > 0) {
257
                $this->props[] = array (
258
                        'name' => $name,
259
                        'type' => $type,
260
                        'startBlock' => $startBlock,
261
                        'size' => $size);
262
263
                // tmp helper to simplify checks
264
                $upName = strtoupper($name);
265
266
                switch ($upName) {
267
                    case 'ROOT ENTRY':
268
                    case 'R':
269
                        $this->rootEntry = count($this->props) - 1;
270
                        break;
271
                    case chr(1).'COMPOBJ':
272
                        break;
273
                    case chr(1).'OLE':
274
                        break;
275
                    case chr(5).'SUMMARYINFORMATION':
276
                        $this->summaryInformation = count($this->props) - 1;
277
                        break;
278
                    case chr(5).'DOCUMENTSUMMARYINFORMATION':
279
                        $this->docSummaryInfos = count($this->props) - 1;
280
                        break;
281
                    case 'CURRENT USER':
282
                        $this->currentUser = count($this->props) - 1;
283
                        break;
284
                    case 'PICTURES':
285
                        $this->pictures = count($this->props) - 1;
286
                        break;
287
                    case 'POWERPOINT DOCUMENT':
288
                        $this->powerpointDocument = count($this->props) - 1;
289
                        break;
290
                    default:
291
                        throw new \Exception('OLE Block Not defined: $upName : '.$upName. ' - $name : "'.$name.'"');
292
                }
293
            }
294
295
            $offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
296
        }
297
    }
298
299
    /**
300
     * Read 4 bytes of data at specified position
301
     *
302
     * @param string $data
303
     * @param int $pos
304
     * @return int
305
     */
306
    private static function getInt4d($data, $pos)
307
    {
308
        // FIX: represent numbers correctly on 64-bit system
309
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
310
        // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
311
        $or24 = ord($data[$pos + 3]);
312
        if ($or24 >= 128) {
313
            // negative number
314
            $ord24 = -abs((256 - $or24) << 24);
315
        } else {
316
            $ord24 = ($or24 & 127) << 24;
317
        }
318
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24;
319
    }
320
}
321