Issues (25)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/CHM.php (1 issue)

Upgrade to new PHP Analysis Engine

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()}");
0 ignored issues
show
The method getContentSection() does not exist on CHMLib\Entry. Did you maybe mean getContentSectionIndex()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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()
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()
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