Skip to content

Commit

Permalink
Initial Net\Browser release.
Browse files Browse the repository at this point in the history
Added a note for future Net\Url::parse_str reimplementation.
Added author to composer.json.
  • Loading branch information
AnrDaemon committed Apr 3, 2018
1 parent 73b09cd commit 2c02dd0
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 22 deletions.
11 changes: 11 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -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

Expand Down
12 changes: 11 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
"name": "anrdaemon/library",
"description": "Published classes from private library",
"license": "WTFPL",
"authors": [
{
"name": "Andrey Repin",
"email": "[email protected]"
}
],
"require": {
"php": "^5.3.6 || ^7.0"
},
Expand All @@ -13,7 +19,11 @@
},
"autoload": {
"psr-4": {
"AnrDaemon\\": "src/",
"AnrDaemon\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"AnrDaemon\\Tests\\": "test/"
}
},
Expand Down
20 changes: 20 additions & 0 deletions src/Exceptions/CurlException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
/** Curl errors wrapper
*
* @version SVN: $Id: CurlException.php 788 2018-04-03 14:58:56Z anrdaemon $
*/

namespace AnrDaemon\Exceptions;

class CurlException
extends \RuntimeException
{
public function __construct($curl = null, \Exception $previous = null)
{
parent::__construct(
is_resource($curl) ? curl_error($curl) : ($curl ?: 'Unable to initialize cURL instance - unknown error.'),
is_resource($curl) ? curl_errno($curl) : 0,
$previous
);
}
}
113 changes: 113 additions & 0 deletions src/Net/Browser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php
/** Simplistic curl wrapper with runtime cookie persistence
*
* @version SVN: $Id: Browser.php 788 2018-04-03 14:58:56Z anrdaemon $
*/

namespace AnrDaemon\Net;

use
AnrDaemon\Exceptions\CurlException;

class Browser
{
protected $curl;
protected $info;

protected function perform(callable $callback, ...$params)
{
$result = $callback($this->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];
}
}
14 changes: 9 additions & 5 deletions src/Net/Url.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
/** URL handling class.
*
* @version SVN: $Id: Url.php 738 2018-03-03 19:03:36Z anrdaemon $
* @version SVN: $Id: Url.php 767 2018-03-19 20:49:28Z anrdaemon $
*/

namespace AnrDaemon\Net;
Expand All @@ -11,10 +11,14 @@
* The class is a read-only collection, the only way to modify its contents
* is to create a new instance of the class.
*
* The class is always trying to populate host/port and scheme upon creation. You may override
* them later on using {@see \AnrDaemon\Net\Url::setParts() self::setParts()}.
* The class is always trying to populate host/port and scheme upon creation,
* unless an empty URL is provided explicitly. You may override them later on
* using {@see \AnrDaemon\Net\Url::parse() self::parse()} or {@see \AnrDaemon\Net\Url::setParts() self::setParts()}.
*
* When parsing the input URI or setting parts, empty values are stripped.
* When parsing the URI or setting parts, empty values are stripped.
*
* Warning: PHP compat: PHP ({@see \parse_str parse_str()}) converts certain characters in request variable names.
* See Note after Example 3 on http://php.net/variables.external#example-88
*
* URL parts can be accessed as properties (`$url->path`), query parts
* can be accessed as array indices (`$url['param']`).
Expand Down Expand Up @@ -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;
}
Expand Down
83 changes: 67 additions & 16 deletions test/Net/UrlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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'];
Expand Down Expand Up @@ -104,7 +120,8 @@ public function validListProvider()
if($value)
{
$data += [
$value => [$value, [
$value => [$value,
$this->_normalized_parts([
'scheme' => $scheme, //
'user' => $user, //
'pass' => $password, //
Expand All @@ -113,14 +130,34 @@ public function validListProvider()
'path' => $path, //
'query' => $this->_parse_str($query), // Convert query string to array
'fragment' => $fragment, //
]],
])],
];
}
}

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()
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down

0 comments on commit 2c02dd0

Please sign in to comment.