Passed
Push — master ( e1f86a...4e1a3a )
by Siad
05:23
created

FileSystem::checkAccess()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 28
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 9
nc 6
nop 2
dl 0
loc 28
ccs 10
cts 10
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Io;
21
22
use Exception;
23
use FilesystemIterator;
24
use Phing\Phing;
25
26
/**
27
 * This is an abstract class for platform specific filesystem implementations
28
 * you have to implement each method in the platform specific filesystem implementation
29
 * classes Your local filesytem implementation must extend this class.
30
 * You should also use this class as a template to write your local implementation
31
 * Some native PHP filesystem specific methods are abstracted here as well. Anyway
32
 * you _must_ always use this methods via a PhingFile object (that by nature uses the
33
 * *FileSystem drivers to access the real filesystem via this class using natives.
34
 *
35
 * FIXME:
36
 *  - Error handling reduced to min fallthrough runtime exceptions
37
 *    more precise errorhandling is done by the PhingFile class
38
 *
39
 * @author Charlie Killian <[email protected]>
40
 * @author Hans Lellelid <[email protected]>
41
 *
42
 * @package phing.system.io
43
 */
44
abstract class FileSystem
45
{
46
47
    /**
48
     * @var int
49
     */
50
    public const BA_EXISTS = 0x01;
51
52
    /**
53
     * @var int
54
     */
55
    public const BA_REGULAR = 0x02;
56
57
    /**
58
     * @var int
59
     */
60
    public const BA_DIRECTORY = 0x04;
61
62
    /**
63
     * @var int
64
     */
65
    public const BA_HIDDEN = 0x08;
66
67
    /**
68
     * Instance for getFileSystem() method.
69
     *
70
     * @var FileSystem
71
     */
72
    private static $fs;
73
74
    /**
75
     * @var File[]
76
     */
77
    private static $filesToDelete = [];
78
79
    /**
80
     * Static method to return the FileSystem singelton representing
81
     * this platform's local filesystem driver.
82
     *
83
     * @return FileSystem
84
     * @throws IOException
85
     */
86 888
    public static function getFileSystem()
87
    {
88 888
        if (self::$fs === null) {
89 7
            switch (Phing::getProperty('host.fstype')) {
90 7
                case 'UNIX':
91 5
                    self::$fs = new UnixFileSystem();
92 5
                    break;
93 2
                case 'WINDOWS':
94 1
                    self::$fs = new WindowsFileSystem();
95 1
                    break;
96
                default:
97 1
                    throw new IOException("Host uses unsupported filesystem, unable to proceed");
98
            }
99
        }
100
101 887
        return self::$fs;
102
    }
103
104
    /* -- Normalization and construction -- */
105
106
    /**
107
     * Return the local filesystem's name-separator character.
108
     */
109
    abstract public function getSeparator();
110
111
    /**
112
     * Return the local filesystem's path-separator character.
113
     */
114
    abstract public function getPathSeparator();
115
116
    /**
117
     * Convert the given pathname string to normal form.  If the string is
118
     * already in normal form then it is simply returned.
119
     *
120
     * @param string $strPath
121
     */
122
    abstract public function normalize($strPath);
123
124
    /**
125
     * Compute the length of this pathname string's prefix.  The pathname
126
     * string must be in normal form.
127
     *
128
     * @param string $pathname
129
     */
130
    abstract public function prefixLength($pathname);
131
132
    /**
133
     * Resolve the child pathname string against the parent.
134
     * Both strings must be in normal form, and the result
135
     * will be a string in normal form.
136
     *
137
     * @param string $parent
138
     * @param string $child
139
     */
140
    abstract public function resolve($parent, $child);
141
142
    /**
143
     * Resolve the given abstract pathname into absolute form.  Invoked by the
144
     * getAbsolutePath and getCanonicalPath methods in the PhingFile class.
145
     *
146
     * @param File $f
147
     */
148
    abstract public function resolveFile(File $f);
149
150
    /**
151
     * Return the parent pathname string to be used when the parent-directory
152
     * argument in one of the two-argument PhingFile constructors is the empty
153
     * pathname.
154
     */
155
    abstract public function getDefaultParent();
156
157
    /**
158
     * Post-process the given URI path string if necessary.  This is used on
159
     * win32, e.g., to transform "/c:/foo" into "c:/foo".  The path string
160
     * still has slash separators; code in the PhingFile class will translate them
161
     * after this method returns.
162
     *
163
     * @param string $path
164
     */
165
    abstract public function fromURIPath($path);
166
167
    /* -- Path operations -- */
168
169
    /**
170
     * Tell whether or not the given abstract pathname is absolute.
171
     *
172
     * @param File $f
173
     */
174
    abstract public function isAbsolute(File $f);
175
176
    /**
177
     * canonicalize filename by checking on disk
178
     *
179
     * @param  string $strPath
180
     * @return mixed  Canonical path or false if the file doesn't exist.
181
     */
182 853
    public function canonicalize($strPath)
183
    {
184 853
        return @realpath($strPath);
185
    }
186
187
    /* -- Attribute accessors -- */
188
189
    /**
190
     * Check whether the file or directory denoted by the given abstract
191
     * pathname may be accessed by this process.  If the second argument is
192
     * false, then a check for read access is made; if the second
193
     * argument is true, then a check for write (not read-write)
194
     * access is made.  Return false if access is denied or an I/O error
195
     * occurs.
196
     *
197
     * @param  File    $f
198
     * @param  boolean $write
199
     * @return bool
200
     */
201 862
    public function checkAccess(File $f, $write = false)
202
    {
203
        // we clear stat cache, its expensive to look up from scratch,
204
        // but we need to be sure
205 862
        @clearstatcache();
1 ignored issue
show
Bug introduced by
Are you sure the usage of clearstatcache() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for clearstatcache(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

205
        /** @scrutinizer ignore-unhandled */ @clearstatcache();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
206
207
208
        // Shouldn't this be $f->GetAbsolutePath() ?
209
        // And why doesn't GetAbsolutePath() work?
210
211 862
        $strPath = (string) $f->getPath();
212
213
        // FIXME
214
        // if file object does denote a file that yet not existst
215
        // path rights are checked
216 862
        if (!@file_exists($strPath) && !is_dir($strPath)) {
217 245
            $strPath = $f->getParent();
218 245
            if ($strPath === null || !is_dir($strPath)) {
219 52
                $strPath = Phing::getProperty("user.dir");
220
            }
221
            //$strPath = dirname($strPath);
222
        }
223
224 862
        if (!$write) {
225 862
            return (bool) @is_readable($strPath);
226
        }
227
228 185
        return (bool) @is_writable($strPath);
229
    }
230
231
    /**
232
     * Whether file can be deleted.
233
     *
234
     * @param  File $f
235
     * @return boolean
236
     */
237
    public function canDelete(File $f)
238
    {
239
        clearstatcache();
240
        $dir = dirname($f->getAbsolutePath());
241
242
        return @is_writable($dir);
243
    }
244
245
    /**
246
     * Return the time at which the file or directory denoted by the given
247
     * abstract pathname was last modified, or zero if it does not exist or
248
     * some other I/O error occurs.
249
     *
250
     * @param  File $f
251
     * @return int
252
     * @throws IOException
253
     */
254 55
    public function getLastModifiedTime(File $f)
255
    {
256 55
        if (!$f->exists()) {
257 5
            return 0;
258
        }
259
260 55
        @clearstatcache();
1 ignored issue
show
Bug introduced by
Are you sure the usage of clearstatcache() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for clearstatcache(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

260
        /** @scrutinizer ignore-unhandled */ @clearstatcache();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
261 55
        error_clear_last();
262 55
        $strPath = (string) $f->getPath();
263
264 55
        if (@is_link($strPath)) {
265 2
            $stats = @lstat($strPath);
266
267 2
            if (!isset($stats['mtime'])) {
268
                $mtime = false;
269
            } else {
270 2
                $mtime = $stats['mtime'];
271
            }
272
        } else {
273 55
            $mtime = @filemtime($strPath);
274
        }
275
276 55
        if (false === $mtime) {
277
            $lastError = error_get_last();
278
            $errormsg = $lastError['message'] ?? 'unknown error';
279
            $msg = "FileSystem::getLastModifiedTime() FAILED. Can not get modified time of $strPath. $errormsg";
280
            throw new IOException($msg);
281
        }
282
283 55
        return (int) $mtime;
284
    }
285
286
    /**
287
     * Return the length in bytes of the file denoted by the given abstract
288
     * pathname, or zero if it does not exist, is a directory, or some other
289
     * I/O error occurs.
290
     *
291
     * @param  File $f
292
     * @return int
293
     * @throws IOException
294
     */
295 27
    public function getLength(File $f)
296
    {
297 27
        error_clear_last();
298 27
        $strPath = (string) $f->getAbsolutePath();
299 27
        $fs = filesize((string) $strPath);
300 27
        if ($fs !== false) {
301 27
            return $fs;
302
        }
303
304
        $lastError = error_get_last();
305
        $errormsg = $lastError['message'] ?? 'unknown error';
306
        $msg = "FileSystem::Read() FAILED. Cannot get filesize of $strPath. $errormsg";
307
        throw new IOException($msg);
308
    }
309
310
    /* -- File operations -- */
311
312
    /**
313
     * Create a new empty file with the given pathname.  Return
314
     * true if the file was created and false if a
315
     * file or directory with the given pathname already exists.  Throw an
316
     * IOException if an I/O error occurs.
317
     *
318
     * @param  string $strPathname Path of the file to be created.
319
     * @return boolean
320
     * @throws IOException
321
     */
322 80
    public function createNewFile($strPathname)
323
    {
324 80
        if (@file_exists($strPathname)) {
325
            return false;
326
        }
327
328
        // Create new file
329 80
        $fp = @fopen($strPathname, "w");
330 80
        if ($fp === false) {
331 1
            $error = error_get_last();
332 1
            throw new IOException(
333 1
                "The file \"$strPathname\" could not be created: " . $error['message'] ?? 'unknown error'
334
            );
335
        }
336 79
        @fclose($fp);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

336
        /** @scrutinizer ignore-unhandled */ @fclose($fp);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
337
338 79
        return true;
339
    }
340
341
    /**
342
     * Delete the file or directory denoted by the given abstract pathname,
343
     * returning true if and only if the operation succeeds.
344
     *
345
     * @param  File    $f
346
     * @param  boolean $recursive
347
     * @throws IOException
348
     */
349 206
    public function delete(File $f, $recursive = false)
350
    {
351 206
        if ($f->isDirectory()) {
352 167
            $this->rmdir($f->getPath(), $recursive);
353
        } else {
354 160
            $this->unlink($f->getPath());
355
        }
356 206
    }
357
358
    /**
359
     * Arrange for the file or directory denoted by the given abstract
360
     * pathname to be deleted when Phing::shutdown is called, returning
361
     * true if and only if the operation succeeds.
362
     *
363
     * @param  File $f
364
     * @throws IOException
365
     */
366
    public function deleteOnExit(File $f)
367
    {
368
        self::$filesToDelete[] = $f;
369
    }
370
371 1
    public static function deleteFilesOnExit()
372
    {
373 1
        foreach (self::$filesToDelete as $file) {
374
            $file->delete();
375
        }
376 1
    }
377
378
    /**
379
     * Create a new directory denoted by the given abstract pathname,
380
     * returning true if and only if the operation succeeds.
381
     *
382
     * The behaviour is the same as of "mkdir" command in Linux.
383
     * Without $mode argument, the directory is created with permissions
384
     * corresponding to the umask setting.
385
     * If $mode argument is specified, umask setting is ignored and
386
     * the permissions are set according to the $mode argument using chmod().
387
     *
388
     * @param  File     $f
389
     * @param  int|null $mode
390
     * @return boolean
391
     */
392 167
    public function createDirectory(&$f, $mode = null)
393
    {
394 167
        if ($mode === null) {
395 164
            $return = @mkdir($f->getAbsolutePath());
396
        } else {
397
            // If the $mode is specified, mkdir() is called with the $mode
398
            // argument so that the new directory does not temporarily have
399
            // higher permissions than it should before chmod() is called.
400 11
            $return = @mkdir($f->getAbsolutePath(), $mode);
401 11
            if ($return) {
402 11
                chmod($f->getAbsolutePath(), $mode);
403
            }
404
        }
405
406 167
        return $return;
407
    }
408
409
    /**
410
     * Rename the file or directory denoted by the first abstract pathname to
411
     * the second abstract pathname, returning true if and only if
412
     * the operation succeeds.
413
     *
414
     * @param  File $f1 abstract source file
415
     * @param  File $f2 abstract destination file
416
     * @return void
417
     * @throws IOException if rename cannot be performed
418
     */
419 1
    public function rename(File $f1, File $f2)
420
    {
421 1
        error_clear_last();
422
        // get the canonical paths of the file to rename
423 1
        $src = $f1->getAbsolutePath();
424 1
        $dest = $f2->getAbsolutePath();
425 1
        if (false === @rename($src, $dest)) {
426
            $lastError = error_get_last();
427
            $errormsg = $lastError['message'] ?? 'unknown error';
428
            $msg = "Rename FAILED. Cannot rename $src to $dest. $errormsg";
429
            throw new IOException($msg);
430
        }
431 1
    }
432
433
    /**
434
     * Set the last-modified time of the file or directory denoted by the
435
     * given abstract pathname returning true if and only if the
436
     * operation succeeds.
437
     *
438
     * @param  File $f
439
     * @param  int  $time
440
     * @return void
441
     * @throws IOException
442
     */
443 75
    public function setLastModifiedTime(File $f, $time)
444
    {
445 75
        error_clear_last();
446 75
        $path = $f->getPath();
447 75
        $success = @touch($path, $time);
448 75
        if (!$success) {
449
            $lastError = error_get_last();
450
            $errormsg = $lastError['message'] ?? 'unknown error';
451
            throw new IOException("Could not touch '" . $path . "' due to: $errormsg");
452
        }
453 75
    }
454
455
    /* -- Basic infrastructure -- */
456
457
    /**
458
     * Compare two abstract pathnames lexicographically.
459
     *
460
     * @param  File $f1
461
     * @param  File $f2
462
     * @return int
463
     * @throws IOException
464
     */
465
    public function compare(File $f1, File $f2)
466
    {
467
        throw new IOException("compare() not implemented by local fs driver");
468
    }
469
470
    /**
471
     * Copy a file.
472
     *
473
     * @param File $src  Source path and name file to copy.
474
     * @param File $dest Destination path and name of new file.
475
     *
476
     * @return void
477
     *
478
     * @throws IOException if file cannot be copied.
479
     */
480 25
    public function copy(File $src, File $dest)
481
    {
482
        // Recursively copy a directory
483 25
        if ($src->isDirectory()) {
484
            $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath());
485
        }
486
487 25
        $srcPath = $src->getAbsolutePath();
488 25
        $destPath = $dest->getAbsolutePath();
489
490 25
        error_clear_last();
491 25
        if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err.
492
            // Add error from php to end of log message. $errormsg.
493
            $lastError = error_get_last();
494
            $errormsg = $lastError['message'] ?? 'unknown error';
495
            $msg = "FileSystem::copy() FAILED. Cannot copy $srcPath to $destPath. $errormsg";
496
            throw new IOException($msg);
497
        }
498
499 25
        $dest->setMode($src->getMode());
500 25
    }
501
502
    /**
503
     * Copy a file, or recursively copy a folder and its contents
504
     *
505
     * @param string $source Source path
506
     * @param string $dest   Destination path
507
     *
508
     * @return  bool   Returns TRUE on success, FALSE on failure
509
     * @author  Aidan Lister <[email protected]>
510
     * @version 1.0.1
511
     * @link    http://aidanlister.com/repos/v/function.copyr.php
512
     */
513
    public function copyr($source, $dest)
514
    {
515
        // Check for symlinks
516
        if (is_link($source)) {
517
            return symlink(readlink($source), $dest);
518
        }
519
520
        // Simple copy for a file
521
        if (is_file($source)) {
522
            return copy($source, $dest);
523
        }
524
525
        // Make destination directory
526
        if (!is_dir($dest) && !mkdir($dest) && !is_dir($dest)) {
527
            return false;
528
        }
529
530
        // Loop through the folder
531
        $dir = dir($source);
532
        while (false !== $entry = $dir->read()) {
533
            // Skip pointers
534
            if ($entry == '.' || $entry == '..') {
535
                continue;
536
            }
537
538
            // Deep copy directories
539
            $this->copyr("$source/$entry", "$dest/$entry");
540
        }
541
542
        // Clean up
543
        $dir->close();
544
545
        return true;
546
    }
547
548
    /**
549
     * Change the ownership on a file or directory.
550
     *
551
     * @param string $pathname Path and name of file or directory.
552
     * @param string $user     The user name or number of the file or directory. See http://us.php.net/chown
553
     *
554
     * @return void
555
     *
556
     * @throws IOException if operation failed.
557
     */
558
    public function chown($pathname, $user)
559
    {
560
        error_clear_last();
561
        if (false === @chown($pathname, $user)) { // FAILED.
562
            $lastError = error_get_last();
563
            $errormsg = $lastError['message'] ?? 'unknown error';
564
            $msg = "FileSystem::chown() FAILED. Cannot chown $pathname. User $user." . (isset($errormsg) ? ' ' . $errormsg : "");
565
            throw new IOException($msg);
566
        }
567
    }
568
569
    /**
570
     * Change the group on a file or directory.
571
     *
572
     * @param string $pathname Path and name of file or directory.
573
     * @param string $group    The group of the file or directory. See http://us.php.net/chgrp
574
     *
575
     * @return void
576
     * @throws IOException if operation failed.
577
     */
578
    public function chgrp($pathname, $group)
579
    {
580
        error_clear_last();
581
        if (false === @chgrp($pathname, $group)) { // FAILED.
582
            $lastError = error_get_last();
583
            $errormsg = $lastError['message'] ?? 'unknown error';
584
            $msg = "FileSystem::chgrp() FAILED. Cannot chown $pathname. Group $group." . (isset($errormsg) ? ' ' . $errormsg : "");
585
            throw new IOException($msg);
586
        }
587
    }
588
589
    /**
590
     * Change the permissions on a file or directory.
591
     *
592
     * @param string $pathname Path and name of file or directory.
593
     * @param int    $mode     The mode (permissions) of the file or
594
     *                         directory. If using octal add leading
595
     *                         0. eg. 0777. Mode is affected by the
596
     *                         umask system setting.
597
     *
598
     * @return void
599
     * @throws IOException if operation failed.
600
     */
601 60
    public function chmod($pathname, $mode)
602
    {
603 60
        error_clear_last();
604 60
        $str_mode = decoct($mode); // Show octal in messages.
605 60
        if (false === @chmod($pathname, $mode)) { // FAILED.
606
            $lastError = error_get_last();
607
            $errormsg = $lastError['message'] ?? 'unknown error';
608
            $msg = "FileSystem::chmod() FAILED. Cannot chmod $pathname. Mode $str_mode." . (isset($errormsg) ? ' ' . $errormsg : "");
609
            throw new IOException($msg);
610
        }
611 60
    }
612
613
    /**
614
     * Locks a file and throws an Exception if this is not possible.
615
     *
616
     * @param  File $f
617
     * @return void
618
     * @throws IOException
619
     */
620
    public function lock(File $f)
621
    {
622
        $filename = $f->getPath();
623
        $fp = @fopen($filename, "w");
624
        $result = @flock($fp, LOCK_EX);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of flock() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

624
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_EX);
Loading history...
625
        @fclose($fp);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

625
        /** @scrutinizer ignore-unhandled */ @fclose($fp);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

625
        @fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
626
        if (!$result) {
627
            throw new IOException("Could not lock file '$filename'");
628
        }
629
    }
