Passed
Push — master ( 972120...1c77fc )
by Michiel
08:19
created

FileUtils::resolveFile()   C

Complexity

Conditions 12
Paths 7

Size

Total Lines 41
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 12.2812

Importance

Changes 0
Metric Value
cc 12
eloc 24
nc 7
nop 2
dl 0
loc 41
ccs 21
cts 24
cp 0.875
crap 12.2812
rs 6.9666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Phing\Exception\BuildException;
24
use Phing\Filter\ChainReaderHelper;
25
use Phing\Phing;
26
use Phing\Project;
27
use Phing\Util\Character;
28
use Phing\Util\StringHelper;
29
30
/**
31
 * File utility class.
32
 * - handles os independent stuff etc
33
 * - mapper stuff
34
 * - filter stuff
35
 *
36
 * @package phing.util
37
 */
38
class FileUtils
39
{
40
    /**
41
     * path separator string, static, obtained from FileSystem (; or :)
42
     */
43
    private static $pathSeparator;
44
45
    /**
46
     * separator string, static, obtained from FileSystem
47
     */
48
    private static $separator;
49
50 4
    public static function getPathSeparator(): string
51
    {
52 4
        if (self::$pathSeparator === null) {
53
            self::$pathSeparator = FileSystem::getFileSystem()->getPathSeparator();
54
        }
55 4
        return self::$pathSeparator;
56
    }
57
58 853
    public static function getSeparator(): string
59
    {
60 853
        if (self::$separator === null) {
61
            self::$separator = FileSystem::getFileSystem()->getSeparator();
62
        }
63 853
        return self::$separator;
64
    }
65
66
    /**
67
     * Returns the path to the temp directory.
68
     *
69
     * @return string
70
     */
71 9
    public static function getTempDir()
72
    {
73 9
        return Phing::getProperty('php.tmpdir');
74
    }
75
76
    /**
77
     * Returns the default file/dir creation mask value
78
     * (The mask value is prepared w.r.t the current user's file-creation mask value)
79
     *
80
     * @param boolean $dirmode Directory creation mask to select
81
     *
82
     * @return int  Creation Mask in octal representation
83
     */
84
    public static function getDefaultFileCreationMask($dirmode = false)
85
    {
86
87
        // Preparing the creation mask base permission
88
        $permission = ($dirmode === true) ? 0777 : 0666;
89
90
        // Default mask information
91
        $defaultmask = sprintf('%03o', ($permission & ($permission - (int) sprintf('%04o', umask()))));
92
93
        return octdec($defaultmask);
94
    }
95
96
    /**
97
     * Returns a new Reader with filterchains applied.  If filterchains are empty,
98
     * simply returns passed reader.
99
     *
100
     * @param Reader $in Reader to modify (if appropriate).
101
     * @param array   &$filterChains filter chains to apply.
102
     * @param Project $project
103
     * @return Reader  Assembled Reader (w/ filter chains).
104
     */
105 36
    public static function getChainedReader(Reader $in, &$filterChains, Project $project)
106
    {
107 36
        if (!empty($filterChains)) {
108 27
            $crh = new ChainReaderHelper();
109 27
            $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
110 27
            $crh->setPrimaryReader($in);
111 27
            $crh->setFilterChains($filterChains);
112 27
            $crh->setProject($project);
113 27
            return $crh->getAssembledReader();
114
        }
115
116 10
        return $in;
117
    }
118
119
    /**
120
     * Copies a file using filter chains.
121
     *
122
     * @param File $sourceFile
123
     * @param File $destFile
124
     * @param boolean $overwrite
125
     * @param boolean $preserveLastModified
126
     * @param array $filterChains
127
     * @param Project $project
128
     * @param integer $mode
129
     * @param bool $preservePermissions
130
     * @param int $granularity
131
     * @return void
132
     * @throws Exception
133
     * @throws IOException
134
     */
135 49
    public function copyFile(
136
        File $sourceFile,
137
        File $destFile,
138
        Project $project,
139
        $overwrite = false,
140
        $preserveLastModified = true,
141
        &$filterChains = null,
142
        $mode = 0755,
143
        $preservePermissions = true,
144
        int $granularity = 0
145
    ) {
146
        if (
147 49
            $overwrite
148 39
            || !$destFile->exists()
149 49
            || $destFile->lastModified() < $sourceFile->lastModified() - $granularity
150
        ) {
151 48
            if ($destFile->exists() && ($destFile->isFile() || $destFile->isLink())) {
152 1
                $destFile->delete();
153
            }
154
155
            // ensure that parent dir of dest file exists!
156 48
            $parent = $destFile->getParentFile();
157 48
            if ($parent !== null && !$parent->exists()) {
158
                // Setting source directory permissions to target
159
                // (On permissions preservation, the target directory permissions
160
                // will be inherited from the source directory, otherwise the 'mode'
161
                // will be used)
162 3
                $dirMode = ($preservePermissions ? $sourceFile->getParentFile()->getMode() : $mode);
163
164 3
                $parent->mkdirs($dirMode);
165
            }
166
167 48
            if ((is_array($filterChains)) && (!empty($filterChains))) {
168 21
                $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
169 21
                $out = new BufferedWriter(new FileWriter($destFile));
170
171
                // New read() methods returns a big buffer.
172 21
                while (-1 !== ($buffer = $in->read())) { // -1 indicates EOF
173 21
                    $out->write($buffer);
174
                }
175
176 21
                if ($in !== null) {
177 21
                    $in->close();
178
                }
179 21
                if ($out !== null) {
180 21
                    $out->close();
181
                }
182
183
                // Set/Copy the permissions on the target
184 21
                if ($preservePermissions === true) {
185 21
                    $destFile->setMode($sourceFile->getMode());
186
                }
187
            } else {
188
                // simple copy (no filtering)
189 27
                $sourceFile->copyTo($destFile);
190
191
                // By default, PHP::Copy also copies the file permissions. Therefore,
192
                // re-setting the mode with the "user file-creation mask" information.
193 27
                if ($preservePermissions === false) {
194
                    $destFile->setMode(FileUtils::getDefaultFileCreationMask());
195
                }
196
            }
197
198 48
            if ($preserveLastModified && !$destFile->isLink()) {
199 2
                $destFile->setLastModified($sourceFile->lastModified());
200
            }
201
        }
202 49
    }
203
204
205
    /**
206
     * Attempts to rename a file from a source to a destination.
207
     * If overwrite is set to true, this method overwrites existing file even if the destination file is newer.
208
     * Otherwise, the source file is renamed only if the destination file is older than it.
209
     *
210
     * @param File $sourceFile
211
     * @param File $destFile
212
     * @return void
213
     */
214 1
    public function renameFile(File $sourceFile, File $destFile, $overwrite = false)
215
    {
216
        // ensure that parent dir of dest file exists!
217 1
        $parent = $destFile->getParentFile();
218 1
        if ($parent !== null) {
219 1
            if (!$parent->exists()) {
220
                $parent->mkdirs();
221
            }
222
        }
223
224 1
        if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
225 1
            if ($destFile->exists()) {
226
                try {
227
                    $destFile->delete();
228
                } catch (Exception $e) {
229
                    throw new BuildException(
230
                        "Unable to remove existing file " . $destFile->__toString() . ": " . $e->getMessage()
231
                    );
232
                }
233
            }
234
        }
235
236 1
        $sourceFile->renameTo($destFile);
237 1
    }
