Completed
Push — master ( eb65f2...526607 )
by Siad
13:16
created

FileSystem::createNewFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 17
ccs 9
cts 10
cp 0.9
crap 3.009
rs 9.9666
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
/**
21
 * This is an abstract class for platform specific filesystem implementations
22
 * you have to implement each method in the platform specific filesystem implementation
23
 * classes Your local filesytem implementation must extend this class.
24
 * You should also use this class as a template to write your local implementation
25
 * Some native PHP filesystem specific methods are abstracted here as well. Anyway
26
 * you _must_ always use this methods via a PhingFile object (that by nature uses the
27
 * *FileSystem drivers to access the real filesystem via this class using natives.
28
 *
29
 * FIXME:
30
 *  - Error handling reduced to min fallthrough runtime exceptions
31
 *    more precise errorhandling is done by the PhingFile class
32
 *
33
 * @author Charlie Killian <[email protected]>
34
 * @author Hans Lellelid <[email protected]>
35
 *
36
 * @package phing.system.io
37
 */
38
abstract class FileSystem
39
{
40
41
    /**
42
     * @var int
43
     */
44
    public const BA_EXISTS = 0x01;
45
46
    /**
47
     * @var int
48
     */
49
    public const BA_REGULAR = 0x02;
50
51
    /**
52
     * @var int
53
     */
54
    public const BA_DIRECTORY = 0x04;
55
56
    /**
57
     * @var int
58
     */
59
    public const BA_HIDDEN = 0x08;
60
61
    /**
62
     * Instance for getFileSystem() method.
63
     *
64
     * @var FileSystem
65
     */
66
    private static $fs;
67
68
    /**
69
     * @var PhingFile[]
70
     */
71
    private static $filesToDelete = [];
72
73
    /**
74
     * Static method to return the FileSystem singelton representing
75
     * this platform's local filesystem driver.
76
     *
77
     * @return FileSystem
78
     * @throws IOException
79
     */
80 811
    public static function getFileSystem()
81
    {
82 811
        if (self::$fs === null) {
83 6
            switch (Phing::getProperty('host.fstype')) {
84 6
                case 'UNIX':
85 4
                    self::$fs = new UnixFileSystem();
86 4
                    break;
87 2
                case 'WINDOWS':
88 1
                    self::$fs = new WindowsFileSystem();
89 1
                    break;
90
                default:
91 1
                    throw new IOException("Host uses unsupported filesystem, unable to proceed");
92
            }
93
        }
94
95 810
        return self::$fs;
96
    }
97
98
    /* -- Normalization and construction -- */
99
100
    /**
101
     * Return the local filesystem's name-separator character.
102
     */
103
    abstract public function getSeparator();
104
105
    /**
106
     * Return the local filesystem's path-separator character.
107
     */
108
    abstract public function getPathSeparator();
109
110
    /**
111
     * Convert the given pathname string to normal form.  If the string is
112
     * already in normal form then it is simply returned.
113
     *
114
     * @param string $strPath
115
     */
116
    abstract public function normalize($strPath);
117
118
    /**
119
     * Compute the length of this pathname string's prefix.  The pathname
120
     * string must be in normal form.
121
     *
122
     * @param string $pathname
123
     */
124
    abstract public function prefixLength($pathname);
125
126
    /**
127
     * Resolve the child pathname string against the parent.
128
     * Both strings must be in normal form, and the result
129
     * will be a string in normal form.
130
     *
131
     * @param string $parent
132
     * @param string $child
133
     */
134
    abstract public function resolve($parent, $child);
135
136
    /**
137
     * Resolve the given abstract pathname into absolute form.  Invoked by the
138
     * getAbsolutePath and getCanonicalPath methods in the PhingFile class.
139
     *
140
     * @param PhingFile $f
141
     */
142
    abstract public function resolveFile(PhingFile $f);
143
144
    /**
145
     * Return the parent pathname string to be used when the parent-directory
146
     * argument in one of the two-argument PhingFile constructors is the empty
147
     * pathname.
148
     */
149
    abstract public function getDefaultParent();
150
151
    /**
152
     * Post-process the given URI path string if necessary.  This is used on
153
     * win32, e.g., to transform "/c:/foo" into "c:/foo".  The path string
154
     * still has slash separators; code in the PhingFile class will translate them
155
     * after this method returns.
156
     *
157
     * @param string $path
158
     */
159
    abstract public function fromURIPath($path);
160
161
    /* -- Path operations -- */
162
163
    /**
164
     * Tell whether or not the given abstract pathname is absolute.
165
     *
166
     * @param PhingFile $f
167
     */
168
    abstract public function isAbsolute(PhingFile $f);
169
170
    /**
171
     * canonicalize filename by checking on disk
172
     *
173
     * @param  string $strPath
174
     * @return mixed  Canonical path or false if the file doesn't exist.
175
     */
176 789
    public function canonicalize($strPath)
177
    {
178 789
        return @realpath($strPath);
179
    }
180
181
    /* -- Attribute accessors -- */
182
183
    /**
184
     * Check whether the file or directory denoted by the given abstract
185
     * pathname may be accessed by this process.  If the second argument is
186
     * false, then a check for read access is made; if the second
187
     * argument is true, then a check for write (not read-write)
188
     * access is made.  Return false if access is denied or an I/O error
189
     * occurs.
190
     *
191
     * @param  PhingFile $f
192
     * @param  boolean $write
193
     * @return bool
194
     */
195 795
    public function checkAccess(PhingFile $f, $write = false)
196
    {
197
        // we clear stat cache, its expensive to look up from scratch,
198
        // but we need to be sure
199 795
        @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

199
        /** @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...
200
201
202
        // Shouldn't this be $f->GetAbsolutePath() ?
203
        // And why doesn't GetAbsolutePath() work?
204
205 795
        $strPath = (string) $f->getPath();
206
207
        // FIXME
208
        // if file object does denote a file that yet not existst
209
        // path rights are checked
210 795
        if (!@file_exists($strPath) && !is_dir($strPath)) {
211 177
            $strPath = $f->getParent();
212 177
            if ($strPath === null || !is_dir($strPath)) {
213 45
                $strPath = Phing::getProperty("user.dir");
214
            }
215
            //$strPath = dirname($strPath);
216
        }
217
218 795
        if (!$write) {
219 795
            return (bool) @is_readable($strPath);
220
        }
221
222 138
        return (bool) @is_writable($strPath);
223
    }
224
225
    /**
226
     * Whether file can be deleted.
227
     *
228
     * @param  PhingFile $f
229
     * @return boolean
230
     */
231
    public function canDelete(PhingFile $f)
232
    {
233
        clearstatcache();
234
        $dir = dirname($f->getAbsolutePath());
235
236
        return @is_writable($dir);
237
    }
238
239
    /**
240
     * Return the time at which the file or directory denoted by the given
241
     * abstract pathname was last modified, or zero if it does not exist or
242
     * some other I/O error occurs.
243
     *
244
     * @param  PhingFile $f
245
     * @return int
246
     * @throws IOException
247
     */
248 41
    public function getLastModifiedTime(PhingFile $f)
249
    {
250 41
        if (!$f->exists()) {
251 7
            return 0;
252
        }
253
254 41
        @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

254
        /** @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...
255 41
        error_clear_last();
256 41
        $strPath = (string) $f->getPath();
257
258 41
        if (@is_link($strPath)) {
259 2
            $stats = @lstat($strPath);
260
261 2
            if (!isset($stats['mtime'])) {
262
                $mtime = false;
263
            } else {
264 2
                $mtime = $stats['mtime'];
265
            }
266
        } else {
267 41
            $mtime = @filemtime($strPath);
268
        }
269
270 41
        if (false === $mtime) {
271
            $lastError = error_get_last();
272
            $errormsg = $lastError['message'] ?? 'unknown error';
273
            $msg = "FileSystem::getLastModifiedTime() FAILED. Can not get modified time of $strPath. $errormsg";
274
            throw new IOException($msg);
275
        }
276
277 41
        return (int) $mtime;
278
    }
279
280
    /**
281
     * Return the length in bytes of the file denoted by the given abstract
282
     * pathname, or zero if it does not exist, is a directory, or some other
283
     * I/O error occurs.
284
     *
285
     * @param  PhingFile $f
286
     * @throws IOException
287
     * @return int
288
     */
289 14
    public function getLength(PhingFile $f)
290
    {
291 14
        error_clear_last();
292 14
        $strPath = (string) $f->getAbsolutePath();
293 14
        $fs = filesize((string) $strPath);
294 14
        if ($fs !== false) {
295 14
            return $fs;
296
        }
297
298
        $lastError = error_get_last();
299
        $errormsg = $lastError['message'] ?? 'unknown error';
300
        $msg = "FileSystem::Read() FAILED. Cannot get filesize of $strPath. $errormsg";
301
        throw new IOException($msg);
302
    }
303
304
    /* -- File operations -- */
305
306
    /**
307
     * Create a new empty file with the given pathname.  Return
308
     * true if the file was created and false if a
309
     * file or directory with the given pathname already exists.  Throw an
310
     * IOException if an I/O error occurs.
311
     *
312
     * @param  string $strPathname Path of the file to be created.
313
     * @throws IOException
314
     * @return boolean
315
     */
316 61
    public function createNewFile($strPathname)
317
    {
318 61
        if (@file_exists($strPathname)) {
319
            return false;
320
        }
321
322
        // Create new file
323 61
        $fp = @fopen($strPathname, "w");
324 61
        if ($fp === false) {
325 1
            $error = error_get_last();
326 1
            throw new IOException(
327 1
                "The file \"$strPathname\" could not be created: " . $error['message'] ?? 'unknown error'
328
            );
329
        }
330 60
        @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

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

606
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_EX);
Loading history...
607
        @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

607
        /** @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 $handle 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

607
        @fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
608
        if (!$result) {
609
            throw new IOException("Could not lock file '$filename'");
610
        }
611
    }
612
613
    /**
614
     * Unlocks a file and throws an IO Error if this is not possible.
615
     *
616
     * @param  PhingFile $f
617
     * @throws IOException
618
     * @return void
619
     */
620
    public function unlock(PhingFile $f)
621
    {
622
        $filename = $f->getPath();
623
        $fp = @fopen($filename, "w");
624
        $result = @flock($fp, LOCK_UN);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle 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_UN);
Loading history...
625
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle 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 unlock file '$filename'");
628
        }
629
    }
630
631
    /**
632
     * Delete a file.
633
     *
634
     * @param string $file Path and/or name of file to delete.
635
     *
636
     * @return void
637
     * @throws IOException - if an error is encountered.
638
     */
639 123
    public function unlink($file)
640
    {
641 123
        error_clear_last();
642 123
        if (false === @unlink($file)) {
643
            $lastError = error_get_last();
644
            $errormsg = $lastError['message'] ?? 'unknown error';
645
            $msg = "FileSystem::unlink() FAILED. Cannot unlink '$file'. $errormsg";
646
            throw new IOException($msg);
647
        }
648 123
    }
649
650
    /**
651
     * Symbolically link a file to another name.
652
     *
653
     * Currently symlink is not implemented on Windows. Don't use if the application is to be portable.
654
     *
655
     * @param  string $target Path and/or name of file to link.
656
     * @param  string $link Path and/or name of link to be created.
657
     * @throws IOException
658
     * @return void
659
     */
660 15
    public function symlink($target, $link)
661
    {
662
663 15
        error_clear_last();
664
        // If Windows OS then symlink() will report it is not supported in
665
        // the build. Use this error instead of checking for Windows as the OS.
666
667 15
        if (false === @symlink($target, $link)) {
668
            $lastError = error_get_last();
669
            $errormsg = $lastError['message'] ?? 'unknown error';
670
            // Add error from php to end of log message.
671
            $msg = "FileSystem::Symlink() FAILED. Cannot symlink '$target' to '$link'. $errormsg";
672
            throw new IOException($msg);
673
        }
674 15
    }
675
676
    /**
677
     * Set the modification and access time on a file to the present time.
678
     *
679
     * @param  string $file Path and/or name of file to touch.
680
     * @param  int $time
681
     * @throws Exception
682
     * @return void
683
     */
684
    public function touch($file, $time = null)
685
    {
686
        error_clear_last();
687
        if (null === $time) {
688
            $error = @touch($file);
689
        } else {
690
            $error = @touch($file, $time);
691
        }
692
693
        if (false === $error) { // FAILED.
694
            $lastError = error_get_last();
695
            $errormsg = $lastError['message'] ?? 'unknown error';
696
            // Add error from php to end of log message.
697
            $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $errormsg";
698
            throw new Exception($msg);
699
        }
700
    }
701
702
    /**
703
     * Delete an empty directory OR a directory and all of its contents.
704
     *
705
     * @param string $dir Path and/or name of directory to delete.
706
     * @param bool $children False: don't delete directory contents.
707
     *                         True: delete directory contents.
708
     *
709
     * @throws Exception
710
     *
711
     * @return void
712
     */
713 118
    public function rmdir($dir, $children = false)
714
    {
715 118
        error_clear_last();
716
717
        // If children=FALSE only delete dir if empty.
718 118
        if (false === $children) {
719 117
            if (false === @rmdir($dir)) { // FAILED.
720
                $lastError = error_get_last();
721
                $errormsg = $lastError['message'] ?? 'unknown error';
722
                // Add error from php to end of log message.
723
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
724
                throw new Exception($msg);
725
            }
726
        } else { // delete contents and dir.
727 2
            $handle = @opendir($dir);
728 2
            $lastError = error_get_last();
729 2
            $errormsg = $lastError['message'] ?? 'unknown error';
730
731 2
            if (false === $handle) { // Error.
732
                $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $errormsg";
733
                throw new Exception($msg);
734
            }
735
            // Read from handle.
736
            // Don't error on readdir().
737 2
            while (false !== ($entry = @readdir($handle))) {
738 2
                if ($entry != '.' && $entry != '..') {
739
                    // Only add / if it isn't already the last char.
740
                    // This ONLY serves the purpose of making the Logger
741
                    // output look nice:)
742
743
                    if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) { // there is a /
744
                        $next_entry = $dir . $entry;
745
                    } else { // no /
746
                        $next_entry = $dir . DIRECTORY_SEPARATOR . $entry;
747
                    }
748
749
                    // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it
750
                    // returns 0. So use == not ===.
751
752
                    // Don't error on is_dir()
753
                    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...
754
                        try {
755
                            $this->unlink($next_entry); // Delete.
756
                        } catch (Exception $e) {
757
                            $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. " . $e->getMessage();
758
                            throw new Exception($msg);
759
                        }
760
                    } else { // Is directory.
761
                        try {
762
                            $this->rmdir($next_entry, true); // Delete
763
                        } catch (Exception $e) {
764
                            $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() $next_entry. " . $e->getMessage();
765
                            throw new Exception($msg);
766
                        }
767
                    }
768
                }
769
            }
770
771
            // Don't error on closedir()
772 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

772
            /** @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...
773
774 2
            error_clear_last();
775 2
            if (false === @rmdir($dir)) { // FAILED.
776
                // Add error from php to end of log message.
777
                $lastError = error_get_last();
778
                $errormsg = $lastError['message'] ?? 'unknown error';
779
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $errormsg";
780
                throw new Exception($msg);
781
            }
782
        }
783 118
    }
784
785
    /**
786
     * Set the umask for file and directory creation.
787
     *
788
     * @param    Int $mode
789
     * @throws   Exception
790
     * @internal param Int $mode . Permissions usually in ocatal. Use leading 0 for
791
     *                    octal. Number between 0 and 0777.
792
     *
793
     * @return void
794
     */
795
    public function umask($mode)
796
    {
797
        error_clear_last();
798
        // CONSIDERME:
799
        // Throw a warning if mode is 0. PHP converts illegal octal numbers to
800
        // 0 so 0 might not be what the user intended.
801
802
        $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...
803
804
        if (false === @umask($mode)) { // FAILED.
805
            $lastError = error_get_last();
806
            $errormsg = $lastError['message'] ?? 'unknown error';
807
            // Add error from php to end of log message.
808
            $msg = "FileSystem::Umask() FAILED. Value $mode. $errormsg";
809
            throw new Exception($msg);
810
        }
811
    }
812
813
    /**
814
     * Compare the modified time of two files.
815
     *
816
     * @param string $file1 Path and name of file1.
817
     * @param string $file2 Path and name of file2.
818
     *
819
     * @return int  1 if file1 is newer.
820
     *              -1 if file2 is newer.
821
     *              0 if files have the same time.
822
     *              Err object on failure.
823
     *
824
     * @throws Exception - if cannot get modified time of either file.
825
     */
826
    public function compareMTimes($file1, $file2)
827
    {
828
        $mtime1 = filemtime($file1);
829
        $mtime2 = filemtime($file2);
830
831
        if ($mtime1 === false) { // FAILED. Log and return err.
832
            // Add error from php to end of log message.
833
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file1.";
834
            throw new Exception($msg);
835
        }
836
837
        if ($mtime2 === false) { // FAILED. Log and return err.
838
            // Add error from php to end of log message.
839
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of $file2.";
840
            throw new Exception($msg);
841
        }
842
843
        // Worked. Log and return compare.
844
        // Compare mtimes.
845
        if ($mtime1 == $mtime2) {
846
            return 0;
847
        }
848
849
        return ($mtime1 < $mtime2) ? -1 : 1; // end compare
850
    }
851
852
    /**
853
     * returns the contents of a directory in an array
854
     *
855
     * @param  PhingFile $f
856
     * @return string[]
857
     */
858 175
    public function listContents(PhingFile $f)
859
    {
860 175
        return array_keys(
861 175
            iterator_to_array(
862 175
                new FilesystemIterator(
863 175
                    $f->getAbsolutePath(),
864 175
                    FilesystemIterator::KEY_AS_FILENAME
865
                )
866
            )
867
        );
868
    }
869
870
    /**
871
     * PHP implementation of the 'which' command.
872
     *
873
     * Used to retrieve/determine the full path for a command.
874
     *
875
     * @param string $executable Executable file to search for
876
     * @param mixed $fallback Default to fallback to.
877
     *
878
     * @return string Full path for the specified executable/command.
879
     */
880 30
    public function which($executable, $fallback = false)
881
    {
882 30
        if (is_string($executable)) {
0 ignored issues
show
introduced by
The condition is_string($executable) is always true.
Loading history...
883 29
            if (trim($executable) === '') {
884
                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...
885
            }
886
        } else {
887 1
            return $fallback;
888
        }
889 29
        if (basename($executable) === $executable) {
890 29
            $path = getenv("PATH");
891
        } else {
892
            $path = dirname($executable);
893
        }
894 29
        $dirSeparator = $this->getSeparator();
895 29
        $pathSeparator = $this->getPathSeparator();
896 29
        $elements = explode($pathSeparator, $path);
897 29
        $amount = count($elements);
898 29
        $fstype = Phing::getProperty('host.fstype');
899 29
        switch ($fstype) {
900 29
            case 'UNIX':
901 29
                for ($count = 0; $count < $amount; ++$count) {
902 29
                    $file = $elements[$count] . $dirSeparator . $executable;
903 29
                    if (file_exists($file) && is_executable($file)) {
904 27
                        return $file;
905
                    }
906
                }
907 4
                break;
908
            case 'WINDOWS':
909
                $exts = getenv('PATHEXT');
910
                if ($exts === false) {
911
                    $exts = ['.exe', '.bat', '.cmd', '.com'];
912
                } else {
913
                    $exts = explode($pathSeparator, $exts);
914
                }
915
                for ($count = 0; $count < $amount; $count++) {
916
                    foreach ($exts as $ext) {
917
                        $file = $elements[$count] . $dirSeparator . $executable . $ext;
918
                        // Not all of the extensions above need to be set executable on Windows for them to be executed.
919
                        // I'm sure there's a joke here somewhere.
920
                        if (file_exists($file)) {
921
                            return $file;
922
                        }
923
                    }
924
                }
925
                break;
926
        }
927 4
        if (file_exists($executable) && is_executable($executable)) {
928
            return $executable;
929
        }
930 4
        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...
931
    }
932
}
933