630
631
    /**
632
     * Unlocks a file and throws an IO Error if this is not possible.
633
     *
634
     * @param  File $f
635
     * @return void
636
     * @throws IOException
637
     */
638
    public function unlock(File $f)
639
    {
640
        $filename = $f->getPath();
641
        $fp = @fopen($filename, "w");
642
        $result = @flock($fp, LOCK_UN);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of flock() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

642
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_UN);
Loading history...
643
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

643
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
644
        if (!$result) {
645
            throw new IOException("Could not unlock file '$filename'");
646
        }
647
    }
648
649
    /**
650
     * Delete a file.
651
     *
652
     * @param string $file Path and/or name of file to delete.
653
     *
654
     * @return void
655
     * @throws IOException - if an error is encountered.
656
     */
657 163
    public function unlink($file)
658
    {
659 163
        error_clear_last();
660 163
        if (false === @unlink($file)) {
661
            $lastError = error_get_last();
662
            $errormsg = $lastError['message'] ?? 'unknown error';
663
            $msg = "FileSystem::unlink() FAILED. Cannot unlink '$file'. $errormsg";
664
            throw new IOException($msg);
665
        }
666 163
    }
667
668
    /**
669
     * Symbolically link a file to another name.
670
     *
671
     * Currently symlink is not implemented on Windows. Don't use if the application is to be portable.
672
     *
673
     * @param  string $target Path and/or name of file to link.
674
     * @param  string $link   Path and/or name of link to be created.
675
     * @return void
676
     * @throws IOException
677
     */
