4 * The core PHP Yadis implementation.
8 * LICENSE: See the COPYING file included in this distribution.
11 * @author JanRain, Inc. <openid@janrain.com>
12 * @copyright 2005-2008 Janrain, Inc.
13 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
17 * Need both fetcher types so we can use the right one based on the
18 * presence or absence of CURL.
20 require_once "Auth/Yadis/PlainHTTPFetcher.php";
21 require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
24 * Need this for parsing HTML (looking for META tags).
26 require_once "Auth/Yadis/ParseHTML.php";
29 * Need this to parse the XRDS document during Yadis discovery.
31 require_once "Auth/Yadis/XRDS.php";
34 * XRDS (yadis) content type
36 define('Auth_Yadis_CONTENT_TYPE', 'application/xrds+xml');
41 define('Auth_Yadis_HEADER_NAME', 'X-XRDS-Location');
44 * Contains the result of performing Yadis discovery on a URI.
48 class Auth_Yadis_DiscoveryResult {
50 // The URI that was passed to the fetcher
51 var $request_uri = null;
53 // The result of following redirects from the request_uri
54 var $normalized_uri = null;
56 // The URI from which the response text was returned (set to
57 // None if there was no XRDS document found)
62 // The content-type returned with the response_text
63 var $content_type = null;
65 // The document returned from the xrds_uri
66 var $response_text = null;
68 // Did the discovery fail miserably?
71 function Auth_Yadis_DiscoveryResult($request_uri)
73 // Initialize the state of the object
74 // sets all attributes to None except the request_uri
75 $this->request_uri = $request_uri;
89 * Returns the list of service objects as described by the XRDS
90 * document, if this yadis object represents a successful Yadis
93 * @return array $services An array of {@link Auth_Yadis_Service}
99 return $this->xrds->services();
105 function usedYadisLocation()
107 // Was the Yadis protocol's indirection used?
108 return $this->normalized_uri != $this->xrds_uri;
113 // Is the response text supposed to be an XRDS document?
114 return ($this->usedYadisLocation() ||
115 $this->content_type == Auth_Yadis_CONTENT_TYPE);
121 * Perform the Yadis protocol on the input URL and return an iterable
122 * of resulting endpoint objects.
124 * input_url: The URL on which to perform the Yadis protocol
126 * @return: The normalized identity URL and an iterable of endpoint
127 * objects generated by the filter function.
129 * xrds_parse_func: a callback which will take (uri, xrds_text) and
130 * return an array of service endpoint objects or null. Usually
131 * array('Auth_OpenID_ServiceEndpoint', 'fromXRDS').
133 * discover_func: if not null, a callback which should take (uri) and
134 * return an Auth_Yadis_Yadis object or null.
136 function Auth_Yadis_getServiceEndpoints($input_url, $xrds_parse_func,
137 $discover_func=null, $fetcher=null)
139 if ($discover_func === null) {
140 $discover_function = array('Auth_Yadis_Yadis', 'discover');
143 $yadis_result = call_user_func_array($discover_func,
144 array($input_url, $fetcher));
146 if ($yadis_result === null) {
147 return array($input_url, array());
150 $endpoints = call_user_func_array($xrds_parse_func,
151 array($yadis_result->normalized_uri,
152 $yadis_result->response_text));
154 if ($endpoints === null) {
155 $endpoints = array();
158 return array($yadis_result->normalized_uri, $endpoints);
162 * This is the core of the PHP Yadis library. This is the only class
163 * a user needs to use to perform Yadis discovery. This class
164 * performs the discovery AND stores the result of the discovery.
166 * First, require this library into your program source:
168 * <pre> require_once "Auth/Yadis/Yadis.php";</pre>
170 * To perform Yadis discovery, first call the "discover" method
171 * statically with a URI parameter:
173 * <pre> $http_response = array();
174 * $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
175 * $yadis_object = Auth_Yadis_Yadis::discover($uri,
176 * $http_response, $fetcher);</pre>
178 * If the discovery succeeds, $yadis_object will be an instance of
179 * {@link Auth_Yadis_Yadis}. If not, it will be null. The XRDS
180 * document found during discovery should have service descriptions,
181 * which can be accessed by calling
183 * <pre> $service_list = $yadis_object->services();</pre>
185 * which returns an array of objects which describe each service.
186 * These objects are instances of Auth_Yadis_Service. Each object
187 * describes exactly one whole Service element, complete with all of
188 * its Types and URIs (no expansion is performed). The common use
189 * case for using the service objects returned by services() is to
190 * write one or more filter functions and pass those to services():
192 * <pre> $service_list = $yadis_object->services(
193 * array("filterByURI",
194 * "filterByExtension"));</pre>
196 * The filter functions (whose names appear in the array passed to
197 * services()) take the following form:
199 * <pre> function myFilter(&$service) {
200 * // Query $service object here. Return true if the service
201 * // matches your query; false if not.
204 * This is an example of a filter which uses a regular expression to
205 * match the content of URI tags (note that the Auth_Yadis_Service
206 * class provides a getURIs() method which you should use instead of
207 * this contrived example):
210 * function URIMatcher(&$service) {
211 * foreach ($service->getElements('xrd:URI') as $uri) {
212 * if (preg_match("/some_pattern/",
213 * $service->parser->content($uri))) {
220 * The filter functions you pass will be called for each service
221 * object to determine which ones match the criteria your filters
222 * specify. The default behavior is that if a given service object
223 * matches ANY of the filters specified in the services() call, it
224 * will be returned. You can specify that a given service object will
225 * be returned ONLY if it matches ALL specified filters by changing
226 * the match mode of services():
228 * <pre> $yadis_object->services(array("filter1", "filter2"),
229 * SERVICES_YADIS_MATCH_ALL);</pre>
231 * See {@link SERVICES_YADIS_MATCH_ALL} and {@link
232 * SERVICES_YADIS_MATCH_ANY}.
234 * Services described in an XRDS should have a library which you'll
235 * probably be using. Those libraries are responsible for defining
236 * filters that can be used with the "services()" call. If you need
237 * to write your own filter, see the documentation for {@link
238 * Auth_Yadis_Service}.
242 class Auth_Yadis_Yadis {
245 * Returns an HTTP fetcher object. If the CURL extension is
246 * present, an instance of {@link Auth_Yadis_ParanoidHTTPFetcher}
247 * is returned. If not, an instance of
248 * {@link Auth_Yadis_PlainHTTPFetcher} is returned.
250 * If Auth_Yadis_CURL_OVERRIDE is defined, this method will always
251 * return a {@link Auth_Yadis_PlainHTTPFetcher}.
253 function getHTTPFetcher($timeout = 20)
255 if (Auth_Yadis_Yadis::curlPresent() &&
256 (!defined('Auth_Yadis_CURL_OVERRIDE'))) {
257 $fetcher = new Auth_Yadis_ParanoidHTTPFetcher($timeout);
259 $fetcher = new Auth_Yadis_PlainHTTPFetcher($timeout);
264 function curlPresent()
266 return function_exists('curl_init');
272 function _getHeader($header_list, $names)
274 foreach ($header_list as $name => $value) {
275 foreach ($names as $n) {
276 if (strtolower($name) == strtolower($n)) {
288 function _getContentType($content_type_header)
290 if ($content_type_header) {
291 $parts = explode(";", $content_type_header);
292 return strtolower($parts[0]);
297 * This should be called statically and will build a Yadis
298 * instance if the discovery process succeeds. This implements
299 * Yadis discovery as specified in the Yadis specification.
301 * @param string $uri The URI on which to perform Yadis discovery.
303 * @param array $http_response An array reference where the HTTP
304 * response object will be stored (see {@link
305 * Auth_Yadis_HTTPResponse}.
307 * @param Auth_Yadis_HTTPFetcher $fetcher An instance of a
308 * Auth_Yadis_HTTPFetcher subclass.
310 * @param array $extra_ns_map An array which maps namespace names
311 * to namespace URIs to be used when parsing the Yadis XRDS
314 * @param integer $timeout An optional fetcher timeout, in seconds.
316 * @return mixed $obj Either null or an instance of
317 * Auth_Yadis_Yadis, depending on whether the discovery
320 function discover($uri, &$fetcher,
321 $extra_ns_map = null, $timeout = 20)
323 $result = new Auth_Yadis_DiscoveryResult($uri);
326 $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
327 ', text/html; q=0.3, application/xhtml+xml; q=0.5');
329 if ($fetcher === null) {
330 $fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
333 $response = $fetcher->get($uri, $headers);
335 if (!$response || ($response->status != 200 and
336 $response->status != 206)) {
341 $result->normalized_uri = $response->final_url;
342 $result->content_type = Auth_Yadis_Yadis::_getHeader(
344 array('content-type'));
346 if ($result->content_type &&
347 (Auth_Yadis_Yadis::_getContentType($result->content_type) ==
348 Auth_Yadis_CONTENT_TYPE)) {
349 $result->xrds_uri = $result->normalized_uri;
351 $yadis_location = Auth_Yadis_Yadis::_getHeader(
353 array(Auth_Yadis_HEADER_NAME));
355 if (!$yadis_location) {
356 $parser = new Auth_Yadis_ParseHTML();
357 $yadis_location = $parser->getHTTPEquiv($response->body);
360 if ($yadis_location) {
361 $result->xrds_uri = $yadis_location;
363 $response = $fetcher->get($yadis_location);
365 if ((!$response) || ($response->status != 200 and
366 $response->status != 206)) {
371 $result->content_type = Auth_Yadis_Yadis::_getHeader(
373 array('content-type'));
377 $result->response_text = $response->body;