Completed
Push — master ( d90c8b...86899b )
by Siad
16:39
created

UnixFileSystem::resolve()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 6.6

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 5
nop 2
dl 0
loc 19
ccs 6
cts 10
cp 0.6
crap 6.6
rs 9.6111
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
 * UnixFileSystem class. This class encapsulates the basic file system functions
22
 * for platforms using the unix (posix)-stylish filesystem. It wraps php native
23
 * functions suppressing normal PHP error reporting and instead uses Exception
24
 * to report and error.
25
 *
26
 * This class is part of a oop based filesystem abstraction and targeted to run
27
 * on all supported php platforms.
28
 *
29
 * Note: For debugging turn track_errors on in the php.ini. The error messages
30
 * and log messages from this class will then be clearer because $php_errormsg
31
 * is passed as part of the message.
32
 *
33
 * FIXME:
34
 *  - Comments
35
 *  - Error handling reduced to min, error are handled by PhingFile mainly
36
 *
37
 * @author Andreas Aderhold, [email protected]
38
 *
39
 * @package phing.system.io
40
 */
41
class UnixFileSystem extends FileSystem
42
{
43
    /**
44
     * returns OS dependent path separator char
45
     *
46
     * @return string
47
     */
48 393
    public function getSeparator()
49
    {
50 393
        return '/';
51
    }
52
53
    /**
54
     * returns OS dependent directory separator char
55
     *
56
     * @return string
57
     */
58 29
    public function getPathSeparator()
59
    {
60 29
        return ':';
61
    }
62
63
    /**
64
     * A normal Unix pathname contains no duplicate slashes and does not end
65
     * with a slash.  It may be the empty string.
66
     *
67
     * Check that the given pathname is normal.  If not, invoke the real
68
     * normalizer on the part of the pathname that requires normalization.
69
     * This way we iterate through the whole pathname string only once.
70
     *
71
     * NOTE: this method no longer expands the tilde (~) character!
72
     *
73
     * @param string $strPathname
74
     *
75
     * @return string
76
     */
77 775
    public function normalize($strPathname)
78
    {
79 775
        if (!strlen($strPathname)) {
80 3
            return '';
81
        }
82
83
        // Start normalising after any scheme that is present.
84
        // This prevents phar:///foo being normalised into phar:/foo
85
        // Use a regex as some paths may not by parsed by parse_url().
86 775
        if (preg_match('{^[a-z][a-z0-9+\-\.]+://}', $strPathname)) {
87
            $i = strpos($strPathname, '://') + 3;
88
        } else {
89 775
            $i = 0;
90
        }
91
92 775
        $n = strlen($strPathname);
93 775
        $prevChar = 0;
94 775
        for (; $i < $n; $i++) {
95 775
            $c = $strPathname[$i];
96 775
            if (($prevChar === '/') && ($c === '/')) {
97 12
                return self::normalizer($strPathname, $n, $i - 1);
0 ignored issues
show
Bug Best Practice introduced by
The method UnixFileSystem::normalizer() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

97
                return self::/** @scrutinizer ignore-call */ normalizer($strPathname, $n, $i - 1);
Loading history...
98
            }
99 775
            $prevChar = $c;
100
        }
101 775
        if ($prevChar === '/') {
102 13
            return self::normalizer($strPathname, $n, $n - 1);
103
        }
104
105 772
        return $strPathname;
106
    }
107
108
    /**
109
     * Normalize the given pathname, whose length is $len, starting at the given
110
     * $offset; everything before this offset is already normal.
111
     *
112
     * @param string $pathname
113
     * @param int $len
114
     * @param int $offset
115
     *
116
     * @return string
117
     */
118 21
    protected function normalizer($pathname, $len, $offset)
119
    {
120 21
        if ($len === 0) {
121
            return $pathname;
122
        }
123 21
        $n = (int) $len;
124 21
        while (($n > 0) && ($pathname[$n - 1] === '/')) {
125 13
            $n--;
126
        }
127 21
        if ($n === 0) {
128 7
            return '/';
129
        }
130 17
        $sb = "";
131
132 17
        if ($offset > 0) {
133 14
            $sb .= substr($pathname, 0, $offset);
134
        }
135 17
        $prevChar = 0;
136 17
        for ($i = $offset; $i < $n; $i++) {
137 12
            $c = $pathname[$i];
138 12
            if (($prevChar === '/') && ($c === '/')) {
139 12
                continue;
140
            }
141 12
            $sb .= $c;
142 12
            $prevChar = $c;
143
        }
144
145 17
        return $sb;
146
    }
147
148
    /**
149
     * Compute the length of the pathname string's prefix.  The pathname
150
     * string must be in normal form.
151
     *
152
     * @param string $pathname
153
     *
154
     * @return int
155
     */
156 772
    public function prefixLength($pathname)
157
    {
158 772
        if (strlen($pathname) === 0) {
159 1
            return 0;
160
        }
161
162 772
        if (class_exists('Phar', false) && method_exists('Phar', 'running')) {
163 772
            $phar = Phar::running();
164 772
            $pharAlias = 'phar://' . Phing::PHAR_ALIAS;
165
166 772
            if ($phar && strpos($pathname, $phar) === 0) {
167
                return strlen($phar);
168
            }
169
170 772
            if ($phar && strpos($pathname, $pharAlias) === 0) {
171
                return strlen($pharAlias);
172
            }
173
        }
174
175 772
        return (($pathname[0] === '/') ? 1 : 0);
176
    }
177
178
    /**
179
     * Resolve the child pathname string against the parent.
180
     * Both strings must be in normal form, and the result
181
     * will be in normal form.
182
     *
183
     * @param string $parent
184
     * @param string $child
185
     *
186
     * @return string
187
     */
188 368
    public function resolve($parent, $child)
189
    {
190 368
        if ($child === "") {
191 2
            return $parent;
192
        }
193
194 368
        if ($child[0] === '/') {
195
            if ($parent === '/') {
196
                return $child;
197
            }
198
199
            return $parent . $child;
200
        }
201
202 368
        if ($parent === '/') {
203
            return $parent . $child;
204
        }
205
206 368
        return $parent . '/' . $child;
207
    }
208
209
    /**
210
     * @return string
211
     */
212
    public function getDefaultParent()
213
    {
214
        return '/';
215
    }
216
217
    /**
218
     * @param PhingFile $f
219
     *
220
     * @return bool
221
     */
222 768
    public function isAbsolute(PhingFile $f)
223
    {
224 768
        return ($f->getPrefixLength() !== 0);
225
    }
226
227
    /**
228
     * the file resolver
229
     *
230
     * @param PhingFile $f
231
     *
232
     * @return string
233
     */
234 768
    public function resolveFile(PhingFile $f)
235
    {
236
        // resolve if parent is a file oject only
237 768
        if ($this->isAbsolute($f)) {
238 768
            return $f->getPath();
239
        }
240
241 94
        return $this->resolve(Phing::getProperty("user.dir"), $f->getPath());
242
    }
243
244
    /* -- most of the following is mapped to the php natives wrapped by FileSystem */
245
246
    /* -- Attribute accessors -- */
247
    /**
248
     * @param PhingFile $f
249
     * @return int
250
     */
251
    public function getBooleanAttributes($f)
252
    {
253
        //$rv = getBooleanAttributes0($f);
254
        $name = $f->getName();
255
        $hidden = (strlen($name) > 0) && ($name[0] == '.');
256
257
        return ($hidden ? FileSystem::BA_HIDDEN : 0);
258
    }
259
260
    /**
261
     * set file readonly on unix
262
     *
263
     * @param  PhingFile $f
264
     * @throws Exception
265
     * @throws IOException
266
     */
267
    public function setReadOnly($f)
268
    {
269
        if ($f instanceof PhingFile) {
0 ignored issues
show
introduced by
$f is always a sub-type of PhingFile.
Loading history...
270
            $strPath = (string) $f->getPath();
271
            $perms = (int) (@fileperms($strPath) & 0444);
272
273
            FileSystem::getFileSystem()->chmod($strPath, $perms);
274
        } else {
275
            throw new Exception("IllegalArgumentType: Argument is not File");
276
        }
277
    }
278
279
    /**
280
     * compares file paths lexicographically
281
     *
282
     * @param  PhingFile $f1
283
     * @param  PhingFile $f2
284
     * @return int
285
     */
286 6
    public function compare(PhingFile $f1, PhingFile $f2)
287
    {
288 6
        $f1Path = $f1->getPath();
289 6
        $f2Path = $f2->getPath();
290
291 6
        return strcmp((string) $f1Path, (string) $f2Path);
292
    }
293
294
    /**
295
     * Copy a file, takes care of symbolic links
296
     *
297
     * @param PhingFile $src Source path and name file to copy.
298
     * @param PhingFile $dest Destination path and name of new file.
299
     *
300
     * @return void
301
     * @throws Exception if file cannot be copied.
302
     */
303 12
    public function copy(PhingFile $src, PhingFile $dest)
304
    {
305 12
        if (!$src->isLink()) {
306 10
            parent::copy($src, $dest);
307 10
            return;
308
        }
309
310 3
        $srcPath = $src->getAbsolutePath();
0 ignored issues
show
Unused Code introduced by
The assignment to $srcPath is dead and can be removed.
Loading history...
311 3
        $destPath = $dest->getAbsolutePath();
312
313 3
        $linkTarget = $src->getLinkTarget();
314 3
        if (false === @symlink($linkTarget, $destPath)) {
315
            $msg = "FileSystem::copy() FAILED. Cannot create symlink from $destPath to $linkTarget.";
316
            throw new Exception($msg);
317
        }
318 3
    }
319
320
    /* -- fs interface --*/
321
322
    /**
323
     * @return array
324
     */
325
    public function listRoots()
326
    {
327
        if (!$this->checkAccess('/', false)) {
0 ignored issues
show
Bug introduced by
'/' of type string is incompatible with the type PhingFile expected by parameter $f of FileSystem::checkAccess(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

327
        if (!$this->checkAccess(/** @scrutinizer ignore-type */ '/', false)) {
Loading history...
328
            die("Can not access root");
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
329
        }
330
331
        return [new PhingFile("/")];
332
    }
333
334
    /**
335
     * @param string $p
336
     * @return string
337
     */
338
    public function fromURIPath($p)
339
    {
340
        if (StringHelper::endsWith("/", $p) && (strlen($p) > 1)) {
341
            // "/foo/" --> "/foo", but "/" --> "/"
342
            $p = substr($p, 0, strlen($p) - 1);
343
        }
344
345
        return $p;
346
    }
347
348
    /**
349
     * Whether file can be deleted.
350
     *
351
     * @param  PhingFile $f
352
     * @return boolean
353
     */
354 142
    public function canDelete(PhingFile $f)
355
    {
356 142
        @clearstatcache();
1 ignored issue
show
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

356
        /** @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...
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...
357 142
        $dir = dirname($f->getAbsolutePath());
358
359 142
        return @is_writable($dir);
360
    }
361
}
362