238
239
    /**
240
     * Interpret the filename as a file relative to the given file -
241
     * unless the filename already represents an absolute filename.
242
     *
243
     * @param File $file the "reference" file for relative paths. This
244
     *                             instance must be an absolute file and must
245
     *                             not contain ./ or ../ sequences (same for \
246
     *                             instead of /).
247
     * @param string $filename a file name
248
     *
249
     * @return File A PhingFile object pointing to an absolute file that doesn't contain ./ or ../ sequences
250
     *                   and uses the correct separator for the current platform.
251
     * @throws IOException
252
     *
253
     */
254 464
    public function resolveFile($file, $filename)
255
    {
256
        // remove this and use the static class constant File::separator
257
        // as soon as ZE2 is ready
258 464
        $fs = FileSystem::getFileSystem();
259
260 464
        $filename = str_replace(['\\', '/'], $fs->getSeparator(), $filename);
261
262
        // deal with absolute files
263
        if (
264 464
            StringHelper::startsWith($fs->getSeparator(), $filename)
265 439
            || (strlen($filename) >= 2
266 439
                && Character::isLetter($filename[0])
267 464
                && $filename[1] === ':')
268
        ) {
269 105
            return new File($this->normalize($filename));
270
        }
271
272 439
        if (strlen($filename) >= 2 && Character::isLetter($filename[0]) && $filename[1] === ':') {
273
            return new File($this->normalize($filename));
274
        }
275
276 439
        $helpFile = new File($file->getAbsolutePath());
277
278 439
        $tok = strtok($filename, $fs->getSeparator());
279 439
        while ($tok !== false) {
280 439
            $part = $tok;
281 439
            if ($part === '..') {
282 170
                $parentFile = $helpFile->getParent();
283 170
                if ($parentFile === null) {
284
                    $msg = "The file or path you specified ($filename) is invalid relative to " . $file->getPath();
285
                    throw new IOException($msg);
286
                }
287 170
                $helpFile = new File($parentFile);
288 439
            } elseif ($part !== '.') {
289 413
                $helpFile = new File($helpFile, $part);
290
            }
291 439
            $tok = strtok($fs->getSeparator());
292
        }
293
294 439
        return new File($helpFile->getAbsolutePath());
295
    }
