Issues (48)

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/Adapter/ZipExtensionAdapter.php (3 issues)

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
/*
4
 * This file is part of Zippy.
5
 *
6
 * (c) Alchemy <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Alchemy\Zippy\Adapter;
13
14
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
15
use Alchemy\Zippy\Adapter\Resource\ZipArchiveResource;
16
use Alchemy\Zippy\Adapter\VersionProbe\ZipExtensionVersionProbe;
17
use Alchemy\Zippy\Archive\Archive;
18
use Alchemy\Zippy\Archive\Member;
19
use Alchemy\Zippy\Exception\NotSupportedException;
20
use Alchemy\Zippy\Exception\RuntimeException;
21
use Alchemy\Zippy\Exception\InvalidArgumentException;
22
use Alchemy\Zippy\Resource\Resource as ZippyResource;
23
use Alchemy\Zippy\Resource\ResourceManager;
24
25
/**
26
 * ZipExtensionAdapter allows you to create and extract files from archives
27
 * using PHP Zip extension
28
 *
29
 * @see http://www.php.net/manual/en/book.zip.php
30
 */
31
class ZipExtensionAdapter extends AbstractAdapter
32
{
33
    private $errorCodesMapping = array(
34
        \ZipArchive::ER_EXISTS => "File already exists",
35
        \ZipArchive::ER_INCONS => "Zip archive inconsistent",
36
        \ZipArchive::ER_INVAL  => "Invalid argument",
37
        \ZipArchive::ER_MEMORY => "Malloc failure",
38
        \ZipArchive::ER_NOENT  => "No such file",
39
        \ZipArchive::ER_NOZIP  => "Not a zip archive",
40
        \ZipArchive::ER_OPEN   => "Can't open file",
41
        \ZipArchive::ER_READ   => "Read error",
42
        \ZipArchive::ER_SEEK   => "Seek error"
43
    );
44
45
    public function __construct(ResourceManager $manager)
46
    {
47
        parent::__construct($manager);
48
        $this->probe = new ZipExtensionVersionProbe();
49
    }
50
51
    /**
52
     * @inheritdoc
53
     */
54
    protected function doListMembers(ResourceInterface $resource)
55
    {
56
        $members = array();
57
        for ($i = 0; $i < $resource->getResource()->numFiles; $i++) {
58
            $stat = $resource->getResource()->statIndex($i);
59
            $members[] = new Member(
60
                $resource,
61
                $this,
62
                $stat['name'],
63
                $stat['size'],
64
                new \DateTime('@' . $stat['mtime']),
65
                0 === strlen($resource->getResource()->getFromIndex($i, 1))
66
            );
67
        }
68
69
        return $members;
70
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75
    public static function getName()
76
    {
77
        return 'zip-extension';
78
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83
    protected function doExtract(ResourceInterface $resource, $to)
84
    {
85
        return $this->extractMembers($resource, null, $to);
86
    }
87
88
    /**
89
     * @inheritdoc
90
     */
91
    protected function doExtractMembers(ResourceInterface $resource, $members, $to, $overwrite = false)
92
    {
93
        if (null === $to) {
94
            // if no destination is given, will extract to zip current folder
95
            $to = dirname(realpath($resource->getResource()->filename));
96
        }
97
98
        if (!is_dir($to)) {
99
            $resource->getResource()->close();
100
            throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
101
        }
102
103
        if (!is_writable($to)) {
104
            $resource->getResource()->close();
105
            throw new InvalidArgumentException(sprintf("%s is not writable", $to));
106
        }
107
108
        if (null !== $members) {
109
            $membersTemp = (array) $members;
110
            if (empty($membersTemp)) {
111
                $resource->getResource()->close();
112
113
                throw new InvalidArgumentException("no members provided");
114
            }
115
            $members = array();
116
            // allows $members to be an array of strings or array of Members
117
            foreach ($membersTemp as $member) {
118
                if ($member instanceof Member) {
119
                    $member = $member->getLocation();
120
                }
121
122
                if ($resource->getResource()->locateName($member) === false) {
123
                    $resource->getResource()->close();
124
125
                    throw new InvalidArgumentException(sprintf('%s is not in the zip file', $member));
126
                }
127
128
                if ($overwrite == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
129
                    if (file_exists($member)) {
130
                        $resource->getResource()->close();
131
132
                        throw new RuntimeException('Target file ' . $member . ' already exists.');
133
                    }
134
                }
135
136
                $members[] = $member;
137
            }
138
        }
139
140
        if (!$resource->getResource()->extractTo($to, $members)) {
141
            $resource->getResource()->close();
142
143
            throw new InvalidArgumentException(sprintf('Unable to extract archive : %s', $resource->getResource()->getStatusString()));
144
        }
145
146
        return new \SplFileInfo($to);
147
    }
148
149
    /**
150
     * @inheritdoc
151
     */
152
    protected function doRemove(ResourceInterface $resource, $files)
153
    {
154
        $files = (array) $files;
155
156
        if (empty($files)) {
157
            throw new InvalidArgumentException("no files provided");
158
        }
159
160
        // either remove all files or none in case of error
161
        foreach ($files as $file) {
162
            if ($resource->getResource()->locateName($file) === false) {
163
                $resource->getResource()->unchangeAll();
164
                $resource->getResource()->close();
165
166
                throw new InvalidArgumentException(sprintf('%s is not in the zip file', $file));
167
            }
168
            if (!$resource->getResource()->deleteName($file)) {
169
                $resource->getResource()->unchangeAll();
170
                $resource->getResource()->close();
171
172
                throw new RuntimeException(sprintf('unable to remove %s', $file));
173
            }
174
        }
175
        $this->flush($resource->getResource());
176
177
        return $files;
178
    }
179
180
    /**
181
     * @inheritdoc
182
     */
183
    protected function doAdd(ResourceInterface $resource, $files, $recursive)
184
    {
185
        $files = (array) $files;
186
        if (empty($files)) {
187
            $resource->getResource()->close();
188
            throw new InvalidArgumentException("no files provided");
189
        }
190
        $this->addEntries($resource, $files, $recursive);
191
192
        return $files;
193
    }
194
195
    /**
196
     * @inheritdoc
197
     */
198
    protected function doCreate($path, $files, $recursive)
199
    {
200
        $files = (array) $files;
201
202
        if (empty($files)) {
203
            throw new NotSupportedException("Cannot create an empty zip");
204
        }
205
206
        $resource = $this->getResource($path, \ZipArchive::CREATE);
207
        $this->addEntries($resource, $files, $recursive);
208
209
        return new Archive($resource, $this, $this->manager);
210
    }
211
212
    /**
213
     * Returns a new instance of the invoked adapter
214
     *
215
     * @return AbstractAdapter
216
     *
217
     * @throws RuntimeException In case object could not be instanciated
218
     */
219
    public static function newInstance()
220
    {
221
        return new ZipExtensionAdapter(ResourceManager::create());
222
    }
223
224
    protected function createResource($path)
225
    {
226
        return $this->getResource($path, \ZipArchive::CHECKCONS);
227
    }
228
229
    private function getResource($path, $mode)
230
    {
231
        $zip = new \ZipArchive();
232
        $res = $zip->open($path, $mode);
233
234
        if ($res !== true) {
235
            throw new RuntimeException($this->errorCodesMapping[$res]);
236
        }
237
238
        return new ZipArchiveResource($zip);
239
    }
240
241
    private function addEntries(ResourceInterface $zipResource, array $files, $recursive)
242
    {
243
        $stack = new \SplStack();
244
245
        $error = null;
246
        $cwd = getcwd();
247
        $collection = $this->manager->handle($cwd, $files);
248
249
        $this->chdir($collection->getContext());
250
251
        $adapter = $this;
252
253
        try {
254
            $collection->forAll(function($i, ZippyResource $resource) use ($zipResource, $stack, $recursive, $adapter) {
255
                $adapter->checkReadability($zipResource->getResource(), $resource->getTarget());
256
                if (is_dir($resource->getTarget())) {
257
                    if ($recursive) {
258
                        $stack->push($resource->getTarget() . ((substr($resource->getTarget(), -1) === DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR));
259
                    } else {
260
                        $adapter->addEmptyDir($zipResource->getResource(), $resource->getTarget());
261
                    }
262
                } else {
263
                    $adapter->addFileToZip($zipResource->getResource(), $resource->getTarget());
264
                }
265
266
                return true;
267
            });
268
269
            // recursively add dirs
270
            while (!$stack->isEmpty()) {
271
                $dir = $stack->pop();
272
                // removes . and ..
273
                $files = array_diff(scandir($dir), array(".", ".."));
274
                if (count($files) > 0) {
275
                    foreach ($files as $file) {
276
                        $file = $dir . $file;
277
                        $this->checkReadability($zipResource->getResource(), $file);
278
                        if (is_dir($file)) {
279
                            $stack->push($file . DIRECTORY_SEPARATOR);
280
                        } else {
281
                            $this->addFileToZip($zipResource->getResource(), $file);
282
                        }
283
                    }
284
                } else {
285
                    $this->addEmptyDir($zipResource->getResource(), $dir);
286
                }
287
            }
288
            $this->flush($zipResource->getResource());
289
290
            $this->manager->cleanup($collection);
291
        } catch (\Exception $e) {
292
            $error = $e;
293
        }
294
295
        $this->chdir($cwd);
296
297
        if ($error) {
298
            throw $error;
299
        }
300
    }
301
302
    /**
303
     * Is public for PHP 5.3 compatibility, should be private
304
     *
305
     * @param \ZipArchive $zip
306
     * @param string      $file
307
     */
308
    public function checkReadability(\ZipArchive $zip, $file)
309
    {
310
        if (!is_readable($file)) {
311
            $zip->unchangeAll();
312
            $zip->close();
313
314
            throw new InvalidArgumentException(sprintf('could not read %s', $file));
315
        }
316
    }
317
318
    /**
319
     * Is public for PHP 5.3 compatibility, should be private
320
     *
321
     * @param \ZipArchive $zip
322
     * @param string      $file
323
     */
324 View Code Duplication
    public function addFileToZip(\ZipArchive $zip, $file)
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.

Loading history...
325
    {
326
        if (!$zip->addFile($file)) {
327
            $zip->unchangeAll();
328
            $zip->close();
329
330
            throw new RuntimeException(sprintf('unable to add %s to the zip file', $file));
331
        }
332
    }
333
334
    /**
335
     * Is public for PHP 5.3 compatibility, should be private
336
     *
337
     * @param \ZipArchive $zip
338
     * @param string      $dir
339
     */
340 View Code Duplication
    public function addEmptyDir(\ZipArchive $zip, $dir)
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.

Loading history...
341
    {
342
        if (!$zip->addEmptyDir($dir)) {
343
            $zip->unchangeAll();
344
            $zip->close();
345
346
            throw new RuntimeException(sprintf('unable to add %s to the zip file', $dir));
347
        }
348
    }
349
350
    /**
351
     * Flushes changes to the archive
352
     *
353
     * @param \ZipArchive $zip
354
     */
355
    private function flush(\ZipArchive $zip) // flush changes by reopening the file
356
    {
357
        $path = $zip->filename;
358
        $zip->close();
359
        $zip->open($path, \ZipArchive::CHECKCONS);
360
    }
361
}
362