|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00027 require_once 'Zend/Uri/Http.php'; 00031 require_once 'Zend/Http/Client/Adapter/Interface.php'; 00035 require_once 'Zend/Http/Client/Adapter/Stream.php'; 00036 00047 class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream 00048 { 00054 protected $socket = null; 00055 00061 protected $connected_to = array(null, null); 00062 00068 protected $out_stream = null; 00069 00075 protected $config = array( 00076 'persistent' => false, 00077 'ssltransport' => 'ssl', 00078 'sslcert' => null, 00079 'sslpassphrase' => null, 00080 'sslusecontext' => false 00081 ); 00082 00088 protected $method = null; 00089 00095 protected $_context = null; 00096 00101 public function __construct() 00102 { 00103 } 00104 00110 public function setConfig($config = array()) 00111 { 00112 if ($config instanceof Zend_Config) { 00113 $config = $config->toArray(); 00114 00115 } elseif (! is_array($config)) { 00116 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00117 throw new Zend_Http_Client_Adapter_Exception( 00118 'Array or Zend_Config object expected, got ' . gettype($config) 00119 ); 00120 } 00121 00122 foreach ($config as $k => $v) { 00123 $this->config[strtolower($k)] = $v; 00124 } 00125 } 00126 00132 public function getConfig() 00133 { 00134 return $this->config; 00135 } 00136 00150 public function setStreamContext($context) 00151 { 00152 if (is_resource($context) && get_resource_type($context) == 'stream-context') { 00153 $this->_context = $context; 00154 00155 } elseif (is_array($context)) { 00156 $this->_context = stream_context_create($context); 00157 00158 } else { 00159 // Invalid parameter 00160 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00161 throw new Zend_Http_Client_Adapter_Exception( 00162 "Expecting either a stream context resource or array, got " . gettype($context) 00163 ); 00164 } 00165 00166 return $this; 00167 } 00168 00176 public function getStreamContext() 00177 { 00178 if (! $this->_context) { 00179 $this->_context = stream_context_create(); 00180 } 00181 00182 return $this->_context; 00183 } 00184 00192 public function connect($host, $port = 80, $secure = false) 00193 { 00194 // If the URI should be accessed via SSL, prepend the Hostname with ssl:// 00195 $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host; 00196 00197 // If we are connected to the wrong host, disconnect first 00198 if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { 00199 if (is_resource($this->socket)) $this->close(); 00200 } 00201 00202 // Now, if we are not connected, connect 00203 if (! is_resource($this->socket) || ! $this->config['keepalive']) { 00204 $context = $this->getStreamContext(); 00205 if ($secure || $this->config['sslusecontext']) { 00206 if ($this->config['sslcert'] !== null) { 00207 if (! stream_context_set_option($context, 'ssl', 'local_cert', 00208 $this->config['sslcert'])) { 00209 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00210 throw new Zend_Http_Client_Adapter_Exception('Unable to set sslcert option'); 00211 } 00212 } 00213 if ($this->config['sslpassphrase'] !== null) { 00214 if (! stream_context_set_option($context, 'ssl', 'passphrase', 00215 $this->config['sslpassphrase'])) { 00216 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00217 throw new Zend_Http_Client_Adapter_Exception('Unable to set sslpassphrase option'); 00218 } 00219 } 00220 } 00221 00222 $flags = STREAM_CLIENT_CONNECT; 00223 if ($this->config['persistent']) $flags |= STREAM_CLIENT_PERSISTENT; 00224 00225 $this->socket = @stream_socket_client($host . ':' . $port, 00226 $errno, 00227 $errstr, 00228 (int) $this->config['timeout'], 00229 $flags, 00230 $context); 00231 00232 if (! $this->socket) { 00233 $this->close(); 00234 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00235 throw new Zend_Http_Client_Adapter_Exception( 00236 'Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr); 00237 } 00238 00239 // Set the stream timeout 00240 if (! stream_set_timeout($this->socket, (int) $this->config['timeout'])) { 00241 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00242 throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout'); 00243 } 00244 00245 // Update connected_to 00246 $this->connected_to = array($host, $port); 00247 } 00248 } 00249 00260 public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') 00261 { 00262 // Make sure we're properly connected 00263 if (! $this->socket) { 00264 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00265 throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are not connected'); 00266 } 00267 00268 $host = $uri->getHost(); 00269 $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host; 00270 if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) { 00271 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00272 throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong host'); 00273 } 00274 00275 // Save request method for later 00276 $this->method = $method; 00277 00278 // Build request headers 00279 $path = $uri->getPath(); 00280 if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); 00281 $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; 00282 foreach ($headers as $k => $v) { 00283 if (is_string($k)) $v = ucfirst($k) . ": $v"; 00284 $request .= "$v\r\n"; 00285 } 00286 00287 if(is_resource($body)) { 00288 $request .= "\r\n"; 00289 } else { 00290 // Add the request body 00291 $request .= "\r\n" . $body; 00292 } 00293 00294 // Send the request 00295 if (! @fwrite($this->socket, $request)) { 00296 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00297 throw new Zend_Http_Client_Adapter_Exception('Error writing request to server'); 00298 } 00299 00300 if(is_resource($body)) { 00301 if(stream_copy_to_stream($body, $this->socket) == 0) { 00302 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00303 throw new Zend_Http_Client_Adapter_Exception('Error writing request to server'); 00304 } 00305 } 00306 00307 return $request; 00308 } 00309 00315 public function read() 00316 { 00317 // First, read headers only 00318 $response = ''; 00319 $gotStatus = false; 00320 $stream = !empty($this->config['stream']); 00321 00322 while (($line = @fgets($this->socket)) !== false) { 00323 $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); 00324 if ($gotStatus) { 00325 $response .= $line; 00326 if (rtrim($line) === '') break; 00327 } 00328 } 00329 00330 $this->_checkSocketReadTimeout(); 00331 00332 $statusCode = Zend_Http_Response::extractCode($response); 00333 00334 // Handle 100 and 101 responses internally by restarting the read again 00335 if ($statusCode == 100 || $statusCode == 101) return $this->read(); 00336 00337 // Check headers to see what kind of connection / transfer encoding we have 00338 $headers = Zend_Http_Response::extractHeaders($response); 00339 00344 if ($statusCode == 304 || $statusCode == 204 || 00345 $this->method == Zend_Http_Client::HEAD) { 00346 00347 // Close the connection if requested to do so by the server 00348 if (isset($headers['connection']) && $headers['connection'] == 'close') { 00349 $this->close(); 00350 } 00351 return $response; 00352 } 00353 00354 // If we got a 'transfer-encoding: chunked' header 00355 if (isset($headers['transfer-encoding'])) { 00356 00357 if (strtolower($headers['transfer-encoding']) == 'chunked') { 00358 00359 do { 00360 $line = @fgets($this->socket); 00361 $this->_checkSocketReadTimeout(); 00362 00363 $chunk = $line; 00364 00365 // Figure out the next chunk size 00366 $chunksize = trim($line); 00367 if (! ctype_xdigit($chunksize)) { 00368 $this->close(); 00369 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00370 throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' . 00371 $chunksize . '" unable to read chunked body'); 00372 } 00373 00374 // Convert the hexadecimal value to plain integer 00375 $chunksize = hexdec($chunksize); 00376 00377 // Read next chunk 00378 $read_to = ftell($this->socket) + $chunksize; 00379 00380 do { 00381 $current_pos = ftell($this->socket); 00382 if ($current_pos >= $read_to) break; 00383 00384 if($this->out_stream) { 00385 if(stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) { 00386 $this->_checkSocketReadTimeout(); 00387 break; 00388 } 00389 } else { 00390 $line = @fread($this->socket, $read_to - $current_pos); 00391 if ($line === false || strlen($line) === 0) { 00392 $this->_checkSocketReadTimeout(); 00393 break; 00394 } 00395 $chunk .= $line; 00396 } 00397 } while (! feof($this->socket)); 00398 00399 $chunk .= @fgets($this->socket); 00400 $this->_checkSocketReadTimeout(); 00401 00402 if(!$this->out_stream) { 00403 $response .= $chunk; 00404 } 00405 } while ($chunksize > 0); 00406 } else { 00407 $this->close(); 00408 throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' . 00409 $headers['transfer-encoding'] . '" transfer encoding'); 00410 } 00411 00412 // We automatically decode chunked-messages when writing to a stream 00413 // this means we have to disallow the Zend_Http_Response to do it again 00414 if ($this->out_stream) { 00415 $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response); 00416 } 00417 // Else, if we got the content-length header, read this number of bytes 00418 } elseif (isset($headers['content-length'])) { 00419 00420 // If we got more than one Content-Length header (see ZF-9404) use 00421 // the last value sent 00422 if (is_array($headers['content-length'])) { 00423 $contentLength = $headers['content-length'][count($headers['content-length']) - 1]; 00424 } else { 00425 $contentLength = $headers['content-length']; 00426 } 00427 00428 $current_pos = ftell($this->socket); 00429 $chunk = ''; 00430 00431 for ($read_to = $current_pos + $contentLength; 00432 $read_to > $current_pos; 00433 $current_pos = ftell($this->socket)) { 00434 00435 if($this->out_stream) { 00436 if(@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) { 00437 $this->_checkSocketReadTimeout(); 00438 break; 00439 } 00440 } else { 00441 $chunk = @fread($this->socket, $read_to - $current_pos); 00442 if ($chunk === false || strlen($chunk) === 0) { 00443 $this->_checkSocketReadTimeout(); 00444 break; 00445 } 00446 00447 $response .= $chunk; 00448 } 00449 00450 // Break if the connection ended prematurely 00451 if (feof($this->socket)) break; 00452 } 00453 00454 // Fallback: just read the response until EOF 00455 } else { 00456 00457 do { 00458 if($this->out_stream) { 00459 if(@stream_copy_to_stream($this->socket, $this->out_stream) == 0) { 00460 $this->_checkSocketReadTimeout(); 00461 break; 00462 } 00463 } else { 00464 $buff = @fread($this->socket, 8192); 00465 if ($buff === false || strlen($buff) === 0) { 00466 $this->_checkSocketReadTimeout(); 00467 break; 00468 } else { 00469 $response .= $buff; 00470 } 00471 } 00472 00473 } while (feof($this->socket) === false); 00474 00475 $this->close(); 00476 } 00477 00478 // Close the connection if requested to do so by the server 00479 if (isset($headers['connection']) && $headers['connection'] == 'close') { 00480 $this->close(); 00481 } 00482 00483 return $response; 00484 } 00485 00490 public function close() 00491 { 00492 if (is_resource($this->socket)) @fclose($this->socket); 00493 $this->socket = null; 00494 $this->connected_to = array(null, null); 00495 } 00496 00503 protected function _checkSocketReadTimeout() 00504 { 00505 if ($this->socket) { 00506 $info = stream_get_meta_data($this->socket); 00507 $timedout = $info['timed_out']; 00508 if ($timedout) { 00509 $this->close(); 00510 require_once 'Zend/Http/Client/Adapter/Exception.php'; 00511 throw new Zend_Http_Client_Adapter_Exception( 00512 "Read timed out after {$this->config['timeout']} seconds", 00513 Zend_Http_Client_Adapter_Exception::READ_TIMEOUT 00514 ); 00515 } 00516 } 00517 } 00518 00525 public function setOutputStream($stream) 00526 { 00527 $this->out_stream = $stream; 00528 return $this; 00529 } 00530 00537 public function __destruct() 00538 { 00539 if (! $this->config['persistent']) { 00540 if ($this->socket) $this->close(); 00541 } 00542 } 00543 }