678 15
    public function symlink($target, $link)
679
    {
680
681 15
        error_clear_last();
682
        // If Windows OS then symlink() will report it is not supported in
683
        // the build. Use this error instead of checking for Windows as the OS.
684
685 15
        if (false === @symlink($target, $link)) {
686
            $lastError = error_get_last();
687
            $errormsg = $lastError['message'] ?? 'unknown error';
688
            // Add error from php to end of log message.
689
            $msg = "FileSystem::Symlink() FAILED. Cannot symlink '$target' to '$link'. $errormsg";
690
            throw new IOException($msg);
691
        }
692 15
    }
693
694
    /**
695
     * Set the modification and access time on a file to the present time.
696
     *
697
     * @param  string $file Path and/or name of file to touch.
698
     * @param  int    $time
699
     * @return void
700
     * @throws Exception
701
     */
702
    public function touch($file, $time = null)
703
    {
704
        error_clear_last();
705
        if (null === $time) {
706
            $error = @touch($file);
707
        } else {
708
            $error = @touch($file, $time);
709
        }
710
711
        if (false === $error) { // FAILED.
712
            $lastError = error_get_last();
713
            $errormsg = $lastError['message'] ?? 'unknown error';
714
            // Add error from php to end of log message.
715
            $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $errormsg";
716
            throw new Exception($msg);
717
        }
718
    }
