* @copyright 2005-2008 Janrain, Inc. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache */ /** * Need both fetcher types so we can use the right one based on the * presence or absence of CURL. */ require_once "Auth/Yadis/PlainHTTPFetcher.php"; require_once "Auth/Yadis/ParanoidHTTPFetcher.php"; /** * Need this for parsing HTML (looking for META tags). */ require_once "Auth/Yadis/ParseHTML.php"; /** * Need this to parse the XRDS document during Yadis discovery. */ require_once "Auth/Yadis/XRDS.php"; /** * XRDS (yadis) content type */ define('Auth_Yadis_CONTENT_TYPE', 'application/xrds+xml'); /** * Yadis header */ define('Auth_Yadis_HEADER_NAME', 'X-XRDS-Location'); /** * Contains the result of performing Yadis discovery on a URI. * * @package OpenID */ class Auth_Yadis_DiscoveryResult { // The URI that was passed to the fetcher var $request_uri = null; // The result of following redirects from the request_uri var $normalized_uri = null; // The URI from which the response text was returned (set to // None if there was no XRDS document found) var $xrds_uri = null; var $xrds = null; // The content-type returned with the response_text var $content_type = null; // The document returned from the xrds_uri var $response_text = null; // Did the discovery fail miserably? var $failed = false; function Auth_Yadis_DiscoveryResult($request_uri) { // Initialize the state of the object // sets all attributes to None except the request_uri $this->request_uri = $request_uri; } function fail() { $this->failed = true; } function isFailure() { return $this->failed; } /** * Returns the list of service objects as described by the XRDS * document, if this yadis object represents a successful Yadis * discovery. * * @return array $services An array of {@link Auth_Yadis_Service} * objects */ function services() { if ($this->xrds) { return $this->xrds->services(); } return null; } function usedYadisLocation() { // Was the Yadis protocol's indirection used? return $this->normalized_uri != $this->xrds_uri; } function isXRDS() { // Is the response text supposed to be an XRDS document? return ($this->usedYadisLocation() || $this->content_type == Auth_Yadis_CONTENT_TYPE); } } /** * * Perform the Yadis protocol on the input URL and return an iterable * of resulting endpoint objects. * * input_url: The URL on which to perform the Yadis protocol * * @return: The normalized identity URL and an iterable of endpoint * objects generated by the filter function. * * xrds_parse_func: a callback which will take (uri, xrds_text) and * return an array of service endpoint objects or null. Usually * array('Auth_OpenID_ServiceEndpoint', 'fromXRDS'). * * discover_func: if not null, a callback which should take (uri) and * return an Auth_Yadis_Yadis object or null. */ function Auth_Yadis_getServiceEndpoints($input_url, $xrds_parse_func, $discover_func=null, $fetcher=null) { if ($discover_func === null) { $discover_function = array('Auth_Yadis_Yadis', 'discover'); } $yadis_result = call_user_func_array($discover_func, array($input_url, $fetcher)); if ($yadis_result === null) { return array($input_url, array()); } $endpoints = call_user_func_array($xrds_parse_func, array($yadis_result->normalized_uri, $yadis_result->response_text)); if ($endpoints === null) { $endpoints = array(); } return array($yadis_result->normalized_uri, $endpoints); } /** * This is the core of the PHP Yadis library. This is the only class * a user needs to use to perform Yadis discovery. This class * performs the discovery AND stores the result of the discovery. * * First, require this library into your program source: * *
require_once "Auth/Yadis/Yadis.php";* * To perform Yadis discovery, first call the "discover" method * statically with a URI parameter: * *
$http_response = array(); * $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); * $yadis_object = Auth_Yadis_Yadis::discover($uri, * $http_response, $fetcher);* * If the discovery succeeds, $yadis_object will be an instance of * {@link Auth_Yadis_Yadis}. If not, it will be null. The XRDS * document found during discovery should have service descriptions, * which can be accessed by calling * *
$service_list = $yadis_object->services();* * which returns an array of objects which describe each service. * These objects are instances of Auth_Yadis_Service. Each object * describes exactly one whole Service element, complete with all of * its Types and URIs (no expansion is performed). The common use * case for using the service objects returned by services() is to * write one or more filter functions and pass those to services(): * *
  $service_list = $yadis_object->services(
 *                               array("filterByURI",
 *                                     "filterByExtension"));
 *
 * The filter functions (whose names appear in the array passed to
 * services()) take the following form:
 *
 *   function myFilter(&$service) {
 *       // Query $service object here.  Return true if the service
 *       // matches your query; false if not.
 *  }
 *
 * This is an example of a filter which uses a regular expression to
 * match the content of URI tags (note that the Auth_Yadis_Service
 * class provides a getURIs() method which you should use instead of
 * this contrived example):
 *
 * 
 *  function URIMatcher(&$service) {
 *      foreach ($service->getElements('xrd:URI') as $uri) {
 *          if (preg_match("/some_pattern/",
 *                         $service->parser->content($uri))) {
 *              return true;
 *          }
 *      }
 *      return false;
 *  }
 *
 * The filter functions you pass will be called for each service
 * object to determine which ones match the criteria your filters
 * specify.  The default behavior is that if a given service object
 * matches ANY of the filters specified in the services() call, it
 * will be returned.  You can specify that a given service object will
 * be returned ONLY if it matches ALL specified filters by changing
 * the match mode of services():
 *
 *   $yadis_object->services(array("filter1", "filter2"),
 *                          SERVICES_YADIS_MATCH_ALL);
 *
 * See {@link SERVICES_YADIS_MATCH_ALL} and {@link
 * SERVICES_YADIS_MATCH_ANY}.
 *
 * Services described in an XRDS should have a library which you'll
 * probably be using.  Those libraries are responsible for defining
 * filters that can be used with the "services()" call.  If you need
 * to write your own filter, see the documentation for {@link
 * Auth_Yadis_Service}.
 *
 * @package OpenID
 */
