| <?php |
| |
| /** |
| * Zend Framework |
| * |
| * LICENSE |
| * |
| * This source file is subject to the new BSD license that is bundled |
| * with this package in the file LICENSE.txt. |
| * It is also available through the world-wide-web at this URL: |
| * http://framework.zend.com/license/new-bsd |
| * If you did not receive a copy of the license and are unable to |
| * obtain it through the world-wide-web, please send an email |
| * to license@zend.com so we can send you a copy immediately. |
| * |
| * @category Zend |
| * @package Zend_Http |
| * @subpackage Client_Adapter |
| * @version $Id: Socket.php 8064 2008-02-16 10:58:39Z thomas $ |
| * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) |
| * @license http://framework.zend.com/license/new-bsd New BSD License |
| */ |
| |
| require_once 'external/Zend/Uri/Http.php'; |
| require_once 'external/Zend/Http/Client/Adapter/Interface.php'; |
| |
| /** |
| * A sockets based (stream_socket_client) adapter class for Zend_Http_Client. Can be used |
| * on almost every PHP environment, and does not require any special extensions. |
| * |
| * @category Zend |
| * @package Zend_Http |
| * @subpackage Client_Adapter |
| * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) |
| * @license http://framework.zend.com/license/new-bsd New BSD License |
| */ |
| class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interface { |
| /** |
| * The socket for server connection |
| * |
| * @var resource|null |
| */ |
| protected $socket = null; |
| |
| /** |
| * What host/port are we connected to? |
| * |
| * @var array |
| */ |
| protected $connected_to = array(null, null); |
| |
| /** |
| * Parameters array |
| * |
| * @var array |
| */ |
| protected $config = array('ssltransport' => 'ssl', 'sslcert' => null, 'sslpassphrase' => null); |
| |
| /** |
| * Request method - will be set by write() and might be used by read() |
| * |
| * @var string |
| */ |
| protected $method = null; |
| |
| /** |
| * Adapter constructor, currently empty. Config is set using setConfig() |
| * |
| */ |
| public function __construct() {} |
| |
| /** |
| * Set the configuration array for the adapter |
| * |
| * @param array $config |
| */ |
| public function setConfig($config = array()) { |
| if (! is_array($config)) { |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('$config expects an array, ' . gettype($config) . ' recieved.'); |
| } |
| |
| foreach ($config as $k => $v) { |
| $this->config[strtolower($k)] = $v; |
| } |
| } |
| |
| /** |
| * Connect to the remote server |
| * |
| * @param string $host |
| * @param int $port |
| * @param boolean $secure |
| * @param int $timeout |
| */ |
| public function connect($host, $port = 80, $secure = false) { |
| // If the URI should be accessed via SSL, prepend the Hostname with ssl:// |
| $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host; |
| |
| // If we are connected to the wrong host, disconnect first |
| if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { |
| if (is_resource($this->socket)) $this->close(); |
| } |
| |
| // Now, if we are not connected, connect |
| if (! is_resource($this->socket) || ! $this->config['keepalive']) { |
| $context = stream_context_create(); |
| if ($secure) { |
| if ($this->config['sslcert'] !== null) { |
| if (! stream_context_set_option($context, 'ssl', 'local_cert', $this->config['sslcert'])) { |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('Unable to set sslcert option'); |
| } |
| } |
| if ($this->config['sslpassphrase'] !== null) { |
| if (! stream_context_set_option($context, 'ssl', 'passphrase', $this->config['sslpassphrase'])) { |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('Unable to set sslpassphrase option'); |
| } |
| } |
| } |
| |
| $this->socket = @stream_socket_client($host . ':' . $port, $errno, $errstr, (int)$this->config['timeout'], STREAM_CLIENT_CONNECT, $context); |
| if (! $this->socket) { |
| $this->close(); |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr); |
| } |
| |
| // Set the stream timeout |
| if (! stream_set_timeout($this->socket, (int)$this->config['timeout'])) { |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout'); |
| } |
| |
| // Update connected_to |
| $this->connected_to = array($host, $port); |
| } |
| } |
| |
| /** |
| * Send request to the remote server |
| * |
| * @param string $method |
| * @param Zend_Uri_Http $uri |
| * @param string $http_ver |
| * @param array $headers |
| * @param string $body |
| * @return string Request as string |
| */ |
| public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') { |
| // Make sure we're properly connected |
| if (! $this->socket) { |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are not connected'); |
| } |
| |
| $host = $uri->getHost(); |
| $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host; |
| if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) { |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong host'); |
| } |
| |
| // Save request method for later |
| $this->method = $method; |
| |
| // Build request headers |
| $path = $uri->getPath(); |
| if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); |
| $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; |
| foreach ($headers as $k => $v) { |
| if (is_string($k)) $v = ucfirst($k) . ": $v"; |
| $request .= "$v\r\n"; |
| } |
| |
| // Add the request body |
| $request .= "\r\n" . $body; |
| |
| // Send the request |
| if (! @fwrite($this->socket, $request)) { |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('Error writing request to server'); |
| } |
| |
| return $request; |
| } |
| |
| /** |
| * Read response from server |
| * |
| * @return string |
| */ |
| public function read() { |
| // First, read headers only |
| $response = ''; |
| $gotStatus = false; |
| while ($line = @fgets($this->socket)) { |
| $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); |
| if ($gotStatus) { |
| $response .= $line; |
| if (! chop($line)) break; |
| } |
| } |
| |
| // Handle 100 and 101 responses internally by restarting the read again |
| if (Zend_Http_Response::extractCode($response) == 100 || Zend_Http_Response::extractCode($response) == 101) return $this->read(); |
| |
| // If this was a HEAD request, return after reading the header (no need to read body) |
| if ($this->method == Zend_Http_Client::HEAD) return $response; |
| |
| // Check headers to see what kind of connection / transfer encoding we have |
| $headers = Zend_Http_Response::extractHeaders($response); |
| |
| // if the connection is set to close, just read until socket closes |
| if (isset($headers['connection']) && $headers['connection'] == 'close') { |
| while ($buff = @fread($this->socket, 8192)) { |
| $response .= $buff; |
| } |
| |
| $this->close(); |
| |
| // Else, if we got a transfer-encoding header (chunked body) |
| } elseif (isset($headers['transfer-encoding'])) { |
| if ($headers['transfer-encoding'] == 'chunked') { |
| do { |
| $chunk = ''; |
| $line = @fgets($this->socket); |
| $chunk .= $line; |
| |
| $hexchunksize = ltrim(chop($line), '0'); |
| $hexchunksize = strlen($hexchunksize) ? strtolower($hexchunksize) : 0; |
| |
| $chunksize = hexdec(chop($line)); |
| if (dechex($chunksize) != $hexchunksize) { |
| @fclose($this->socket); |
| require_once 'external/Zend/Http/Client/Adapter/Exception.php'; |
| throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' . $hexchunksize . '" unable to read chunked body'); |
| } |
| |
| $left_to_read = $chunksize; |
| while ($left_to_read > 0) { |
| $line = @fread($this->socket, $left_to_read); |
| $chunk .= $line; |
| $left_to_read -= strlen($line); |
| } |
| |
| $chunk .= @fgets($this->socket); |
| $response .= $chunk; |
| } while ($chunksize > 0); |
| } else { |
| throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' . $headers['transfer-encoding'] . '" transfer encoding'); |
| } |
| |
| // Else, if we got the content-length header, read this number of bytes |
| } elseif (isset($headers['content-length'])) { |
| $left_to_read = $headers['content-length']; |
| $chunk = ''; |
| while ($left_to_read > 0) { |
| $chunk = @fread($this->socket, $left_to_read); |
| $left_to_read -= strlen($chunk); |
| $response .= $chunk; |
| } |
| |
| // Fallback: just read the response (should not happen) |
| } else { |
| while ($buff = @fread($this->socket, 8192)) { |
| $response .= $buff; |
| } |
| |
| $this->close(); |
| } |
| |
| return $response; |
| } |
| |
| /** |
| * Close the connection to the server |
| * |
| */ |
| public function close() { |
| if (is_resource($this->socket)) @fclose($this->socket); |
| $this->socket = null; |
| $this->connected_to = array(null, null); |
| } |
| |
| /** |
| * Destructor: make sure the socket is disconnected |
| * |
| */ |
| public function __destruct() { |
| if ($this->socket) $this->close(); |
| } |
| } |