296
297
    /**
298
     * Normalize the given absolute path.
299
     *
300
     * This includes:
301
     *   - Uppercase the drive letter if there is one.
302
     *   - Remove redundant slashes after the drive spec.
303
     *   - resolve all ./, .\, ../ and ..\ sequences.
304
     *   - DOS style paths that start with a drive letter will have
305
     *     \ as the separator.
306
     *
307
     * @param string $path Path to normalize.
308
     *
309
     * @return string
310
     * @throws IOException
311
     *
312
     */
313 853
    public function normalize($path)
314
    {
315 853
        $path = (string) $path;
316 853
        $orig = $path;
317
318 853
        $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
319
320
        // make sure we are dealing with an absolute path
321
        if (
322 853
            !StringHelper::startsWith(DIRECTORY_SEPARATOR, $path)
323
            && !(strlen($path) >= 2
324
                && Character::isLetter($path[0])
325 853
                && $path[1] === ':')
326
        ) {
327
            throw new IOException("$path is not an absolute path");
328
        }
329
330 853
        $dosWithDrive = false;
331 853
        $root = null;
332
333
        // Eliminate consecutive slashes after the drive spec
334
335 853
        if (strlen($path) >= 2 && Character::isLetter($path[0]) && $path[1] === ':') {
336
            $dosWithDrive = true;
337
338
            $ca = str_replace('/', '\\', $path);
339
340
            $path = strtoupper($ca[0]) . ':';
341
342
            for ($i = 2, $_i = strlen($ca); $i < $_i; $i++) {
343
                if (
344
                    ($ca[$i] !== '\\')
345
                    || ($ca[$i] === '\\'
346
                        && $ca[$i - 1] !== '\\')
347
                ) {
348
                    $path .= $ca[$i];
349
                }
350
            }
351
352
            $path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
353
354
            if (strlen($path) == 2) {
355
                $root = $path;
356
                $path = "";
357
            } else {
358
                $root = substr($path, 0, 3);
359
                $path = substr($path, 3);
360
            }
361
        } else {
362 853
            if (strlen($path) == 1) {
363 3
                $root = DIRECTORY_SEPARATOR;
364 3
                $path = "";
365
            } else {
366 850
                if ($path[1] == DIRECTORY_SEPARATOR) {
367
                    // UNC drive
368
                    $root = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
369
                    $path = substr($path, 2);
370
                } else {
371 850
                    $root = DIRECTORY_SEPARATOR;
372 850
                    $path = substr($path, 1);
373
                }
374
            }
375
        }
376
377 853
        $s = [];
378 853
        $s[] = $root;
379 853
        $tok = strtok($path, DIRECTORY_SEPARATOR);
380 853
        while ($tok !== false) {
381 850
            $thisToken = $tok;
382 850
            if ("." === $thisToken) {
383
                $tok = strtok(DIRECTORY_SEPARATOR);
384
                continue;
385
            }
386
387 850
            if (".." === $thisToken) {
388 1
                if (count($s) < 2) {
389
                    // using '..' in path that is too short
390
                    throw new IOException("Cannot resolve path: $orig");
391
                }
392
393 1
                array_pop($s);
394
            } else { // plain component
395 850
                $s[] = $thisToken;
396
            }
397 850
            $tok = strtok(DIRECTORY_SEPARATOR);
398
        }
399
400 853
        $sb = "";
401 853
        for ($i = 0, $_i = count($s); $i < $_i; $i++) {
402 853
            if ($i > 1) {
403
                // not before the filesystem root and not after it, since root
404
                // already contains one
405 850
                $sb .= DIRECTORY_SEPARATOR;
406
            }
407 853
            $sb .= (string) $s[$i];
408
        }
409
410
411 853
        $path = (string) $sb;
412 853
        if ($dosWithDrive === true) {
413
            $path = str_replace('/', '\\', $path);
414
        }
415
416 853
        return $path;
417
    }
