Source of file Client.php
Size: 43,182 Bytes - Last Modified: 2014-03-12T23:21:18+01:00
/home/theseer/Downloads/ZendFramework-2.3.0/library/Zend/Http/Client.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413 | <?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; } } |