Issues (557)

Security Analysis    not enabled

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

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  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.
  Header Injection
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/Phing/Io/FileUtils.php (3 issues)

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Io;
22
23
use Exception;
24
use Phing\Exception\BuildException;
25
use Phing\Filter\ChainReaderHelper;
26
use Phing\Phing;
27
use Phing\Project;
28
use Phing\Util\Character;
29
use Phing\Util\StringHelper;
30
31
/**
32
 * File utility class.
33
 * - handles os independent stuff etc
34
 * - mapper stuff
35
 * - filter stuff.
36
 */
37
class FileUtils
38
{
39
    /**
40
     * path separator string, static, obtained from FileSystem (; or :).
41
     */
42
    private static $pathSeparator;
43
44
    /**
45
     * separator string, static, obtained from FileSystem.
46
     */
47
    private static $separator;
48
49
    /**
50
     * @var false
51
     */
52
    private $dosWithDrive;
53
54
    /**
55
     * @throws IOException
56
     */
57 6
    public static function getPathSeparator(): string
58
    {
59 6
        if (null === self::$pathSeparator) {
60
            self::$pathSeparator = FileSystem::getFileSystem()->getPathSeparator();
61
        }
62
63 6
        return self::$pathSeparator;
64
    }
65
66
    /**
67
     * @throws IOException
68
     */
69 941
    public static function getSeparator(): string
70
    {
71 941
        if (null === self::$separator) {
72
            self::$separator = FileSystem::getFileSystem()->getSeparator();
73
        }
74
75 941
        return self::$separator;
76
    }
77
78
    /**
79
     * Returns the path to the temp directory.
80
     *
81
     * @return string
82
     */
83 9
    public static function getTempDir()
84
    {
85 9
        return Phing::getProperty('php.tmpdir');
86
    }
87
88
    /**
89
     * Returns the default file/dir creation mask value
90
     * (The mask value is prepared w.r.t the current user's file-creation mask value).
91
     *
92
     * @param bool $dirmode Directory creation mask to select
93
     *
94
     * @return int Creation Mask in octal representation
95
     */
96
    public static function getDefaultFileCreationMask($dirmode = false): int
97
    {
98
        // Preparing the creation mask base permission
99
        $permission = (true === $dirmode) ? 0777 : 0666;
100
101
        // Default mask information
102
        $defaultmask = sprintf('%03o', ($permission & ($permission - (int) sprintf('%04o', umask()))));
103
104
        return octdec($defaultmask);
0 ignored issues
show
Bug Best Practice introduced by
The expression return octdec($defaultmask) could return the type double which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
105
    }
106
107
    /**
108
     * Returns a new Reader with filterchains applied.  If filterchains are empty,
109
     * simply returns passed reader.
110
     *
111
     * @param Reader $in            reader to modify (if appropriate)
112
     * @param array  &$filterChains filter chains to apply
113
     *
114
     * @return Reader assembled Reader (w/ filter chains)
115
     */
116 42
    public static function getChainedReader(Reader $in, &$filterChains, Project $project)
117
    {
118 42
        if (!empty($filterChains)) {
119 32
            $crh = new ChainReaderHelper();
120 32
            $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
121 32
            $crh->setPrimaryReader($in);
122 32
            $crh->setFilterChains($filterChains);
123 32
            $crh->setProject($project);
124
125 32
            return $crh->getAssembledReader();
126
        }
127
128 11
        return $in;
129
    }
130
131
    /**
132
     * Copies a file using filter chains.
133
     *
134
     * @param bool  $overwrite
135
     * @param bool  $preserveLastModified
136
     * @param array $filterChains
137
     * @param int   $mode
138
     * @param bool  $preservePermissions
139
     *
140
     * @throws IOException
141
     */
142 53
    public function copyFile(
143
        File $sourceFile,
144
        File $destFile,
145
        Project $project,
146
        $overwrite = false,
147
        $preserveLastModified = true,
148
        &$filterChains = null,
149
        $mode = 0755,
150
        $preservePermissions = true,
151
        int $granularity = 0
152
    ) {
153
        if (
154 53
            $overwrite
155 42
            || !$destFile->exists()
156 53
            || $destFile->lastModified() < $sourceFile->lastModified() - $granularity
157
        ) {
158 52
            if ($destFile->exists() && ($destFile->isFile() || $destFile->isLink())) {
159 1
                $destFile->delete();
160
            }
161
162
            // ensure that parent dir of dest file exists!
163 52
            $parent = $destFile->getParentFile();
164 52
            if (null !== $parent && !$parent->exists()) {
165
                // Setting source directory permissions to target
166
                // (On permissions preservation, the target directory permissions
167
                // will be inherited from the source directory, otherwise the 'mode'
168
                // will be used)
169 7
                $dirMode = ($preservePermissions ? $sourceFile->getParentFile()->getMode() : $mode);
170
171 7
                $parent->mkdirs($dirMode);
172
            }
173
174 52
            if ((is_array($filterChains)) && (!empty($filterChains))) {
175 23
                $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
176 23
                $out = new BufferedWriter(new FileWriter($destFile));
177
178
                // New read() methods returns a big buffer.
179 23
                while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
180 23
                    $out->write($buffer);
181
                }
182
183 23
                if (null !== $in) {
184 23
                    $in->close();
185
                }
186 23
                if (null !== $out) {
187 23
                    $out->close();
188
                }
189
190
                // Set/Copy the permissions on the target
191 23
                if (true === $preservePermissions) {
192 23
                    $destFile->setMode($sourceFile->getMode());
193
                }
194
            } else {
195
                // simple copy (no filtering)
196 29
                $sourceFile->copyTo($destFile);
197
198
                // By default, PHP::Copy also copies the file permissions. Therefore,
199
                // re-setting the mode with the "user file-creation mask" information.
200 29
                if (false === $preservePermissions) {
201
                    $destFile->setMode(FileUtils::getDefaultFileCreationMask());
202
                }
203
            }
204
205 52
            if ($preserveLastModified && !$destFile->isLink()) {
206 2
                $destFile->setLastModified($sourceFile->lastModified());
207
            }
208
        }
209
    }