719
720
    /**
721
     * Delete an empty directory OR a directory and all of its contents.
722
     *
723
     * @param string $dir      Path and/or name of directory to delete.
724
     * @param bool   $children False: don't delete directory contents.
725
     *                         True: delete directory contents.
726
     *
727
     * @return void
728
     * @throws Exception
729
     */
730 167
    public function rmdir($dir, $children = false)
731
    {
732 167
        error_clear_last();
733
734
        // If children=FALSE only delete dir if empty.
735 167
        if (false === $children) {
736 166
            if (false === @rmdir($dir)) { // FAILED.
737
                $lastError = error_get_last();
738
                $errormsg = $lastError['message'] ?? 'unknown error';
739
                // Add error from php to end of log message.
740
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
741 166
                throw new Exception($msg);
742
            }
743
        } else { // delete contents and dir.
744 2
            $handle = @opendir($dir);
745 2
            $lastError = error_get_last();
746 2
            $errormsg = $lastError['message'] ?? 'unknown error';
747
748 2
            if (false === $handle) { // Error.
749
                $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $errormsg";
750
                throw new Exception($msg);
751
            }
752
            // Read from handle.
753
            // Don't error on readdir().
754 2
            while (false !== ($entry = @readdir($handle))) {
755 2
                if ($entry != '.' && $entry != '..') {
756
                    // Only add / if it isn't already the last char.
757
                    // This ONLY serves the purpose of making the Logger
758
                    // output look nice:)
759
760
                    if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) { // there is a /
761
                        $next_entry = $dir . $entry;
762
                    } else { // no /
763
                        $next_entry = $dir . DIRECTORY_SEPARATOR . $entry;
764
                    }
765
766
                    // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it
767
                    // returns 0. So use == not ===.
768
769
                    // Don't error on is_dir()
770
                    if (false == @is_dir($next_entry)) { // Is file.
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...
771
                        try {
772
                            $this->unlink($next_entry); // Delete.
773
                        } catch (Exception $e) {
774
                            $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. " . $e->getMessage();
775
                            throw new Exception($msg);
776
                        }
777
                    } else { // Is directory.
778
                        try {
779
                            $this->rmdir($next_entry, true); // Delete
780
                        } catch (Exception $e) {
781
                            $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() $next_entry. " . $e->getMessage();
782
                            throw new Exception($msg);
783
                        }
784
                    }
785
                }
786
            }
787
788
            // Don't error on closedir()
789 2
            @closedir($handle);
1 ignored issue
show
Bug introduced by
Are you sure the usage of closedir($handle) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for closedir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

789
            /** @scrutinizer ignore-unhandled */ @closedir($handle);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
790
791 2
            error_clear_last();
792 2
            if (false === @rmdir($dir)) { // FAILED.
793
                // Add error from php to end of log message.
794
                $lastError = error_get_last();
795
                $errormsg = $lastError['message'] ?? 'unknown error';
796
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
797
                throw new Exception($msg);
798
            }
799
        }
