Added the zend framework 2 library, the path is specified in line no.26 in zend_modul...
[openemr.git] / interface / modules / zend_modules / library / Zend / Http / Client.php
blob003fabad5a1b6219a5bd7c82eb6e713bb380498a
1 <?php
2 /**
3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
8 */
10 namespace Zend\Http;
12 use ArrayIterator;
13 use Traversable;
14 use Zend\Stdlib;
15 use Zend\Stdlib\ArrayUtils;
16 use Zend\Stdlib\ErrorHandler;
17 use Zend\Uri\Http;
19 /**
20 * Http client
22 class Client implements Stdlib\DispatchableInterface
24 /**
25 * @const string Supported HTTP Authentication methods
27 const AUTH_BASIC = 'basic';
28 const AUTH_DIGEST = 'digest'; // not implemented yet
30 /**
31 * @const string POST data encoding methods
33 const ENC_URLENCODED = 'application/x-www-form-urlencoded';
34 const ENC_FORMDATA = 'multipart/form-data';
36 /**
37 * @const string DIGEST Authentication
39 const DIGEST_REALM = 'realm';
40 const DIGEST_QOP = 'qop';
41 const DIGEST_NONCE = 'nonce';
42 const DIGEST_OPAQUE = 'opaque';
43 const DIGEST_NC = 'nc';
44 const DIGEST_CNONCE = 'cnonce';
46 /**
47 * @var Response
49 protected $response;
51 /**
52 * @var Request
54 protected $request;
56 /**
57 * @var Client/Adapter
59 protected $adapter;
61 /**
62 * @var array
64 protected $auth = array();
66 /**
67 * @var string
69 protected $streamName = null;
71 /**
72 * @var array of Header\SetCookie
74 protected $cookies = array();
76 /**
77 * @var string
79 protected $encType = '';
81 /**
82 * @var Request
84 protected $lastRawRequest = null;
86 /**
87 * @var Response
89 protected $lastRawResponse = null;
91 /**
92 * @var int
94 protected $redirectCounter = 0;
96 /**
97 * Configuration array, set using the constructor or using ::setOptions()
99 * @var array
101 protected $config = array(
102 'maxredirects' => 5,
103 'strictredirects' => false,
104 'useragent' => 'Zend\Http\Client',
105 'timeout' => 10,
106 'adapter' => 'Zend\Http\Client\Adapter\Socket',
107 'httpversion' => Request::VERSION_11,
108 'storeresponse' => true,
109 'keepalive' => false,
110 'outputstream' => false,
111 'encodecookies' => true,
112 'argseparator' => null,
113 'rfc3986strict' => false
117 * Fileinfo magic database resource
119 * This variable is populated the first time _detectFileMimeType is called
120 * and is then reused on every call to this method
122 * @var resource
124 protected static $fileInfoDb = null;
127 * Constructor
129 * @param string $uri
130 * @param array|Traversable $options
132 public function __construct($uri = null, $options = null)
134 if ($uri !== null) {
135 $this->setUri($uri);
137 if ($options !== null) {
138 $this->setOptions($options);
143 * Set configuration parameters for this HTTP client
145 * @param array|Traversable $options
146 * @return Client
147 * @throws Client\Exception\InvalidArgumentException
149 public function setOptions($options = array())
151 if ($options instanceof Traversable) {
152 $options = ArrayUtils::iteratorToArray($options);
154 if (!is_array($options)) {
155 throw new Client\Exception\InvalidArgumentException('Config parameter is not valid');
158 /** Config Key Normalization */
159 foreach ($options as $k => $v) {
160 $this->config[str_replace(array('-', '_', ' ', '.'), '', strtolower($k))] = $v; // replace w/ normalized
163 // Pass configuration options to the adapter if it exists
164 if ($this->adapter instanceof Client\Adapter\AdapterInterface) {
165 $this->adapter->setOptions($options);
168 return $this;
172 * Load the connection adapter
174 * While this method is not called more than one for a client, it is
175 * separated from ->request() to preserve logic and readability
177 * @param Client\Adapter\AdapterInterface|string $adapter
178 * @return Client
179 * @throws Client\Exception\InvalidArgumentException
181 public function setAdapter($adapter)
183 if (is_string($adapter)) {
184 if (!class_exists($adapter)) {
185 throw new Client\Exception\InvalidArgumentException('Unable to locate adapter class "' . $adapter . '"');
187 $adapter = new $adapter;
190 if (! $adapter instanceof Client\Adapter\AdapterInterface) {
191 throw new Client\Exception\InvalidArgumentException('Passed adapter is not a HTTP connection adapter');
194 $this->adapter = $adapter;
195 $config = $this->config;
196 unset($config['adapter']);
197 $this->adapter->setOptions($config);
198 return $this;
202 * Load the connection adapter
204 * @return Client\Adapter\AdapterInterface $adapter
206 public function getAdapter()
208 if (! $this->adapter) {
209 $this->setAdapter($this->config['adapter']);
212 return $this->adapter;
216 * Set request
218 * @param Request $request
219 * @return Client
221 public function setRequest(Request $request)
223 $this->request = $request;
224 return $this;
228 * Get Request
230 * @return Request
232 public function getRequest()
234 if (empty($this->request)) {
235 $this->request = new Request();
237 return $this->request;
241 * Set response
243 * @param Response $response
244 * @return Client
246 public function setResponse(Response $response)
248 $this->response = $response;
249 return $this;
253 * Get Response
255 * @return Response
257 public function getResponse()
259 if (empty($this->response)) {
260 $this->response = new Response();
262 return $this->response;
267 * Get the last request (as a string)
269 * @return string
271 public function getLastRawRequest()
273 return $this->lastRawRequest;
277 * Get the last response (as a string)
279 * @return string
281 public function getLastRawResponse()
283 return $this->lastRawResponse;
287 * Get the redirections count
289 * @return int
291 public function getRedirectionsCount()
293 return $this->redirectCounter;
297 * Set Uri (to the request)
299 * @param string|Http $uri
300 * @return Client
302 public function setUri($uri)
304 if (!empty($uri)) {
305 // remember host of last request
306 $lastHost = $this->getRequest()->getUri()->getHost();
307 $this->getRequest()->setUri($uri);
309 // if host changed, the HTTP authentication should be cleared for security
310 // reasons, see #4215 for a discussion - currently authentication is also
311 // cleared for peer subdomains due to technical limits
312 $nextHost = $this->getRequest()->getUri()->getHost();
313 if (!preg_match('/' . preg_quote($lastHost, '/') . '$/i', $nextHost)) {
314 $this->clearAuth();
317 // Set auth if username and password has been specified in the uri
318 if ($this->getUri()->getUser() && $this->getUri()->getPassword()) {
319 $this->setAuth($this->getUri()->getUser(), $this->getUri()->getPassword());
322 // We have no ports, set the defaults
323 if (! $this->getUri()->getPort()) {
324 $this->getUri()->setPort(($this->getUri()->getScheme() == 'https' ? 443 : 80));
327 return $this;
331 * Get uri (from the request)
333 * @return Http
335 public function getUri()
337 return $this->getRequest()->getUri();
341 * Set the HTTP method (to the request)
343 * @param string $method
344 * @return Client
346 public function setMethod($method)
348 $method = $this->getRequest()->setMethod($method)->getMethod();
350 if (($method == Request::METHOD_POST || $method == Request::METHOD_PUT ||
351 $method == Request::METHOD_DELETE || $method == Request::METHOD_PATCH)
352 && empty($this->encType)) {
353 $this->setEncType(self::ENC_URLENCODED);
356 return $this;
360 * Get the HTTP method
362 * @return string
364 public function getMethod()
366 return $this->getRequest()->getMethod();
370 * Set the query string argument separator
372 * @param string $argSeparator
373 * @return Client
375 public function setArgSeparator($argSeparator)
377 $this->setOptions(array("argseparator" => $argSeparator));
378 return $this;
382 * Get the query string argument separator
384 * @return string
386 public function getArgSeparator()
388 $argSeparator = $this->config['argseparator'];
389 if (empty($argSeparator)) {
390 $argSeparator = ini_get('arg_separator.output');
391 $this->setArgSeparator($argSeparator);
393 return $argSeparator;
397 * Set the encoding type and the boundary (if any)
399 * @param string $encType
400 * @param string $boundary
401 * @return Client
403 public function setEncType($encType, $boundary = null)
405 if (!empty($encType)) {
406 if (!empty($boundary)) {
407 $this->encType = $encType . "; boundary={$boundary}";
408 } else {
409 $this->encType = $encType;
412 return $this;
416 * Get the encoding type
418 * @return string
420 public function getEncType()
422 return $this->encType;
426 * Set raw body (for advanced use cases)
428 * @param string $body
429 * @return Client
431 public function setRawBody($body)
433 $this->getRequest()->setContent($body);
434 return $this;
438 * Set the POST parameters
440 * @param array $post
441 * @return Client
443 public function setParameterPost(array $post)
445 $this->getRequest()->getPost()->fromArray($post);
446 return $this;
450 * Set the GET parameters
452 * @param array $query
453 * @return Client
455 public function setParameterGet(array $query)
457 $this->getRequest()->getQuery()->fromArray($query);
458 return $this;
462 * Reset all the HTTP parameters (request, response, etc)
464 * @param bool $clearCookies Also clear all valid cookies? (defaults to false)
465 * @param bool $clearAuth Also clear http authentication? (defaults to true)
466 * @return Client
468 public function resetParameters($clearCookies = false /*, $clearAuth = true */)
470 $clearAuth = true;
471 if (func_num_args() > 1) {
472 $clearAuth = func_get_arg(1);
475 $uri = $this->getUri();
477 $this->streamName = null;
478 $this->encType = null;
479 $this->request = null;
480 $this->response = null;
481 $this->lastRawRequest = null;
482 $this->lastRawResponse = null;
484 $this->setUri($uri);
486 if ($clearCookies) {
487 $this->clearCookies();
490 if ($clearAuth) {
491 $this->clearAuth();
494 return $this;
498 * Return the current cookies
500 * @return array
502 public function getCookies()
504 return $this->cookies;
508 * Get the cookie Id (name+domain+path)
510 * @param Header\SetCookie|Header\Cookie $cookie
511 * @return string|bool
513 protected function getCookieId($cookie)
515 if (($cookie instanceof Header\SetCookie) || ($cookie instanceof Header\Cookie)) {
516 return $cookie->getName() . $cookie->getDomain() . $cookie->getPath();
518 return false;
522 * Add a cookie
524 * @param array|ArrayIterator|Header\SetCookie|string $cookie
525 * @param string $value
526 * @param string $expire
527 * @param string $path
528 * @param string $domain
529 * @param bool $secure
530 * @param bool $httponly
531 * @param string $maxAge
532 * @param string $version
533 * @throws Exception\InvalidArgumentException
534 * @return Client
536 public function addCookie($cookie, $value = null, $expire = null, $path = null, $domain = null, $secure = false, $httponly = true, $maxAge = null, $version = null)
538 if (is_array($cookie) || $cookie instanceof ArrayIterator) {
539 foreach ($cookie as $setCookie) {
540 if ($setCookie instanceof Header\SetCookie) {
541 $this->cookies[$this->getCookieId($setCookie)] = $setCookie;
542 } else {
543 throw new Exception\InvalidArgumentException('The cookie parameter is not a valid Set-Cookie type');
546 } elseif (is_string($cookie) && $value !== null) {
547 $setCookie = new Header\SetCookie($cookie, $value, $expire, $path, $domain, $secure, $httponly, $maxAge, $version);
548 $this->cookies[$this->getCookieId($setCookie)] = $setCookie;
549 } elseif ($cookie instanceof Header\SetCookie) {
550 $this->cookies[$this->getCookieId($cookie)] = $cookie;
551 } else {
552 throw new Exception\InvalidArgumentException('Invalid parameter type passed as Cookie');
554 return $this;
558 * Set an array of cookies
560 * @param array $cookies
561 * @throws Exception\InvalidArgumentException
562 * @return Client
564 public function setCookies($cookies)
566 if (is_array($cookies)) {
567 $this->clearCookies();
568 foreach ($cookies as $name => $value) {
569 $this->addCookie($name, $value);
571 } else {
572 throw new Exception\InvalidArgumentException('Invalid cookies passed as parameter, it must be an array');
574 return $this;
578 * Clear all the cookies
580 public function clearCookies()
582 $this->cookies = array();
586 * Set the headers (for the request)
588 * @param Headers|array $headers
589 * @throws Exception\InvalidArgumentException
590 * @return Client
592 public function setHeaders($headers)
594 if (is_array($headers)) {
595 $newHeaders = new Headers();
596 $newHeaders->addHeaders($headers);
597 $this->getRequest()->setHeaders($newHeaders);
598 } elseif ($headers instanceof Headers) {
599 $this->getRequest()->setHeaders($headers);
600 } else {
601 throw new Exception\InvalidArgumentException('Invalid parameter headers passed');
603 return $this;
607 * Check if exists the header type specified
609 * @param string $name
610 * @return bool
612 public function hasHeader($name)
614 $headers = $this->getRequest()->getHeaders();
616 if ($headers instanceof Headers) {
617 return $headers->has($name);
620 return false;
624 * Get the header value of the request
626 * @param string $name
627 * @return string|bool
629 public function getHeader($name)
631 $headers = $this->getRequest()->getHeaders();
633 if ($headers instanceof Headers) {
634 if ($headers->get($name)) {
635 return $headers->get($name)->getFieldValue();
638 return false;
642 * Set streaming for received data
644 * @param string|bool $streamfile Stream file, true for temp file, false/null for no streaming
645 * @return \Zend\Http\Client
647 public function setStream($streamfile = true)
649 $this->setOptions(array("outputstream" => $streamfile));
650 return $this;
654 * Get status of streaming for received data
655 * @return bool|string
657 public function getStream()
659 if (null !== $this->streamName) {
660 return $this->streamName;
663 return $this->config['outputstream'];
667 * Create temporary stream
669 * @throws Exception\RuntimeException
670 * @return resource
672 protected function openTempStream()
674 $this->streamName = $this->config['outputstream'];
676 if (!is_string($this->streamName)) {
677 // If name is not given, create temp name
678 $this->streamName = tempnam(
679 isset($this->config['streamtmpdir']) ? $this->config['streamtmpdir'] : sys_get_temp_dir(),
680 'Zend\Http\Client'
684 ErrorHandler::start();
685 $fp = fopen($this->streamName, "w+b");
686 $error = ErrorHandler::stop();
687 if (false === $fp) {
688 if ($this->adapter instanceof Client\Adapter\AdapterInterface) {
689 $this->adapter->close();
691 throw new Exception\RuntimeException("Could not open temp file {$this->streamName}", 0, $error);
694 return $fp;
698 * Create a HTTP authentication "Authorization:" header according to the
699 * specified user, password and authentication method.
701 * @param string $user
702 * @param string $password
703 * @param string $type
704 * @throws Exception\InvalidArgumentException
705 * @return Client
707 public function setAuth($user, $password, $type = self::AUTH_BASIC)
709 if (!defined('self::AUTH_' . strtoupper($type))) {
710 throw new Exception\InvalidArgumentException("Invalid or not supported authentication type: '$type'");
712 if (empty($user)) {
713 throw new Exception\InvalidArgumentException("The username cannot be empty");
716 $this->auth = array (
717 'user' => $user,
718 'password' => $password,
719 'type' => $type
723 return $this;
727 * Clear http authentication
729 public function clearAuth()
731 $this->auth = array();
735 * Calculate the response value according to the HTTP authentication type
737 * @see http://www.faqs.org/rfcs/rfc2617.html
738 * @param string $user
739 * @param string $password
740 * @param string $type
741 * @param array $digest
742 * @param null|string $entityBody
743 * @throws Exception\InvalidArgumentException
744 * @return string|bool
746 protected function calcAuthDigest($user, $password, $type = self::AUTH_BASIC, $digest = array(), $entityBody = null)
748 if (!defined('self::AUTH_' . strtoupper($type))) {
749 throw new Exception\InvalidArgumentException("Invalid or not supported authentication type: '$type'");
751 $response = false;
752 switch (strtolower($type)) {
753 case self::AUTH_BASIC :
754 // In basic authentication, the user name cannot contain ":"
755 if (strpos($user, ':') !== false) {
756 throw new Exception\InvalidArgumentException("The user name cannot contain ':' in Basic HTTP authentication");
758 $response = base64_encode($user . ':' . $password);
759 break;
760 case self::AUTH_DIGEST :
761 if (empty($digest)) {
762 throw new Exception\InvalidArgumentException("The digest cannot be empty");
764 foreach ($digest as $key => $value) {
765 if (!defined('self::DIGEST_' . strtoupper($key))) {
766 throw new Exception\InvalidArgumentException("Invalid or not supported digest authentication parameter: '$key'");
769 $ha1 = md5($user . ':' . $digest['realm'] . ':' . $password);
770 if (empty($digest['qop']) || strtolower($digest['qop']) == 'auth') {
771 $ha2 = md5($this->getMethod() . ':' . $this->getUri()->getPath());
772 } elseif (strtolower($digest['qop']) == 'auth-int') {
773 if (empty($entityBody)) {
774 throw new Exception\InvalidArgumentException("I cannot use the auth-int digest authentication without the entity body");
776 $ha2 = md5($this->getMethod() . ':' . $this->getUri()->getPath() . ':' . md5($entityBody));
778 if (empty($digest['qop'])) {
779 $response = md5($ha1 . ':' . $digest['nonce'] . ':' . $ha2);
780 } else {
781 $response = md5($ha1 . ':' . $digest['nonce'] . ':' . $digest['nc']
782 . ':' . $digest['cnonce'] . ':' . $digest['qoc'] . ':' . $ha2);
784 break;
786 return $response;
790 * Dispatch
792 * @param Stdlib\RequestInterface $request
793 * @param Stdlib\ResponseInterface $response
794 * @return Stdlib\ResponseInterface
796 public function dispatch(Stdlib\RequestInterface $request, Stdlib\ResponseInterface $response = null)
798 $response = $this->send($request);
799 return $response;
803 * Send HTTP request
805 * @param Request $request
806 * @return Response
807 * @throws Exception\RuntimeException
808 * @throws Client\Exception\RuntimeException
810 public function send(Request $request = null)
812 if ($request !== null) {
813 $this->setRequest($request);
816 $this->redirectCounter = 0;
817 $response = null;
819 $adapter = $this->getAdapter();
821 // Send the first request. If redirected, continue.
822 do {
823 // uri
824 $uri = $this->getUri();
826 // query
827 $query = $this->getRequest()->getQuery();
829 if (!empty($query)) {
830 $queryArray = $query->toArray();
832 if (!empty($queryArray)) {
833 $newUri = $uri->toString();
834 $queryString = http_build_query($query, null, $this->getArgSeparator());
836 if ($this->config['rfc3986strict']) {
837 $queryString = str_replace('+', '%20', $queryString);
840 if (strpos($newUri, '?') !== false) {
841 $newUri .= $this->getArgSeparator() . $queryString;
842 } else {
843 $newUri .= '?' . $queryString;
846 $uri = new Http($newUri);
849 // If we have no ports, set the defaults
850 if (!$uri->getPort()) {
851 $uri->setPort($uri->getScheme() == 'https' ? 443 : 80);
854 // method
855 $method = $this->getRequest()->getMethod();
857 // body
858 $body = $this->prepareBody();
860 // headers
861 $headers = $this->prepareHeaders($body, $uri);
863 $secure = $uri->getScheme() == 'https';
865 // cookies
866 $cookie = $this->prepareCookies($uri->getHost(), $uri->getPath(), $secure);
867 if ($cookie->getFieldValue()) {
868 $headers['Cookie'] = $cookie->getFieldValue();
871 // check that adapter supports streaming before using it
872 if (is_resource($body) && !($adapter instanceof Client\Adapter\StreamInterface)) {
873 throw new Client\Exception\RuntimeException('Adapter does not support streaming');
876 // calling protected method to allow extending classes
877 // to wrap the interaction with the adapter
878 $response = $this->doRequest($uri, $method, $secure, $headers, $body);
880 if (! $response) {
881 throw new Exception\RuntimeException('Unable to read response, or response is empty');
884 if ($this->config['storeresponse']) {
885 $this->lastRawResponse = $response;
886 } else {
887 $this->lastRawResponse = null;
890 if ($this->config['outputstream']) {
891 $stream = $this->getStream();
892 if (!is_resource($stream) && is_string($stream)) {
893 $stream = fopen($stream, 'r');
895 $streamMetaData = stream_get_meta_data($stream);
896 if ($streamMetaData['seekable']) {
897 rewind($stream);
899 // cleanup the adapter
900 $adapter->setOutputStream(null);
901 $response = Response\Stream::fromStream($response, $stream);
902 $response->setStreamName($this->streamName);
903 if (!is_string($this->config['outputstream'])) {
904 // we used temp name, will need to clean up
905 $response->setCleanup(true);
907 } else {
908 $response = $this->getResponse()->fromString($response);
911 // Get the cookies from response (if any)
912 $setCookies = $response->getCookie();
913 if (!empty($setCookies)) {
914 $this->addCookie($setCookies);
917 // If we got redirected, look for the Location header
918 if ($response->isRedirect() && ($response->getHeaders()->has('Location'))) {
920 // Avoid problems with buggy servers that add whitespace at the
921 // end of some headers
922 $location = trim($response->getHeaders()->get('Location')->getFieldValue());
924 // Check whether we send the exact same request again, or drop the parameters
925 // and send a GET request
926 if ($response->getStatusCode() == 303 ||
927 ((! $this->config['strictredirects']) && ($response->getStatusCode() == 302 ||
928 $response->getStatusCode() == 301))) {
930 $this->resetParameters(false, false);
931 $this->setMethod(Request::METHOD_GET);
935 // If we got a well formed absolute URI
936 if (($scheme = substr($location, 0, 6)) &&
937 ($scheme == 'http:/' || $scheme == 'https:')) {
938 // setURI() clears parameters if host changed, see #4215
939 $this->setUri($location);
940 } else {
942 // Split into path and query and set the query
943 if (strpos($location, '?') !== false) {
944 list($location, $query) = explode('?', $location, 2);
945 } else {
946 $query = '';
948 $this->getUri()->setQuery($query);
950 // Else, if we got just an absolute path, set it
951 if (strpos($location, '/') === 0) {
952 $this->getUri()->setPath($location);
953 // Else, assume we have a relative path
954 } else {
955 // Get the current path directory, removing any trailing slashes
956 $path = $this->getUri()->getPath();
957 $path = rtrim(substr($path, 0, strrpos($path, '/')), "/");
958 $this->getUri()->setPath($path . '/' . $location);
961 ++$this->redirectCounter;
963 } else {
964 // If we didn't get any location, stop redirecting
965 break;
968 } while ($this->redirectCounter <= $this->config['maxredirects']);
970 $this->response = $response;
971 return $response;
975 * Fully reset the HTTP client (auth, cookies, request, response, etc.)
977 * @return Client
979 public function reset()
981 $this->resetParameters();
982 $this->clearAuth();
983 $this->clearCookies();
985 return $this;
989 * Set a file to upload (using a POST request)
991 * Can be used in two ways:
993 * 1. $data is null (default): $filename is treated as the name if a local file which
994 * will be read and sent. Will try to guess the content type using mime_content_type().
995 * 2. $data is set - $filename is sent as the file name, but $data is sent as the file
996 * contents and no file is read from the file system. In this case, you need to
997 * manually set the Content-Type ($ctype) or it will default to
998 * application/octet-stream.
1000 * @param string $filename Name of file to upload, or name to save as
1001 * @param string $formname Name of form element to send as
1002 * @param string $data Data to send (if null, $filename is read and sent)
1003 * @param string $ctype Content type to use (if $data is set and $ctype is
1004 * null, will be application/octet-stream)
1005 * @return Client
1006 * @throws Exception\RuntimeException
1008 public function setFileUpload($filename, $formname, $data = null, $ctype = null)
1010 if ($data === null) {
1011 ErrorHandler::start();
1012 $data = file_get_contents($filename);
1013 $error = ErrorHandler::stop();
1014 if ($data === false) {
1015 throw new Exception\RuntimeException("Unable to read file '{$filename}' for upload", 0, $error);
1017 if (!$ctype) {
1018 $ctype = $this->detectFileMimeType($filename);
1022 $this->getRequest()->getFiles()->set($filename, array(
1023 'formname' => $formname,
1024 'filename' => basename($filename),
1025 'ctype' => $ctype,
1026 'data' => $data
1029 return $this;
1033 * Remove a file to upload
1035 * @param string $filename
1036 * @return bool
1038 public function removeFileUpload($filename)
1040 $file = $this->getRequest()->getFiles()->get($filename);
1041 if (!empty($file)) {
1042 $this->getRequest()->getFiles()->set($filename, null);
1043 return true;
1045 return false;
1049 * Prepare Cookies
1051 * @param string $domain
1052 * @param string $path
1053 * @param bool $secure
1054 * @return Header\Cookie|bool
1056 protected function prepareCookies($domain, $path, $secure)
1058 $validCookies = array();
1060 if (!empty($this->cookies)) {
1061 foreach ($this->cookies as $id => $cookie) {
1062 if ($cookie->isExpired()) {
1063 unset($this->cookies[$id]);
1064 continue;
1067 if ($cookie->isValidForRequest($domain, $path, $secure)) {
1068 // OAM hack some domains try to set the cookie multiple times
1069 $validCookies[$cookie->getName()] = $cookie;
1074 $cookies = Header\Cookie::fromSetCookieArray($validCookies);
1075 $cookies->setEncodeValue($this->config['encodecookies']);
1077 return $cookies;
1081 * Prepare the request headers
1083 * @param resource|string $body
1084 * @param Http $uri
1085 * @throws Exception\RuntimeException
1086 * @return array
1088 protected function prepareHeaders($body, $uri)
1090 $headers = array();
1092 // Set the host header
1093 if ($this->config['httpversion'] == Request::VERSION_11) {
1094 $host = $uri->getHost();
1095 // If the port is not default, add it
1096 if (!(($uri->getScheme() == 'http' && $uri->getPort() == 80) ||
1097 ($uri->getScheme() == 'https' && $uri->getPort() == 443))) {
1098 $host .= ':' . $uri->getPort();
1101 $headers['Host'] = $host;
1104 // Set the connection header
1105 if (!$this->getRequest()->getHeaders()->has('Connection')) {
1106 if (!$this->config['keepalive']) {
1107 $headers['Connection'] = 'close';
1111 // Set the Accept-encoding header if not set - depending on whether
1112 // zlib is available or not.
1113 if (!$this->getRequest()->getHeaders()->has('Accept-Encoding')) {
1114 if (function_exists('gzinflate')) {
1115 $headers['Accept-Encoding'] = 'gzip, deflate';
1116 } else {
1117 $headers['Accept-Encoding'] = 'identity';
1122 // Set the user agent header
1123 if (!$this->getRequest()->getHeaders()->has('User-Agent') && isset($this->config['useragent'])) {
1124 $headers['User-Agent'] = $this->config['useragent'];
1127 // Set HTTP authentication if needed
1128 if (!empty($this->auth)) {
1129 switch ($this->auth['type']) {
1130 case self::AUTH_BASIC :
1131 $auth = $this->calcAuthDigest($this->auth['user'], $this->auth['password'], $this->auth['type']);
1132 if ($auth !== false) {
1133 $headers['Authorization'] = 'Basic ' . $auth;
1135 break;
1136 case self::AUTH_DIGEST :
1137 throw new Exception\RuntimeException("The digest authentication is not implemented yet");
1141 // Content-type
1142 $encType = $this->getEncType();
1143 if (!empty($encType)) {
1144 $headers['Content-Type'] = $encType;
1147 if (!empty($body)) {
1148 if (is_resource($body)) {
1149 $fstat = fstat($body);
1150 $headers['Content-Length'] = $fstat['size'];
1151 } else {
1152 $headers['Content-Length'] = strlen($body);
1156 // Merge the headers of the request (if any)
1157 // here we need right 'http field' and not lowercase letters
1158 $requestHeaders = $this->getRequest()->getHeaders();
1159 foreach ($requestHeaders as $requestHeaderElement) {
1160 $headers[$requestHeaderElement->getFieldName()] = $requestHeaderElement->getFieldValue();
1162 return $headers;
1167 * Prepare the request body (for PATCH, POST and PUT requests)
1169 * @return string
1170 * @throws \Zend\Http\Client\Exception\RuntimeException
1172 protected function prepareBody()
1174 // According to RFC2616, a TRACE request should not have a body.
1175 if ($this->getRequest()->isTrace()) {
1176 return '';
1179 $rawBody = $this->getRequest()->getContent();
1180 if (!empty($rawBody)) {
1181 return $rawBody;
1184 $body = '';
1185 $totalFiles = 0;
1187 if (!$this->getRequest()->getHeaders()->has('Content-Type')) {
1188 $totalFiles = count($this->getRequest()->getFiles()->toArray());
1189 // If we have files to upload, force encType to multipart/form-data
1190 if ($totalFiles > 0) {
1191 $this->setEncType(self::ENC_FORMDATA);
1193 } else {
1194 $this->setEncType($this->getHeader('Content-Type'));
1197 // If we have POST parameters or files, encode and add them to the body
1198 if (count($this->getRequest()->getPost()->toArray()) > 0 || $totalFiles > 0) {
1199 if (stripos($this->getEncType(), self::ENC_FORMDATA) === 0) {
1200 $boundary = '---ZENDHTTPCLIENT-' . md5(microtime());
1201 $this->setEncType(self::ENC_FORMDATA, $boundary);
1203 // Get POST parameters and encode them
1204 $params = self::flattenParametersArray($this->getRequest()->getPost()->toArray());
1205 foreach ($params as $pp) {
1206 $body .= $this->encodeFormData($boundary, $pp[0], $pp[1]);
1209 // Encode files
1210 foreach ($this->getRequest()->getFiles()->toArray() as $file) {
1211 $fhead = array('Content-Type' => $file['ctype']);
1212 $body .= $this->encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead);
1214 $body .= "--{$boundary}--\r\n";
1215 } elseif (stripos($this->getEncType(), self::ENC_URLENCODED) === 0) {
1216 // Encode body as application/x-www-form-urlencoded
1217 $body = http_build_query($this->getRequest()->getPost()->toArray());
1218 } else {
1219 throw new Client\Exception\RuntimeException("Cannot handle content type '{$this->encType}' automatically");
1223 return $body;
1228 * Attempt to detect the MIME type of a file using available extensions
1230 * This method will try to detect the MIME type of a file. If the fileinfo
1231 * extension is available, it will be used. If not, the mime_magic
1232 * extension which is deprecated but is still available in many PHP setups
1233 * will be tried.
1235 * If neither extension is available, the default application/octet-stream
1236 * MIME type will be returned
1238 * @param string $file File path
1239 * @return string MIME type
1241 protected function detectFileMimeType($file)
1243 $type = null;
1245 // First try with fileinfo functions
1246 if (function_exists('finfo_open')) {
1247 if (static::$fileInfoDb === null) {
1248 ErrorHandler::start();
1249 static::$fileInfoDb = finfo_open(FILEINFO_MIME);
1250 ErrorHandler::stop();
1253 if (static::$fileInfoDb) {
1254 $type = finfo_file(static::$fileInfoDb, $file);
1257 } elseif (function_exists('mime_content_type')) {
1258 $type = mime_content_type($file);
1261 // Fallback to the default application/octet-stream
1262 if (! $type) {
1263 $type = 'application/octet-stream';
1266 return $type;
1270 * Encode data to a multipart/form-data part suitable for a POST request.
1272 * @param string $boundary
1273 * @param string $name
1274 * @param mixed $value
1275 * @param string $filename
1276 * @param array $headers Associative array of optional headers @example ("Content-Transfer-Encoding" => "binary")
1277 * @return string
1279 public function encodeFormData($boundary, $name, $value, $filename = null, $headers = array())
1281 $ret = "--{$boundary}\r\n" .
1282 'Content-Disposition: form-data; name="' . $name . '"';
1284 if ($filename) {
1285 $ret .= '; filename="' . $filename . '"';
1287 $ret .= "\r\n";
1289 foreach ($headers as $hname => $hvalue) {
1290 $ret .= "{$hname}: {$hvalue}\r\n";
1292 $ret .= "\r\n";
1293 $ret .= "{$value}\r\n";
1295 return $ret;
1299 * Convert an array of parameters into a flat array of (key, value) pairs
1301 * Will flatten a potentially multi-dimentional array of parameters (such
1302 * as POST parameters) into a flat array of (key, value) paris. In case
1303 * of multi-dimentional arrays, square brackets ([]) will be added to the
1304 * key to indicate an array.
1306 * @since 1.9
1308 * @param array $parray
1309 * @param string $prefix
1310 * @return array
1312 protected function flattenParametersArray($parray, $prefix = null)
1314 if (!is_array($parray)) {
1315 return $parray;
1318 $parameters = array();
1320 foreach ($parray as $name => $value) {
1321 // Calculate array key
1322 if ($prefix) {
1323 if (is_int($name)) {
1324 $key = $prefix . '[]';
1325 } else {
1326 $key = $prefix . "[$name]";
1328 } else {
1329 $key = $name;
1332 if (is_array($value)) {
1333 $parameters = array_merge($parameters, $this->flattenParametersArray($value, $key));
1335 } else {
1336 $parameters[] = array($key, $value);
1340 return $parameters;
1344 * Separating this from send method allows subclasses to wrap
1345 * the interaction with the adapter
1347 * @param Http $uri
1348 * @param string $method
1349 * @param bool $secure
1350 * @param array $headers
1351 * @param string $body
1352 * @return string the raw response
1353 * @throws Exception\RuntimeException
1355 protected function doRequest(Http $uri, $method, $secure = false, $headers = array(), $body = '')
1357 // Open the connection, send the request and read the response
1358 $this->adapter->connect($uri->getHost(), $uri->getPort(), $secure);
1360 if ($this->config['outputstream']) {
1361 if ($this->adapter instanceof Client\Adapter\StreamInterface) {
1362 $stream = $this->openTempStream();
1363 $this->adapter->setOutputStream($stream);
1364 } else {
1365 throw new Exception\RuntimeException('Adapter does not support streaming');
1368 // HTTP connection
1369 $this->lastRawRequest = $this->adapter->write($method,
1370 $uri, $this->config['httpversion'], $headers, $body);
1372 return $this->adapter->read();
1376 * Create a HTTP authentication "Authorization:" header according to the
1377 * specified user, password and authentication method.
1379 * @see http://www.faqs.org/rfcs/rfc2617.html
1380 * @param string $user
1381 * @param string $password
1382 * @param string $type
1383 * @return string
1384 * @throws Client\Exception\InvalidArgumentException
1386 public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC)
1388 $authHeader = null;
1390 switch ($type) {
1391 case self::AUTH_BASIC:
1392 // In basic authentication, the user name cannot contain ":"
1393 if (strpos($user, ':') !== false) {
1394 throw new Client\Exception\InvalidArgumentException("The user name cannot contain ':' in 'Basic' HTTP authentication");
1397 $authHeader = 'Basic ' . base64_encode($user . ':' . $password);
1398 break;
1400 //case self::AUTH_DIGEST:
1402 * @todo Implement digest authentication
1404 // break;
1406 default:
1407 throw new Client\Exception\InvalidArgumentException("Not a supported HTTP authentication type: '$type'");
1410 return $authHeader;