http_socket.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: http_socket.php 8269 2009-08-01 23:38:26Z jperras $ */
00003 /**
00004  * HTTP Socket connection class.
00005  *
00006  * PHP versions 4 and 5
00007  *
00008  * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
00009  * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00010  *
00011  * Licensed under The MIT License
00012  * Redistributions of files must retain the above copyright notice.
00013  *
00014  * @filesource
00015  * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00016  * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00017  * @package       cake
00018  * @subpackage    cake.cake.libs
00019  * @since         CakePHP(tm) v 1.2.0
00020  * @version       $Revision: 8269 $
00021  * @modifiedby    $LastChangedBy: jperras $
00022  * @lastmodified  $Date: 2009-08-01 19:38:26 -0400 (Sat, 01 Aug 2009) $
00023  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
00024  */
00025 App::import('Core', array('Socket', 'Set', 'Router'));
00026 /**
00027  * Cake network socket connection class.
00028  *
00029  * Core base class for HTTP network communication.
00030  *
00031  * @package       cake
00032  * @subpackage    cake.cake.libs
00033  */
00034 class HttpSocket extends CakeSocket {
00035 /**
00036  * Object description
00037  *
00038  * @var string
00039  * @access public
00040  */
00041     var $description = 'HTTP-based DataSource Interface';
00042 /**
00043  * When one activates the $quirksMode by setting it to true, all checks meant to enforce RFC 2616 (HTTP/1.1 specs)
00044  * will be disabled and additional measures to deal with non-standard responses will be enabled.
00045  *
00046  * @var boolean
00047  * @access public
00048  */
00049     var $quirksMode = false;
00050 /**
00051  * The default values to use for a request
00052  *
00053  * @var array
00054  * @access public
00055  */
00056     var $request = array(
00057         'method' => 'GET',
00058         'uri' => array(
00059             'scheme' => 'http',
00060             'host' => null,
00061             'port' => 80,
00062             'user' => null,
00063             'pass' => null,
00064             'path' => null,
00065             'query' => null,
00066             'fragment' => null
00067         ),
00068         'auth' => array(
00069             'method' => 'Basic',
00070             'user' => null,
00071             'pass' => null
00072         ),
00073         'version' => '1.1',
00074         'body' => '',
00075         'line' => null,
00076         'header' => array(
00077             'Connection' => 'close',
00078             'User-Agent' => 'CakePHP'
00079         ),
00080         'raw' => null,
00081         'cookies' => array()
00082     );
00083 /**
00084 * The default structure for storing the response
00085 *
00086 * @var array
00087 * @access public
00088 */
00089     var $response = array(
00090         'raw' => array(
00091             'status-line' => null,
00092             'header' => null,
00093             'body' => null,
00094             'response' => null
00095         ),
00096         'status' => array(
00097             'http-version' => null,
00098             'code' => null,
00099             'reason-phrase' => null
00100         ),
00101         'header' => array(),
00102         'body' => '',
00103         'cookies' => array()
00104     );
00105 /**
00106  * Default configuration settings for the HttpSocket
00107  *
00108  * @var array
00109  * @access public
00110  */
00111     var $config = array(
00112         'persistent' => false,
00113         'host'       => 'localhost',
00114         'protocol'   => 'tcp',
00115         'port'       => 80,
00116         'timeout'    => 30,
00117         'request' => array(
00118             'uri' => array(
00119                 'scheme' => 'http',
00120                 'host' => 'localhost',
00121                 'port' => 80
00122             ),
00123             'auth' => array(
00124                 'method' => 'Basic',
00125                 'user' => null,
00126                 'pass' => null
00127             ),
00128             'cookies' => array()
00129         )
00130     );
00131 /**
00132  * String that represents a line break.
00133  *
00134  * @var string
00135  * @access public
00136  */
00137     var $lineBreak = "\r\n";
00138 
00139 /**
00140  * Build an HTTP Socket using the specified configuration.
00141  *
00142  * @param array $config Configuration
00143  */
00144     function __construct($config = array()) {
00145         if (is_string($config)) {
00146             $this->configUri($config);
00147         } elseif (is_array($config)) {
00148             if (isset($config['request']['uri']) && is_string($config['request']['uri'])) {
00149                 $this->configUri($config['request']['uri']);
00150                 unset($config['request']['uri']);
00151             }
00152             $this->config = Set::merge($this->config, $config);
00153         }
00154         parent::__construct($this->config);
00155     }
00156 /**
00157  * Issue the specified request.
00158  *
00159  * @param mixed $request Either an URI string, or an array defining host/uri
00160  * @return mixed false on error, request body on success
00161  * @access public
00162  */
00163     function request($request = array()) {
00164         $this->reset(false);
00165 
00166         if (is_string($request)) {
00167             $request = array('uri' => $request);
00168         } elseif (!is_array($request)) {
00169             return false;
00170         }
00171 
00172         if (!isset($request['uri'])) {
00173             $request['uri'] = null;
00174         }
00175         $uri = $this->parseUri($request['uri']);
00176 
00177         if (!isset($uri['host'])) {
00178             $host = $this->config['host'];
00179         }
00180         if (isset($request['host'])) {
00181             $host = $request['host'];
00182             unset($request['host']);
00183         }
00184 
00185         $request['uri'] = $this->url($request['uri']);
00186         $request['uri'] = $this->parseUri($request['uri'], true);
00187         $this->request = Set::merge($this->request, $this->config['request'], $request);
00188 
00189         $this->configUri($this->request['uri']);
00190 
00191         if (isset($host)) {
00192             $this->config['host'] = $host;
00193         }
00194         $cookies = null;
00195 
00196         if (is_array($this->request['header'])) {
00197             $this->request['header'] = $this->parseHeader($this->request['header']);
00198             if (!empty($this->request['cookies'])) {
00199                 $cookies = $this->buildCookies($this->request['cookies']);
00200             }
00201             $this->request['header'] = array_merge(array('Host' => $this->request['uri']['host']), $this->request['header']);
00202         }
00203 
00204         if (isset($this->request['auth']['user']) && isset($this->request['auth']['pass'])) {
00205             $this->request['header']['Authorization'] = $this->request['auth']['method'] . " " . base64_encode($this->request['auth']['user'] . ":" . $this->request['auth']['pass']);
00206         }
00207         if (isset($this->request['uri']['user']) && isset($this->request['uri']['pass'])) {
00208             $this->request['header']['Authorization'] = $this->request['auth']['method'] . " " . base64_encode($this->request['uri']['user'] . ":" . $this->request['uri']['pass']);
00209         }
00210 
00211         if (is_array($this->request['body'])) {
00212             $this->request['body'] = $this->httpSerialize($this->request['body']);
00213         }
00214 
00215         if (!empty($this->request['body']) && !isset($this->request['header']['Content-Type'])) {
00216             $this->request['header']['Content-Type'] = 'application/x-www-form-urlencoded';
00217         }
00218 
00219         if (!empty($this->request['body']) && !isset($this->request['header']['Content-Length'])) {
00220             $this->request['header']['Content-Length'] = strlen($this->request['body']);
00221         }
00222 
00223         $connectionType = null;
00224         if (isset($this->request['header']['Connection'])) {
00225             $connectionType = $this->request['header']['Connection'];
00226         }
00227         $this->request['header'] = $this->buildHeader($this->request['header']).$cookies;
00228 
00229         if (empty($this->request['line'])) {
00230             $this->request['line'] = $this->buildRequestLine($this->request);
00231         }
00232 
00233         if ($this->quirksMode === false && $this->request['line'] === false) {
00234             return $this->response = false;
00235         }
00236 
00237         if ($this->request['line'] !== false) {
00238             $this->request['raw'] = $this->request['line'];
00239         }
00240 
00241         if ($this->request['header'] !== false) {
00242             $this->request['raw'] .= $this->request['header'];
00243         }
00244 
00245         $this->request['raw'] .= "\r\n";
00246         $this->request['raw'] .= $this->request['body'];
00247         $this->write($this->request['raw']);
00248 
00249         $response = null;
00250         while ($data = $this->read()) {
00251             $response .= $data;
00252         }
00253 
00254         if ($connectionType == 'close') {
00255             $this->disconnect();
00256         }
00257 
00258         $this->response = $this->parseResponse($response);
00259         if (!empty($this->response['cookies'])) {
00260             $this->config['request']['cookies'] = array_merge($this->config['request']['cookies'], $this->response['cookies']);
00261         }
00262 
00263         return $this->response['body'];
00264     }
00265 /**
00266  * Issues a GET request to the specified URI, query, and request.
00267  *
00268  * @param mixed $uri URI to request (see {@link parseUri()})
00269  * @param array $query Query to append to URI
00270  * @param array $request An indexed array with indexes such as 'method' or uri
00271  * @return mixed Result of request
00272  * @access public
00273  */
00274     function get($uri = null, $query = array(), $request = array()) {
00275         if (!empty($query)) {
00276             $uri =$this->parseUri($uri);
00277             if (isset($uri['query'])) {
00278                 $uri['query'] = array_merge($uri['query'], $query);
00279             } else {
00280                 $uri['query'] = $query;
00281             }
00282             $uri = $this->buildUri($uri);
00283         }
00284 
00285         $request = Set::merge(array('method' => 'GET', 'uri' => $uri), $request);
00286         return $this->request($request);
00287     }
00288 
00289 /**
00290  * Issues a POST request to the specified URI, query, and request.
00291  *
00292  * @param mixed $uri URI to request (see {@link parseUri()})
00293  * @param array $query Query to append to URI
00294  * @param array $request An indexed array with indexes such as 'method' or uri
00295  * @return mixed Result of request
00296  * @access public
00297  */
00298     function post($uri = null, $data = array(), $request = array()) {
00299         $request = Set::merge(array('method' => 'POST', 'uri' => $uri, 'body' => $data), $request);
00300         return $this->request($request);
00301     }
00302 /**
00303  * Issues a PUT request to the specified URI, query, and request.
00304  *
00305  * @param mixed $uri URI to request (see {@link parseUri()})
00306  * @param array $query Query to append to URI
00307  * @param array $request An indexed array with indexes such as 'method' or uri
00308  * @return mixed Result of request
00309  * @access public
00310  */
00311     function put($uri = null, $data = array(), $request = array()) {
00312         $request = Set::merge(array('method' => 'PUT', 'uri' => $uri, 'body' => $data), $request);
00313         return $this->request($request);
00314     }
00315 /**
00316  * Issues a DELETE request to the specified URI, query, and request.
00317  *
00318  * @param mixed $uri URI to request (see {@link parseUri()})
00319  * @param array $query Query to append to URI
00320  * @param array $request An indexed array with indexes such as 'method' or uri
00321  * @return mixed Result of request
00322  * @access public
00323  */
00324     function delete($uri = null, $data = array(), $request = array()) {
00325         $request = Set::merge(array('method' => 'DELETE', 'uri' => $uri, 'body' => $data), $request);
00326         return $this->request($request);
00327     }
00328 /**
00329  * undocumented function
00330  *
00331  * @param unknown $url
00332  * @param unknown $uriTemplate
00333  * @return void
00334  * @access public
00335  */
00336     function url($url = null, $uriTemplate = null) {
00337         if (is_null($url)) {
00338             $url = '/';
00339         }
00340         if (is_string($url)) {
00341             if ($url{0} == '/') {
00342                 $url = $this->config['request']['uri']['host'].':'.$this->config['request']['uri']['port'] . $url;
00343             }
00344             if (!preg_match('/^.+:\/\/|\*|^\//', $url)) {
00345                 $url = $this->config['request']['uri']['scheme'].'://'.$url;
00346             }
00347         } elseif (!is_array($url) && !empty($url)) {
00348             return false;
00349         }
00350 
00351         $base = array_merge($this->config['request']['uri'], array('scheme' => array('http', 'https'), 'port' => array(80, 443)));
00352         $url = $this->parseUri($url, $base);
00353 
00354         if (empty($url)) {
00355             $url = $this->config['request']['uri'];
00356         }
00357 
00358         if (!empty($uriTemplate)) {
00359             return $this->buildUri($url, $uriTemplate);
00360         }
00361         return $this->buildUri($url);
00362     }
00363 /**
00364  * Parses the given message and breaks it down in parts.
00365  *
00366  * @param string $message Message to parse
00367  * @return array Parsed message (with indexed elements such as raw, status, header, body)
00368  * @access protected
00369  */
00370     function parseResponse($message) {
00371         if (is_array($message)) {
00372             return $message;
00373         } elseif (!is_string($message)) {
00374             return false;
00375         }
00376 
00377         static $responseTemplate;
00378 
00379         if (empty($responseTemplate)) {
00380             $classVars = get_class_vars(__CLASS__);
00381             $responseTemplate = $classVars['response'];
00382         }
00383 
00384         $response = $responseTemplate;
00385 
00386         if (!preg_match("/^(.+\r\n)(.*)(?<=\r\n)\r\n/Us", $message, $match)) {
00387             return false;
00388         }
00389 
00390         list($null, $response['raw']['status-line'], $response['raw']['header']) = $match;
00391         $response['raw']['response'] = $message;
00392         $response['raw']['body'] = substr($message, strlen($match[0]));
00393 
00394         if (preg_match("/(.+) ([0-9]{3}) (.+)\r\n/DU", $response['raw']['status-line'], $match)) {
00395             $response['status']['http-version'] = $match[1];
00396             $response['status']['code'] = (int)$match[2];
00397             $response['status']['reason-phrase'] = $match[3];
00398         }
00399 
00400         $response['header'] = $this->parseHeader($response['raw']['header']);
00401         $transferEncoding = null;
00402         if (isset($response['header']['Transfer-Encoding'])) {
00403             $transferEncoding = $response['header']['Transfer-Encoding'];
00404         }
00405         $decoded = $this->decodeBody($response['raw']['body'], $transferEncoding);
00406         $response['body'] = $decoded['body'];
00407 
00408         if (!empty($decoded['header'])) {
00409             $response['header'] = $this->parseHeader($this->buildHeader($response['header']).$this->buildHeader($decoded['header']));
00410         }
00411 
00412         if (!empty($response['header'])) {
00413             $response['cookies'] = $this->parseCookies($response['header']);
00414         }
00415 
00416         foreach ($response['raw'] as $field => $val) {
00417             if ($val === '') {
00418                 $response['raw'][$field] = null;
00419             }
00420         }
00421 
00422         return $response;
00423     }
00424 /**
00425  * Generic function to decode a $body with a given $encoding. Returns either an array with the keys
00426  * 'body' and 'header' or false on failure.
00427  *
00428  * @param string $body A string continaing the body to decode
00429  * @param mixed $encoding Can be false in case no encoding is being used, or a string representing the encoding
00430  * @return mixed Array or false
00431  * @access protected
00432  */
00433     function decodeBody($body, $encoding = 'chunked') {
00434         if (!is_string($body)) {
00435             return false;
00436         }
00437         if (empty($encoding)) {
00438             return array('body' => $body, 'header' => false);
00439         }
00440         $decodeMethod = 'decode'.Inflector::camelize(str_replace('-', '_', $encoding)).'Body';
00441 
00442         if (!is_callable(array(&$this, $decodeMethod))) {
00443             if (!$this->quirksMode) {
00444                 trigger_error(sprintf(__('HttpSocket::decodeBody - Unknown encoding: %s. Activate quirks mode to surpress error.', true), h($encoding)), E_USER_WARNING);
00445             }
00446             return array('body' => $body, 'header' => false);
00447         }
00448         return $this->{$decodeMethod}($body);
00449     }
00450 /**
00451  * Decodes a chunked message $body and returns either an array with the keys 'body' and 'header' or false as
00452  * a result.
00453  *
00454  * @param string $body A string continaing the chunked body to decode
00455  * @return mixed Array or false
00456  * @access protected
00457  */
00458     function decodeChunkedBody($body) {
00459         if (!is_string($body)) {
00460             return false;
00461         }
00462 
00463         $decodedBody = null;
00464         $chunkLength = null;
00465 
00466         while ($chunkLength !== 0) {
00467             if (!preg_match("/^([0-9a-f]+) *(?:;(.+)=(.+))?\r\n/iU", $body, $match)) {
00468                 if (!$this->quirksMode) {
00469                     trigger_error(__('HttpSocket::decodeChunkedBody - Could not parse malformed chunk. Activate quirks mode to do this.', true), E_USER_WARNING);
00470                     return false;
00471                 }
00472                 break;
00473             }
00474 
00475             $chunkSize = 0;
00476             $hexLength = 0;
00477             $chunkExtensionName = '';
00478             $chunkExtensionValue = '';
00479             if (isset($match[0])) {
00480                 $chunkSize = $match[0];
00481             }
00482             if (isset($match[1])) {
00483                 $hexLength = $match[1];
00484             }
00485             if (isset($match[2])) {
00486                 $chunkExtensionName = $match[2];
00487             }
00488             if (isset($match[3])) {
00489                 $chunkExtensionValue = $match[3];
00490             }
00491 
00492             $body = substr($body, strlen($chunkSize));
00493             $chunkLength = hexdec($hexLength);
00494             $chunk = substr($body, 0, $chunkLength);
00495             if (!empty($chunkExtensionName)) {
00496                 /**
00497                  * @todo See if there are popular chunk extensions we should implement
00498                  */
00499             }
00500             $decodedBody .= $chunk;
00501             if ($chunkLength !== 0) {
00502                 $body = substr($body, $chunkLength+strlen("\r\n"));
00503             }
00504         }
00505 
00506         $entityHeader = false;
00507         if (!empty($body)) {
00508             $entityHeader = $this->parseHeader($body);
00509         }
00510         return array('body' => $decodedBody, 'header' => $entityHeader);
00511     }
00512 /**
00513  * Parses and sets the specified URI into current request configuration.
00514  *
00515  * @param mixed $uri URI (see {@link parseUri()})
00516  * @return array Current configuration settings
00517  * @access protected
00518  */
00519     function configUri($uri = null) {
00520         if (empty($uri)) {
00521             return false;
00522         }
00523 
00524         if (is_array($uri)) {
00525             $uri = $this->parseUri($uri);
00526         } else {
00527             $uri = $this->parseUri($uri, true);
00528         }
00529 
00530         if (!isset($uri['host'])) {
00531             return false;
00532         }
00533 
00534         $config = array(
00535             'request' => array(
00536                 'uri' => array_intersect_key($uri, $this->config['request']['uri']),
00537                 'auth' => array_intersect_key($uri, $this->config['request']['auth'])
00538             )
00539         );
00540         $this->config = Set::merge($this->config, $config);
00541         $this->config = Set::merge($this->config, array_intersect_key($this->config['request']['uri'], $this->config));
00542         return $this->config;
00543     }
00544 /**
00545  * Takes a $uri array and turns it into a fully qualified URL string
00546  *
00547  * @param array $uri A $uri array, or uses $this->config if left empty
00548  * @param string $uriTemplate The Uri template/format to use
00549  * @return string A fully qualified URL formated according to $uriTemplate
00550  * @access protected
00551  */
00552     function buildUri($uri = array(), $uriTemplate = '%scheme://%user:%pass@%host:%port/%path?%query#%fragment') {
00553         if (is_string($uri)) {
00554             $uri = array('host' => $uri);
00555         }
00556         $uri = $this->parseUri($uri, true);
00557 
00558         if (!is_array($uri) || empty($uri)) {
00559             return false;
00560         }
00561 
00562         $uri['path'] = preg_replace('/^\//', null, $uri['path']);
00563         $uri['query'] = $this->httpSerialize($uri['query']);
00564         $stripIfEmpty = array(
00565             'query' => '?%query',
00566             'fragment' => '#%fragment',
00567             'user' => '%user:%pass@'
00568         );
00569 
00570         foreach ($stripIfEmpty as $key => $strip) {
00571             if (empty($uri[$key])) {
00572                 $uriTemplate = str_replace($strip, null, $uriTemplate);
00573             }
00574         }
00575 
00576         $defaultPorts = array('http' => 80, 'https' => 443);
00577         if (array_key_exists($uri['scheme'], $defaultPorts) && $defaultPorts[$uri['scheme']] == $uri['port']) {
00578             $uriTemplate = str_replace(':%port', null, $uriTemplate);
00579         }
00580 
00581         foreach ($uri as $property => $value) {
00582             $uriTemplate = str_replace('%'.$property, $value, $uriTemplate);
00583         }
00584 
00585         if ($uriTemplate === '/*') {
00586             $uriTemplate = '*';
00587         }
00588         return $uriTemplate;
00589     }
00590 /**
00591  * Parses the given URI and breaks it down into pieces as an indexed array with elements
00592  * such as 'scheme', 'port', 'query'.
00593  *
00594  * @param string $uri URI to parse
00595  * @param mixed $base If true use default URI config, otherwise indexed array to set 'scheme', 'host', 'port', etc.
00596  * @return array Parsed URI
00597  * @access protected
00598  */
00599     function parseUri($uri = null, $base = array()) {
00600         $uriBase = array(
00601             'scheme' => array('http', 'https'),
00602             'host' => null,
00603             'port' => array(80, 443),
00604             'user' => null,
00605             'pass' => null,
00606             'path' => '/',
00607             'query' => null,
00608             'fragment' => null
00609         );
00610 
00611         if (is_string($uri)) {
00612             $uri = parse_url($uri);
00613         }
00614         if (!is_array($uri) || empty($uri)) {
00615             return false;
00616         }
00617         if ($base === true) {
00618             $base = $uriBase;
00619         }
00620 
00621         if (isset($base['port'], $base['scheme']) && is_array($base['port']) && is_array($base['scheme'])) {
00622             if (isset($uri['scheme']) && !isset($uri['port'])) {
00623                 $base['port'] = $base['port'][array_search($uri['scheme'], $base['scheme'])];
00624             } elseif (isset($uri['port']) && !isset($uri['scheme'])) {
00625                 $base['scheme'] = $base['scheme'][array_search($uri['port'], $base['port'])];
00626             }
00627         }
00628 
00629         if (is_array($base) && !empty($base)) {
00630             $uri = array_merge($base, $uri);
00631         }
00632 
00633         if (isset($uri['scheme']) && is_array($uri['scheme'])) {
00634             $uri['scheme'] = array_shift($uri['scheme']);
00635         }
00636         if (isset($uri['port']) && is_array($uri['port'])) {
00637             $uri['port'] = array_shift($uri['port']);
00638         }
00639 
00640         if (array_key_exists('query', $uri)) {
00641             $uri['query'] = $this->parseQuery($uri['query']);
00642         }
00643 
00644         if (!array_intersect_key($uriBase, $uri)) {
00645             return false;
00646         }
00647         return $uri;
00648     }
00649 /**
00650  * This function can be thought of as a reverse to PHP5's http_build_query(). It takes a given query string and turns it into an array and
00651  * supports nesting by using the php bracket syntax. So this menas you can parse queries like:
00652  *
00653  * - ?key[subKey]=value
00654  * - ?key[]=value1&key[]=value2
00655  *
00656  * A leading '?' mark in $query is optional and does not effect the outcome of this function. For the complete capabilities of this implementation
00657  * take a look at HttpSocketTest::testParseQuery()
00658  *
00659  * @param mixed $query A query string to parse into an array or an array to return directly "as is"
00660  * @return array The $query parsed into a possibly multi-level array. If an empty $query is given, an empty array is returned.
00661  * @access protected
00662  */
00663     function parseQuery($query) {
00664         if (is_array($query)) {
00665             return $query;
00666         }
00667         $parsedQuery = array();
00668 
00669         if (is_string($query) && !empty($query)) {
00670             $query = preg_replace('/^\?/', '', $query);
00671             $items = explode('&', $query);
00672 
00673             foreach ($items as $item) {
00674                 if (strpos($item, '=') !== false) {
00675                     list($key, $value) = explode('=', $item);
00676                 } else {
00677                     $key = $item;
00678                     $value = null;
00679                 }
00680 
00681                 $key = urldecode($key);
00682                 $value = urldecode($value);
00683 
00684                 if (preg_match_all('/\[([^\[\]]*)\]/iUs', $key, $matches)) {
00685                     $subKeys = $matches[1];
00686                     $rootKey = substr($key, 0, strpos($key, '['));
00687                     if (!empty($rootKey)) {
00688                         array_unshift($subKeys, $rootKey);
00689                     }
00690                     $queryNode =& $parsedQuery;
00691 
00692                     foreach ($subKeys as $subKey) {
00693                         if (!is_array($queryNode)) {
00694                             $queryNode = array();
00695                         }
00696 
00697                         if ($subKey === '') {
00698                             $queryNode[] = array();
00699                             end($queryNode);
00700                             $subKey = key($queryNode);
00701                         }
00702                         $queryNode =& $queryNode[$subKey];
00703                     }
00704                     $queryNode = $value;
00705                 } else {
00706                     $parsedQuery[$key] = $value;
00707                 }
00708             }
00709         }
00710         return $parsedQuery;
00711     }
00712 /**
00713  * Builds a request line according to HTTP/1.1 specs. Activate quirks mode to work outside specs.
00714  *
00715  * @param array $request Needs to contain a 'uri' key. Should also contain a 'method' key, otherwise defaults to GET.
00716  * @param string $versionToken The version token to use, defaults to HTTP/1.1
00717  * @return string Request line
00718  * @access protected
00719  */
00720     function buildRequestLine($request = array(), $versionToken = 'HTTP/1.1') {
00721         $asteriskMethods = array('OPTIONS');
00722 
00723         if (is_string($request)) {
00724             $isValid = preg_match("/(.+) (.+) (.+)\r\n/U", $request, $match);
00725             if (!$this->quirksMode && (!$isValid || ($match[2] == '*' && !in_array($match[3], $asteriskMethods)))) {
00726                 trigger_error(__('HttpSocket::buildRequestLine - Passed an invalid request line string. Activate quirks mode to do this.', true), E_USER_WARNING);
00727                 return false;
00728             }
00729             return $request;
00730         } elseif (!is_array($request)) {
00731             return false;
00732         } elseif (!array_key_exists('uri', $request)) {
00733             return false;
00734         }
00735 
00736         $request['uri'] = $this->parseUri($request['uri']);
00737         $request = array_merge(array('method' => 'GET'), $request);
00738         $request['uri'] = $this->buildUri($request['uri'], '/%path?%query');
00739 
00740         if (!$this->quirksMode && $request['uri'] === '*' && !in_array($request['method'], $asteriskMethods)) {
00741             trigger_error(sprintf(__('HttpSocket::buildRequestLine - The "*" asterisk character is only allowed for the following methods: %s. Activate quirks mode to work outside of HTTP/1.1 specs.', true), join(',', $asteriskMethods)), E_USER_WARNING);
00742             return false;
00743         }
00744         return $request['method'].' '.$request['uri'].' '.$versionToken.$this->lineBreak;
00745     }
00746 /**
00747  * Serializes an array for transport.
00748  *
00749  * @param array $data Data to serialize
00750  * @return string Serialized variable
00751  * @access protected
00752  */
00753     function httpSerialize($data = array()) {
00754         if (is_string($data)) {
00755             return $data;
00756         }
00757         if (empty($data) || !is_array($data)) {
00758             return false;
00759         }
00760         return substr(Router::queryString($data), 1);
00761     }
00762 /**
00763  * Builds the header.
00764  *
00765  * @param array $header Header to build
00766  * @return string Header built from array
00767  * @access protected
00768  */
00769     function buildHeader($header, $mode = 'standard') {
00770         if (is_string($header)) {
00771             return $header;
00772         } elseif (!is_array($header)) {
00773             return false;
00774         }
00775 
00776         $returnHeader = '';
00777         foreach ($header as $field => $contents) {
00778             if (is_array($contents) && $mode == 'standard') {
00779                 $contents = join(',', $contents);
00780             }
00781             foreach ((array)$contents as $content) {
00782                 $contents = preg_replace("/\r\n(?![\t ])/", "\r\n ", $content);
00783                 $field = $this->escapeToken($field);
00784 
00785                 $returnHeader .= $field.': '.$contents.$this->lineBreak;
00786             }
00787         }
00788         return $returnHeader;
00789     }
00790 
00791 /**
00792  * Parses an array based header.
00793  *
00794  * @param array $header Header as an indexed array (field => value)
00795  * @return array Parsed header
00796  * @access protected
00797  */
00798     function parseHeader($header) {
00799         if (is_array($header)) {
00800             foreach ($header as $field => $value) {
00801                 unset($header[$field]);
00802                 $field = strtolower($field);
00803                 preg_match_all('/(?:^|(?<=-))[a-z]/U', $field, $offsets, PREG_OFFSET_CAPTURE);
00804 
00805                 foreach ($offsets[0] as $offset) {
00806                     $field = substr_replace($field, strtoupper($offset[0]), $offset[1], 1);
00807                 }
00808                 $header[$field] = $value;
00809             }
00810             return $header;
00811         } elseif (!is_string($header)) {
00812             return false;
00813         }
00814 
00815         preg_match_all("/(.+):(.+)(?:(?<![\t ])" . $this->lineBreak . "|\$)/Uis", $header, $matches, PREG_SET_ORDER);
00816 
00817         $header = array();
00818         foreach ($matches as $match) {
00819             list(, $field, $value) = $match;
00820 
00821             $value = trim($value);
00822             $value = preg_replace("/[\t ]\r\n/", "\r\n", $value);
00823 
00824             $field = $this->unescapeToken($field);
00825 
00826             $field = strtolower($field);
00827             preg_match_all('/(?:^|(?<=-))[a-z]/U', $field, $offsets, PREG_OFFSET_CAPTURE);
00828             foreach ($offsets[0] as $offset) {
00829                 $field = substr_replace($field, strtoupper($offset[0]), $offset[1], 1);
00830             }
00831 
00832             if (!isset($header[$field])) {
00833                 $header[$field] = $value;
00834             } else {
00835                 $header[$field] = array_merge((array)$header[$field], (array)$value);
00836             }
00837         }
00838         return $header;
00839     }
00840 /**
00841  * undocumented function
00842  *
00843  * @param unknown $header
00844  * @return void
00845  * @access public
00846  * @todo Make this 100% RFC 2965 confirm
00847  */
00848     function parseCookies($header) {
00849         if (!isset($header['Set-Cookie'])) {
00850             return false;
00851         }
00852 
00853         $cookies = array();
00854         foreach ((array)$header['Set-Cookie'] as $cookie) {
00855             if (strpos($cookie, '";"') !== false) {
00856                 $cookie = str_replace('";"', "{__cookie_replace__}", $cookie);
00857                 $parts  = str_replace("{__cookie_replace__}", '";"', preg_split('/\;/', $cookie));
00858             } else {
00859                 $parts = preg_split('/\;[ \t]*/', $cookie);
00860             }
00861 
00862             list($name, $value) = explode('=', array_shift($parts), 2);
00863             $cookies[$name] = compact('value');
00864 
00865             foreach ($parts as $part) {
00866                 if (strpos($part, '=') !== false) {
00867                     list($key, $value) = explode('=', $part);
00868                 } else {
00869                     $key = $part;
00870                     $value = true;
00871                 }
00872 
00873                 $key = strtolower($key);
00874                 if (!isset($cookies[$name][$key])) {
00875                     $cookies[$name][$key] = $value;
00876                 }
00877             }
00878         }
00879         return $cookies;
00880     }
00881 /**
00882  * undocumented function
00883  *
00884  * @param unknown $cookies
00885  * @return void
00886  * @access public
00887  * @todo Refactor token escape mechanism to be configurable
00888  */
00889     function buildCookies($cookies) {
00890         $header = array();
00891         foreach ($cookies as $name => $cookie) {
00892             $header[] = $name.'='.$this->escapeToken($cookie['value'], array(';'));
00893         }
00894         $header = $this->buildHeader(array('Cookie' => $header), 'pragmatic');
00895         return $header;
00896     }
00897 /**
00898  * undocumented function
00899  *
00900  * @return void
00901  * @access public
00902  */
00903     function saveCookies() {
00904 
00905     }
00906 /**
00907  * undocumented function
00908  *
00909  * @return void
00910  * @access public
00911  */
00912     function loadCookies() {
00913 
00914     }
00915 /**
00916  * Unescapes a given $token according to RFC 2616 (HTTP 1.1 specs)
00917  *
00918  * @param string $token Token to unescape
00919  * @return string Unescaped token
00920  * @access protected
00921  * @todo Test $chars parameter
00922  */
00923     function unescapeToken($token, $chars = null) {
00924         $regex = '/"(['.join('', $this->__tokenEscapeChars(true, $chars)).'])"/';
00925         $token = preg_replace($regex, '\\1', $token);
00926         return $token;
00927     }
00928 /**
00929  * Escapes a given $token according to RFC 2616 (HTTP 1.1 specs)
00930  *
00931  * @param string $token Token to escape
00932  * @return string Escaped token
00933  * @access protected
00934  * @todo Test $chars parameter
00935  */
00936     function escapeToken($token, $chars = null) {
00937         $regex = '/(['.join('', $this->__tokenEscapeChars(true, $chars)).'])/';
00938         $token = preg_replace($regex, '"\\1"', $token);
00939         return $token;
00940     }
00941 /**
00942  * Gets escape chars according to RFC 2616 (HTTP 1.1 specs).
00943  *
00944  * @param boolean $hex true to get them as HEX values, false otherwise
00945  * @return array Escape chars
00946  * @access private
00947  * @todo Test $chars parameter
00948  */
00949     function __tokenEscapeChars($hex = true, $chars = null) {
00950         if (!empty($chars)) {
00951             $escape = $chars;
00952         } else {
00953             $escape = array('"', "(", ")", "<", ">", "@", ",", ";", ":", "\\", "/", "[", "]", "?", "=", "{", "}", " ");
00954             for ($i = 0; $i <= 31; $i++) {
00955                 $escape[] = chr($i);
00956             }
00957             $escape[] = chr(127);
00958         }
00959 
00960         if ($hex == false) {
00961             return $escape;
00962         }
00963         $regexChars = '';
00964         foreach ($escape as $key => $char) {
00965             $escape[$key] = '\\x'.str_pad(dechex(ord($char)), 2, '0', STR_PAD_LEFT);
00966         }
00967         return $escape;
00968     }
00969 /**
00970  * Resets the state of this HttpSocket instance to it's initial state (before Object::__construct got executed) or does
00971  * the same thing partially for the request and the response property only.
00972  *
00973  * @param boolean $full If set to false only HttpSocket::response and HttpSocket::request are reseted
00974  * @return boolean True on success
00975  * @access public
00976  */
00977     function reset($full = true) {
00978         static $initalState = array();
00979         if (empty($initalState)) {
00980             $initalState = get_class_vars(__CLASS__);
00981         }
00982 
00983         if ($full == false) {
00984             $this->request = $initalState['request'];
00985             $this->response = $initalState['response'];
00986             return true;
00987         }
00988         parent::reset($initalState);
00989         return true;
00990     }
00991 }
00992 ?>

Generated on Sun Nov 22 00:30:53 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7