Test Failed
Push — test ( 0c4d14...2be48d )
by Tom
02:17
created

LibFs::isPortableFilename()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

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