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

WindowsFileSystem::listRoots()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 12
nop 0
dl 0
loc 23
ccs 0
cts 14
cp 0
crap 42
rs 9.2222
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
 * @package   phing.system.io
22
 */
23
class WindowsFileSystem extends FileSystem
24
{
25
    protected $slash;
26
    protected $altSlash;
27
    protected $semicolon;
28
29
    private static $driveDirCache = [];
30
31
    /**
32
     *
33
     */
34 1
    public function __construct()
35
    {
36 1
        $this->slash = self::getSeparator();
0 ignored issues
show
Bug Best Practice introduced by
The method 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

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

37
        /** @scrutinizer ignore-call */ 
38
        $this->semicolon = self::getPathSeparator();
Loading history...
38 1
        $this->altSlash = ($this->slash === '\\') ? '/' : '\\';
39 1
    }
40
41
    /**
42
     * @param $c
43
     * @return bool
44
     */
45
    public function isSlash($c)
46
    {
47
        return ($c == '\\') || ($c == '/');
48
    }
49
50
    /**
51
     * @param $c
52
     * @return bool
53
     */
54
    public function isLetter($c)
55
    {
56
        return ((ord($c) >= ord('a')) && (ord($c) <= ord('z')))
57
            || ((ord($c) >= ord('A')) && (ord($c) <= ord('Z')));
58
    }
59
60
    /**
61
     * @param $p
62
     * @return string
63
     */
64
    public function slashify($p)
65
    {
66
        if ((strlen($p) > 0) && ($p[0] != $this->slash)) {
67
            return $this->slash . $p;
68
        }
69
70
        return $p;
71
    }
72
73
    /* -- Normalization and construction -- */
74
75
    /**
76
     * @return string
77
     */
78 1
    public function getSeparator()
79
    {
80
        // the ascii value of is the \
81 1
        return chr(92);
82
    }
83
84
    /**
85
     * @return string
86
     */
87 1
    public function getPathSeparator()
88
    {
89 1
        return ';';
90
    }
91
92
    /**
93
     * A normal Win32 pathname contains no duplicate slashes, except possibly
94
     * for a UNC prefix, and does not end with a slash.  It may be the empty
95
     * string.  Normalized Win32 pathnames have the convenient property that
96
     * the length of the prefix almost uniquely identifies the type of the path
97
     * and whether it is absolute or relative:
98
     *
99
     *    0  relative to both drive and directory
100
     *    1  drive-relative (begins with '\\')
101
     *    2  absolute UNC (if first char is '\\'), else directory-relative (has form "z:foo")
102
     *    3  absolute local pathname (begins with "z:\\")
103
     *
104
     * @param  $strPath
105
     * @param  $len
106
     * @param  $sb
107
     * @return int
108
     */
109
    public function normalizePrefix($strPath, $len, &$sb)
110
    {
111
        $src = 0;
112
        while (($src < $len) && $this->isSlash($strPath[$src])) {
113
            $src++;
114
        }
115
        $c = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $c is dead and can be removed.
Loading history...
116
        if (
117
            ($len - $src >= 2)
118
            && $this->isLetter($c = $strPath[$src])
119
            && $strPath[$src + 1] === ':'
120
        ) {
121
            /* Remove leading slashes if followed by drive specifier.
122
             * This hack is necessary to support file URLs containing drive
123
             * specifiers (e.g., "file://c:/path").  As a side effect,
124
             * "/c:/path" can be used as an alternative to "c:/path". */
125
            $sb .= $c;
126
            $sb .= ':';
127
            $src += 2;
128
        } else {
129
            $src = 0;
130
            if (
131
                ($len >= 2)
132
                && $this->isSlash($strPath[0])
133
                && $this->isSlash($strPath[1])
134
            ) {
135
                /* UNC pathname: Retain first slash; leave src pointed at
136
                 * second slash so that further slashes will be collapsed
137
                 * into the second slash.  The result will be a pathname
138
                 * beginning with "\\\\" followed (most likely) by a host
139
                 * name. */
140
                $src = 1;
141
                $sb .= $this->slash;
142
            }
143
        }
144
145
        return $src;
146
    }
147
148
    /**
149
     * Normalize the given pathname, whose length is len, starting at the given
150
     * offset; everything before this offset is already normal.
151
     *
152
     * @param  $strPath
153
     * @param  $len
154
     * @param  $offset
155
     * @return string
156
     */
157
    protected function normalizer($strPath, $len, $offset)
158
    {
159
        if ($len == 0) {
160
            return $strPath;
161
        }
162
        if ($offset < 3) {
163
            $offset = 0; //Avoid fencepost cases with UNC pathnames
164
        }
165
        $src = 0;
166
        $slash = $this->slash;
167
        $sb = "";
168
169
        if ($offset == 0) {
170
            // Complete normalization, including prefix
171
            $src = $this->normalizePrefix($strPath, $len, $sb);
172
        } else {
173
            // Partial normalization
174
            $src = $offset;
175
            $sb .= substr($strPath, 0, $offset);
176
        }
177
178
        // Remove redundant slashes from the remainder of the path, forcing all
179
        // slashes into the preferred slash
180
        while ($src < $len) {
181
            $c = $strPath[$src++];
182
            if ($this->isSlash($c)) {
183
                while (($src < $len) && $this->isSlash($strPath[$src])) {
184
                    $src++;
185
                }
186
                if ($src === $len) {
187
                    /* Check for trailing separator */
188
                    $sn = (int) strlen($sb);
189
                    if (($sn == 2) && ($sb[1] === ':')) {
190
                        // "z:\\"
191
                        $sb .= $slash;
192
                        break;
193
                    }
194
                    if ($sn === 0) {
195
                        // "\\"
196
                        $sb .= $slash;
197
                        break;
198
                    }
199
                    if (($sn === 1) && ($this->isSlash($sb[0]))) {
200
                        /* "\\\\" is not collapsed to "\\" because "\\\\" marks
201
                        the beginning of a UNC pathname.  Even though it is
202
                        not, by itself, a valid UNC pathname, we leave it as
203
                        is in order to be consistent with the win32 APIs,
204
                        which treat this case as an invalid UNC pathname
205
                        rather than as an alias for the root directory of
206
                        the current drive. */
207
                        $sb .= $slash;
208
                        break;
209
                    }
210
                    // Path does not denote a root directory, so do not append
211
                    // trailing slash
212
                    break;
213
                }
214
215
                $sb .= $slash;
216
            } else {
217
                $sb .= $c;
218
            }
219
        }
220
        $rv = (string) $sb;
221
222
        return $rv;
223
    }
224
225
    /**
226
     * Check that the given pathname is normal.  If not, invoke the real
227
     * normalizer on the part of the pathname that requires normalization.
228
     * This way we iterate through the whole pathname string only once.
229
     *
230
     * @param  string $strPath
231
     * @return string
232
     */
233
    public function normalize($strPath)
234
    {
235
        $strPath = $this->fixEncoding($strPath);
236
237
        if ($this->_isPharArchive($strPath)) {
238
            return str_replace('\\', '/', $strPath);
239
        }
240
241
        $n = strlen($strPath);
242
        $slash = $this->slash;
243
        $altSlash = $this->altSlash;
244
        $prev = 0;
245
        for ($i = 0; $i < $n; $i++) {
246
            $c = $strPath[$i];
247
            if ($c === $altSlash) {
248
                return $this->normalizer($strPath, $n, ($prev === $slash) ? $i - 1 : $i);
249
            }
250
            if (($c === $slash) && ($prev === $slash) && ($i > 1)) {
251
                return $this->normalizer($strPath, $n, $i - 1);
252
            }
253
            if (($c === ':') && ($i > 1)) {
254
                return $this->normalizer($strPath, $n, 0);
255
            }
256
            $prev = $c;
257
        }
258
        if ($prev === $slash) {
259
            return $this->normalizer($strPath, $n, $n - 1);
260
        }
261
262
        return $strPath;
263
    }
264
265
    /**
266
     * @param string $strPath
267
     * @return int
268
     */
269
    public function prefixLength($strPath)
270
    {
271
        if ($this->_isPharArchive($strPath)) {
272
            return 0;
273
        }
274
275
        $path = (string) $strPath;
276
        $slash = (string) $this->slash;
277
        $n = (int) strlen($path);
278
        if ($n === 0) {
279
            return 0;
280
        }
281
        $c0 = $path[0];
282
        $c1 = ($n > 1) ? $path[1] :
283
            0;
284
        if ($c0 === $slash) {
285
            if ($c1 === $slash) {
286
                return 2; // absolute UNC pathname "\\\\foo"
287
            }
288
289
            return 1; // drive-relative "\\foo"
290
        }
291
292
        if ($this->isLetter($c0) && ($c1 === ':')) {
293
            if (($n > 2) && ($path[2]) === $slash) {
294
                return 3; // Absolute local pathname "z:\\foo" */
295
            }
296
297
            return 2; // Directory-relative "z:foo"
298
        }
299
300
        return 0; // Completely relative
301
    }
302
303
    /**
304
     * @param string $parent
305
     * @param string $child
306
     * @return string
307
     */
308
    public function resolve($parent, $child)
309
    {
310
        $parent = (string) $parent;
311
        $child = (string) $child;
312
        $slash = (string) $this->slash;
313
314
        $pn = (int) strlen($parent);
315
        if ($pn === 0) {
316
            return $child;
317
        }
318
        $cn = (int) strlen($child);
319
        if ($cn === 0) {
320
            return $parent;
321
        }
322
323
        $c = $child;
324
        if (($cn > 1) && ($c[0] === $slash)) {
325
            if ($c[1] === $slash) {
326
                // drop prefix when child is a UNC pathname
327
                $c = substr($c, 2);
328
            } else {
329
                //Drop prefix when child is drive-relative */
330
                $c = substr($c, 1);
331
            }
332
        }
333
334
        $p = $parent;
335
        if ($p[$pn - 1] === $slash) {
336
            $p = substr($p, 0, $pn - 1);
337
        }
338
339
        return $p . $this->slashify($c);
340
    }
341
342
    /**
343
     * @return string
344
     */
345
    public function getDefaultParent()
346
    {
347
        return (string) ("" . $this->slash);
348
    }
349
350
    /**
351
     * @param string $strPath
352
     * @return string
353
     */
354
    public function fromURIPath($strPath)
355
    {
356
        $p = (string) $strPath;
357
        if ((strlen($p) > 2) && ($p[2] === ':')) {
358
            // "/c:/foo" --> "c:/foo"
359
            $p = substr($p, 1);
360
361
            // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
362
            if ((strlen($p) > 3) && StringHelper::endsWith('/', $p)) {
363
                $p = substr($p, 0, strlen($p) - 1);
364
            }
365
        } elseif ((strlen($p) > 1) && StringHelper::endsWith('/', $p)) {
366
            // "/foo/" --> "/foo"
367
            $p = substr($p, 0, strlen($p) - 1);
368
        }
369
370
        return (string) $p;
371
    }
372
373
    /* -- Path operations -- */
374
375
    /**
376
     * @param PhingFile $f
377
     * @return bool
378
     */
379
    public function isAbsolute(PhingFile $f)
380
    {
381
        $pl = (int) $f->getPrefixLength();
382
        $p = (string) $f->getPath();
383
384
        return ((($pl === 2) && ($p[0] === $this->slash)) || ($pl === 3) || ($pl === 1 && $p[0] === $this->slash));
385
    }
386
387
    /**
388
     * private
389
     *
390
     * @param  $d
391
     * @return int
392
     */
393
    public function _driveIndex($d)
394
    {
395
        $d = (string) $d[0];
396
        if ((ord($d) >= ord('a')) && (ord($d) <= ord('z'))) {
397
            return ord($d) - ord('a');
398
        }
399
        if ((ord($d) >= ord('A')) && (ord($d) <= ord('Z'))) {
400
            return ord($d) - ord('A');
401
        }
402
403
        return -1;
404
    }
405
406
    /**
407
     * private
408
     *
409
     * @param  $strPath
410
     * @return bool
411
     */
412
    public function _isPharArchive($strPath)
413
    {
414
        return (strpos($strPath, 'phar://') === 0);
415
    }
416
417
    /**
418
     * @param $drive
419
     * @return null
420
     */
421
    public function _getDriveDirectory($drive)
422
    {
423
        $drive = (string) $drive[0];
424
        $i = (int) $this->_driveIndex($drive);
425
        if ($i < 0) {
426
            return null;
427
        }
428
429
        $s = (self::$driveDirCache[$i] ?? null);
430
431
        if ($s !== null) {
432
            return $s;
433
        }
434
435
        $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 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...
436
        self::$driveDirCache[$i] = $s;
437
438
        return $s;
439
    }
440
441
    /**
442
     * @return string
443
     */
444
    public function _getUserPath()
445
    {
446
        //For both compatibility and security, we must look this up every time
447
        return (string) $this->normalize(Phing::getProperty("user.dir"));
448
    }
449
450
    /**
451
     * @param $path
452
     * @return null|string
453
     */
454
    public function _getDrive($path)
455
    {
456
        $path = (string) $path;
457
        $pl = $this->prefixLength($path);
458
459
        return ($pl === 3) ? substr($path, 0, 2) : null;
460
    }
461
462
    /**
463
     * @param PhingFile $f
464
     */
465
    public function resolveFile(PhingFile $f)
466
    {
467
        $path = $f->getPath();
468
        $pl = (int) $f->getPrefixLength();
469
470
        if (($pl === 2) && ($path[0] === $this->slash)) {
471
            return $path; // UNC
472
        }
473
474
        if ($pl === 3) {
475
            return $path; // Absolute local
476
        }
477
478
        if ($pl === 0) {
479
            if ($this->_isPharArchive($path)) {
480
                return $path;
481
            }
482
483
            return (string) ($this->_getUserPath() . $this->slashify($path)); //Completely relative
484
        }
485
486
        if ($pl === 1) { // Drive-relative
487
            $up = (string) $this->_getUserPath();
488
            $ud = (string) $this->_getDrive($up);
489
            if ($ud !== null) {
0 ignored issues
show
introduced by
The condition $ud !== null is always true.
Loading history...
490
                return (string) $ud . $path;
491
            }
492
493
            return (string) $up . $path; //User dir is a UNC path
494
        }
495
496
        if ($pl === 2) { // Directory-relative
497
            $up = (string) $this->_getUserPath();
498
            $ud = (string) $this->_getDrive($up);
499
            if (($ud !== null) && StringHelper::startsWith($ud, $path)) {
500
                return (string) ($up . $this->slashify(substr($path, 2)));
501
            }
502
            $drive = (string) $path[0];
503
            $dir = (string) $this->_getDriveDirectory($drive);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->_getDriveDirectory($drive) targeting 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...
504
505
            if ($dir !== null) {
0 ignored issues
show
introduced by
The condition $dir !== null is always true.
Loading history...
506
                /* When resolving a directory-relative path that refers to a
507
                drive other than the current drive, insist that the caller
508
                have read permission on the result */
509
                $p = (string) $drive . (':' . $dir . $this->slashify(substr($path, 2)));
510
511
                if (!$this->checkAccess(new PhingFile($p), false)) {
512
                    throw new IOException("Can't resolve path $p");
513
                }
514
515
                return $p;
516
            }
517
518
            return (string) $drive . ':' . $this->slashify(substr($path, 2)); //fake it
519
        }
520
521
        throw new InvalidArgumentException("Unresolvable path: " . $path);
522
    }
523
524
    /* -- most of the following is mapped to the functions mapped th php natives in FileSystem */
525
526
    /* -- Basic infrastructure -- */
527
528
    /**
529
     * compares file paths lexicographically
530
     *
531
     * @param  PhingFile $f1
532
     * @param  PhingFile $f2
533
     * @return int
534
     */
535
    public function compare(PhingFile $f1, PhingFile $f2)
536
    {
537
        $f1Path = $f1->getPath();
538
        $f2Path = $f2->getPath();
539
540
        return strcasecmp((string) $f1Path, (string) $f2Path);
541
    }
542
543
    /**
544
     * On Windows platforms, PHP will mangle non-ASCII characters, see http://bugs.php.net/bug.php?id=47096
545
     *
546
     * @param  $strPath
547
     * @return mixed|string
548
     */
549
    private function fixEncoding($strPath)
550
    {
551
        $codepage = 'CP' . trim(strstr(setlocale(LC_CTYPE, ''), '.'), '.');
552
        if (function_exists('iconv')) {
553
            $strPath = iconv('UTF-8', $codepage . '//IGNORE', $strPath);
554
        } elseif (function_exists('mb_convert_encoding')) {
555
            $strPath = mb_convert_encoding($strPath, $codepage, 'UTF-8');
556
        }
557
        return $strPath;
558
    }
559
}
560