210
211
    /**
212
     * Attempts to rename a file from a source to a destination.
213
     * If overwrite is set to true, this method overwrites existing file even if the destination file is newer.
214
     * Otherwise, the source file is renamed only if the destination file is older than it.
215
     *
216
     * @param mixed $overwrite
217
     *
218
     * @throws IOException
219
     */
220 1
    public function renameFile(File $sourceFile, File $destFile, $overwrite = false): void
221
    {
222
        // ensure that parent dir of dest file exists!
223 1
        $parent = $destFile->getParentFile();
224 1
        if (null !== $parent) {
225 1
            if (!$parent->exists()) {
226
                $parent->mkdirs();
227
            }
228
        }
229
230 1
        if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
231 1
            if ($destFile->exists()) {
232
                try {
233
                    $destFile->delete();
234
                } catch (Exception $e) {
235
                    throw new BuildException(
236
                        'Unable to remove existing file ' . $destFile->__toString() . ': ' . $e->getMessage()
237
                    );
238
                }
239
            }
240
        }
241
242 1
        $sourceFile->renameTo($destFile);
243
    }
244
245
    /**
246
     * Interpret the filename as a file relative to the given file -
247
     * unless the filename already represents an absolute filename.
248
     *
249
     * @param File   $file     the "reference" file for relative paths. This
250
     *                         instance must be an absolute file and must
251
     *                         not contain ./ or ../ sequences (same for \
252
     *                         instead of /).
253
     * @param string $filename a file name
254
     *
255
     * @throws IOException
256
     *
257
     * @return File A PhingFile object pointing to an absolute file that doesn't contain ./ or ../ sequences
258
     *              and uses the correct separator for the current platform.
259
     */
260 503
    public function resolveFile(File $file, string $filename): File
