TYPO3 API  SVNRelease
Yadis.php
Go to the documentation of this file.
00001 <?php
00002 
00003 /**
00004  * The core PHP Yadis implementation.
00005  *
00006  * PHP versions 4 and 5
00007  *
00008  * LICENSE: See the COPYING file included in this distribution.
00009  *
00010  * @package OpenID
00011  * @author JanRain, Inc. <openid@janrain.com>
00012  * @copyright 2005-2008 Janrain, Inc.
00013  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
00014  */
00015 
00016 /**
00017  * Need both fetcher types so we can use the right one based on the
00018  * presence or absence of CURL.
00019  */
00020 require_once "Auth/Yadis/PlainHTTPFetcher.php";
00021 require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
00022 
00023 /**
00024  * Need this for parsing HTML (looking for META tags).
00025  */
00026 require_once "Auth/Yadis/ParseHTML.php";
00027 
00028 /**
00029  * Need this to parse the XRDS document during Yadis discovery.
00030  */
00031 require_once "Auth/Yadis/XRDS.php";
00032 
00033 /**
00034  * XRDS (yadis) content type
00035  */
00036 define('Auth_Yadis_CONTENT_TYPE', 'application/xrds+xml');
00037 
00038 /**
00039  * Yadis header
00040  */
00041 define('Auth_Yadis_HEADER_NAME', 'X-XRDS-Location');
00042 
00043 /**
00044  * Contains the result of performing Yadis discovery on a URI.
00045  *
00046  * @package OpenID
00047  */
00048 class Auth_Yadis_DiscoveryResult {
00049 
00050     // The URI that was passed to the fetcher
00051     var $request_uri = null;
00052 
00053     // The result of following redirects from the request_uri
00054     var $normalized_uri = null;
00055 
00056     // The URI from which the response text was returned (set to
00057     // None if there was no XRDS document found)
00058     var $xrds_uri = null;
00059 
00060     var $xrds = null;
00061 
00062     // The content-type returned with the response_text
00063     var $content_type = null;
00064 
00065     // The document returned from the xrds_uri
00066     var $response_text = null;
00067 
00068     // Did the discovery fail miserably?
00069     var $failed = false;
00070 
00071     function Auth_Yadis_DiscoveryResult($request_uri)
00072     {
00073         // Initialize the state of the object
00074         // sets all attributes to None except the request_uri
00075         $this->request_uri = $request_uri;
00076     }
00077 
00078     function fail()
00079     {
00080         $this->failed = true;
00081     }
00082 
00083     function isFailure()
00084     {
00085         return $this->failed;
00086     }
00087 
00088     /**
00089      * Returns the list of service objects as described by the XRDS
00090      * document, if this yadis object represents a successful Yadis
00091      * discovery.
00092      *
00093      * @return array $services An array of {@link Auth_Yadis_Service}
00094      * objects
00095      */
00096     function services()
00097     {
00098         if ($this->xrds) {
00099             return $this->xrds->services();
00100         }
00101 
00102         return null;
00103     }
00104 
00105     function usedYadisLocation()
00106     {
00107         // Was the Yadis protocol's indirection used?
00108         return $this->normalized_uri != $this->xrds_uri;
00109     }
00110 
00111     function isXRDS()
00112     {
00113         // Is the response text supposed to be an XRDS document?
00114         return ($this->usedYadisLocation() ||
00115                 $this->content_type == Auth_Yadis_CONTENT_TYPE);
00116     }
00117 }
00118 
00119 /**
00120  *
00121  * Perform the Yadis protocol on the input URL and return an iterable
00122  * of resulting endpoint objects.
00123  *
00124  * input_url: The URL on which to perform the Yadis protocol
00125  *
00126  * @return: The normalized identity URL and an iterable of endpoint
00127  * objects generated by the filter function.
00128  *
00129  * xrds_parse_func: a callback which will take (uri, xrds_text) and
00130  * return an array of service endpoint objects or null.  Usually
00131  * array('Auth_OpenID_ServiceEndpoint', 'fromXRDS').
00132  *
00133  * discover_func: if not null, a callback which should take (uri) and
00134  * return an Auth_Yadis_Yadis object or null.
00135  */
00136 function Auth_Yadis_getServiceEndpoints($input_url, $xrds_parse_func,
00137                                         $discover_func=null, $fetcher=null)
00138 {
00139     if ($discover_func === null) {
00140         $discover_function = array('Auth_Yadis_Yadis', 'discover');
00141     }
00142 
00143     $yadis_result = call_user_func_array($discover_func,
00144                                          array($input_url, $fetcher));
00145 
00146     if ($yadis_result === null) {
00147         return array($input_url, array());
00148     }
00149 
00150     $endpoints = call_user_func_array($xrds_parse_func,
00151                       array($yadis_result->normalized_uri,
00152                             $yadis_result->response_text));
00153 
00154     if ($endpoints === null) {
00155         $endpoints = array();
00156     }
00157 
00158     return array($yadis_result->normalized_uri, $endpoints);
00159 }
00160 
00161 /**
00162  * This is the core of the PHP Yadis library.  This is the only class
00163  * a user needs to use to perform Yadis discovery.  This class
00164  * performs the discovery AND stores the result of the discovery.
00165  *
00166  * First, require this library into your program source:
00167  *
00168  * <pre>  require_once "Auth/Yadis/Yadis.php";</pre>
00169  *
00170  * To perform Yadis discovery, first call the "discover" method
00171  * statically with a URI parameter:
00172  *
00173  * <pre>  $http_response = array();
00174  *  $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
00175  *  $yadis_object = Auth_Yadis_Yadis::discover($uri,
00176  *                                    $http_response, $fetcher);</pre>
00177  *
00178  * If the discovery succeeds, $yadis_object will be an instance of
00179  * {@link Auth_Yadis_Yadis}.  If not, it will be null.  The XRDS
00180  * document found during discovery should have service descriptions,
00181  * which can be accessed by calling
00182  *
00183  * <pre>  $service_list = $yadis_object->services();</pre>
00184  *
00185  * which returns an array of objects which describe each service.
00186  * These objects are instances of Auth_Yadis_Service.  Each object
00187  * describes exactly one whole Service element, complete with all of
00188  * its Types and URIs (no expansion is performed).  The common use
00189  * case for using the service objects returned by services() is to
00190  * write one or more filter functions and pass those to services():
00191  *
00192  * <pre>  $service_list = $yadis_object->services(
00193  *                               array("filterByURI",
00194  *                                     "filterByExtension"));</pre>
00195  *
00196  * The filter functions (whose names appear in the array passed to
00197  * services()) take the following form:
00198  *
00199  * <pre>  function myFilter(&$service) {
00200  *       // Query $service object here.  Return true if the service
00201  *       // matches your query; false if not.
00202  *  }</pre>
00203  *
00204  * This is an example of a filter which uses a regular expression to
00205  * match the content of URI tags (note that the Auth_Yadis_Service
00206  * class provides a getURIs() method which you should use instead of
00207  * this contrived example):
00208  *
00209  * <pre>
00210  *  function URIMatcher(&$service) {
00211  *      foreach ($service->getElements('xrd:URI') as $uri) {
00212  *          if (preg_match("/some_pattern/",
00213  *                         $service->parser->content($uri))) {
00214  *              return true;
00215  *          }
00216  *      }
00217  *      return false;
00218  *  }</pre>
00219  *
00220  * The filter functions you pass will be called for each service
00221  * object to determine which ones match the criteria your filters
00222  * specify.  The default behavior is that if a given service object
00223  * matches ANY of the filters specified in the services() call, it
00224  * will be returned.  You can specify that a given service object will
00225  * be returned ONLY if it matches ALL specified filters by changing
00226  * the match mode of services():
00227  *
00228  * <pre>  $yadis_object->services(array("filter1", "filter2"),
00229  *                          SERVICES_YADIS_MATCH_ALL);</pre>
00230  *
00231  * See {@link SERVICES_YADIS_MATCH_ALL} and {@link
00232  * SERVICES_YADIS_MATCH_ANY}.
00233  *
00234  * Services described in an XRDS should have a library which you'll
00235  * probably be using.  Those libraries are responsible for defining
00236  * filters that can be used with the "services()" call.  If you need
00237  * to write your own filter, see the documentation for {@link
00238  * Auth_Yadis_Service}.
00239  *
00240  * @package OpenID
00241  */
00242 class Auth_Yadis_Yadis {
00243 
00244     /**
00245      * Returns an HTTP fetcher object.  If the CURL extension is
00246      * present, an instance of {@link Auth_Yadis_ParanoidHTTPFetcher}
00247      * is returned.  If not, an instance of
00248      * {@link Auth_Yadis_PlainHTTPFetcher} is returned.
00249      *
00250      * If Auth_Yadis_CURL_OVERRIDE is defined, this method will always
00251      * return a {@link Auth_Yadis_PlainHTTPFetcher}.
00252      */
00253     function getHTTPFetcher($timeout = 20)
00254     {
00255         if (Auth_Yadis_Yadis::curlPresent() &&
00256             (!defined('Auth_Yadis_CURL_OVERRIDE'))) {
00257             $fetcher = new Auth_Yadis_ParanoidHTTPFetcher($timeout);
00258         } else {
00259             $fetcher = new Auth_Yadis_PlainHTTPFetcher($timeout);
00260         }
00261         return $fetcher;
00262     }
00263 
00264     function curlPresent()
00265     {
00266         return function_exists('curl_init');
00267     }
00268 
00269     /**
00270      * @access private
00271      */
00272     function _getHeader($header_list, $names)
00273     {
00274         foreach ($header_list as $name => $value) {
00275             foreach ($names as $n) {
00276                 if (strtolower($name) == strtolower($n)) {
00277                     return $value;
00278                 }
00279             }
00280         }
00281 
00282         return null;
00283     }
00284 
00285     /**
00286      * @access private
00287      */
00288     function _getContentType($content_type_header)
00289     {
00290         if ($content_type_header) {
00291             $parts = explode(";", $content_type_header);
00292             return strtolower($parts[0]);
00293         }
00294     }
00295 
00296     /**
00297      * This should be called statically and will build a Yadis
00298      * instance if the discovery process succeeds.  This implements
00299      * Yadis discovery as specified in the Yadis specification.
00300      *
00301      * @param string $uri The URI on which to perform Yadis discovery.
00302      *
00303      * @param array $http_response An array reference where the HTTP
00304      * response object will be stored (see {@link
00305      * Auth_Yadis_HTTPResponse}.
00306      *
00307      * @param Auth_Yadis_HTTPFetcher $fetcher An instance of a
00308      * Auth_Yadis_HTTPFetcher subclass.
00309      *
00310      * @param array $extra_ns_map An array which maps namespace names
00311      * to namespace URIs to be used when parsing the Yadis XRDS
00312      * document.
00313      *
00314      * @param integer $timeout An optional fetcher timeout, in seconds.
00315      *
00316      * @return mixed $obj Either null or an instance of
00317      * Auth_Yadis_Yadis, depending on whether the discovery
00318      * succeeded.
00319      */
00320     function discover($uri, $fetcher,
00321                       $extra_ns_map = null, $timeout = 20)
00322     {
00323         $result = new Auth_Yadis_DiscoveryResult($uri);
00324 
00325         $request_uri = $uri;
00326         $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
00327                          ', text/html; q=0.3, application/xhtml+xml; q=0.5');
00328 
00329         if ($fetcher === null) {
00330             $fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
00331         }
00332 
00333         $response = $fetcher->get($uri, $headers);
00334 
00335         if (!$response || ($response->status != 200 and
00336                            $response->status != 206)) {
00337             $result->fail();
00338             return $result;
00339         }
00340 
00341         $result->normalized_uri = $response->final_url;
00342         $result->content_type = Auth_Yadis_Yadis::_getHeader(
00343                                        $response->headers,
00344                                        array('content-type'));
00345 
00346         if ($result->content_type &&
00347             (Auth_Yadis_Yadis::_getContentType($result->content_type) ==
00348              Auth_Yadis_CONTENT_TYPE)) {
00349             $result->xrds_uri = $result->normalized_uri;
00350         } else {
00351             $yadis_location = Auth_Yadis_Yadis::_getHeader(
00352                                                  $response->headers,
00353                                                  array(Auth_Yadis_HEADER_NAME));
00354 
00355             if (!$yadis_location) {
00356                 $parser = new Auth_Yadis_ParseHTML();
00357                 $yadis_location = $parser->getHTTPEquiv($response->body);
00358             }
00359 
00360             if ($yadis_location) {
00361                 $result->xrds_uri = $yadis_location;
00362 
00363                 $response = $fetcher->get($yadis_location);
00364 
00365                 if ((!$response) || ($response->status != 200 and
00366                                      $response->status != 206)) {
00367                     $result->fail();
00368                     return $result;
00369                 }
00370 
00371                 $result->content_type = Auth_Yadis_Yadis::_getHeader(
00372                                                          $response->headers,
00373                                                          array('content-type'));
00374             }
00375         }
00376 
00377         $result->response_text = $response->body;
00378         return $result;
00379     }
00380 }
00381 
00382 ?>