800 167
    }
801
802
    /**
803
     * Set the umask for file and directory creation.
804
     *
805
     * @param    Int $mode
806
     * @return   void
807
     * @throws   Exception
808
     * @internal param Int $mode . Permissions usually in ocatal. Use leading 0 for
809
     *                    octal. Number between 0 and 0777.
810
     */
811
    public function umask($mode)
812
    {
813
        error_clear_last();
814
        // CONSIDERME:
815
        // Throw a warning if mode is 0. PHP converts illegal octal numbers to
816
        // 0 so 0 might not be what the user intended.
817
818
        $str_mode = decoct($mode); // Show octal in messages.
0 ignored issues
show
Unused Code introduced by
The assignment to $str_mode is dead and can be removed.
Loading history...
819
820
        if (false === @umask($mode)) { // FAILED.
821
            $lastError = error_get_last();
822
            $errormsg = $lastError['message'] ?? 'unknown error';
823
            // Add error from php to end of log message.
824
            $msg = "FileSystem::Umask() FAILED. Value $mode. $errormsg";
825
            throw new Exception($msg);
826
        }
827
    }
828
829
    /**
830
     * Compare the modified time of two files.
831
     *
832
     * @param string $file1 Path and name of file1.
833
     * @param string $file2 Path and name of file2.
834
     *
835
     * @return int  1 if file1 is newer.
836
     *              -1 if file2 is newer.
837
     *              0 if files have the same time.
838
     *              Err object on failure.
839
     *
840
     * @throws Exception - if cannot get modified time of either file.
841
     */
