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

WindowsFileSystem::getDefaultParent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
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
namespace Phing\Io;
21
22
use InvalidArgumentException;
23
use Phing\Phing;
24
use Phing\Util\StringHelper;
25
26
/**
27
 * @package   phing.system.io
28
 */
29
class WindowsFileSystem extends FileSystem
30
{
31
    protected $slash;
32
    protected $altSlash;
33
    protected $semicolon;
34
35
    private static $driveDirCache = [];
36
37
    /**
38
     *
39
     */
40 1
    public function __construct()
41
    {
42 1
        $this->slash = self::getSeparator();
0 ignored issues
show
Bug Best Practice introduced by
The method Phing\Io\WindowsFileSystem::getSeparator() 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

42
        /** @scrutinizer ignore-call */ 
43
        $this->slash = self::getSeparator();
Loading history...
43 1
        $this->semicolon = self::getPathSeparator();
0 ignored issues
show
Bug Best Practice introduced by
The method Phing\Io\WindowsFileSystem::getPathSeparator() 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

43
        /** @scrutinizer ignore-call */ 
44
        $this->semicolon = self::getPathSeparator();
Loading history...
44 1
        $this->altSlash = ($this->slash === '\\') ? '/' : '\\';
45 1
    }
46
47
    /**
48
     * @param $c
49
     * @return bool
50
     */
51
    public function isSlash($c)
52
    {
53
        return ($c == '\\') || ($c == '/');
54
    }
55
56
    /**
57
     * @param $c
58
     * @return bool
59
     */
60
    public function isLetter($c)
61
    {
62
        return ((ord($c) >= ord('a')) && (ord($c) <= ord('z')))
63
            || ((ord($c) >= ord('A')) && (ord($c) <= ord('Z')));
64
    }
65
66
    /**
67
     * @param $p
68
     * @return string
69
     */
70
    public function slashify($p)
71
    {
72
        if ((strlen($p) > 0) && ($p[0] != $this->slash)) {
73
            return $this->slash . $p;
74
        }
75
76
        return $p;
77
    }
78
79
    /* -- Normalization and construction -- */
80
81
    /**
82
     * @return string
83
     */
84 1
    public function getSeparator()
85
    {
86
        // the ascii value of is the \
87 1
        return chr(92);
88
    }
89
90
    /**
91
     * @return string
92
     */
93 1
    public function getPathSeparator()
94
    {
95 1
        return ';';
96
    }
97
98
    /**
99
     * A normal Win32 pathname contains no duplicate slashes, except possibly
100
     * for a UNC prefix, and does not end with a slash.  It may be the empty
101
     * string.  Normalized Win32 pathnames have the convenient property that
102
     * the length of the prefix almost uniquely identifies the type of the path
103
     * and whether it is absolute or relative:
104
     *
105
     *    0  relative to both drive and directory
106
     *    1  drive-relative (begins with '\\')
107
     *    2  absolute UNC (if first char is '\\'), else directory-relative (has form "z:foo")
108
     *    3  absolute local pathname (begins with "z:\\")
109
     *
110
     * @param  $strPath
111
     * @param  $len
112
     * @param  $sb
113
     * @return int
114
     */
115
    public function normalizePrefix($strPath, $len, &$sb)
116
    {
117
        $src = 0;
118
        while (($src < $len) && $this->isSlash($strPath[$src])) {
119
            $src++;
120
        }
121
        $c = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $c is dead and can be removed.
Loading history...
122
        if (
123
            ($len - $src >= 2)
124
            && $this->isLetter($c = $strPath[$src])
125
            && $strPath[$src + 1] === ':'
126
        ) {
127
            /* Remove leading slashes if followed by drive specifier.
128
             * This hack is necessary to support file URLs containing drive
129
             * specifiers (e.g., "file://c:/path").  As a side effect,
130
             * "/c:/path" can be used as an alternative to "c:/path". */
131
            $sb .= $c;
132
            $sb .= ':';
133
            $src += 2;
134
        } else {
135
            $src = 0;
136
            if (
137
                ($len >= 2)
138
                && $this->isSlash($strPath[0])
139
                && $this->isSlash($strPath[1])
140
            ) {
141
                /* UNC pathname: Retain first slash; leave src pointed at
142
                 * second slash so that further slashes will be collapsed
143
                 * into the second slash.  The result will be a pathname
144
                 * beginning with "\\\\" followed (most likely) by a host
145
                 * name. */
146
                $src = 1;
147
                $sb .= $this->slash;
148
            }
149
        }
150
151
        return $src;
152
    }
153
154
    /**
155
     * Normalize the given pathname, whose length is len, starting at the given
156
     * offset; everything before this offset is already normal.
157
     *
158
     * @param  $strPath
159
     * @param  $len
160
     * @param  $offset
161
     * @return string
162
     */
163
    protected function normalizer($strPath, $len, $offset)
164
    {
165
        if ($len == 0) {
166
            return $strPath;
167
        }
168
        if ($offset < 3) {
169
            $offset = 0; //Avoid fencepost cases with UNC pathnames
170
        }
171
        $src = 0;
172
        $slash = $this->slash;
173
        $sb = "";
174
175
        if ($offset == 0) {
176
            // Complete normalization, including prefix
177
            $src = $this->normalizePrefix($strPath, $len, $sb);
178
        } else {
179
            // Partial normalization
180
            $src = $offset;
181
            $sb .= substr($strPath, 0, $offset);
182
        }
183
184
        // Remove redundant slashes from the remainder of the path, forcing all
185
        // slashes into the preferred slash
186
        while ($src < $len) {
187
            $c = $strPath[$src++];
188
            if ($this->isSlash($c)) {
189
                while (($src < $len) && $this->isSlash($strPath[$src])) {
190
                    $src++;
191
                }
192
                if ($src === $len) {
193
                    /* Check for trailing separator */
194
                    $sn = (int) strlen($sb);
195
                    if (($sn == 2) && ($sb[1] === ':')) {
196
                        // "z:\\"
197
                        $sb .= $slash;
198
                        break;
199
                    }
200
                    if ($sn === 0) {
201
                        // "\\"
202
                        $sb .= $slash;
203
                        break;
204
                    }
205
                    if (($sn === 1) && ($this->isSlash($sb[0]))) {
206
                        /* "\\\\" is not collapsed to "\\" because "\\\\" marks
207
                        the beginning of a UNC pathname.  Even though it is
208
                        not, by itself, a valid UNC pathname, we leave it as
209
                        is in order to be consistent with the win32 APIs,
210
                        which treat this case as an invalid UNC pathname
211
                        rather than as an alias for the root directory of
212
                        the current drive. */
213
                        $sb .= $slash;
214
                        break;
215
                    }
216
                    // Path does not denote a root directory, so do not append
217
                    // trailing slash
218
                    break;
219
                }
220
221
                $sb .= $slash;
222
            } else {
223
                $sb .= $c;
224
            }
225
        }
226
        $rv = (string) $sb;
227
228
        return $rv;
229
    }
230
231
    /**
232
     * Check that the given pathname is normal.  If not, invoke the real
233
     * normalizer on the part of the pathname that requires normalization.
234
     * This way we iterate through the whole pathname string only once.
235
     *
236
     * @param string $strPath
237
     * @return string
238
     */
239
    public function normalize($strPath)
240
    {
241
        $strPath = $this->fixEncoding($strPath);
242
243
        if ($this->_isPharArchive($strPath)) {
244
            return str_replace('\\', '/', $strPath);
245
        }
246
247
        $n = strlen($strPath);
0 ignored issues
show
Bug introduced by
It seems like $strPath can also be of type array; however, parameter $string of strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

247
        $n = strlen(/** @scrutinizer ignore-type */ $strPath);
Loading history...
248
        $slash = $this->slash;
249
        $altSlash = $this->altSlash;
250
        $prev = 0;
251
        for ($i = 0; $i < $n; $i++) {
252
            $c = $strPath[$i];
253
            if ($c === $altSlash) {
254
                return $this->normalizer($strPath, $n, ($prev === $slash) ? $i - 1 : $i);
255
            }
256
            if (($c === $slash) && ($prev === $slash) && ($i > 1)) {
257
                return $this->normalizer($strPath, $n, $i - 1);
258
            }
259
            if (($c === ':') && ($i > 1)) {
260
                return $this->normalizer($strPath, $n, 0);
261
            }
262
            $prev = $c;
263
        }
264
        if ($prev === $slash) {
265
            return $this->normalizer($strPath, $n, $n - 1);
266
        }
267
268
        return $strPath;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $strPath also could return the type array which is incompatible with the documented return type string.
Loading history...
269
    }
270
271
    /**
272
     * @param string $strPath
273
     * @return int
274
     */
275
    public function prefixLength($strPath)
276
    {
277
        if ($this->_isPharArchive($strPath)) {
278
            return 0;
279
        }
280
281
        $path = (string) $strPath;
282
        $slash = (string) $this->slash;
283
        $n = (int) strlen($path);
284
        if ($n === 0) {
285
            return 0;
286
        }
287
        $c0 = $path[0];
288
        $c1 = ($n > 1) ? $path[1] :
289
            0;
290
        if ($c0 === $slash) {
291
            if ($c1 === $slash) {
292
                return 2; // absolute UNC pathname "\\\\foo"
293
            }
294
295
            return 1; // drive-relative "\\foo"
296
        }
297
298
        if ($this->isLetter($c0) && ($c1 === ':')) {
299
            if (($n > 2) && ($path[2]) === $slash) {
300
                return 3; // Absolute local pathname "z:\\foo" */
301
            }
302
303
            return 2; // Directory-relative "z:foo"
304
        }
305
306
        return 0; // Completely relative
307
    }
308
309
    /**
310
     * @param string $parent
311
     * @param string $child
312
     * @return string
313
     */
314
    public function resolve($parent, $child)
315
    {
316
        $parent = (string) $parent;
317
        $child = (string) $child;
318
        $slash = (string) $this->slash;
319
320
        $pn = (int) strlen($parent);
321
        if ($pn === 0) {
322
            return $child;
323
        }
324
        $cn = (int) strlen($child);
325
        if ($cn === 0) {
326
            return $parent;
327
        }
328
329
        $c = $child;
330
        if (($cn > 1) && ($c[0] === $slash)) {
331
            if ($c[1] === $slash) {
332
                // drop prefix when child is a UNC pathname
333
                $c = substr($c, 2);
334
            } else {
335
                //Drop prefix when child is drive-relative */
336
                $c = substr($c, 1);
337
            }
338
        }
339
340
        $p = $parent;
341
        if ($p[$pn - 1] === $slash) {
342
            $p = substr($p, 0, $pn - 1);
343
        }
344
345
        return $p . $this->slashify($c);
346
    }
347
348
    /**
349
     * @return string
350
     */
351
    public function getDefaultParent()
352
    {
353
        return (string) ("" . $this->slash);
354
    }
355
356
    /**
357
     * @param string $strPath
358
     * @return string
359
     */
360
    public function fromURIPath($strPath)
361
    {
362
        $p = (string) $strPath;
363
        if ((strlen($p) > 2) && ($p[2] === ':')) {
364
            // "/c:/foo" --> "c:/foo"
365
            $p = substr($p, 1);
366
367
            // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
368
            if ((strlen($p) > 3) && StringHelper::endsWith('/', $p)) {
369
                $p = substr($p, 0, strlen($p) - 1);
370
            }
371
        } elseif ((strlen($p) > 1) && StringHelper::endsWith('/', $p)) {
372
            // "/foo/" --> "/foo"
373
            $p = substr($p, 0, strlen($p) - 1);
374
        }
375
376
        return (string) $p;
377
    }
378
379
    /* -- Path operations -- */
380
381
    /**
382
     * @param File $f
383
     * @return bool
384
     */
385
    public function isAbsolute(File $f)
386
    {
387
        $pl = (int) $f->getPrefixLength();
388
        $p = (string) $f->getPath();
389
390
        return ((($pl === 2) && ($p[0] === $this->slash)) || ($pl === 3) || ($pl === 1 && $p[0] === $this->slash));
391
    }
392
393
    /**
394
     * private
395
     *
396
     * @param  $d
397
     * @return int
398
     */
399
    public function _driveIndex($d)
400
    {
401
        $d = (string) $d[0];
402
        if ((ord($d) >= ord('a')) && (ord($d) <= ord('z'))) {
403
            return ord($d) - ord('a');
404
        }
405
        if ((ord($d) >= ord('A')) && (ord($d) <= ord('Z'))) {
406
            return ord($d) - ord('A');
407
        }
408
409
        return -1;
410
    }
411
412
    /**
413
     * private
414
     *
415
     * @param  $strPath
416
     * @return bool
417
     */
418
    public function _isPharArchive($strPath)
419
    {
420
        return (strpos($strPath, 'phar://') === 0);
421
    }
422
423
    /**
424
     * @param $drive
425
     * @return null
426
     */
427
    public function _getDriveDirectory($drive)
428
    {
429
        $drive = (string) $drive[0];
430
        $i = (int) $this->_driveIndex($drive);
431
        if ($i < 0) {
432
            return null;
433
        }
434
435
        $s = (self::$driveDirCache[$i] ?? null);
436
437
        if ($s !== null) {
438
            return $s;
439
        }
440
441
        $s = $this->_getDriveDirectory($i + 1);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $s is correct as $this->_getDriveDirectory($i + 1) targeting Phing\Io\WindowsFileSystem::_getDriveDirectory() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
442
        self::$driveDirCache[$i] = $s;
443
444
        return $s;
445
    }
446
447
    /**
448
     * @return string
449
     */
450
    public function _getUserPath()
451
    {
452
        //For both compatibility and security, we must look this up every time
453
        return (string) $this->normalize(Phing::getProperty("user.dir"));
454
    }
455
456
    /**
457
     * @param $path
458
     * @return null|string
459
     */
460
    public function _getDrive($path)
461
    {
462
        $path = (string) $path;
463
        $pl = $this->prefixLength($path);
464
465
        return ($pl === 3) ? substr($path, 0, 2) : null;
466
    }
467
468
    /**
469
     * @param File $f
470
     */
471
    public function resolveFile(File $f)
472
    {
473
        $path = $f->getPath();
474
        $pl = (int) $f->getPrefixLength();
475
476
        if (($pl === 2) && ($path[0] === $this->slash)) {
477
            return $path; // UNC
478
        }
479
480
        if ($pl === 3) {
481
            return $path; // Absolute local
482
        }
483
484
        if ($pl === 0) {
485
            if ($this->_isPharArchive($path)) {
486
                return $path;
487
            }
488
489
            return (string) ($this->_getUserPath() . $this->slashify($path)); //Completely relative
490
        }
491
492
        if ($pl === 1) { // Drive-relative
493
            $up = (string) $this->_getUserPath();
494
            $ud = (string) $this->_getDrive($up);
495
            if ($ud !== null) {
0 ignored issues
show
introduced by
The condition $ud !== null is always true.
Loading history...
496
                return (string) $ud . $path;
497
            }
498
499
            return (string) $up . $path; //User dir is a UNC path
500
        }
501
502
        if ($pl === 2) { // Directory-relative
503
            $up = (string) $this->_getUserPath();
504
            $ud = (string) $this->_getDrive($up);
505
            if (($ud !== null) && StringHelper::startsWith($ud, $path)) {
506
                return (string) ($up . $this->slashify(substr($path, 2)));
507
            }
508
            $drive = (string) $path[0];
509
            $dir = (string) $this->_getDriveDirectory($drive);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->_getDriveDirectory($drive) targeting Phing\Io\WindowsFileSystem::_getDriveDirectory() 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...
510
511
            if ($dir !== null) {
0 ignored issues
show
introduced by
The condition $dir !== null is always true.
Loading history...
512
                /* When resolving a directory-relative path that refers to a
513
                drive other than the current drive, insist that the caller
514
                have read permission on the result */
515
                $p = (string) $drive . (':' . $dir . $this->slashify(substr($path, 2)));
516
517
                if (!$this->checkAccess(new File($p), false)) {
518
                    throw new IOException("Can't resolve path $p");
519
                }
520
521
                return $p;
522
            }
523
524
            return (string) $drive . ':' . $this->slashify(substr($path, 2)); //fake it
525
        }
526
527
        throw new InvalidArgumentException("Unresolvable path: " . $path);
528
    }
529
530
    /* -- most of the following is mapped to the functions mapped th php natives in FileSystem */
531
532
    /* -- Basic infrastructure -- */
533
534
    /**
535
     * compares file paths lexicographically
536
     *
537
     * @param File $f1
538
     * @param File $f2
539
     * @return int
540
     */
541
    public function compare(File $f1, File $f2)
542
    {
543
        $f1Path = $f1->getPath();
544
        $f2Path = $f2->getPath();
545
546
        return strcasecmp((string) $f1Path, (string) $f2Path);
547
    }
548
549
    /**
550
     * On Windows platforms, PHP will mangle non-ASCII characters, see http://bugs.php.net/bug.php?id=47096
551
     *
552
     * @param  $strPath
553
     * @return mixed|string
554
     */
555
    private function fixEncoding($strPath)
556
    {
557
        $charSet = trim(strstr(setlocale(LC_CTYPE, ''), '.'), '.');
558
        if ($charSet === 'utf8') {
559
            return $strPath;
560
        }
561
        $codepage = 'CP' . $charSet;
562
        if (function_exists('iconv')) {
563
            $strPath = iconv('UTF-8', $codepage . '//IGNORE', $strPath);
564
        } elseif (function_exists('mb_convert_encoding')) {
565
            $strPath = mb_convert_encoding($strPath, $codepage, 'UTF-8');
566
        }
567
        return $strPath;
568
    }
569
}
570