418
419
    /**
420
     * Create a temporary file in a given directory.
421
     *
422
     * <p>The file denoted by the returned abstract pathname did not
423
     * exist before this method was invoked, any subsequent invocation
424
     * of this method will yield a different file name.</p>
425
     *
426
     * @param string $prefix prefix before the random number.
427
     * @param string $suffix file extension; include the '.'.
428
     * @param File $parentDir Directory to create the temporary file in;
429
     *                                sys_get_temp_dir() used if not specified.
430
     * @param boolean $deleteOnExit whether to set the tempfile for deletion on
431
     *                                normal exit.
432
     * @param boolean $createFile true if the file must actually be created. If false
433
     *                                chances exist that a file with the same name is
434
     *                                created in the time between invoking this method
435
     *                                and the moment the file is actually created. If
436
     *                                possible set to true.
437
     * @return File            a File reference to the new temporary file.
438
     * @throws BuildException
439
     */
440 1
    public function createTempFile(
441
        $prefix,
442
        $suffix,
443
        File $parentDir = null,
444
        $deleteOnExit = false,
445
        $createFile = false
446
    ) {
447 1
        $result = null;
448 1
        $parent = ($parentDir === null) ? self::getTempDir() : $parentDir->getPath();
449
450 1
        if ($createFile) {
451
            try {
452
                $directory = new File($parent);
453
                // quick but efficient hack to create a unique filename ;-)
454
                $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
455
                do {
456
                    $result = new File($directory, $prefix . substr(md5(time()), 0, 8) . $suffix);
457
                } while (file_exists($result->getPath()));
458
459
                $fs = FileSystem::getFileSystem();
460
                $fs->createNewFile($result->getPath());
461
                $fs->lock($result);
462
            } catch (IOException $e) {
463
                throw new BuildException("Could not create tempfile in " . $parent, $e);
464
            }
465
        } else {
466
            do {
467 1
                $result = new File($parent, $prefix . substr(md5((string) time()), 0, 8) . $suffix);
468 1
            } while ($result->exists());
469
        }
470
471 1
        if ($deleteOnExit) {
472
            $result->deleteOnExit();
473
        }
474
475 1
        return $result;
476
    }
477
478
    /**
479
     * @param File $file1
480
     * @param File $file2
481
     *
482
     * @return boolean Whether contents of two files is the same.
483
     */
484 14
    public function contentEquals(File $file1, File $file2)
485
    {
486 14
        if (!($file1->exists() && $file2->exists())) {
487 2
            return false;
488
        }
489
490 13
        if (!($file1->canRead() && $file2->canRead())) {
491
            return false;
492
        }
493
494 13
        if ($file1->isDirectory() || $file2->isDirectory()) {
495 1
            return false;
496
        }
497
498 13
        $c1 = file_get_contents($file1->getAbsolutePath());
499 13
        $c2 = file_get_contents($file2->getAbsolutePath());
500
501 13
        return trim($c1) == trim($c2);
502
    }
503
}
504