842
    public function compareMTimes($file1, $file2)
843
    {
844
        $mtime1 = filemtime($file1);
845
        $mtime2 = filemtime($file2);
846
847
        if ($mtime1 === false) { // FAILED. Log and return err.
848
            // Add error from php to end of log message.
849
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file1.";
850
            throw new Exception($msg);
851
        }
852
853
        if ($mtime2 === false) { // FAILED. Log and return err.
854
            // Add error from php to end of log message.
855
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file2.";
856
            throw new Exception($msg);
857
        }
858
859
        // Worked. Log and return compare.
860
        // Compare mtimes.
861
        if ($mtime1 == $mtime2) {
862
            return 0;
863
        }
864
865
        return ($mtime1 < $mtime2) ? -1 : 1; // end compare
866
    }
867
868
    /**
869
     * returns the contents of a directory in an array
870
     *
871
     * @param  File $f
872
     * @return string[]
873
     */
874 219
    public function listContents(File $f)
875
    {
876 219
        return array_keys(
877 219
            iterator_to_array(
878 219
                new FilesystemIterator(
879 219
                    $f->getAbsolutePath(),
880 219
                    FilesystemIterator::KEY_AS_FILENAME
881
                )
882
            )
883
        );
884
    }
885
886
    /**
887
     * PHP implementation of the 'which' command.
888
     *
889
     * Used to retrieve/determine the full path for a command.
890
     *
891
     * @param string $executable Executable file to search for
892
     * @param mixed  $fallback   Default to fallback to.
893
     *
894
     * @return string Full path for the specified executable/command.
895
     */
