Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
14.00% |
7 / 50 |
CRAP | |
30.72% |
157 / 511 |
Client | |
0.00% |
0 / 1 |
|
14.00% |
7 / 50 |
13099.67 | |
30.72% |
157 / 511 |
__construct($uri = null, $options = null) | |
0.00% |
0 / 1 |
3.21 | |
71.43% |
5 / 7 |
|||
setOptions($options = array()) | |
0.00% |
0 / 1 |
6.81 | |
58.33% |
7 / 12 |
|||
setAdapter($adapter) | |
0.00% |
0 / 1 |
4.07 | |
83.33% |
10 / 12 |
|||
getAdapter() | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
setRequest(Request $request) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getRequest() | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
setResponse(Response $response) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getResponse() | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
getLastRawRequest() | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getLastRawResponse() | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getRedirectionsCount() | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
setUri($uri) | |
0.00% |
0 / 1 |
10.14 | |
60.00% |
9 / 15 |
|||
getUri() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setMethod($method) | |
100.00% |
1 / 1 |
6 | |
100.00% |
7 / 7 |
|||
getMethod() | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
setArgSeparator($argSeparator) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getArgSeparator() | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
setEncType($encType, $boundary = null) | |
0.00% |
0 / 1 |
3.21 | |
71.43% |
5 / 7 |
|||
getEncType() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setRawBody($body) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setParameterPost(array $post) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
setParameterGet(array $query) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
resetParameters($clearCookies = false /*, $clearAuth = true */) | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 19 |
|||
getCookies() | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getCookieId($cookie) | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 3 |
|||
addCookie($cookie, $value = null, $expire = null, $path = null, $domain = null, $secure = false, $httponly = true, $maxAge = null, $version = null) | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 15 |
|||
setCookies($cookies) | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 8 |
|||
clearCookies() | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
setHeaders($headers) | |
0.00% |
0 / 1 |
3.33 | |
66.67% |
6 / 9 |
|||
hasHeader($name) | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
getHeader($name) | |
0.00% |
0 / 1 |
3.33 | |
66.67% |
4 / 6 |
|||
setStream($streamfile = true) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getStream() | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
openTempStream() | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 15 |
|||
setAuth($user, $password, $type = self::AUTH_BASIC) | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 9 |
|||
clearAuth() | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
calcAuthDigest($user, $password, $type = self::AUTH_BASIC, $digest = array(), $entityBody = null) | |
0.00% |
0 / 1 |
182 | |
0.00% |
0 / 32 |
|||
dispatch(Stdlib\RequestInterface $request, Stdlib\ResponseInterface $response = null) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
send(Request $request = null) | |
0.00% |
0 / 1 |
315.74 | |
33.33% |
31 / 93 |
|||
reset() | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 4 |
|||
setFileUpload($filename, $formname, $data = null, $ctype = null) | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 16 |
|||
removeFileUpload($filename) | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 5 |
|||
prepareCookies($domain, $path, $secure) | |
0.00% |
0 / 1 |
11.64 | |
35.71% |
5 / 14 |
|||
prepareHeaders($body, $uri) | |
0.00% |
0 / 1 |
34.81 | |
66.67% |
34 / 51 |
|||
prepareBody() | |
0.00% |
0 / 1 |
37.94 | |
39.39% |
13 / 33 |
|||
detectFileMimeType($file) | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 17 |
|||
encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 12 |
|||
flattenParametersArray($parray, $prefix = null) | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 17 |
|||
doRequest(Http $uri, $method, $secure = false, $headers = array(), $body = '') | |
0.00% |
0 / 1 |
4.46 | |
45.45% |
5 / 11 |
|||
encodeAuthHeader($user, $password, $type = self::AUTH_BASIC) | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 10 |
<?php | |
/** | |
* Zend Framework (http://framework.zend.com/) | |
* | |
* @link http://github.com/zendframework/zf2 for the canonical source repository | |
* @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) | |
* @license http://framework.zend.com/license/new-bsd New BSD License | |
*/ | |
namespace Zend\Http; | |
use ArrayIterator; | |
use Traversable; | |
use Zend\Stdlib; | |
use Zend\Stdlib\ArrayUtils; | |
use Zend\Stdlib\ErrorHandler; | |
use Zend\Uri\Http; | |
/** | |
* Http client | |
*/ | |
class Client implements Stdlib\DispatchableInterface | |
{ | |
/** | |
* @const string Supported HTTP Authentication methods | |
*/ | |
const AUTH_BASIC = 'basic'; | |
const AUTH_DIGEST = 'digest'; // not implemented yet | |
/** | |
* @const string POST data encoding methods | |
*/ | |
const ENC_URLENCODED = 'application/x-www-form-urlencoded'; | |
const ENC_FORMDATA = 'multipart/form-data'; | |
/** | |
* @const string DIGEST Authentication | |
*/ | |
const DIGEST_REALM = 'realm'; | |
const DIGEST_QOP = 'qop'; | |
const DIGEST_NONCE = 'nonce'; | |
const DIGEST_OPAQUE = 'opaque'; | |
const DIGEST_NC = 'nc'; | |
const DIGEST_CNONCE = 'cnonce'; | |
/** | |
* @var Response | |
*/ | |
protected $response; | |
/** | |
* @var Request | |
*/ | |
protected $request; | |
/** | |
* @var Client/Adapter | |
*/ | |
protected $adapter; | |
/** | |
* @var array | |
*/ | |
protected $auth = array(); | |
/** | |
* @var string | |
*/ | |
protected $streamName = null; | |
/** | |
* @var array of Header\SetCookie | |
*/ | |
protected $cookies = array(); | |
/** | |
* @var string | |
*/ | |
protected $encType = ''; | |
/** | |
* @var Request | |
*/ | |
protected $lastRawRequest = null; | |
/** | |
* @var Response | |
*/ | |
protected $lastRawResponse = null; | |
/** | |
* @var int | |
*/ | |
protected $redirectCounter = 0; | |
/** | |
* Configuration array, set using the constructor or using ::setOptions() | |
* | |
* @var array | |
*/ | |
protected $config = array( | |
'maxredirects' => 5, | |
'strictredirects' => false, | |
'useragent' => 'Zend\Http\Client', | |
'timeout' => 10, | |
'adapter' => 'Zend\Http\Client\Adapter\Socket', | |
'httpversion' => Request::VERSION_11, | |
'storeresponse' => true, | |
'keepalive' => false, | |
'outputstream' => false, | |
'encodecookies' => true, | |
'argseparator' => null, | |
'rfc3986strict' => false | |
); | |
/** | |
* Fileinfo magic database resource | |
* | |
* This variable is populated the first time _detectFileMimeType is called | |
* and is then reused on every call to this method | |
* | |
* @var resource | |
*/ | |
protected static $fileInfoDb = null; | |
/** | |
* Constructor | |
* | |
* @param string $uri | |
* @param array|Traversable $options | |
*/ | |
public function __construct($uri = null, $options = null) | |
{ | |
if ($uri !== null) { | |
$this->setUri($uri); | |
} | |
if ($options !== null) { | |
$this->setOptions($options); | |
} | |
} | |
/** | |
* Set configuration parameters for this HTTP client | |
* | |
* @param array|Traversable $options | |
* @return Client | |
* @throws Client\Exception\InvalidArgumentException | |
*/ | |
public function setOptions($options = array()) | |
{ | |
if ($options instanceof Traversable) { | |
$options = ArrayUtils::iteratorToArray($options); | |
} | |
if (!is_array($options)) { | |
throw new Client\Exception\InvalidArgumentException('Config parameter is not valid'); | |
} | |
/** Config Key Normalization */ | |
foreach ($options as $k => $v) { | |
$this->config[str_replace(array('-', '_', ' ', '.'), '', strtolower($k))] = $v; // replace w/ normalized | |
} | |
// Pass configuration options to the adapter if it exists | |
if ($this->adapter instanceof Client\Adapter\AdapterInterface) { | |
$this->adapter->setOptions($options); | |
} | |
return $this; | |
} | |
/** | |
* Load the connection adapter | |
* | |
* While this method is not called more than one for a client, it is | |
* separated from ->request() to preserve logic and readability | |
* | |
* @param Client\Adapter\AdapterInterface|string $adapter | |
* @return Client | |
* @throws Client\Exception\InvalidArgumentException | |
*/ | |
public function setAdapter($adapter) | |
{ | |
if (is_string($adapter)) { | |
if (!class_exists($adapter)) { | |
throw new Client\Exception\InvalidArgumentException('Unable to locate adapter class "' . $adapter . '"'); | |
} | |
$adapter = new $adapter; | |
} | |
if (! $adapter instanceof Client\Adapter\AdapterInterface) { | |
throw new Client\Exception\InvalidArgumentException('Passed adapter is not a HTTP connection adapter'); | |
} | |
$this->adapter = $adapter; | |
$config = $this->config; | |
unset($config['adapter']); | |
$this->adapter->setOptions($config); | |
return $this; | |
} | |
/** | |
* Load the connection adapter | |
* | |
* @return Client\Adapter\AdapterInterface $adapter | |
*/ | |
public function getAdapter() | |
{ | |
if (! $this->adapter) { | |
$this->setAdapter($this->config['adapter']); | |
} | |
return $this->adapter; | |
} | |
/** | |
* Set request | |
* | |
* @param Request $request | |
* @return Client | |
*/ | |
public function setRequest(Request $request) | |
{ | |
$this->request = $request; | |
return $this; | |
} | |
/** | |
* Get Request | |
* | |
* @return Request | |
*/ | |
public function getRequest() | |
{ | |
if (empty($this->request)) { | |
$this->request = new Request(); | |
} | |
return $this->request; | |
} | |
/** | |
* Set response | |
* | |
* @param Response $response | |
* @return Client | |
*/ | |
public function setResponse(Response $response) | |
{ | |
$this->response = $response; | |
return $this; | |
} | |
/** | |
* Get Response | |
* | |
* @return Response | |
*/ | |
public function getResponse() | |
{ | |
if (empty($this->response)) { | |
$this->response = new Response(); | |
} | |
return $this->response; | |
} | |
/** | |
* Get the last request (as a string) | |
* | |
* @return string | |
*/ | |
public function getLastRawRequest() | |
{ | |
return $this->lastRawRequest; | |
} | |
/** | |
* Get the last response (as a string) | |
* | |
* @return string | |
*/ | |
public function getLastRawResponse() | |
{ | |
return $this->lastRawResponse; | |
} | |
/** | |
* Get the redirections count | |
* | |
* @return int | |
*/ | |
public function getRedirectionsCount() | |
{ | |
return $this->redirectCounter; | |
} | |
/** | |
* Set Uri (to the request) | |
* | |
* @param string|Http $uri | |
* @return Client | |
*/ | |
public function setUri($uri) | |
{ | |
if (!empty($uri)) { | |
// remember host of last request | |
$lastHost = $this->getRequest()->getUri()->getHost(); | |
$this->getRequest()->setUri($uri); | |
// if host changed, the HTTP authentication should be cleared for security | |
// reasons, see #4215 for a discussion - currently authentication is also | |
// cleared for peer subdomains due to technical limits | |
$nextHost = $this->getRequest()->getUri()->getHost(); | |
if (!preg_match('/' . preg_quote($lastHost, '/') . '$/i', $nextHost)) { | |
$this->clearAuth(); | |
} | |
// Set auth if username and password has been specified in the uri | |
if ($this->getUri()->getUser() && $this->getUri()->getPassword()) { | |
$this->setAuth($this->getUri()->getUser(), $this->getUri()->getPassword()); | |
} | |
// We have no ports, set the defaults | |
if (! $this->getUri()->getPort()) { | |
$this->getUri()->setPort(($this->getUri()->getScheme() == 'https' ? 443 : 80)); | |
} | |
} | |
return $this; | |
} | |
/** | |
* Get uri (from the request) | |
* | |
* @return Http | |
*/ | |
public function getUri() | |
{ | |
return $this->getRequest()->getUri(); | |
} | |
/** | |
* Set the HTTP method (to the request) | |
* | |
* @param string $method | |
* @return Client | |
*/ | |
public function setMethod($method) | |
{ | |
$method = $this->getRequest()->setMethod($method)->getMethod(); | |
if (($method == Request::METHOD_POST || $method == Request::METHOD_PUT || | |
$method == Request::METHOD_DELETE || $method == Request::METHOD_PATCH) | |
&& empty($this->encType)) { | |
$this->setEncType(self::ENC_URLENCODED); | |
} | |
return $this; | |
} | |
/** | |
* Get the HTTP method | |
* | |
* @return string | |
*/ | |
public function getMethod() | |
{ | |
return $this->getRequest()->getMethod(); | |
} | |
/** | |
* Set the query string argument separator | |
* | |
* @param string $argSeparator | |
* @return Client | |
*/ | |
public function setArgSeparator($argSeparator) | |
{ | |
$this->setOptions(array("argseparator" => $argSeparator)); | |
return $this; | |
} | |
/** | |
* Get the query string argument separator | |
* | |
* @return string | |
*/ | |
public function getArgSeparator() | |
{ | |
$argSeparator = $this->config['argseparator']; | |
if (empty($argSeparator)) { | |
$argSeparator = ini_get('arg_separator.output'); | |
$this->setArgSeparator($argSeparator); | |
} | |
return $argSeparator; | |
} | |
/** | |
* Set the encoding type and the boundary (if any) | |
* | |
* @param string $encType | |
* @param string $boundary | |
* @return Client | |
*/ | |
public function setEncType($encType, $boundary = null) | |
{ | |
if (!empty($encType)) { | |
if (!empty($boundary)) { | |
$this->encType = $encType . "; boundary={$boundary}"; | |
} else { | |
$this->encType = $encType; | |
} | |
} | |
return $this; | |
} | |
/** | |
* Get the encoding type | |
* | |
* @return string | |
*/ | |
public function getEncType() | |
{ | |
return $this->encType; | |
} | |
/** | |
* Set raw body (for advanced use cases) | |
* | |
* @param string $body | |
* @return Client | |
*/ | |
public function setRawBody($body) | |
{ | |
$this->getRequest()->setContent($body); | |
return $this; | |
} | |
/** | |
* Set the POST parameters | |
* | |
* @param array $post | |
* @return Client | |
*/ | |
public function setParameterPost(array $post) | |
{ | |
$this->getRequest()->getPost()->fromArray($post); | |
return $this; | |
} | |
/** | |
* Set the GET parameters | |
* | |
* @param array $query | |
* @return Client | |
*/ | |
public function setParameterGet(array $query) | |
{ | |
$this->getRequest()->getQuery()->fromArray($query); | |
return $this; | |
} | |
/** | |
* Reset all the HTTP parameters (request, response, etc) | |
* | |
* @param bool $clearCookies Also clear all valid cookies? (defaults to false) | |
* @param bool $clearAuth Also clear http authentication? (defaults to true) | |
* @return Client | |
*/ | |
public function resetParameters($clearCookies = false /*, $clearAuth = true */) | |
{ | |
$clearAuth = true; | |
if (func_num_args() > 1) { | |
$clearAuth = func_get_arg(1); | |
} | |
$uri = $this->getUri(); | |
$this->streamName = null; | |
$this->encType = null; | |
$this->request = null; | |
$this->response = null; | |
$this->lastRawRequest = null; | |
$this->lastRawResponse = null; | |
$this->setUri($uri); | |
if ($clearCookies) { | |
$this->clearCookies(); | |
} | |
if ($clearAuth) { | |
$this->clearAuth(); | |
} | |
return $this; | |
} | |
/** | |
* Return the current cookies | |
* | |
* @return array | |
*/ | |
public function getCookies() | |
{ | |
return $this->cookies; | |
} | |
/** | |
* Get the cookie Id (name+domain+path) | |
* | |
* @param Header\SetCookie|Header\Cookie $cookie | |
* @return string|bool | |
*/ | |
protected function getCookieId($cookie) | |
{ | |
if (($cookie instanceof Header\SetCookie) || ($cookie instanceof Header\Cookie)) { | |
return $cookie->getName() . $cookie->getDomain() . $cookie->getPath(); | |
} | |
return false; | |
} | |
/** | |
* Add a cookie | |
* | |
* @param array|ArrayIterator|Header\SetCookie|string $cookie | |
* @param string $value | |
* @param string $expire | |
* @param string $path | |
* @param string $domain | |
* @param bool $secure | |
* @param bool $httponly | |
* @param string $maxAge | |
* @param string $version | |
* @throws Exception\InvalidArgumentException | |
* @return Client | |
*/ | |
public function addCookie($cookie, $value = null, $expire = null, $path = null, $domain = null, $secure = false, $httponly = true, $maxAge = null, $version = null) | |
{ | |
if (is_array($cookie) || $cookie instanceof ArrayIterator) { | |
foreach ($cookie as $setCookie) { | |
if ($setCookie instanceof Header\SetCookie) { | |
$this->cookies[$this->getCookieId($setCookie)] = $setCookie; | |
} else { | |
throw new Exception\InvalidArgumentException('The cookie parameter is not a valid Set-Cookie type'); | |
} | |
} | |
} elseif (is_string($cookie) && $value !== null) { | |
$setCookie = new Header\SetCookie($cookie, $value, $expire, $path, $domain, $secure, $httponly, $maxAge, $version); | |
$this->cookies[$this->getCookieId($setCookie)] = $setCookie; | |
} elseif ($cookie instanceof Header\SetCookie) { | |
$this->cookies[$this->getCookieId($cookie)] = $cookie; | |
} else { | |
throw new Exception\InvalidArgumentException('Invalid parameter type passed as Cookie'); | |
} | |
return $this; | |
} | |
/** | |
* Set an array of cookies | |
* | |
* @param array $cookies | |
* @throws Exception\InvalidArgumentException | |
* @return Client | |
*/ | |
public function setCookies($cookies) | |
{ | |
if (is_array($cookies)) { | |
$this->clearCookies(); | |
foreach ($cookies as $name => $value) { | |
$this->addCookie($name, $value); | |
} | |
} else { | |
throw new Exception\InvalidArgumentException('Invalid cookies passed as parameter, it must be an array'); | |
} | |
return $this; | |
} | |
/** | |
* Clear all the cookies | |
*/ | |
public function clearCookies() | |
{ | |
$this->cookies = array(); | |
} | |
/** | |
* Set the headers (for the request) | |
* | |
* @param Headers|array $headers | |
* @throws Exception\InvalidArgumentException | |
* @return Client | |
*/ | |
public function setHeaders($headers) | |
{ | |
if (is_array($headers)) { | |
$newHeaders = new Headers(); | |
$newHeaders->addHeaders($headers); | |
$this->getRequest()->setHeaders($newHeaders); | |
} elseif ($headers instanceof Headers) { | |
$this->getRequest()->setHeaders($headers); | |
} else { | |
throw new Exception\InvalidArgumentException('Invalid parameter headers passed'); | |
} | |
return $this; | |
} | |
/** | |
* Check if exists the header type specified | |
* | |
* @param string $name | |
* @return bool | |
*/ | |
public function hasHeader($name) | |
{ | |
$headers = $this->getRequest()->getHeaders(); | |
if ($headers instanceof Headers) { | |
return $headers->has($name); | |
} | |
return false; | |
} | |
/** | |
* Get the header value of the request | |
* | |
* @param string $name | |
* @return string|bool | |
*/ | |
public function getHeader($name) | |
{ | |
$headers = $this->getRequest()->getHeaders(); | |
if ($headers instanceof Headers) { | |
if ($headers->get($name)) { | |
return $headers->get($name)->getFieldValue(); | |
} | |
} | |
return false; | |
} | |
/** | |
* Set streaming for received data | |
* | |
* @param string|bool $streamfile Stream file, true for temp file, false/null for no streaming | |
* @return \Zend\Http\Client | |
*/ | |
public function setStream($streamfile = true) | |
{ | |
$this->setOptions(array("outputstream" => $streamfile)); | |
return $this; | |
} | |
/** | |
* Get status of streaming for received data | |
* @return bool|string | |
*/ | |
public function getStream() | |
{ | |
if (null !== $this->streamName) { | |
return $this->streamName; | |
} | |
return $this->config['outputstream']; | |
} | |
/** | |
* Create temporary stream | |
* | |
* @throws Exception\RuntimeException | |
* @return resource | |
*/ | |
protected function openTempStream() | |
{ | |
$this->streamName = $this->config['outputstream']; | |
if (!is_string($this->streamName)) { | |
// If name is not given, create temp name | |
$this->streamName = tempnam( | |
isset($this->config['streamtmpdir']) ? $this->config['streamtmpdir'] : sys_get_temp_dir(), | |
'Zend\Http\Client' | |
); | |
} | |
ErrorHandler::start(); | |
$fp = fopen($this->streamName, "w+b"); | |
$error = ErrorHandler::stop(); | |
if (false === $fp) { | |
if ($this->adapter instanceof Client\Adapter\AdapterInterface) { | |
$this->adapter->close(); | |
} | |
throw new Exception\RuntimeException("Could not open temp file {$this->streamName}", 0, $error); | |
} | |
return $fp; | |
} | |
/** | |
* Create a HTTP authentication "Authorization:" header according to the | |
* specified user, password and authentication method. | |
* | |
* @param string $user | |
* @param string $password | |
* @param string $type | |
* @throws Exception\InvalidArgumentException | |
* @return Client | |
*/ | |
public function setAuth($user, $password, $type = self::AUTH_BASIC) | |
{ | |
if (!defined('self::AUTH_' . strtoupper($type))) { | |
throw new Exception\InvalidArgumentException("Invalid or not supported authentication type: '$type'"); | |
} | |
if (empty($user)) { | |
throw new Exception\InvalidArgumentException("The username cannot be empty"); | |
} | |
$this->auth = array ( | |
'user' => $user, | |
'password' => $password, | |
'type' => $type | |
); | |
return $this; | |
} | |
/** | |
* Clear http authentication | |
*/ | |
public function clearAuth() | |
{ | |
$this->auth = array(); | |
} | |
/** | |
* Calculate the response value according to the HTTP authentication type | |
* | |
* @see http://www.faqs.org/rfcs/rfc2617.html | |
* @param string $user | |
* @param string $password | |
* @param string $type | |
* @param array $digest | |
* @param null|string $entityBody | |
* @throws Exception\InvalidArgumentException | |
* @return string|bool | |
*/ | |
protected function calcAuthDigest($user, $password, $type = self::AUTH_BASIC, $digest = array(), $entityBody = null) | |
{ | |
if (!defined('self::AUTH_' . strtoupper($type))) { | |
throw new Exception\InvalidArgumentException("Invalid or not supported authentication type: '$type'"); | |
} | |
$response = false; | |
switch (strtolower($type)) { | |
case self::AUTH_BASIC : | |
// In basic authentication, the user name cannot contain ":" | |
if (strpos($user, ':') !== false) { | |
throw new Exception\InvalidArgumentException("The user name cannot contain ':' in Basic HTTP authentication"); | |
} | |
$response = base64_encode($user . ':' . $password); | |
break; | |
case self::AUTH_DIGEST : | |
if (empty($digest)) { | |
throw new Exception\InvalidArgumentException("The digest cannot be empty"); | |
} | |
foreach ($digest as $key => $value) { | |
if (!defined('self::DIGEST_' . strtoupper($key))) { | |
throw new Exception\InvalidArgumentException("Invalid or not supported digest authentication parameter: '$key'"); | |
} | |
} | |
$ha1 = md5($user . ':' . $digest['realm'] . ':' . $password); | |
if (empty($digest['qop']) || strtolower($digest['qop']) == 'auth') { | |
$ha2 = md5($this->getMethod() . ':' . $this->getUri()->getPath()); | |
} elseif (strtolower($digest['qop']) == 'auth-int') { | |
if (empty($entityBody)) { | |
throw new Exception\InvalidArgumentException("I cannot use the auth-int digest authentication without the entity body"); | |
} | |
$ha2 = md5($this->getMethod() . ':' . $this->getUri()->getPath() . ':' . md5($entityBody)); | |
} | |
if (empty($digest['qop'])) { | |
$response = md5($ha1 . ':' . $digest['nonce'] . ':' . $ha2); | |
} else { | |
$response = md5($ha1 . ':' . $digest['nonce'] . ':' . $digest['nc'] | |
. ':' . $digest['cnonce'] . ':' . $digest['qoc'] . ':' . $ha2); | |
} | |
break; | |
} | |
return $response; | |
} | |
/** | |
* Dispatch | |
* | |
* @param Stdlib\RequestInterface $request | |
* @param Stdlib\ResponseInterface $response | |
* @return Stdlib\ResponseInterface | |
*/ | |
public function dispatch(Stdlib\RequestInterface $request, Stdlib\ResponseInterface $response = null) | |
{ | |
$response = $this->send($request); | |
return $response; | |
} | |
/** | |
* Send HTTP request | |
* | |
* @param Request $request | |
* @return Response | |
* @throws Exception\RuntimeException | |
* @throws Client\Exception\RuntimeException | |
*/ | |
public function send(Request $request = null) | |
{ | |
if ($request !== null) { | |
$this->setRequest($request); | |
} | |
$this->redirectCounter = 0; | |
$response = null; | |
$adapter = $this->getAdapter(); | |
// Send the first request. If redirected, continue. | |
do { | |
// uri | |
$uri = $this->getUri(); | |
// query | |
$query = $this->getRequest()->getQuery(); | |
if (!empty($query)) { | |
$queryArray = $query->toArray(); | |
if (!empty($queryArray)) { | |
$newUri = $uri->toString(); | |
$queryString = http_build_query($query, null, $this->getArgSeparator()); | |
if ($this->config['rfc3986strict']) { | |
$queryString = str_replace('+', '%20', $queryString); | |
} | |
if (strpos($newUri, '?') !== false) { | |
$newUri .= $this->getArgSeparator() . $queryString; | |
} else { | |
$newUri .= '?' . $queryString; | |
} | |
$uri = new Http($newUri); | |
} | |
} | |
// If we have no ports, set the defaults | |
if (!$uri->getPort()) { | |
$uri->setPort($uri->getScheme() == 'https' ? 443 : 80); | |
} | |
// method | |
$method = $this->getRequest()->getMethod(); | |
// body | |
$body = $this->prepareBody(); | |
// headers | |
$headers = $this->prepareHeaders($body, $uri); | |
$secure = $uri->getScheme() == 'https'; | |
// cookies | |
$cookie = $this->prepareCookies($uri->getHost(), $uri->getPath(), $secure); | |
if ($cookie->getFieldValue()) { | |
$headers['Cookie'] = $cookie->getFieldValue(); | |
} | |
// check that adapter supports streaming before using it | |
if (is_resource($body) && !($adapter instanceof Client\Adapter\StreamInterface)) { | |
throw new Client\Exception\RuntimeException('Adapter does not support streaming'); | |
} | |
// calling protected method to allow extending classes | |
// to wrap the interaction with the adapter | |
$response = $this->doRequest($uri, $method, $secure, $headers, $body); | |
if (! $response) { | |
throw new Exception\RuntimeException('Unable to read response, or response is empty'); | |
} | |
if ($this->config['storeresponse']) { | |
$this->lastRawResponse = $response; | |
} else { | |
$this->lastRawResponse = null; | |
} | |
if ($this->config['outputstream']) { | |
$stream = $this->getStream(); | |
if (!is_resource($stream) && is_string($stream)) { | |
$stream = fopen($stream, 'r'); | |
} | |
$streamMetaData = stream_get_meta_data($stream); | |
if ($streamMetaData['seekable']) { | |
rewind($stream); | |
} | |
// cleanup the adapter | |
$adapter->setOutputStream(null); | |
$response = Response\Stream::fromStream($response, $stream); | |
$response->setStreamName($this->streamName); | |
if (!is_string($this->config['outputstream'])) { | |
// we used temp name, will need to clean up | |
$response->setCleanup(true); | |
} | |
} else { | |
$response = $this->getResponse()->fromString($response); | |
} | |
// Get the cookies from response (if any) | |
$setCookies = $response->getCookie(); | |
if (!empty($setCookies)) { | |
$this->addCookie($setCookies); | |
} | |
// If we got redirected, look for the Location header | |
if ($response->isRedirect() && ($response->getHeaders()->has('Location'))) { | |
// Avoid problems with buggy servers that add whitespace at the | |
// end of some headers | |
$location = trim($response->getHeaders()->get('Location')->getFieldValue()); | |
// Check whether we send the exact same request again, or drop the parameters | |
// and send a GET request | |
if ($response->getStatusCode() == 303 || | |
((! $this->config['strictredirects']) && ($response->getStatusCode() == 302 || | |
$response->getStatusCode() == 301))) { | |
$this->resetParameters(false, false); | |
$this->setMethod(Request::METHOD_GET); | |
} | |
// If we got a well formed absolute URI | |
if (($scheme = substr($location, 0, 6)) && | |
($scheme == 'http:/' || $scheme == 'https:')) { | |
// setURI() clears parameters if host changed, see #4215 | |
$this->setUri($location); | |
} else { | |
// Split into path and query and set the query | |
if (strpos($location, '?') !== false) { | |
list($location, $query) = explode('?', $location, 2); | |
} else { | |
$query = ''; | |
} | |
$this->getUri()->setQuery($query); | |
// Else, if we got just an absolute path, set it | |
if (strpos($location, '/') === 0) { | |
$this->getUri()->setPath($location); | |
// Else, assume we have a relative path | |
} else { | |
// Get the current path directory, removing any trailing slashes | |
$path = $this->getUri()->getPath(); | |
$path = rtrim(substr($path, 0, strrpos($path, '/')), "/"); | |
$this->getUri()->setPath($path . '/' . $location); | |
} | |
} | |
++$this->redirectCounter; | |
} else { | |
// If we didn't get any location, stop redirecting | |
break; | |
} | |
} while ($this->redirectCounter <= $this->config['maxredirects']); | |
$this->response = $response; | |
return $response; | |
} | |
/** | |
* Fully reset the HTTP client (auth, cookies, request, response, etc.) | |
* | |
* @return Client | |
*/ | |
public function reset() | |
{ | |
$this->resetParameters(); | |
$this->clearAuth(); | |
$this->clearCookies(); | |
return $this; | |
} | |
/** | |
* Set a file to upload (using a POST request) | |
* | |
* Can be used in two ways: | |
* | |
* 1. $data is null (default): $filename is treated as the name if a local file which | |
* will be read and sent. Will try to guess the content type using mime_content_type(). | |
* 2. $data is set - $filename is sent as the file name, but $data is sent as the file | |
* contents and no file is read from the file system. In this case, you need to | |
* manually set the Content-Type ($ctype) or it will default to | |
* application/octet-stream. | |
* | |
* @param string $filename Name of file to upload, or name to save as | |
* @param string $formname Name of form element to send as | |
* @param string $data Data to send (if null, $filename is read and sent) | |
* @param string $ctype Content type to use (if $data is set and $ctype is | |
* null, will be application/octet-stream) | |
* @return Client | |
* @throws Exception\RuntimeException | |
*/ | |
public function setFileUpload($filename, $formname, $data = null, $ctype = null) | |
{ | |
if ($data === null) { | |
ErrorHandler::start(); | |
$data = file_get_contents($filename); | |
$error = ErrorHandler::stop(); | |
if ($data === false) { | |
throw new Exception\RuntimeException("Unable to read file '{$filename}' for upload", 0, $error); | |
} | |
if (!$ctype) { | |
$ctype = $this->detectFileMimeType($filename); | |
} | |
} | |
$this->getRequest()->getFiles()->set($filename, array( | |
'formname' => $formname, | |
'filename' => basename($filename), | |
'ctype' => $ctype, | |
'data' => $data | |
)); | |
return $this; | |
} | |
/** | |
* Remove a file to upload | |
* | |
* @param string $filename | |
* @return bool | |
*/ | |
public function removeFileUpload($filename) | |
{ | |
$file = $this->getRequest()->getFiles()->get($filename); | |
if (!empty($file)) { | |
$this->getRequest()->getFiles()->set($filename, null); | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Prepare Cookies | |
* | |
* @param string $domain | |
* @param string $path | |
* @param bool $secure | |
* @return Header\Cookie|bool | |
*/ | |
protected function prepareCookies($domain, $path, $secure) | |
{ | |
$validCookies = array(); | |
if (!empty($this->cookies)) { | |
foreach ($this->cookies as $id => $cookie) { | |
if ($cookie->isExpired()) { | |
unset($this->cookies[$id]); | |
continue; | |
} | |
if ($cookie->isValidForRequest($domain, $path, $secure)) { | |
// OAM hack some domains try to set the cookie multiple times | |
$validCookies[$cookie->getName()] = $cookie; | |
} | |
} | |
} | |
$cookies = Header\Cookie::fromSetCookieArray($validCookies); | |
$cookies->setEncodeValue($this->config['encodecookies']); | |
return $cookies; | |
} | |
/** | |
* Prepare the request headers | |
* | |
* @param resource|string $body | |
* @param Http $uri | |
* @throws Exception\RuntimeException | |
* @return array | |
*/ | |
protected function prepareHeaders($body, $uri) | |
{ | |
$headers = array(); | |
// Set the host header | |
if ($this->config['httpversion'] == Request::VERSION_11) { | |
$host = $uri->getHost(); | |
// If the port is not default, add it | |
if (!(($uri->getScheme() == 'http' && $uri->getPort() == 80) || | |
($uri->getScheme() == 'https' && $uri->getPort() == 443))) { | |
$host .= ':' . $uri->getPort(); | |
} | |
$headers['Host'] = $host; | |
} | |
// Set the connection header | |
if (!$this->getRequest()->getHeaders()->has('Connection')) { | |
if (!$this->config['keepalive']) { | |
$headers['Connection'] = 'close'; | |
} | |
} | |
// Set the Accept-encoding header if not set - depending on whether | |
// zlib is available or not. | |
if (!$this->getRequest()->getHeaders()->has('Accept-Encoding')) { | |
if (function_exists('gzinflate')) { | |
$headers['Accept-Encoding'] = 'gzip, deflate'; | |
} else { | |
$headers['Accept-Encoding'] = 'identity'; | |
} | |
} | |
// Set the user agent header | |
if (!$this->getRequest()->getHeaders()->has('User-Agent') && isset($this->config['useragent'])) { | |
$headers['User-Agent'] = $this->config['useragent']; | |
} | |
// Set HTTP authentication if needed | |
if (!empty($this->auth)) { | |
switch ($this->auth['type']) { | |
case self::AUTH_BASIC : | |
$auth = $this->calcAuthDigest($this->auth['user'], $this->auth['password'], $this->auth['type']); | |
if ($auth !== false) { | |
$headers['Authorization'] = 'Basic ' . $auth; | |
} | |
break; | |
case self::AUTH_DIGEST : | |
throw new Exception\RuntimeException("The digest authentication is not implemented yet"); | |
} | |
} | |
// Content-type | |
$encType = $this->getEncType(); | |
if (!empty($encType)) { | |
$headers['Content-Type'] = $encType; | |
} | |
if (!empty($body)) { | |
if (is_resource($body)) { | |
$fstat = fstat($body); | |
$headers['Content-Length'] = $fstat['size']; | |
} else { | |
$headers['Content-Length'] = strlen($body); | |
} | |
} | |
// Merge the headers of the request (if any) | |
// here we need right 'http field' and not lowercase letters | |
$requestHeaders = $this->getRequest()->getHeaders(); | |
foreach ($requestHeaders as $requestHeaderElement) { | |
$headers[$requestHeaderElement->getFieldName()] = $requestHeaderElement->getFieldValue(); | |
} | |
return $headers; | |
} | |
/** | |
* Prepare the request body (for PATCH, POST and PUT requests) | |
* | |
* @return string | |
* @throws \Zend\Http\Client\Exception\RuntimeException | |
*/ | |
protected function prepareBody() | |
{ | |
// According to RFC2616, a TRACE request should not have a body. | |
if ($this->getRequest()->isTrace()) { | |
return ''; | |
} | |
$rawBody = $this->getRequest()->getContent(); | |
if (!empty($rawBody)) { | |
return $rawBody; | |
} | |
$body = ''; | |
$totalFiles = 0; | |
if (!$this->getRequest()->getHeaders()->has('Content-Type')) { | |
$totalFiles = count($this->getRequest()->getFiles()->toArray()); | |
// If we have files to upload, force encType to multipart/form-data | |
if ($totalFiles > 0) { | |
$this->setEncType(self::ENC_FORMDATA); | |
} | |
} else { | |
$this->setEncType($this->getHeader('Content-Type')); | |
} | |
// If we have POST parameters or files, encode and add them to the body | |
if (count($this->getRequest()->getPost()->toArray()) > 0 || $totalFiles > 0) { | |
if (stripos($this->getEncType(), self::ENC_FORMDATA) === 0) { | |
$boundary = '---ZENDHTTPCLIENT-' . md5(microtime()); | |
$this->setEncType(self::ENC_FORMDATA, $boundary); | |
// Get POST parameters and encode them | |
$params = self::flattenParametersArray($this->getRequest()->getPost()->toArray()); | |
foreach ($params as $pp) { | |
$body .= $this->encodeFormData($boundary, $pp[0], $pp[1]); | |
} | |
// Encode files | |
foreach ($this->getRequest()->getFiles()->toArray() as $file) { | |
$fhead = array('Content-Type' => $file['ctype']); | |
$body .= $this->encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead); | |
} | |
$body .= "--{$boundary}--\r\n"; | |
} elseif (stripos($this->getEncType(), self::ENC_URLENCODED) === 0) { | |
// Encode body as application/x-www-form-urlencoded | |
$body = http_build_query($this->getRequest()->getPost()->toArray()); | |
} else { | |
throw new Client\Exception\RuntimeException("Cannot handle content type '{$this->encType}' automatically"); | |
} | |
} | |
return $body; | |
} | |
/** | |
* Attempt to detect the MIME type of a file using available extensions | |
* | |
* This method will try to detect the MIME type of a file. If the fileinfo | |
* extension is available, it will be used. If not, the mime_magic | |
* extension which is deprecated but is still available in many PHP setups | |
* will be tried. | |
* | |
* If neither extension is available, the default application/octet-stream | |
* MIME type will be returned | |
* | |
* @param string $file File path | |
* @return string MIME type | |
*/ | |
protected function detectFileMimeType($file) | |
{ | |
$type = null; | |
// First try with fileinfo functions | |
if (function_exists('finfo_open')) { | |
if (static::$fileInfoDb === null) { | |
ErrorHandler::start(); | |
static::$fileInfoDb = finfo_open(FILEINFO_MIME); | |
ErrorHandler::stop(); | |
} | |
if (static::$fileInfoDb) { | |
$type = finfo_file(static::$fileInfoDb, $file); | |
} | |
} elseif (function_exists('mime_content_type')) { | |
$type = mime_content_type($file); | |
} | |
// Fallback to the default application/octet-stream | |
if (! $type) { | |
$type = 'application/octet-stream'; | |
} | |
return $type; | |
} | |
/** | |
* Encode data to a multipart/form-data part suitable for a POST request. | |
* | |
* @param string $boundary | |
* @param string $name | |
* @param mixed $value | |
* @param string $filename | |
* @param array $headers Associative array of optional headers @example ("Content-Transfer-Encoding" => "binary") | |
* @return string | |
*/ | |
public function encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) | |
{ | |
$ret = "--{$boundary}\r\n" . | |
'Content-Disposition: form-data; name="' . $name . '"'; | |
if ($filename) { | |
$ret .= '; filename="' . $filename . '"'; | |
} | |
$ret .= "\r\n"; | |
foreach ($headers as $hname => $hvalue) { | |
$ret .= "{$hname}: {$hvalue}\r\n"; | |
} | |
$ret .= "\r\n"; | |
$ret .= "{$value}\r\n"; | |
return $ret; | |
} | |
/** | |
* Convert an array of parameters into a flat array of (key, value) pairs | |
* | |
* Will flatten a potentially multi-dimentional array of parameters (such | |
* as POST parameters) into a flat array of (key, value) paris. In case | |
* of multi-dimentional arrays, square brackets ([]) will be added to the | |
* key to indicate an array. | |
* | |
* @since 1.9 | |
* | |
* @param array $parray | |
* @param string $prefix | |
* @return array | |
*/ | |
protected function flattenParametersArray($parray, $prefix = null) | |
{ | |
if (!is_array($parray)) { | |
return $parray; | |
} | |
$parameters = array(); | |
foreach ($parray as $name => $value) { | |
// Calculate array key | |
if ($prefix) { | |
if (is_int($name)) { | |
$key = $prefix . '[]'; | |
} else { | |
$key = $prefix . "[$name]"; | |
} | |
} else { | |
$key = $name; | |
} | |
if (is_array($value)) { | |
$parameters = array_merge($parameters, $this->flattenParametersArray($value, $key)); | |
} else { | |
$parameters[] = array($key, $value); | |
} | |
} | |
return $parameters; | |
} | |
/** | |
* Separating this from send method allows subclasses to wrap | |
* the interaction with the adapter | |
* | |
* @param Http $uri | |
* @param string $method | |
* @param bool $secure | |
* @param array $headers | |
* @param string $body | |
* @return string the raw response | |
* @throws Exception\RuntimeException | |
*/ | |
protected function doRequest(Http $uri, $method, $secure = false, $headers = array(), $body = '') | |
{ | |
// Open the connection, send the request and read the response | |
$this->adapter->connect($uri->getHost(), $uri->getPort(), $secure); | |
if ($this->config['outputstream']) { | |
if ($this->adapter instanceof Client\Adapter\StreamInterface) { | |
$stream = $this->openTempStream(); | |
$this->adapter->setOutputStream($stream); | |
} else { | |
throw new Exception\RuntimeException('Adapter does not support streaming'); | |
} | |
} | |
// HTTP connection | |
$this->lastRawRequest = $this->adapter->write($method, | |
$uri, $this->config['httpversion'], $headers, $body); | |
return $this->adapter->read(); | |
} | |
/** | |
* Create a HTTP authentication "Authorization:" header according to the | |
* specified user, password and authentication method. | |
* | |
* @see http://www.faqs.org/rfcs/rfc2617.html | |
* @param string $user | |
* @param string $password | |
* @param string $type | |
* @return string | |
* @throws Client\Exception\InvalidArgumentException | |
*/ | |
public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC) | |
{ | |
$authHeader = null; | |
switch ($type) { | |
case self::AUTH_BASIC: | |
// In basic authentication, the user name cannot contain ":" | |
if (strpos($user, ':') !== false) { | |
throw new Client\Exception\InvalidArgumentException("The user name cannot contain ':' in 'Basic' HTTP authentication"); | |
} | |
$authHeader = 'Basic ' . base64_encode($user . ':' . $password); | |
break; | |
//case self::AUTH_DIGEST: | |
/** | |
* @todo Implement digest authentication | |
*/ | |
// break; | |
default: | |
throw new Client\Exception\InvalidArgumentException("Not a supported HTTP authentication type: '$type'"); | |
} | |
return $authHeader; | |
} | |
} |