Passed
Push — master ( cb5cd1...da0f4b )
by Siad
10:24
created

FileUtils::getDefaultFileCreationMask()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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