import
[web.mtrack] / inc / lib / Auth / Yadis / Yadis.php
1 <?php
2
3 /**
4  * The core PHP Yadis implementation.
5  *
6  * PHP versions 4 and 5
7  *
8  * LICENSE: See the COPYING file included in this distribution.
9  *
10  * @package OpenID
11  * @author JanRain, Inc. <openid@janrain.com>
12  * @copyright 2005-2008 Janrain, Inc.
13  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
14  */
15
16 /**
17  * Need both fetcher types so we can use the right one based on the
18  * presence or absence of CURL.
19  */
20 require_once "Auth/Yadis/PlainHTTPFetcher.php";
21 require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
22
23 /**
24  * Need this for parsing HTML (looking for META tags).
25  */
26 require_once "Auth/Yadis/ParseHTML.php";
27
28 /**
29  * Need this to parse the XRDS document during Yadis discovery.
30  */
31 require_once "Auth/Yadis/XRDS.php";
32
33 /**
34  * XRDS (yadis) content type
35  */
36 define('Auth_Yadis_CONTENT_TYPE', 'application/xrds+xml');
37
38 /**
39  * Yadis header
40  */
41 define('Auth_Yadis_HEADER_NAME', 'X-XRDS-Location');
42
43 /**
44  * Contains the result of performing Yadis discovery on a URI.
45  *
46  * @package OpenID
47  */
48 class Auth_Yadis_DiscoveryResult {
49
50     // The URI that was passed to the fetcher
51     var $request_uri = null;
52
53     // The result of following redirects from the request_uri
54     var $normalized_uri = null;
55
56     // The URI from which the response text was returned (set to
57     // None if there was no XRDS document found)
58     var $xrds_uri = null;
59
60     var $xrds = null;
61
62     // The content-type returned with the response_text
63     var $content_type = null;
64
65     // The document returned from the xrds_uri
66     var $response_text = null;
67
68     // Did the discovery fail miserably?
69     var $failed = false;
70
71     function Auth_Yadis_DiscoveryResult($request_uri)
72     {
73         // Initialize the state of the object
74         // sets all attributes to None except the request_uri
75         $this->request_uri = $request_uri;
76     }
77
78     function fail()
79     {
80         $this->failed = true;
81     }
82
83     function isFailure()
84     {
85         return $this->failed;
86     }
87
88     /**
89      * Returns the list of service objects as described by the XRDS
90      * document, if this yadis object represents a successful Yadis
91      * discovery.
92      *
93      * @return array $services An array of {@link Auth_Yadis_Service}
94      * objects
95      */
96     function services()
97     {
98         if ($this->xrds) {
99             return $this->xrds->services();
100         }
101
102         return null;
103     }
104
105     function usedYadisLocation()
106     {
107         // Was the Yadis protocol's indirection used?
108         return $this->normalized_uri != $this->xrds_uri;
109     }
110
111     function isXRDS()
112     {
113         // Is the response text supposed to be an XRDS document?
114         return ($this->usedYadisLocation() ||
115                 $this->content_type == Auth_Yadis_CONTENT_TYPE);
116     }
117 }
118
119 /**
120  *
121  * Perform the Yadis protocol on the input URL and return an iterable
122  * of resulting endpoint objects.
123  *
124  * input_url: The URL on which to perform the Yadis protocol
125  *
126  * @return: The normalized identity URL and an iterable of endpoint
127  * objects generated by the filter function.
128  *
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').
132  *
133  * discover_func: if not null, a callback which should take (uri) and
134  * return an Auth_Yadis_Yadis object or null.
135  */
136 function Auth_Yadis_getServiceEndpoints($input_url, $xrds_parse_func,
137                                         $discover_func=null, $fetcher=null)
138 {
139     if ($discover_func === null) {
140         $discover_function = array('Auth_Yadis_Yadis', 'discover');
141     }
142
143     $yadis_result = call_user_func_array($discover_func,
144                                          array($input_url, $fetcher));
145
146     if ($yadis_result === null) {
147         return array($input_url, array());
148     }
149
150     $endpoints = call_user_func_array($xrds_parse_func,
151                       array($yadis_result->normalized_uri,
152                             $yadis_result->response_text));
153
154     if ($endpoints === null) {
155         $endpoints = array();
156     }
157
158     return array($yadis_result->normalized_uri, $endpoints);
159 }
160
161 /**
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.
165  *
166  * First, require this library into your program source:
167  *
168  * <pre>  require_once "Auth/Yadis/Yadis.php";</pre>
169  *
170  * To perform Yadis discovery, first call the "discover" method
171  * statically with a URI parameter:
172  *
173  * <pre>  $http_response = array();
174  *  $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
175  *  $yadis_object = Auth_Yadis_Yadis::discover($uri,
176  *                                    $http_response, $fetcher);</pre>
177  *
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
182  *
183  * <pre>  $service_list = $yadis_object->services();</pre>
184  *
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():
191  *
192  * <pre>  $service_list = $yadis_object->services(
193  *                               array("filterByURI",
194  *                                     "filterByExtension"));</pre>
195  *
196  * The filter functions (whose names appear in the array passed to
197  * services()) take the following form:
198  *
199  * <pre>  function myFilter(&$service) {
200  *       // Query $service object here.  Return true if the service
201  *       // matches your query; false if not.
202  *  }</pre>
203  *
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):
208  *
209  * <pre>
210  *  function URIMatcher(&$service) {
211  *      foreach ($service->getElements('xrd:URI') as $uri) {
212  *          if (preg_match("/some_pattern/",
213  *                         $service->parser->content($uri))) {
214  *              return true;
215  *          }
216  *      }
217  *      return false;
218  *  }</pre>
219  *
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():
227  *
228  * <pre>  $yadis_object->services(array("filter1", "filter2"),
229  *                          SERVICES_YADIS_MATCH_ALL);</pre>
230  *
231  * See {@link SERVICES_YADIS_MATCH_ALL} and {@link
232  * SERVICES_YADIS_MATCH_ANY}.
233  *
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}.
239  *
240  * @package OpenID
241  */
242 class Auth_Yadis_Yadis {
243
244     /**
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.
249      *
250      * If Auth_Yadis_CURL_OVERRIDE is defined, this method will always
251      * return a {@link Auth_Yadis_PlainHTTPFetcher}.
252      */
253     function getHTTPFetcher($timeout = 20)
254     {
255         if (Auth_Yadis_Yadis::curlPresent() &&
256             (!defined('Auth_Yadis_CURL_OVERRIDE'))) {
257             $fetcher = new Auth_Yadis_ParanoidHTTPFetcher($timeout);
258         } else {
259             $fetcher = new Auth_Yadis_PlainHTTPFetcher($timeout);
260         }
261         return $fetcher;
262     }
263
264     function curlPresent()
265     {
266         return function_exists('curl_init');
267     }
268
269     /**
270      * @access private
271      */
272     function _getHeader($header_list, $names)
273     {
274         foreach ($header_list as $name => $value) {
275             foreach ($names as $n) {
276                 if (strtolower($name) == strtolower($n)) {
277                     return $value;
278                 }
279             }
280         }
281
282         return null;
283     }
284
285     /**
286      * @access private
287      */
288     function _getContentType($content_type_header)
289     {
290         if ($content_type_header) {
291             $parts = explode(";", $content_type_header);
292             return strtolower($parts[0]);
293         }
294     }
295
296     /**
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.
300      *
301      * @param string $uri The URI on which to perform Yadis discovery.
302      *
303      * @param array $http_response An array reference where the HTTP
304      * response object will be stored (see {@link
305      * Auth_Yadis_HTTPResponse}.
306      *
307      * @param Auth_Yadis_HTTPFetcher $fetcher An instance of a
308      * Auth_Yadis_HTTPFetcher subclass.
309      *
310      * @param array $extra_ns_map An array which maps namespace names
311      * to namespace URIs to be used when parsing the Yadis XRDS
312      * document.
313      *
314      * @param integer $timeout An optional fetcher timeout, in seconds.
315      *
316      * @return mixed $obj Either null or an instance of
317      * Auth_Yadis_Yadis, depending on whether the discovery
318      * succeeded.
319      */
320     function discover($uri, &$fetcher,
321                       $extra_ns_map = null, $timeout = 20)
322     {
323         $result = new Auth_Yadis_DiscoveryResult($uri);
324
325         $request_uri = $uri;
326         $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
327                          ', text/html; q=0.3, application/xhtml+xml; q=0.5');
328
329         if ($fetcher === null) {
330             $fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
331         }
332
333         $response = $fetcher->get($uri, $headers);
334
335         if (!$response || ($response->status != 200 and
336                            $response->status != 206)) {
337             $result->fail();
338             return $result;
339         }
340
341         $result->normalized_uri = $response->final_url;
342         $result->content_type = Auth_Yadis_Yadis::_getHeader(
343                                        $response->headers,
344                                        array('content-type'));
345
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;
350         } else {
351             $yadis_location = Auth_Yadis_Yadis::_getHeader(
352                                                  $response->headers,
353                                                  array(Auth_Yadis_HEADER_NAME));
354
355             if (!$yadis_location) {
356                 $parser = new Auth_Yadis_ParseHTML();
357                 $yadis_location = $parser->getHTTPEquiv($response->body);
358             }
359
360             if ($yadis_location) {
361                 $result->xrds_uri = $yadis_location;
362
363                 $response = $fetcher->get($yadis_location);
364
365                 if ((!$response) || ($response->status != 200 and
366                                      $response->status != 206)) {
367                     $result->fail();
368                     return $result;
369                 }
370
371                 $result->content_type = Auth_Yadis_Yadis::_getHeader(
372                                                          $response->headers,
373                                                          array('content-type'));
374             }
375         }
376
377         $result->response_text = $response->body;
378         return $result;
379     }
380 }
381
382 ?>