Completed
Push — master ( 89abc8...a320a7 )
by Siad
16:11
created

FileUtils::createTempFile()   B

Complexity

Conditions 7
Paths 20

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 15.4039

Importance

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