-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Utility\Path::info($path) - enhanced pathinfo() function. - always returns all components; - correctly report absence of extension for dot-files; - adds missing dot to exceptions. Utility\Path::normalize() - provides path normalization. - handles both *NIX and Windows (X:...) style paths; - handles leaking relative paths if explicitly allowed; - tripping an exception if relative path is trying to escape itself; - allows customizable output directory separator.
- Loading branch information
Showing
3 changed files
with
202 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,6 @@ | ||
------------------------------------------------------------------------ | ||
r900 | anrdaemon | 2018-09-04 19:03:27 +0300 (Tue, 04 Sep 2018) | 3 lines | ||
r904 | anrdaemon | 2018-09-07 02:58:13 +0300 (Fri, 07 Sep 2018) | 2 lines | ||
|
||
* Net\Browser: Improved docblocks for method callers. | ||
= Sync'd .todo. | ||
|
||
------------------------------------------------------------------------ | ||
r880 | anrdaemon | 2018-09-01 06:56:56 +0300 (Sat, 01 Sep 2018) | 2 lines | ||
|
||
+ Net\Browser: Added __clone and __destruct handlers. | ||
|
||
------------------------------------------------------------------------ | ||
r879 | anrdaemon | 2018-09-01 06:35:50 +0300 (Sat, 01 Sep 2018) | 2 lines | ||
|
||
+ HTTP PUT and custom request method implementations. | ||
|
||
------------------------------------------------------------------------ | ||
r878 | anrdaemon | 2018-09-01 06:07:17 +0300 (Sat, 01 Sep 2018) | 2 lines | ||
|
||
* Moved common request steps into a helper method. | ||
|
||
------------------------------------------------------------------------ | ||
r877 | anrdaemon | 2018-09-01 05:52:07 +0300 (Sat, 01 Sep 2018) | 3 lines | ||
|
||
* Fixed Net\Browser::setOpt to behave and report. | ||
+ Added CurlOptions helper to better report errors. | ||
|
||
------------------------------------------------------------------------ | ||
r876 | anrdaemon | 2018-09-01 01:27:16 +0300 (Sat, 01 Sep 2018) | 2 lines | ||
|
||
+ Implement ability to return basic request status as a single array. | ||
|
||
------------------------------------------------------------------------ | ||
r875 | anrdaemon | 2018-09-01 01:23:26 +0300 (Sat, 01 Sep 2018) | 4 lines | ||
|
||
+ Added a (b)lo(a)t of docblocks to Net\Browser. | ||
* Prepared put and custom requests for existence. | ||
Sad but I can't make it any better than that. | ||
|
||
------------------------------------------------------------------------ | ||
r874 | anrdaemon | 2018-08-31 20:02:00 +0300 (Fri, 31 Aug 2018) | 2 lines | ||
|
||
* Repaired initial docblock, removing legacy behavior references. | ||
|
||
------------------------------------------------------------------------ | ||
r873 | anrdaemon | 2018-08-31 18:44:46 +0300 (Fri, 31 Aug 2018) | 2 lines | ||
|
||
* Used assertEquals where applicable for better failure representation. | ||
|
||
------------------------------------------------------------------------ | ||
r872 | anrdaemon | 2018-08-31 18:42:04 +0300 (Fri, 31 Aug 2018) | 3 lines | ||
|
||
* Reordered methods. | ||
* Added PHPUnit v7 compatible exception trap. | ||
|
||
------------------------------------------------------------------------ | ||
r871 | anrdaemon | 2018-08-31 16:20:04 +0300 (Fri, 31 Aug 2018) | 2 lines | ||
|
||
* Use static $url in tests. | ||
+ Added Utility\Path class to fix/enhance standard functions. | ||
|
||
------------------------------------------------------------------------ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
<?php | ||
|
||
namespace AnrDaemon\Utility; | ||
|
||
class Path | ||
{ | ||
/** Creates fixed pathinfo structure | ||
* | ||
* Meaning, "$dirname/$filename$extension" eq. rtrim($path, "\\/"). | ||
* | ||
* Contrary to the {@see \pathinfo() pathinfo()}, all members of the structure always set. | ||
* | ||
* @param string $path The path to get info on. | ||
* @return array Fixed pathinfo structure. | ||
*/ | ||
public static function info($path) | ||
{ | ||
$p = pathinfo($path); | ||
if(empty($p["filename"])) | ||
{ | ||
$p["filename"] = $p["basename"]; | ||
unset($p["extension"]); | ||
} | ||
if(!empty($p["extension"])) | ||
{ | ||
$p["extension"] = ".{$p["extension"]}"; | ||
} | ||
|
||
return $p + ["extension" => ""]; | ||
} | ||
|
||
/** Path normalizer part examinator. | ||
* @internal | ||
* @throws \UnexpectedValueException if relative path is trying to escape above current directory, unless explicitly allowed. | ||
*/ | ||
protected static function examine($part, array &$array, $path_relative, $allow_escape = false) | ||
{ | ||
if($part === '.') | ||
{ | ||
return; | ||
} | ||
|
||
if($part !== '..') | ||
{ | ||
$array[] = $part; | ||
return; | ||
} | ||
|
||
// $part == '..', handle escaping. | ||
$last = end($array); | ||
if($last === '..') | ||
{ // Escaping is allowed and we're already on the run. | ||
$array[] = $part; | ||
return; | ||
} | ||
|
||
if($last !== false) | ||
{ // $last element exists - move up the stack. | ||
array_pop($array); | ||
return; | ||
} | ||
|
||
if(!$path_relative) | ||
{ // Path is not relative - skip updir. | ||
return; | ||
} | ||
|
||
if(!$allow_escape) | ||
throw new \UnexpectedValueException('Attempt to traverse outside the root directory.'); | ||
|
||
$array[] = $part; | ||
} | ||
|
||
/** Normalize path string, removing '.'/'..'/empty components. | ||
* | ||
* Warning: This function is NOT intended to handle URL's ("//host/path")! | ||
* Please use {@see \parse_url() parse_url()} first. | ||
* | ||
* @param string $path The path to normalize. | ||
* @param bool $allow_escape Is the path relative? Defaults to autodetect. Paths declared explicitly relative get slightly different treatment. | ||
* @param string $directory_separator Output directory separator. Defaults to DIRECTORY_SEPARATOR. | ||
* @return string The normalized string. | ||
* @throws \UnexpectedValueException if relative path is trying to escape above current directory, unless explicitly allowed. | ||
*/ | ||
public static function normalize($path, $allow_escape = false, $directory_separator = DIRECTORY_SEPARATOR) | ||
{ | ||
$path = (string)$path; | ||
if($path === '') | ||
return $path; | ||
|
||
$disk = null; | ||
$path_relative = false; | ||
|
||
// If path is not explicitly relative, test if it's an absolute and possibly Windows path | ||
// Convert first byte to uppercase. | ||
$char = ord($path[0]) & 0xdf; | ||
if($char & 0x80) | ||
{ // Multibyte character - path is relative | ||
$path_relative = true; | ||
} | ||
// Windows disk prefix "{A..Z}:" | ||
elseif(strlen($path) > 1 && $char > 0x40 && $char < 0x5b && $path[1] === ':') | ||
{ | ||
if(strlen($path) === 2) | ||
return $path; | ||
|
||
$disk = substr($path, 0, 2); | ||
$path = substr($path, 2); | ||
} | ||
|
||
if($path[0] !== "/" && $path[0] !== "\\") | ||
{ // First byte is not a slash | ||
$path_relative = true; | ||
} | ||
|
||
$ta = []; | ||
$part = strtok($path, "/\\"); | ||
while(false !== $part) | ||
{ | ||
static::examine($part, $ta, $path_relative, $allow_escape); | ||
$part = strtok("/\\"); | ||
} | ||
|
||
return $disk . ($path_relative ? '' : $directory_separator) . join($directory_separator, $ta); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
|
||
namespace AnrDaemon\Tests\Utility; | ||
|
||
use AnrDaemon\Utility\Path; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
final class PathTest | ||
extends TestCase | ||
{ | ||
public function defaultPairsProvider() | ||
{ | ||
$data = [ | ||
"empty path" => array("", ""), | ||
"root" => array('/', '/'), | ||
"relative" => array("foo", "foo"), | ||
"dot dir" => array('/foo/bar', '/foo/./bar'), | ||
"absolute path inescapable" => array('/', '/Foo/Bar/../../../..'), | ||
"multi slash #1" => array('/', '//'), | ||
"multi slash #2" => array('/', '///'), | ||
"multi slash #3" => array('/Foo', '///Foo'), | ||
"otherdir" => array('/bar', '/foo/../bar/'), | ||
"windows disk only" => array("D:", "D:"), | ||
"windows disk root" => array('c:/', 'c:\\'), | ||
"windows disk relative" => array("e:g", "e:g"), | ||
"windows multi slash #1" => array('c:/foo/bar', 'c:/foo//bar'), | ||
"windows multi slash #2" => array('C:/foo/bar', 'C://foo//bar'), | ||
"windows multi slash #3" => array('C:/foo/bar', 'C:///foo//bar'), | ||
"windows otherdir" => array('C:/bar', 'C:/foo/../bar'), | ||
]; | ||
|
||
return $data; | ||
} | ||
|
||
public function escapablePairsProvider() | ||
{ | ||
$data = [ | ||
"simple" => array('../foo', '../foo'), | ||
"simple otherdir" => array('../bar', '../foo/../bar'), | ||
"chained escape" => array("../../bar", "a/../../b/../../bar"), | ||
"double collapse" => array('../src', 'Foo/Bar/../../../src'), | ||
"windows otherdir" => array('c:../b', 'c:.\\..\\a\\..\\b'), | ||
]; | ||
|
||
return $data; | ||
} | ||
|
||
/** Test normalization of standard pairs | ||
* | ||
* @dataProvider defaultPairsProvider | ||
*/ | ||
public function testNormalizeStandardPair($target, $path) | ||
{ | ||
$this->assertTrue($target === Path::normalize($path, null, "/")); | ||
} | ||
|
||
/** Test normalization of escapable pairs | ||
* | ||
* @dataProvider escapablePairsProvider | ||
*/ | ||
public function testNormalizeEscapingPair($target, $path) | ||
{ | ||
$this->assertTrue($target === Path::normalize($path, true, "/")); | ||
} | ||
|
||
/** Test exception on escape attempt | ||
* | ||
* @expectedException \UnexpectedValueException | ||
*/ | ||
public function testExceptionOnEscapeAttempt() | ||
{ | ||
$path = Path::normalize("..", false); | ||
} | ||
} |