diff --git a/CHANGES.txt b/CHANGES.txt index 052e7da..2911b27 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,14 @@ +------------------------------------------------------------------------ +r788 | anrdaemon | 2018-04-03 17:58:56 +0300 (Tue, 03 Apr 2018) | 2 lines + ++ Net\Browser initial release. + +------------------------------------------------------------------------ +r778 | anrdaemon | 2018-03-30 15:17:41 +0300 (Fri, 30 Mar 2018) | 3 lines + ++ Added author. ++ Put notes/todo under version control. + ------------------------------------------------------------------------ r767 | anrdaemon | 2018-03-19 23:49:28 +0300 (Mon, 19 Mar 2018) | 2 lines diff --git a/composer.json b/composer.json index 8be3d5a..578274e 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,12 @@ "name": "anrdaemon/library", "description": "Published classes from private library", "license": "WTFPL", + "authors": [ + { + "name": "Andrey Repin", + "email": "anrdaemon@yandex.ru" + } + ], "require": { "php": "^5.3.6 || ^7.0" }, @@ -13,7 +19,11 @@ }, "autoload": { "psr-4": { - "AnrDaemon\\": "src/", + "AnrDaemon\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { "AnrDaemon\\Tests\\": "test/" } }, diff --git a/src/Exceptions/CurlException.php b/src/Exceptions/CurlException.php new file mode 100644 index 0000000..28676ef --- /dev/null +++ b/src/Exceptions/CurlException.php @@ -0,0 +1,20 @@ +curl, ...$params); + if(curl_errno($this->curl) !== CURLE_OK) + throw new CurlException($this->curl); + + if($result === false) + throw new CurlException("Unable to perform $callback - unknown error."); + + return $result; + } + +// Information and configuration + + public function getInfo($name) + { + return $this->perform('curl_getinfo', $name); + } + + public function setOpt($name, $value = null) + { + return + is_array($name) + ? $this->perform('curl_setopt_array', $name) + : $this->perform('curl_setopt', $name, $value); + } + +// Method handling + + public function get($url) + { + $this->info = null; + $this->setOpt([ + CURLOPT_HTTPGET => true, + CURLOPT_URL => "$url", + ]); + $result = $this->perform('curl_exec'); + $this->info = $this->perform('curl_getinfo'); + return $result; + } + + public function post($url, $data = null) + { + $this->info = null; + $this->setOpt([ + CURLOPT_POST => true, + CURLOPT_URL => "$url", + CURLOPT_POSTFIELDS => $data ?: '', + ]); + $result = $this->perform('curl_exec'); + $this->info = $this->perform('curl_getinfo'); + return $result; + } +/* // Does not work, requires an actual file resource. + public function put($url, \SplFileObject $data) + { + $this->setOpt([ + CURLOPT_PUT => true, + CURLOPT_INFILE => $data, + ]); + return $this->perform('curl_exec'); + } +*/ +/* + public function customRequest($url, $data = null) + { + $this->setOpt([ + CURLOPT_CUSTOMREQUEST => '???', + CURLOPT_URL => "$url", + ]); + return $this->perform('curl_exec'); + } +*/ +// Magic! + + public function __construct(array $params = null) + { + $result = curl_init(); + if($result === false) + throw new CurlException; + + $this->curl = $result; + $this->perform('curl_setopt_array', (array)$params + [ + CURLOPT_COOKIEFILE => '', + CURLOPT_COOKIESESSION => true, + CURLOPT_SAFE_UPLOAD => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CAINFO => ini_get('openssl.cafile'), + CURLOPT_CAPATH => ini_get('openssl.capath'), + ]); + } + + public function __get($name) + { + return $this->info[$name]; + } +} diff --git a/src/Net/Url.php b/src/Net/Url.php index 0afa5a4..c58cacf 100644 --- a/src/Net/Url.php +++ b/src/Net/Url.php @@ -1,7 +1,7 @@ path`), query parts * can be accessed as array indices (`$url['param']`). @@ -99,7 +103,7 @@ protected function _parse_str($string) { if(!is_string($string)) return $string; - + // TODO:query strtok($query, ini_get('arg_separator.input')); parse_str($string, $query); return $query; } diff --git a/test/Net/UrlTest.php b/test/Net/UrlTest.php index f3b0458..3dbfa3f 100644 --- a/test/Net/UrlTest.php +++ b/test/Net/UrlTest.php @@ -20,7 +20,23 @@ protected function _parse_str($string) return $query; } - protected function getParams(Url $url) + protected function _normalized_parts(array $parts) + { + static $blank = [ + 'scheme' => null, // - e.g. http + 'user' => null, // + 'pass' => null, // + 'host' => null, // + 'port' => null, // + 'path' => null, // + 'query' => null, // - after the question mark ? + 'fragment' => null, // - after the hashmark # + ]; + + return array_replace($blank, array_intersect_key($parts, $blank)); + } + + protected function getParts(Url $url) { $test = (function(){ return $this->params; })->bindTo($url, $url); return $test(); @@ -38,7 +54,7 @@ public function validListProvider() $schemes = [ null, 'http']; $users = [ null, 'user']; $passs = [ null, 'password']; - $hosts = [ null, 'host', 'www.host.tld']; + $hosts = [ null, 'localhost', 'www.example.org']; $ports = [ null, 8080]; $paths = [ null, '/', '/path', '/path/']; $querys = [ null, 'query=string']; @@ -104,7 +120,8 @@ public function validListProvider() if($value) { $data += [ - $value => [$value, [ + $value => [$value, + $this->_normalized_parts([ 'scheme' => $scheme, // 'user' => $user, // 'pass' => $password, // @@ -113,7 +130,7 @@ public function validListProvider() 'path' => $path, // 'query' => $this->_parse_str($query), // Convert query string to array 'fragment' => $fragment, // - ]], + ])], ]; } } @@ -121,6 +138,26 @@ public function validListProvider() return $data; } + /** Provide a list of potentially mangled characters + */ + public function mangledCharsProvider() + { + $list = array_diff(range(' ', '~'), range('0', '9'), range('A', 'Z'), range('a', 'z'), ['&', ';']); + $data = []; + foreach($list as $char) + { + $name = "?" . urlencode($char) . "=1"; + $data["'{$name}' ($char)"] = [ + $name, + $this->_normalized_parts([ + 'query' => [$char => '1'], + ]), + ]; + } + + return $data; + } + /** Provide a list of well-known schemes and ports */ public function standardSchemesProvider() @@ -161,16 +198,7 @@ public function standardSchemesProvider() */ public function testCreateEmptyClassFromEmptyString() { - $this->assertTrue([ - 'scheme' => null, // - e.g. http - 'user' => null, // - 'pass' => null, // - 'host' => null, // - 'port' => null, // - 'path' => null, // - 'query' => null, // - after the question mark ? - 'fragment' => null, // - after the hashmark # - ] === $this->getParams(new Url(''))); + $this->assertTrue($this->_normalized_parts([]) === $this->getParts(new Url(''))); } /** Test if parsing invalid URL throws exception. @@ -188,9 +216,32 @@ public function testParseUrlWithException() * @dataProvider validListProvider * @depends testCreateEmptyClassFromEmptyString */ - public function testParseValidUrl($url, $params) + public function testParseValidUrl($url, $parts) + { + $this->assertTrue($parts === $this->getParts($this->url->parse($url))); + } + + /** Test parsing of valid URL's mandgled by PHP's parse_str + * + * @see http://php.net/manual/en/language.variables.external.php Variables From External Sources + * @dataProvider mangledCharsProvider + * @depends testCreateEmptyClassFromEmptyString + */ + public function testParseQueryString($url, $parts) { - $this->assertTrue($params === $this->getParams($this->url->parse($url))); + try + { + $this->assertTrue($parts === $this->getParts($this->url->parse($url))); + } + catch(\PHPUnit_Framework_ExpectationFailedException $e) + { + $key = array_keys($parts['query']); + $key = reset($key); + if(in_array($key, [' ', '.', '['])) + return $this->markTestIncomplete('Mangled names of variables from external sources.'); + + throw $e; + } } /** Test scheme-port normalization for well-known protocols