Passed
Push — test ( 1931e0...fcdc81 )
by Tom
02:59
created

LibFs::isPortableFilename()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 11
ccs 4
cts 4
cp 1
crap 2
rs 10
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines;
6
7
use UnexpectedValueException;
8
9
/**
10
 * Class LibFs - Low level file-system utility functions
11
 *
12
 * @covers \Ktomk\Pipelines\LibFs
13
 */
14
class LibFs
15
{
16
    /**
17
     * @param string $path
18
     * @param string $mode [optional]
19
     *
20
     * @return bool
21
     */
22 2
    public static function canFopen($path, $mode = null)
23
    {
24 2
        if (null === $mode) {
25 1
            $mode = 'rb';
26
        }
27
28 2
        $handle = @fopen($path, $mode);
29 2
        if (false === $handle) {
30 1
            return false;
31
        }
32
33
        /** @scrutinizer ignore-unhandled */
34 1
        @fclose($handle);
35
36 1
        return true;
37
    }
38
39
    /**
40
     * locate (readable) file by basename upward all parent directories
41
     *
42
     * @param string $basename
43
     * @param string $directory [optional] directory to operate from, defaults
44
     *               to "." (relative path of present working directory)
45
     *
46
     * @return null|string
47
     */
48 4
    public static function fileLookUp($basename, $directory = null)
49
    {
50 4
        if ('' === $directory || null === $directory) {
51 1
            $directory = '.';
52
        }
53
54
        for (
55 4
            $dirName = $directory, $old = null;
56 4
            $old !== $dirName;
57 2
            $old = $dirName, $dirName = dirname($dirName)
58
        ) {
59 4
            $test = $dirName . '/' . $basename;
60 4
            if (self::isReadableFile($test)) {
61 3
                return $test;
62
            }
63
        }
64
65 1
        return null;
66
    }
67
68
    /**
69
     * check if path is absolute
70
     *
71
     * @param string $path
72
     *
73
     * @return bool
74
     */
75 8
    public static function isAbsolutePath($path)
76
    {
77
        // TODO: a variant with PHP stream wrapper prefix support
78
        /* @see isStreamUri */
79
80 8
        $count = strspn($path, '/', 0, 3) % 2;
81
82 8
        return (bool)$count;
83
    }
84
85
    /**
86
     * check if path is basename
87
     *
88
     * @param string $path
89
     *
90
     * @return bool
91
     */
92 5
    public static function isBasename($path)
93
    {
94 5
        if (in_array($path, array('', '.', '..'), true)) {
95 1
            return false;
96
        }
97
98 4
        if (false !== strpos($path, '/')) {
99 3
            return false;
100
        }
101
102 1
        return true;
103
    }
104
105
    /**
106
     * see 2.2 Standards permit the exclusion of bad filenames / POSIX.1-2008
107
     *
108
     * @link https://dwheeler.com/essays/fixing-unix-linux-filenames.html
109
     *
110
     * @param string $filename
111
     *
112
     * @return bool
113
     */
114 10
    public static function isPortableFilename($filename)
115
    {
116
        # A-Z, a-z, 0-9, <period>, <underscore>, and <hyphen>)
117 10
        $result = preg_match('(^(?!-)[A-Za-z0-9._-]+$)', $filename);
118 10
        if (false === $result) {
119
            // @codeCoverageIgnoreStart
120
            throw new UnexpectedValueException('preg_match pattern failed');
121
            // @codeCoverageIgnoreEnd
122
        }
123
124 10
        return 1 === $result;
125
    }
126
127
    /**
128
     * @param string $path
129
     *
130
     * @return bool
131
     */
132 11
    public static function isReadableFile($path)
133
    {
134 11
        if (!is_file($path)) {
135 4
            return false;
136
        }
137
138 9
        return is_readable($path) ?: self::canFopen($path, 'rb');
139
    }
140
141
    /**
142
     * create directory if not yet exists
143
     *
144
     * @param string $path
145
     * @param int $mode [optional]
146
     *
147
     * @return string
148
     */
149 7
    public static function mkDir($path, $mode = 0777)
150
    {
151 7
        if (!is_dir($path)) {
152
            /** @noinspection NestedPositiveIfStatementsInspection */
153 6
            if (!mkdir($path, $mode, true) && !is_dir($path)) {
154
                // @codeCoverageIgnoreStart
155
                throw new \RuntimeException(
156
                    sprintf('Directory "%s" was not created', $path)
157
                );
158
                // @codeCoverageIgnoreEnd
159
            }
160
        }
161
162 7
        return $path;
163
    }
164
165
    /**
166
     * Normalize a path as/if common in PHP
167
     *
168
     * E.g. w/ phar:// in front which means w/ stream wrappers in
169
     * mind.
170
     *
171
     * @param string $path
172
     *
173
     * @return string
174
     */
175 5
    public static function normalizePath($path)
176
    {
177 5
        $buffer = $path;
178
179 5
        $scheme = '';
180
        // TODO support for all supported stream wrappers (w/ absolute/relative notation?)
181
        /* @see isStreamUri */
182
        /* @see isAbsolutePath */
183 5
        if (0 === strpos($buffer, 'phar://') || 0 === strpos($buffer, 'file://')) {
184 4
            $scheme = substr($buffer, 0, 7);
185 4
            $buffer = substr($buffer, 7);
186
        }
187
188 5
        $normalized = self::normalizePathSegments($buffer);
189
190 5
        return $scheme . $normalized;
191
    }
192
193
    /**
194
     * Resolve relative path segments in a path on it's own
195
     *
196
     * This is not realpath, not resolving any links.
197
     *
198
     * @param string $path
199
     *
200
     * @return string
201
     */
202 18
    public static function normalizePathSegments($path)
203
    {
204 18
        if ('' === $path) {
205 1
            return $path;
206
        }
207
208 17
        $buffer = $path;
209
210 17
        $prefix = '';
211 17
        $len = strspn($buffer, '/');
212 17
        if (0 < $len) {
213 9
            $prefix = substr($buffer, 0, $len);
214 9
            $buffer = substr($buffer, $len);
215
        }
216
217 17
        $buffer = rtrim($buffer, '/');
218
219 17
        if (in_array($buffer, array('', '.'), true)) {
220 4
            return $prefix;
221
        }
222
223 13
        $pos = strpos($buffer, '/');
224 13
        if (false === $pos) {
225 2
            return $prefix . $buffer;
226
        }
227
228 11
        $buffer = preg_replace('~/+~', '/', $buffer);
229
230 11
        $segments = explode('/', $buffer);
231 11
        $stack = array();
232 11
        foreach ($segments as $segment) {
233 11
            $i = count($stack) - 1;
234 11
            if ('.' === $segment) {
235 2
                continue;
236
            }
237
238 11
            if ('..' !== $segment) {
239 11
                $stack[] = $segment;
240
241 11
                continue;
242
            }
243
244 9
            if (($i > -1) && '..' !== $stack[$i]) {
245 9
                array_pop($stack);
246
247 9
                continue;
248
            }
249
250 1
            $stack[] = $segment;
251
        }
252
253 11
        return $prefix . implode('/', $stack);
254
    }
255
256
    /**
257
     * rename a file
258
     *
259
     * @param string $old
260
     * @param string $new
261
     *
262
     * @return string new file-name
263
     */
264 1
    public static function rename($old, $new)
265
    {
266 1
        if (!@rename($old, $new)) {
267 1
            throw new \RuntimeException(sprintf('Failed to rename "%s" to "%s"', $old, $new));
268
        }
269
270 1
        return $new;
271
    }
272
273
    /**
274
     * @param string $file
275
     *
276
     * @return string
277
     */
278 5
    public static function rm($file)
279
    {
280 5
        if (self::isReadableFile($file)) {
281 5
            unlink($file);
282
        }
283
284 5
        return $file;
285
    }
286
287
    /**
288
     * @param string $dir
289
     *
290
     * @throws UnexpectedValueException
291
     *
292
     * @return void
293
     */
294 3
    public static function rmDir($dir)
295
    {
296 3
        $result = @lstat($dir);
297 3
        if (false === $result) {
298 1
            return;
299
        }
300
301 3
        $dirs = array();
302 3
        $dirs[] = $dir;
303 3
        for ($i = 0; isset($dirs[$i]); $i++) {
304 3
            $current = $dirs[$i];
305 3
            $result = @scandir($current);
306 3
            if (false === $result) {
307 1
                throw new UnexpectedValueException(sprintf('Failed to open directory: %s', $current));
308
            }
309 2
            $files = array_diff($result, array('.', '..'));
310 2
            foreach ($files as $file) {
311 1
                $path = "${current}/${file}";
312 1
                if (is_dir($path)) {
313 1
                    $dirs[] = $path;
314 1
                } elseif (is_file($path)) {
315 1
                    self::rm($path);
316
                }
317
            }
318
        }
319
320 2
        while (null !== ($pop = array_pop($dirs))) {
321
            /* @scrutinizer ignore-unhandled */
322 2
            @rmdir($pop);
323
        }
324 2
    }
325
326
    /**
327
     * create symbolic link, recreate if it exists
328
     *
329
     * @param string $target
330
     * @param string $link
331
     *
332
     * @return void
333
     */
334
    public static function symlink($target, $link)
335
    {
336 1
        self::unlink($link);
337 1
        symlink($target, $link);
338 1
    }
339
340
    /**
341
     * @param string $link
342
     *
343
     * @return void
344
     */
345
    public static function unlink($link)
346
    {
347 1
        if (is_link($link)) {
348 1
            unlink($link);
349
        }
350 1
    }
351
}
352