896 6
    public function which($executable, $fallback = false)
897
    {
898 6
        if (is_string($executable)) {
0 ignored issues
show
introduced by
The condition is_string($executable) is always true.
Loading history...
899 5
            if (trim($executable) === '') {
900 5
                return $fallback;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fallback could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
901
            }
902
        } else {
903 1
            return $fallback;
904
        }
905 5
        if (basename($executable) === $executable) {
906 5
            $path = getenv("PATH");
907
        } else {
908
            $path = dirname($executable);
909
        }
910 5
        $dirSeparator = $this->getSeparator();
911 5
        $pathSeparator = $this->getPathSeparator();
912 5
        $elements = explode($pathSeparator, $path);
913 5
        $amount = count($elements);
914 5
        $fstype = Phing::getProperty('host.fstype');
915 5
        switch ($fstype) {
916 5
            case 'UNIX':
917 5
                for ($count = 0; $count < $amount; ++$count) {
918 5
                    $file = $elements[$count] . $dirSeparator . $executable;
919 5
                    if (file_exists($file) && is_executable($file)) {
920 3
                        return $file;
921
                    }
922
                }
923 2
                break;
924
            case 'WINDOWS':
925
                $exts = getenv('PATHEXT');
926
                if ($exts === false) {
927
                    $exts = ['.exe', '.bat', '.cmd', '.com'];
928
                } else {
929
                    $exts = explode($pathSeparator, $exts);
930
                }
931
                for ($count = 0; $count < $amount; $count++) {
932
                    foreach ($exts as $ext) {
933
                        $file = $elements[$count] . $dirSeparator . $executable . $ext;
934
                        // Not all of the extensions above need to be set executable on Windows for them to be executed.
935
                        // I'm sure there's a joke here somewhere.
936
                        if (file_exists($file)) {
937
                            return $file;
938
                        }
939
                    }
940
                }
941
                break;
942
        }
943 2
        if (file_exists($executable) && is_executable($executable)) {
944
            return $executable;
945
        }
946 2
        return $fallback;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fallback could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
947
    }
948
}
949