261
    {
262
        // remove this and use the static class constant File::separator
263
        // as soon as ZE2 is ready
264 503
        $fs = FileSystem::getFileSystem();
265
266 503
        $filename = str_replace(['\\', '/'], $fs->getSeparator(), $filename);
267
268
        // deal with absolute files
269
        if (
270 503
            StringHelper::startsWith($fs->getSeparator(), $filename)
271 503
            || (strlen($filename) >= 2
272 503
                && Character::isLetter($filename[0])
273 503
                && ':' === $filename[1])
274
        ) {
275 111
            return new File($this->normalize($filename));
276
        }
277
278 478
        if (strlen($filename) >= 2 && Character::isLetter($filename[0]) && ':' === $filename[1]) {
279
            return new File($this->normalize($filename));
280
        }
281
282 478
        $helpFile = new File($file->getAbsolutePath());
283
284 478
        $tok = strtok($filename, $fs->getSeparator());
285 478
        while (false !== $tok) {
286 478
            $part = $tok;
287 478
            if ('..' === $part) {
288 174
                $parentFile = $helpFile->getParent();
289 174
                if (null === $parentFile) {
290
                    $msg = "The file or path you specified ({$filename}) is invalid relative to " . $file->getPath();
291
292
                    throw new IOException($msg);
293
                }
294 174
                $helpFile = new File($parentFile);
295 478
            } elseif ('.' !== $part) {
296 447
                $helpFile = new File($helpFile, $part);
297
            }
298 478
            $tok = strtok($fs->getSeparator());
299
        }
300
301 478
        return new File($helpFile->getAbsolutePath());
302
    }
303
304
    /**
305
     * Normalize the given absolute path.
306
     *
307
     * This includes:
308
     *   - Uppercase the drive letter if there is one.
309
     *   - Remove redundant slashes after the drive spec.
310
     *   - resolve all ./, .\, ../ and ..\ sequences.
311
     *   - DOS style paths that start with a drive letter will have
312
     *     \ as the separator.
313
     *
314
     * @param string $path path to normalize
315
     *
316
     * @throws IOException
317
     * @throws BuildException
318
     */
319 925
    public function normalize(string $path): string
320
    {
321 925
        $dissect = $this->dissect($path);
322 925
        $sep = self::getSeparator();
323
324 925
        $s = [];
325 925
        $s[] = $dissect[0];
326 925
        $tok = strtok($dissect[1], $sep);
327 925
        while (false !== $tok) {
328 921
            $thisToken = $tok;
329 921
            if ('.' === $thisToken) {
330
                $tok = strtok($sep);
331
332
                continue;
333
            }
334
335 921
            if ('..' === $thisToken) {
336
                if (count($s) < 2) {
337
                    // using '..' in path that is too short
338
                    throw new IOException("Cannot resolve path: {$path}");
339
                }
340
341
                array_pop($s);
342
            } else { // plain component
343 921
                $s[] = $thisToken;
344
            }
345 921
            $tok = strtok($sep);
346
        }
347
348 925
        $sb = '';
349 925
        foreach ($s as $i => $v) {
350 925
            if ($i > 1) {
351
                // not before the filesystem root and not after it, since root
352
                // already contains one
353 921
                $sb .= $sep;
354
            }
355 925
            $sb .= $v;
356
        }
357
358 925
        $path = $sb;
359 925
        if (true === $this->dosWithDrive) {
360
            $path = str_replace('/', '\\', $path);
361
        }
362
363 925
        return $path;
364
    }
365
366
    /**
367
     * Dissect the specified absolute path.
368
     *
369
     * @throws BuildException
370
     * @throws IOException
371
     *
372
     * @return array {root, remainig path}
373
     */
374 925
    public function dissect(string $path): array
