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

FileUtils::getChainedReader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

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