class Auth_Yadis_Yadis {
    /**
     * Returns an HTTP fetcher object.  If the CURL extension is
     * present, an instance of {@link Auth_Yadis_ParanoidHTTPFetcher}
     * is returned.  If not, an instance of
     * {@link Auth_Yadis_PlainHTTPFetcher} is returned.
     *
     * If Auth_Yadis_CURL_OVERRIDE is defined, this method will always
     * return a {@link Auth_Yadis_PlainHTTPFetcher}.
     */
    function getHTTPFetcher($timeout = 20)
    {
        if (Auth_Yadis_Yadis::curlPresent() &&
            (!defined('Auth_Yadis_CURL_OVERRIDE'))) {
            $fetcher = new Auth_Yadis_ParanoidHTTPFetcher($timeout);
        } else {
            $fetcher = new Auth_Yadis_PlainHTTPFetcher($timeout);
        }
        return $fetcher;
    }
    function curlPresent()
    {
        return function_exists('curl_init');
    }
    /**
     * @access private
     */
    function _getHeader($header_list, $names)
    {
        foreach ($header_list as $name => $value) {
            foreach ($names as $n) {
                if (strtolower($name) == strtolower($n)) {
                    return $value;
                }
            }
        }
        return null;
    }
    /**
     * @access private
     */
    function _getContentType($content_type_header)
    {
        if ($content_type_header) {
            $parts = explode(";", $content_type_header);
            return strtolower($parts[0]);
        }
    }
    /**
     * This should be called statically and will build a Yadis
     * instance if the discovery process succeeds.  This implements
     * Yadis discovery as specified in the Yadis specification.
     *
     * @param string $uri The URI on which to perform Yadis discovery.
     *
     * @param array $http_response An array reference where the HTTP
     * response object will be stored (see {@link
     * Auth_Yadis_HTTPResponse}.
     *
     * @param Auth_Yadis_HTTPFetcher $fetcher An instance of a
     * Auth_Yadis_HTTPFetcher subclass.
     *
     * @param array $extra_ns_map An array which maps namespace names
     * to namespace URIs to be used when parsing the Yadis XRDS
     * document.
     *
     * @param integer $timeout An optional fetcher timeout, in seconds.
     *
     * @return mixed $obj Either null or an instance of
     * Auth_Yadis_Yadis, depending on whether the discovery
     * succeeded.
     */
    function discover($uri, &$fetcher,
                      $extra_ns_map = null, $timeout = 20)
    {
        $result = new Auth_Yadis_DiscoveryResult($uri);
        $request_uri = $uri;
        $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
                         ', text/html; q=0.3, application/xhtml+xml; q=0.5');
        if ($fetcher === null) {
            $fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
        }
        $response = $fetcher->get($uri, $headers);
        if (!$response || ($response->status != 200 and
                           $response->status != 206)) {
            $result->fail();
            return $result;
        }
        $result->normalized_uri = $response->final_url;
        $result->content_type = Auth_Yadis_Yadis::_getHeader(
                                       $response->headers,
                                       array('content-type'));
        if ($result->content_type &&
            (Auth_Yadis_Yadis::_getContentType($result->content_type) ==
             Auth_Yadis_CONTENT_TYPE)) {
            $result->xrds_uri = $result->normalized_uri;
        } else {
            $yadis_location = Auth_Yadis_Yadis::_getHeader(
                                                 $response->headers,
                                                 array(Auth_Yadis_HEADER_NAME));
            if (!$yadis_location) {
                $parser = new Auth_Yadis_ParseHTML();
                $yadis_location = $parser->getHTTPEquiv($response->body);
            }
            if ($yadis_location) {
                $result->xrds_uri = $yadis_location;
                $response = $fetcher->get($yadis_location);
                if ((!$response) || ($response->status != 200 and
                                     $response->status != 206)) {
                    $result->fail();
                    return $result;
                }
                $result->content_type = Auth_Yadis_Yadis::_getHeader(
                                                         $response->headers,
                                                         array('content-type'));
            }
        }
        $result->response_text = $response->body;
        return $result;
    }
}
?>