375
    {
376 925
        $sep = self::getSeparator();
377 925
        $path = str_replace(['\\', '/'], $sep, $path);
378
379
        // make sure we are dealing with an absolute path
380
        if (
381 925
            !StringHelper::startsWith($sep, $path)
382 925
            && !(strlen($path) >= 2
383 925
                && Character::isLetter($path[0])
384 925
                && ':' === $path[1])
385
        ) {
386
            throw new BuildException("{$path} is not an absolute path");
387
        }
388
389 925
        $this->dosWithDrive = false;
390 925
        $root = null;
391
392
        // Eliminate consecutive slashes after the drive spec
393
394 925
        if (strlen($path) >= 2 && Character::isLetter($path[0]) && ':' === $path[1]) {
395
            $this->dosWithDrive = true;
0 ignored issues
show
Documentation Bug introduced by
The property $dosWithDrive was declared of type false, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
396
397
            $ca = str_replace('/', '\\', $path);
398
399
            $path = strtoupper($ca[0]) . ':';
400
401
            for ($i = 2, $_i = strlen($ca); $i < $_i; ++$i) {
402
                if (
403
                    ('\\' !== $ca[$i])
404
                    || ('\\' === $ca[$i]
405
                        && '\\' !== $ca[$i - 1])
406
                ) {
407
                    $path .= $ca[$i];
408
                }
409
            }
410
411
            $path = str_replace('\\', $sep, $path);
412
413
            if (2 === strlen($path)) {
414
                $root = $path;
415
                $path = '';
416
            } else {
417
                $root = substr($path, 0, 3);
418
                $path = substr($path, 3);
419
            }
420
        } else {
421 925
            if (1 === strlen($path)) {
422 4
                $root = $sep;
423 4
                $path = '';
424
            } else {
425 921
                if ($path[1] === $sep) {
426
                    // UNC drive
427
                    $root = $sep . $sep;
428
                    $path = substr($path, 2);
429
                } else {
430 921
                    $root = $sep;
431 921
                    $path = substr($path, 1);
432
                }
433
            }
434
        }
435
436 925
        return [$root, $path];
437
    }
438
439
    /**
440
     * Create a temporary file in a given directory.
441
     *
442
     * <p>The file denoted by the returned abstract pathname did not
443
     * exist before this method was invoked, any subsequent invocation
444
     * of this method will yield a different file name.</p>
445
     *
446
     * @param string $prefix       prefix before the random number
447
     * @param string $suffix       file extension; include the '.'.
448
     * @param File   $parentDir    directory to create the temporary file in;
449
     *                             sys_get_temp_dir() used if not specified
450
     * @param bool   $deleteOnExit whether to set the tempfile for deletion on
451
     *                             normal exit
452
     * @param bool   $createFile   true if the file must actually be created. If false
453
     *                             chances exist that a file with the same name is
454
     *                             created in the time between invoking this method
455
     *                             and the moment the file is actually created. If
456
     *                             possible set to true.
457
     *
458
     * @throws BuildException
459
     *
460
     * @return File a File reference to the new temporary file
461
     */
462 2
    public function createTempFile(
463
        $prefix,
464
        $suffix,
465
        ?File $parentDir = null,
466
        $deleteOnExit = false,
467
        $createFile = false
468
    ): File {
469 2
        $result = null;
470 2
        $parent = (null === $parentDir) ? self::getTempDir() : $parentDir->getPath();
471
472 2
        if ($createFile) {
473
            try {
474
                $directory = new File($parent);
475
                // quick but efficient hack to create a unique filename ;-)
476
                $result = null;
0 ignored issues
show
The assignment to $result is dead and can be removed.
Loading history...
477
                do {
478
                    $result = new File($directory, $prefix . substr(md5((string) microtime()), 0, 8) . $suffix);
479
                } while (file_exists($result->getPath()));
480
481
                $fs = FileSystem::getFileSystem();
482
                $fs->createNewFile($result->getPath());
483
                $fs->lock($result);
484
            } catch (IOException $e) {
485
                throw new BuildException('Could not create tempfile in ' . $parent, $e);
486
            }
487
        } else {
488
            do {
489 2
                $result = new File($parent, $prefix . substr(md5((string) microtime()), 0, 8) . $suffix);
490 2
            } while ($result->exists());
491
        }
492
493 2
        if ($deleteOnExit) {
494
            $result->deleteOnExit();
495
        }
496
497 2
        return $result;
498
    }
499
500
    /**
501
     * @throws IOException
502
     *
503
     * @return bool whether contents of two files is the same
504
     */
505 14
    public function contentEquals(File $file1, File $file2): bool
506
    {
507 14
        if (!($file1->exists() && $file2->exists())) {
508 2
            return false;
509
        }
510
511 13
        if (!($file1->canRead() && $file2->canRead())) {
512
            return false;
513
        }
514
515 13
        if ($file1->isDirectory() || $file2->isDirectory()) {
516 1
            return false;
517
        }
518
519 13
        $c1 = file_get_contents($file1->getAbsolutePath());
520 13
        $c2 = file_get_contents($file2->getAbsolutePath());
521
522 13
        return trim($c1) === trim($c2);
523
    }
524
}
525