This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace CHMLib; |
||
4 | |||
5 | use Exception; |
||
6 | use CHMLib\Reader\Reader; |
||
7 | use CHMLib\Reader\StringReader; |
||
8 | use CHMLib\Exception\UnexpectedHeaderException; |
||
9 | use CHMLib\Reader\FileReader; |
||
10 | |||
11 | /** |
||
12 | * Handle the contents of a CHM file. |
||
13 | */ |
||
14 | class CHM |
||
15 | { |
||
16 | /** |
||
17 | * The reader that provides the data. |
||
18 | * |
||
19 | * @var \CHMLib\Reader\Reader |
||
20 | */ |
||
21 | protected $reader; |
||
22 | |||
23 | /** |
||
24 | * The CHM initial header. |
||
25 | * |
||
26 | * @var \CHMLib\Header\ITSF |
||
27 | */ |
||
28 | protected $itsf; |
||
29 | |||
30 | /** |
||
31 | * The directory listing header. |
||
32 | * |
||
33 | * @var \CHMLib\Header\ITSP |
||
34 | */ |
||
35 | protected $itsp; |
||
36 | |||
37 | /** |
||
38 | * The entries found in this CHM. |
||
39 | * |
||
40 | * @var \CHMLib\Entry[] |
||
41 | */ |
||
42 | protected $entries; |
||
43 | |||
44 | /** |
||
45 | * The data sections. |
||
46 | * |
||
47 | * @var \CHMLib\Section\Section[] |
||
48 | */ |
||
49 | protected $sections; |
||
50 | |||
51 | /** |
||
52 | * The TOC. |
||
53 | * |
||
54 | * @var \CHMLib\TOCIndex\Tree|null|false |
||
55 | */ |
||
56 | protected $toc; |
||
57 | |||
58 | /** |
||
59 | * The index. |
||
60 | * |
||
61 | * @var \CHMLib\TOCIndex\Tree|null|false |
||
62 | */ |
||
63 | protected $index; |
||
64 | |||
65 | /** |
||
66 | * Initializes the instance. |
||
67 | * |
||
68 | * @param \CHMLib\Reader\Reader $reader The reader that provides the data. |
||
69 | * |
||
70 | * @throws \Exception Throws an Exception in case of errors. |
||
71 | */ |
||
72 | 4 | public function __construct(Reader $reader) |
|
73 | { |
||
74 | 4 | $this->reader = $reader; |
|
75 | 4 | $reader->setPosition(0); |
|
76 | 4 | $this->itsf = new Header\ITSF($reader); |
|
77 | 4 | if ($this->itsf->getSectionOffset() >= 0 && $this->itsf->getSectionLength() >= 16 /* === 24*/) { |
|
78 | 4 | $reader->setPosition($this->itsf->getSectionOffset()); |
|
79 | 4 | /* Unknown (510) */ $reader->readUInt32(); |
|
80 | 4 | /* Unknown (0) */ $reader->readUInt32(); |
|
81 | 4 | $totalLength = $reader->readUInt64(); |
|
82 | 4 | if ($totalLength !== $reader->getLength()) { |
|
83 | throw new Exception("Invalid CHM size: expected length $totalLength, current length {$reader->getLength()}"); |
||
84 | } |
||
85 | } |
||
86 | 4 | $reader->setPosition($this->itsf->getDirectoryOffset()); |
|
87 | 4 | $this->itsp = new Header\ITSP($reader); |
|
88 | |||
89 | 4 | $expectedDirectoryLength = $this->itsf->getDirectoryLength(); |
|
90 | 4 | $calculatedDirectoryLength = $this->itsp->getHeaderLength() + $this->itsp->getNumberOfDirectoryChunks() * $this->itsp->getDirectoryChunkSize(); |
|
91 | 4 | if ($expectedDirectoryLength !== $calculatedDirectoryLength) { |
|
92 | throw new Exception("Unexpected directory list size (expected: $expectedDirectoryLength, calculated: $calculatedDirectoryLength)"); |
||
93 | } |
||
94 | |||
95 | 4 | $this->sections = array(); |
|
96 | 4 | $this->sections[0] = new Section\UncompressedSection($this); |
|
97 | |||
98 | 4 | $this->entries = $this->retrieveEntryList(); |
|
99 | |||
100 | 4 | $this->retrieveSectionList(); |
|
101 | |||
102 | 4 | $this->toc = null; |
|
103 | 4 | $this->index = null; |
|
104 | 4 | } |
|
105 | |||
106 | /** |
||
107 | * Destruct the instance. |
||
108 | */ |
||
109 | public function __destruct() |
||
110 | { |
||
111 | unset($this->reader); |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * Create a new CHM instance reading a file. |
||
116 | * |
||
117 | * @param string $filename |
||
118 | * |
||
119 | * @return static |
||
120 | */ |
||
121 | 4 | public static function fromFile($filename) |
|
122 | { |
||
123 | 4 | $reader = new FileReader($filename); |
|
124 | |||
125 | 4 | return new static($reader); |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * Get the reader that provides the data. |
||
130 | * |
||
131 | * @return \CHMLib\Reader\Reader |
||
132 | */ |
||
133 | 486 | public function getReader() |
|
134 | { |
||
135 | 486 | return $this->reader; |
|
136 | } |
||
137 | |||
138 | /** |
||
139 | * Get the CHM initial header. |
||
140 | * |
||
141 | * @return \CHMLib\Header\ITSF |
||
142 | */ |
||
143 | 4 | public function getITSF() |
|
144 | { |
||
145 | 4 | return $this->itsf; |
|
146 | } |
||
147 | |||
148 | /** |
||
149 | * Get the directory listing header. |
||
150 | * |
||
151 | * @return \CHMLib\Header\ITSP |
||
152 | */ |
||
153 | public function getITSP() |
||
154 | { |
||
155 | return $this->itsp; |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Get an entry given its full path. |
||
160 | * |
||
161 | * @param string $path The full path of the entry to look for. |
||
162 | * @param bool $caseSensitive Perform a case-sensitive search? |
||
163 | * |
||
164 | * @return \CHMLib\Entry|null |
||
165 | */ |
||
166 | 491 | public function getEntryByPath($path, $caseSensitive = false) |
|
167 | { |
||
168 | 491 | if (isset($this->entries[$path])) { |
|
169 | 491 | return $this->entries[$path]; |
|
170 | } |
||
171 | if ($caseSensitive) { |
||
172 | return null; |
||
173 | } |
||
174 | $entries = array_change_key_case($this->entries, CASE_LOWER); |
||
175 | $path = strtolower($path); |
||
176 | return isset($entries[$path]) ? $entries[$path] : null; |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Get the entries contained in this CHM. |
||
181 | * |
||
182 | * @param int|null $type One or more Entry::TYPE_... values (defaults to Entry::TYPE_FILE | Entry::TYPE_DIRECTORY if null). |
||
183 | */ |
||
184 | 3 | public function getEntries($type = null) |
|
185 | { |
||
186 | 3 | if ($type === null) { |
|
187 | $type = Entry::TYPE_FILE | Entry::TYPE_DIRECTORY; |
||
188 | } |
||
189 | 3 | $result = array(); |
|
190 | 3 | foreach ($this->entries as $entry) { |
|
191 | 3 | if (($entry->getType() & $type) !== 0) { |
|
192 | 3 | $result[] = $entry; |
|
193 | } |
||
194 | } |
||
195 | |||
196 | 3 | return $result; |
|
197 | } |
||
198 | |||
199 | /** |
||
200 | * Return a section given its index. |
||
201 | * |
||
202 | * @param int $i |
||
203 | * |
||
204 | * @return \CHMLib\Section\Section|null |
||
205 | */ |
||
206 | 491 | public function getSectionByIndex($i) |
|
207 | { |
||
208 | 491 | return isset($this->sections[$i]) ? $this->sections[$i] : null; |
|
209 | } |
||
210 | |||
211 | /** |
||
212 | * Retrieve the list of the entries contained in this CHM. |
||
213 | * |
||
214 | * @throws \Exception Throws an Exception in case of errors. |
||
215 | * |
||
216 | * @return \CHMLib\Entry[] |
||
217 | */ |
||
218 | 4 | protected function retrieveEntryList() |
|
219 | { |
||
220 | 4 | $result = array(); |
|
221 | 4 | $chunkOffset = $this->itsf->getDirectoryOffset() + $this->itsp->getHeaderLength(); |
|
222 | 4 | $chunkSize = $this->itsp->getDirectoryChunkSize(); |
|
223 | 4 | for ($i = $this->itsp->getFirstPMGLChunkNumber(), $l = $this->itsp->getLastPMGLChunkNumber(); $i <= $l; ++$i) { |
|
224 | 4 | $offset = $chunkOffset + $i * $chunkSize; |
|
225 | 4 | $this->reader->setPosition($offset); |
|
226 | try { |
||
227 | 4 | $pmgl = new Header\PMGL($this->reader); |
|
228 | } catch (UnexpectedHeaderException $x) { |
||
229 | if ($x->getFoundHeader() !== 'PMGI') { |
||
230 | throw $x; |
||
231 | } |
||
232 | $this->reader->setPosition($offset); |
||
233 | new Header\PMGI($this->reader); |
||
234 | $pmgl = null; |
||
235 | } |
||
236 | 4 | if ($pmgl !== null) { |
|
237 | 4 | $end = $offset + $chunkSize - $pmgl->getFreeSpace(); |
|
238 | 4 | $cur = $this->reader->getPosition(); |
|
239 | 4 | while ($cur < $end) { |
|
240 | 4 | $this->reader->setPosition($cur); |
|
241 | 4 | $entry = new Entry($this); |
|
242 | 4 | $result[$entry->getPath()] = $entry; |
|
243 | 4 | $cur = $this->reader->getPosition(); |
|
244 | } |
||
245 | } |
||
246 | } |
||
247 | |||
248 | 4 | return $result; |
|
249 | } |
||
250 | |||
251 | /** |
||
252 | * Retrieve the list of the data sections contained in this CHM. |
||
253 | * |
||
254 | * @throws \Exception Throws an Exception in case of errors. |
||
255 | */ |
||
256 | 4 | protected function retrieveSectionList() |
|
257 | { |
||
258 | 4 | $nameList = $this->getEntryByPath('::DataSpace/NameList'); |
|
259 | |||
260 | 4 | if ($nameList === null) { |
|
261 | throw new Exception("Missing required entry: '::DataSpace/NameList'"); |
||
262 | } |
||
263 | 4 | if ($nameList->getContentSectionIndex() !== 0) { |
|
264 | throw new Exception("The content of the entry '{$nameList->getPath()}' should be in section 0, but it's in section {$nameList->getContentSection()}"); |
||
265 | } |
||
266 | |||
267 | 4 | $nameListReader = new StringReader($nameList->getContents()); |
|
268 | 4 | /* Length */ $nameListReader->readUInt16(); |
|
269 | 4 | $numSections = $nameListReader->readUInt16(); |
|
270 | 4 | if ($numSections === 0) { |
|
271 | throw new Exception('No content section defined.'); |
||
272 | } |
||
273 | 4 | for ($i = 0; $i < $numSections; ++$i) { |
|
274 | 4 | $nameLength = $nameListReader->readUInt16(); |
|
275 | 4 | $utf16name = $nameListReader->readString($nameLength * 2); |
|
276 | 4 | $nameListReader->readUInt16(); |
|
277 | 4 | $name = iconv('UTF-16LE', 'UTF-8', $utf16name); |
|
278 | 4 | switch ($name) { |
|
279 | 4 | case 'Uncompressed': |
|
280 | 4 | break; |
|
281 | 4 | case 'MSCompressed': |
|
282 | 4 | if ($i === 0) { |
|
283 | throw new Exception('First data section should be Uncompressed'); |
||
284 | } else { |
||
285 | 4 | $this->sections[$i] = new Section\MSCompressedSection($this); |
|
286 | } |
||
287 | 4 | break; |
|
288 | default: |
||
289 | throw new Exception("Unknown data section: $name"); |
||
290 | } |
||
291 | } |
||
292 | 4 | } |
|
293 | |||
294 | /** |
||
295 | * Get the TOC of this CHM file (if available). |
||
296 | * |
||
297 | * @return \CHMLib\TOCIndex\Tree|null |
||
298 | */ |
||
299 | 1 | View Code Duplication | public function getTOC() |
0 ignored issues
–
show
|
|||
300 | { |
||
301 | 1 | if ($this->toc === null) { |
|
302 | 1 | $r = false; |
|
303 | 1 | foreach ($this->entries as $entry) { |
|
304 | 1 | if ($entry->isFile() && strcasecmp(substr($entry->getPath(), -4), '.hhc') === 0) { |
|
305 | 1 | $r = TOCIndex\Tree::fromString($this, $entry->getContents()); |
|
306 | 1 | break; |
|
307 | } |
||
308 | } |
||
309 | 1 | $this->toc = $r; |
|
310 | } |
||
311 | |||
312 | 1 | return ($this->toc === false) ? null : $this->toc; |
|
313 | } |
||
314 | |||
315 | /** |
||
316 | * Get the index of this CHM file (if available). |
||
317 | * |
||
318 | * @return \CHMLib\TOCIndex\Tree|null |
||
319 | */ |
||
320 | View Code Duplication | public function getIndex() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
321 | { |
||
322 | if ($this->index === null) { |
||
323 | $r = false; |
||
324 | foreach ($this->entries as $entry) { |
||
325 | if ($entry->isFile() && strcasecmp(substr($entry->getPath(), -4), '.hhk') === 0) { |
||
326 | $r = TOCIndex\Tree::fromString($this, $entry->getContents()); |
||
327 | break; |
||
328 | } |
||
329 | } |
||
330 | $this->index = $r; |
||
331 | } |
||
332 | |||
333 | return ($this->index === false) ? null : $this->index; |
||
334 | } |
||
335 | } |
||
336 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.