Passed
Push — test ( 142764...0be9e7 )
by Tom
03:02
created

LibFsPath::clamp()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 13
ccs 0
cts 7
cp 0
crap 12
rs 10
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines;
6
7
/**
8
 * Class LibFsPath - path utility functions
9
 *
10
 * @package Ktomk\Pipelines
11
 */
12
class LibFsPath
13
{
14
    /**
15
     * clamp windows path
16
     *
17
     *
18
     * @param string $path
19
     *
20
     * @return string
21
     */
22
    public static function clamp($path)
23
    {
24
        if ('/' === DIRECTORY_SEPARATOR) {
25
            return $path;
26
        }
27
28
        $buffer = strtr($path, '\\', '/');
29
30
        if (preg_match('~^[a-z]:~i', $buffer)) {
31
            $buffer = '/' . strtolower($buffer[0]) . substr($buffer, 2);
32
        }
33
34
        return $buffer;
35
    }
36
37
    /**
38
     * check if path is absolute
39
     *
40
     * @param string $path
41
     *
42
     * @return bool
43
     */
44 13
    public static function isAbsolute($path)
45
    {
46
        // TODO: a variant with PHP stream wrapper prefix support
47
        /* @see LibFsStream::isUri */
48
49 13
        $count = strspn($path, '/', 0, 3) % 2;
50
51 13
        return (bool)$count;
52
    }
53
54
    /**
55
     * check if path is basename
56
     *
57
     * @param string $path
58
     *
59
     * @return bool
60
     */
61 5
    public static function isBasename($path)
62
    {
63 5
        if (in_array($path, array('', '.', '..'), true)) {
64 1
            return false;
65
        }
66
67 4
        if (false !== strpos($path, '/')) {
68 3
            return false;
69
        }
70
71 1
        return true;
72
    }
73
74
    /**
75
     * Resolve relative path segments in a path on it's own
76
     *
77
     * This is not realpath, not resolving any links.
78
     *
79
     * @param string $path
80
     *
81
     * @return string
82
     */
83 23
    public static function normalizeSegments($path)
84
    {
85 23
        if ('' === $path) {
86 1
            return $path;
87
        }
88
89 22
        $buffer = $path;
90
91 22
        $prefix = '';
92 22
        $len = strspn($buffer, '/');
93 22
        if (0 < $len) {
94 14
            $prefix = substr($buffer, 0, $len);
95 14
            $buffer = substr($buffer, $len);
96
        }
97
98 22
        $buffer = rtrim($buffer, '/');
99
100 22
        if (in_array($buffer, array('', '.'), true)) {
101 4
            return $prefix;
102
        }
103
104 18
        $pos = strpos($buffer, '/');
105 18
        if (false === $pos) {
106 3
            return $prefix . $buffer;
107
        }
108
109 15
        $buffer = preg_replace('~/+~', '/', $buffer);
110
111 15
        $segments = explode('/', $buffer);
112 15
        $stack = array();
113 15
        foreach ($segments as $segment) {
114 15
            $i = count($stack) - 1;
115 15
            if ('.' === $segment) {
116 3
                continue;
117
            }
118
119 15
            if ('..' !== $segment) {
120 13
                $stack[] = $segment;
121
122 13
                continue;
123
            }
124
125 11
            if (($i > -1) && '..' !== $stack[$i]) {
126 9
                array_pop($stack);
127
128 9
                continue;
129
            }
130
131 4
            $stack[] = $segment;
132
        }
133
134 15
        return $prefix . implode('/', $stack);
135
    }
136
137
    /**
138
     * @param string $path
139
     *
140
     * @return bool
141
     */
142 5
    public static function containsRelativeSegment($path)
143
    {
144 5
        $segments = array_flip(explode('/', $path));
145
146 5
        return isset($segments['.']) || isset($segments['..']);
147
    }
148
149
    /**
150
     * Normalize a path as/if common in PHP
151
     *
152
     * E.g. w/ phar:// in front which means w/ stream wrappers in
153
     * mind.
154
     *
155
     * @param string $path
156
     *
157
     * @return string
158
     */
159 5
    public static function normalize($path)
160
    {
161 5
        $buffer = $path;
162
163 5
        $scheme = '';
164
        // TODO support for all supported stream wrappers (w/ absolute/relative notation?)
165
        /* @see LibFsStream::isUri */
166
        /* @see LibFsPath::isAbsolute */
167 5
        if (0 === strpos($buffer, 'phar://') || 0 === strpos($buffer, 'file://')) {
168 4
            $scheme = substr($buffer, 0, 7);
169 4
            $buffer = substr($buffer, 7);
170
        }
171
172 5
        $normalized = self::normalizeSegments($buffer);
173
174 5
        return $scheme . $normalized;
175
    }
176
177
    /**
178
     * @param string $path
179
     *
180
     * @return bool
181
     */
182 4
    public static function isPortable($path)
183
    {
184 4
        return 1 === Preg::match('(^(?>/?(?!-)[A-Za-z0-9._-]+)+$)', $path);
185
    }
186
187
    /**
188
     * @param string $path
189
     *
190
     * @return string
191
     */
192 5
    public static function gateAbsolutePortable($path)
193
    {
194 5
        if (!self::isAbsolute($path)) {
195 1
            throw new \InvalidArgumentException(sprintf('not an absolute path: "%s"', $path));
196
        }
197
198 4
        $normalized = self::normalizeSegments($path);
199 4
        if (self::containsRelativeSegment($normalized)) {
200 1
            throw new \InvalidArgumentException(sprintf('not a fully qualified path: "%s"', $path));
201
        }
202
203 3
        if (!self::isPortable($path)) {
204 1
            throw new \InvalidArgumentException(sprintf('not a portable path: "%s"', $path));
205
        }
206
207 2
        return $normalized;
208
    }
209
}
210