5, 'strictredirects' => false, 'useragent' => 'Zend_Http_Client', 'timeout' => 10, 'adapter' => 'Zend_Http_Client_Adapter_Socket', 'httpversion' => self::HTTP_1, 'keepalive' => false, 'storeresponse' => true, 'strict' => true); /** * The adapter used to preform the actual connection to the server * * @var Zend_Http_Client_Adapter_Interface */ protected $adapter = null; /** * Request URI * * @var Zend_Uri_Http */ protected $uri; /** * Associative array of request headers * * @var array */ protected $headers = array(); /** * HTTP request method * * @var string */ protected $method = self::GET; /** * Associative array of GET parameters * * @var array */ protected $paramsGet = array(); /** * Assiciative array of POST parameters * * @var array */ protected $paramsPost = array(); /** * Request body content type (for POST requests) * * @var string */ protected $enctype = null; /** * The raw post data to send. Could be set by setRawData($data, $enctype). * * @var string */ protected $raw_post_data = null; /** * HTTP Authentication settings * * Expected to be an associative array with this structure: * $this->auth = array('user' => 'username', 'password' => 'password', 'type' => 'basic') * Where 'type' should be one of the supported authentication types (see the AUTH_* * constants), for example 'basic' or 'digest'. * * If null, no authentication will be used. * * @var array|null */ protected $auth; /** * File upload arrays (used in POST requests) * * An associative array, where each element is of the format: * 'name' => array('filename.txt', 'text/plain', 'This is the actual file contents') * * @var array */ protected $files = array(); /** * The client's cookie jar * * @var Zend_Http_CookieJar */ protected $cookiejar = null; /** * The last HTTP request sent by the client, as string * * @var string */ protected $last_request = null; /** * The last HTTP response received by the client * * @var Zend_Http_Response */ protected $last_response = null; /** * Redirection counter * * @var int */ protected $redirectCounter = 0; /** * Contructor method. Will create a new HTTP client. Accepts the target * URL and optionally and array of headers. * * @param Zend_Uri_Http|string $uri * @param array $headers Optional request headers to set */ public function __construct($uri = null, $config = null) { if ($uri !== null) $this->setUri($uri); if ($config !== null) $this->setConfig($config); } /** * Set the URI for the next request * * @param Zend_Uri_Http|string $uri * @return Zend_Http_Client * @throws Zend_Http_Client_Exception */ public function setUri($uri) { if (is_string($uri)) { $uri = Zend_Uri::factory($uri); } if (! $uri instanceof Zend_Uri_Http) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception('Passed parameter is not a valid HTTP URI.'); } // We have no ports, set the defaults if (! $uri->getPort()) { $uri->setPort(($uri->getScheme() == 'https' ? 443 : 80)); } $this->uri = $uri; return $this; } /** * Get the URI for the next request * * @param boolean $as_string If true, will return the URI as a string * @return Zend_Uri_Http|string */ public function getUri($as_string = false) { if ($as_string && $this->uri instanceof Zend_Uri_Http) { return $this->uri->__toString(); } else { return $this->uri; } } /** * Set configuration parameters for this HTTP client * * @param array $config * @return Zend_Http_Client */ public function setConfig($config = array()) { if (! is_array($config)) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception('Expected array parameter, given ' . gettype($config)); } foreach ($config as $k => $v) $this->config[strtolower($k)] = $v; return $this; } /** * Set the next request's method * * Validated the passed method and sets it. If we have files set for * POST requests, and the new method is not POST, the files are silently * dropped. * * @param string $method * @return Zend_Http_Client */ public function setMethod($method = self::GET) { if (! preg_match('/^[A-Za-z_]+$/', $method)) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("'{$method}' is not a valid HTTP request method."); } if ($method == self::POST && $this->enctype === null) $this->setEncType(self::ENC_URLENCODED); $this->method = $method; return $this; } /** * Set one or more request headers * * This function can be used in several ways to set the client's request * headers: * 1. By providing two parameters: $name as the header to set (eg. 'Host') * and $value as it's value (eg. 'www.example.com'). * 2. By providing a single header string as the only parameter * eg. 'Host: www.example.com' * 3. By providing an array of headers as the first parameter * eg. array('host' => 'www.example.com', 'x-foo: bar'). In This case * the function will call itself recursively for each array item. * * @param string|array $name Header name, full header string ('Header: value') * or an array of headers * @param mixed $value Header value or null * @return Zend_Http_Client */ public function setHeaders($name, $value = null) { // If we got an array, go recusive! if (is_array($name)) { foreach ($name as $k => $v) { if (is_string($k)) { $this->setHeaders($k, $v); } else { $this->setHeaders($v, null); } } } else { // Check if $name needs to be split if ($value === null && (strpos($name, ':') > 0)) list($name, $value) = explode(':', $name, 2); // Make sure the name is valid if we are in strict mode if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("{$name} is not a valid HTTP header name"); } $normalized_name = strtolower($name); // If $value is null or false, unset the header if ($value === null || $value === false) { unset($this->headers[$normalized_name]); // Else, set the header } else { // Header names are storred lowercase internally. if (is_string($value)) $value = trim($value); $this->headers[$normalized_name] = array($name, $value); } } return $this; } /** * Get the value of a specific header * * Note that if the header has more than one value, an array * will be returned. * * @param unknown_type $key * @return string|array|null The header value or null if it is not set */ public function getHeader($key) { $key = strtolower($key); if (isset($this->headers[$key])) { return $this->headers[$key][1]; } else { return null; } } /** * Set a GET parameter for the request. Wrapper around _setParameter * * @param string|array $name * @param string $value * @return Zend_Http_Client */ public function setParameterGet($name, $value = null) { if (is_array($name)) { foreach ($name as $k => $v) $this->_setParameter('GET', $k, $v); } else { $this->_setParameter('GET', $name, $value); } return $this; } /** * Set a POST parameter for the request. Wrapper around _setParameter * * @param string|array $name * @param string $value * @return Zend_Http_Client */ public function setParameterPost($name, $value = null) { if (is_array($name)) { foreach ($name as $k => $v) $this->_setParameter('POST', $k, $v); } else { $this->_setParameter('POST', $name, $value); } return $this; } /** * Set a GET or POST parameter - used by SetParameterGet and SetParameterPost * * @param string $type GET or POST * @param string $name * @param string $value */ protected function _setParameter($type, $name, $value) { $parray = array(); $type = strtolower($type); switch ($type) { case 'get': $parray = &$this->paramsGet; break; case 'post': $parray = &$this->paramsPost; break; } if ($value === null) { if (isset($parray[$name])) unset($parray[$name]); } else { $parray[$name] = $value; } } /** * Get the number of redirections done on the last request * * @return int */ public function getRedirectionsCount() { return $this->redirectCounter; } /** * Set HTTP authentication parameters * * $type should be one of the supported types - see the self::AUTH_* * constants. * * To enable authentication: * * $this->setAuth('shahar', 'secret', Zend_Http_Client::AUTH_BASIC); * * * To disable authentication: * * $this->setAuth(false); * * * @see http://www.faqs.org/rfcs/rfc2617.html * @param string|false $user User name or false disable authentication * @param string $password Password * @param string $type Authentication type * @return Zend_Http_Client */ public function setAuth($user, $password = '', $type = self::AUTH_BASIC) { // If we got false or null, disable authentication if ($user === false || $user === null) { $this->auth = null; // Else, set up authentication } else { // Check we got a proper authentication type if (! defined('self::AUTH_' . strtoupper($type))) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("Invalid or not supported authentication type: '$type'"); } $this->auth = array('user' => (string)$user, 'password' => (string)$password, 'type' => $type); } return $this; } /** * Set the HTTP client's cookie jar. * * A cookie jar is an object that holds and maintains cookies across HTTP requests * and responses. * * @param Zend_Http_CookieJar|boolean $cookiejar Existing cookiejar object, true to create a new one, false to disable * @return Zend_Http_Client */ public function setCookieJar($cookiejar = true) { if (! class_exists('Zend_Http_CookieJar')) require_once 'external/Zend/Http/CookieJar.php'; if ($cookiejar instanceof Zend_Http_CookieJar) { $this->cookiejar = $cookiejar; } elseif ($cookiejar === true) { $this->cookiejar = new Zend_Http_CookieJar(); } elseif (! $cookiejar) { $this->cookiejar = null; } else { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception('Invalid parameter type passed as CookieJar'); } return $this; } /** * Return the current cookie jar or null if none. * * @return Zend_Http_CookieJar|null */ public function getCookieJar() { return $this->cookiejar; } /** * Add a cookie to the request. If the client has no Cookie Jar, the cookies * will be added directly to the headers array as "Cookie" headers. * * @param Zend_Http_Cookie|string $cookie * @param string|null $value If "cookie" is a string, this is the cookie value. * @return Zend_Http_Client */ public function setCookie($cookie, $value = null) { if (! class_exists('Zend_Http_Cookie')) require_once 'external/Zend/Http/Cookie.php'; if (is_array($cookie)) { foreach ($cookie as $c => $v) { if (is_string($c)) { $this->setCookie($c, $v); } else { $this->setCookie($v); } } return $this; } if ($value !== null) $value = urlencode($value); if (isset($this->cookiejar)) { if ($cookie instanceof Zend_Http_Cookie) { $this->cookiejar->addCookie($cookie); } elseif (is_string($cookie) && $value !== null) { $cookie = Zend_Http_Cookie::fromString("{$cookie}={$value}", $this->uri); $this->cookiejar->addCookie($cookie); } } else { if ($cookie instanceof Zend_Http_Cookie) { $name = $cookie->getName(); $value = $cookie->getValue(); $cookie = $name; } if (preg_match("/[=,; \t\r\n\013\014]/", $cookie)) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("Cookie name cannot contain these characters: =,; \t\r\n\013\014 ({$cookie})"); } $value = addslashes($value); if (! isset($this->headers['cookie'])) $this->headers['cookie'] = array('Cookie', ''); $this->headers['cookie'][1] .= $cookie . '=' . $value . '; '; } 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 Zend_Http_Client */ public function setFileUpload($filename, $formname, $data = null, $ctype = null) { if ($data === null) { if (($data = @file_get_contents($filename)) === false) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("Unable to read file '{$filename}' for upload"); } if (! $ctype && function_exists('mime_content_type')) $ctype = mime_content_type($filename); } // Force enctype to multipart/form-data $this->setEncType(self::ENC_FORMDATA); if ($ctype === null) $ctype = 'application/octet-stream'; $this->files[$formname] = array(basename($filename), $ctype, $data); return $this; } /** * Set the encoding type for POST data * * @param string $enctype * @return Zend_Http_Client */ public function setEncType($enctype = self::ENC_URLENCODED) { $this->enctype = $enctype; return $this; } /** * Set the raw (already encoded) POST data. * * This function is here for two reasons: * 1. For advanced user who would like to set their own data, already encoded * 2. For backwards compatibilty: If someone uses the old post($data) method. * this method will be used to set the encoded data. * * @param string $data * @param string $enctype * @return Zend_Http_Client */ public function setRawData($data, $enctype = null) { $this->raw_post_data = $data; $this->setEncType($enctype); return $this; } /** * Clear all GET and POST parameters * * Should be used to reset the request parameters if the client is * used for several concurrent requests. * * @return Zend_Http_Client */ public function resetParameters() { // Reset parameter data $this->paramsGet = array(); $this->paramsPost = array(); $this->files = array(); $this->raw_post_data = null; // Clear outdated headers if (isset($this->headers['content-type'])) unset($this->headers['content-type']); if (isset($this->headers['content-length'])) unset($this->headers['content-length']); return $this; } /** * Get the last HTTP request as string * * @return string */ public function getLastRequest() { return $this->last_request; } /** * Get the last HTTP response received by this client * * If $config['storeresponse'] is set to false, or no response was * stored yet, will return null * * @return Zend_Http_Response or null if none */ public function getLastResponse() { return $this->last_response; } /** * Load the connection adapter * * While this method is not called more than one for a client, it is * seperated from ->request() to preserve logic and readability * * @param Zend_Http_Client_Adapter_Interface|string $adapter */ public function setAdapter($adapter) { if (is_string($adapter)) { try { Zend_Loader::loadClass($adapter); } catch (Zend_Exception $e) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("Unable to load adapter '$adapter': {$e->getMessage()}"); } $adapter = new $adapter(); } if (! $adapter instanceof Zend_Http_Client_Adapter_Interface) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception('Passed adapter is not a HTTP connection adapter'); } $this->adapter = $adapter; $config = $this->config; unset($config['adapter']); $this->adapter->setConfig($config); } /** * Send the HTTP request and return an HTTP response object * * @param string $method * @return Zend_Http_Response */ public function request($method = null) { if (! $this->uri instanceof Zend_Uri_Http) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception('No valid URI has been passed to the client'); } if ($method) $this->setMethod($method); $this->redirectCounter = 0; $response = null; // Make sure the adapter is loaded if ($this->adapter == null) $this->setAdapter($this->config['adapter']); // Send the first request. If redirected, continue. do { // Clone the URI and add the additional GET parameters to it $uri = clone $this->uri; if (! empty($this->paramsGet)) { $query = $uri->getQuery(); if (! empty($query)) $query .= '&'; $query .= http_build_query($this->paramsGet, null, '&'); $uri->setQuery($query); } $body = $this->prepare_body(); $headers = $this->prepare_headers(); // Open the connection, send the request and read the response $this->adapter->connect($uri->getHost(), $uri->getPort(), ($uri->getScheme() == 'https' ? true : false)); $this->last_request = $this->adapter->write($this->method, $uri, $this->config['httpversion'], $headers, $body); $response = $this->adapter->read(); if (! $response) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception('Unable to read response, or response is empty'); } $response = Zend_Http_Response::fromString($response); if ($this->config['storeresponse']) $this->last_response = $response; // Load cookies into cookie jar if (isset($this->cookiejar)) $this->cookiejar->addCookiesFromResponse($response, $uri); // If we got redirected, look for the Location header if ($response->isRedirect() && ($location = $response->getHeader('location'))) { // Check whether we send the exact same request again, or drop the parameters // and send a GET request if ($response->getStatus() == 303 || ((! $this->config['strictredirects']) && ($response->getStatus() == 302 || $response->getStatus() == 301))) { $this->resetParameters(); $this->setMethod(self::GET); } // If we got a well formed absolute URI if (Zend_Uri_Http::check($location)) { $this->setHeaders('host', null); $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->uri->setQuery($query); // Else, if we got just an absolute path, set it if (strpos($location, '/') === 0) { $this->uri->setPath($location); // Else, assume we have a relative path } else { // Get the current path directory, removing any trailing slashes $path = $this->uri->getPath(); $path = rtrim(substr($path, 0, strrpos($path, '/')), "/"); $this->uri->setPath($path . '/' . $location); } } ++ $this->redirectCounter; } else { // If we didn't get any location, stop redirecting break; } } while ($this->redirectCounter < $this->config['maxredirects']); return $response; } /** * Prepare the request headers * * @return array */ protected function prepare_headers() { $headers = array(); // Set the host header if (! isset($this->headers['host'])) { $host = $this->uri->getHost(); // If the port is not default, add it if (! (($this->uri->getScheme() == 'http' && $this->uri->getPort() == 80) || ($this->uri->getScheme() == 'https' && $this->uri->getPort() == 443))) { $host .= ':' . $this->uri->getPort(); } $headers[] = "Host: {$host}"; } // Set the connection header if (! isset($this->headers['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 (! isset($this->headers['accept-encoding'])) { if (function_exists('gzinflate')) { $headers[] = 'Accept-encoding: gzip, deflate'; } else { $headers[] = 'Accept-encoding: identity'; } } // Set the content-type header if ($this->method == self::POST && (! isset($this->headers['content-type']) && isset($this->enctype))) { $headers[] = "Content-type: {$this->enctype}"; } // Set the user agent header if (! isset($this->headers['user-agent']) && isset($this->config['useragent'])) { $headers[] = "User-agent: {$this->config['useragent']}"; } // Set HTTP authentication if needed if (is_array($this->auth)) { $auth = self::encodeAuthHeader($this->auth['user'], $this->auth['password'], $this->auth['type']); $headers[] = "Authorization: {$auth}"; } // Load cookies from cookie jar if (isset($this->cookiejar)) { $cookstr = $this->cookiejar->getMatchingCookies($this->uri, true, Zend_Http_CookieJar::COOKIE_STRING_CONCAT); if ($cookstr) $headers[] = "Cookie: {$cookstr}"; } // Add all other user defined headers foreach ($this->headers as $header) { list($name, $value) = $header; if (is_array($value)) $value = implode(', ', $value); $headers[] = "$name: $value"; } return $headers; } /** * Prepare the request body (for POST and PUT requests) * * @return string */ protected function prepare_body() { // According to RFC2616, a TRACE request should not have a body. if ($this->method == self::TRACE) { return ''; } // If we have raw_post_data set, just use it as the body. if (isset($this->raw_post_data)) { $this->setHeaders('Content-length', strlen($this->raw_post_data)); return $this->raw_post_data; } $body = ''; // If we have files to upload, force enctype to multipart/form-data if (count($this->files) > 0) $this->setEncType(self::ENC_FORMDATA); // If we have POST parameters or files, encode and add them to the body if (count($this->paramsPost) > 0 || count($this->files) > 0) { switch ($this->enctype) { case self::ENC_FORMDATA: // Encode body as multipart/form-data $boundary = '---ZENDHTTPCLIENT-' . md5(microtime()); $this->setHeaders('Content-type', self::ENC_FORMDATA . "; boundary={$boundary}"); // Get POST parameters and encode them $params = $this->_getParametersRecursive($this->paramsPost); foreach ($params as $pp) { $body .= self::encodeFormData($boundary, $pp[0], $pp[1]); } // Encode files foreach ($this->files as $name => $file) { $fhead = array('Content-type' => $file[1]); $body .= self::encodeFormData($boundary, $name, $file[2], $file[0], $fhead); } $body .= "--{$boundary}--\r\n"; break; case self::ENC_URLENCODED: // Encode body as application/x-www-form-urlencoded $this->setHeaders('Content-type', self::ENC_URLENCODED); $body = http_build_query($this->paramsPost, '', '&'); break; default: require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("Cannot handle content type '{$this->enctype}' automatically." . " Please use Zend_Http_Client::setRawData to send this kind of content."); break; } } if ($body) $this->setHeaders('Content-length', strlen($body)); return $body; } /** * Helper method that gets a possibly multi-level parameters array (get or * post) and flattens it. * * The method returns an array of (key, value) pairs (because keys are not * necessarily unique. If one of the parameters in as array, it will also * add a [] suffix to the key. * * @param array $parray The parameters array * @param bool $urlencode Whether to urlencode the name and value * @return array */ protected function _getParametersRecursive($parray, $urlencode = false) { if (! is_array($parray)) return $parray; $parameters = array(); foreach ($parray as $name => $value) { if ($urlencode) $name = urlencode($name); // If $value is an array, iterate over it if (is_array($value)) { $name .= ($urlencode ? '%5B%5D' : '[]'); foreach ($value as $subval) { if ($urlencode) $subval = urlencode($subval); $parameters[] = array($name, $subval); } } else { if ($urlencode) $value = urlencode($value); $parameters[] = array($name, $value); } } return $parameters; } /** * 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 static 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; } /** * 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 */ 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) { require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("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: require_once 'external/Zend/Http/Client/Exception.php'; throw new Zend_Http_Client_Exception("Not a supported HTTP authentication type: '$type'"); } return $authHeader; } }