summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Auth/OpenID.php12
-rw-r--r--Services/Yadis/HTTPFetcher.php92
-rw-r--r--Services/Yadis/Manager.php303
-rw-r--r--Services/Yadis/ParanoidHTTPFetcher.php (renamed from Auth/OpenID/ParanoidHTTPFetcher.php)22
-rw-r--r--Services/Yadis/ParseHTML.php270
-rw-r--r--Services/Yadis/PlainHTTPFetcher.php (renamed from Auth/OpenID/PlainHTTPFetcher.php)16
-rw-r--r--Services/Yadis/XML.php346
-rw-r--r--Services/Yadis/XRDS.php418
-rw-r--r--Services/Yadis/Yadis.php309
-rw-r--r--Tests/Auth/OpenID/Consumer.php31
-rw-r--r--Tests/Auth/OpenID/Discover.php4
-rw-r--r--Tests/TestDriver.php95
-rw-r--r--admin/makedoc.sh4
13 files changed, 1844 insertions, 78 deletions
diff --git a/Auth/OpenID.php b/Auth/OpenID.php
index 534fc9d..3cd296f 100644
--- a/Auth/OpenID.php
+++ b/Auth/OpenID.php
@@ -20,8 +20,8 @@
/**
* Require the fetcher code.
*/
-require_once "Auth/OpenID/PlainHTTPFetcher.php";
-require_once "Auth/OpenID/ParanoidHTTPFetcher.php";
+require_once "Services/Yadis/PlainHTTPFetcher.php";
+require_once "Services/Yadis/ParanoidHTTPFetcher.php";
/**
* Status code returned by the server when the only option is to show
@@ -117,11 +117,11 @@ class Auth_OpenID {
*/
function getHTTPFetcher()
{
- if (defined('Auth_OpenID_CURL_PRESENT') &&
- Auth_OpenID_CURL_PRESENT) {
- $fetcher = new Auth_OpenID_ParanoidHTTPFetcher();
+ if (defined('Services_Yadis_CURL_PRESENT') &&
+ Services_Yadis_CURL_PRESENT) {
+ $fetcher = new Services_Yadis_ParanoidHTTPFetcher();
} else {
- $fetcher = new Auth_OpenID_PlainHTTPFetcher();
+ $fetcher = new Services_Yadis_PlainHTTPFetcher();
}
return $fetcher;
}
diff --git a/Services/Yadis/HTTPFetcher.php b/Services/Yadis/HTTPFetcher.php
new file mode 100644
index 0000000..97940a4
--- /dev/null
+++ b/Services/Yadis/HTTPFetcher.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * This module contains the HTTP fetcher interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Services_Yadis_HTTPResponse {
+ function Services_Yadis_HTTPResponse($final_url = null, $status = null,
+ $headers = null, $body = null)
+ {
+ $this->final_url = $final_url;
+ $this->status = $status;
+ $this->headers = $headers;
+ $this->body = $body;
+ }
+}
+
+/**
+ * This class is the interface for HTTP fetchers the Yadis library
+ * uses. This interface is only important if you need to write a new
+ * fetcher for some reason.
+ *
+ * @access private
+ * @package Yadis
+ */
+class Services_Yadis_HTTPFetcher {
+
+ var $timeout = 20; // timeout in seconds.
+
+ /**
+ * Return whether a URL should be allowed. Override this method to
+ * conform to your local policy.
+ *
+ * By default, will attempt to fetch any http or https URL.
+ */
+ function allowedURL($url)
+ {
+ return $this->URLHasAllowedScheme($url);
+ }
+
+ /**
+ * Is this an http or https URL?
+ *
+ * @access private
+ */
+ function URLHasAllowedScheme($url)
+ {
+ return (bool)preg_match('/^https?:\/\//i', $url);
+ }
+
+ /**
+ * @access private
+ */
+ function _findRedirect($headers)
+ {
+ foreach ($headers as $line) {
+ if (strpos($line, "Location: ") === 0) {
+ $parts = explode(" ", $line, 2);
+ return $parts[1];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Fetches the specified URL using optional extra headers and
+ * returns the server's response.
+ *
+ * @param string $url The URL to be fetched.
+ * @param array $extra_headers An array of header strings
+ * (e.g. "Accept: text/html").
+ * @return mixed $result An array of ($code, $url, $headers,
+ * $body) if the URL could be fetched; null if the URL does not
+ * pass the URLHasAllowedScheme check or if the server's response
+ * is malformed.
+ */
+ function get($url, $headers)
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Services/Yadis/Manager.php b/Services/Yadis/Manager.php
new file mode 100644
index 0000000..67a9ad1
--- /dev/null
+++ b/Services/Yadis/Manager.php
@@ -0,0 +1,303 @@
+<?php
+
+/**
+ * Yadis service manager to be used during yadis-driven authentication
+ * attempts.
+ *
+ * @package Yadis
+ */
+
+/**
+ * The base session class used by the Services_Yadis_Manager. This
+ * class wraps the default PHP session machinery and should be
+ * subclassed if your application doesn't use PHP sessioning.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_PHPSession {
+ /**
+ * Set a session key/value pair.
+ *
+ * @param string $name The name of the session key to add.
+ * @param string $value The value to add to the session.
+ */
+ function set($name, $value)
+ {
+ $_SESSION[$name] = $value;
+ }
+
+ /**
+ * Get a key's value from the session.
+ *
+ * @param string $name The name of the key to retrieve.
+ * @param string $default The optional value to return if the key
+ * is not found in the session.
+ * @return string $result The key's value in the session or
+ * $default if it isn't found.
+ */
+ function get($name, $default=null)
+ {
+ if (array_key_exists($name, $_SESSION)) {
+ return $_SESSION[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Remove a key/value pair from the session.
+ *
+ * @param string $name The name of the key to remove.
+ */
+ function del($name)
+ {
+ unset($_SESSION[$name]);
+ }
+}
+
+/**
+ * The Yadis service manager which stores state in a session and
+ * iterates over <Service> elements in a Yadis XRDS document and lets
+ * a caller attempt to use each one. This is used by the Yadis
+ * library internally.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_Manager {
+
+ /**
+ * Intialize a new yadis service manager.
+ *
+ * @access private
+ */
+ function Services_Yadis_Manager($starting_url, $yadis_url,
+ $services, $session_key)
+ {
+ // The URL that was used to initiate the Yadis protocol
+ $this->starting_url = $starting_url;
+
+ // The URL after following redirects (the identifier)
+ $this->yadis_url = $yadis_url;
+
+ // List of service elements
+ $this->services = $services;
+
+ $this->session_key = $session_key;
+
+ // Reference to the current service object
+ $this->_current = null;
+
+ // Stale flag for cleanup if PHP lib has trouble.
+ $this->stale = false;
+ }
+
+ /**
+ * @access private
+ */
+ function length()
+ {
+ // How many untried services remain?
+ return count($this->services);
+ }
+
+ /**
+ * Return the next service
+ *
+ * $this->current() will continue to return that service until the
+ * next call to this method.
+ */
+ function nextService()
+ {
+
+ if ($this->services) {
+ $this->_current = array_shift($this->services);
+ } else {
+ $this->_current = null;
+ }
+
+ return $this->_current;
+ }
+
+ /**
+ * @access private
+ */
+ function current()
+ {
+ // Return the current service.
+ // Returns None if there are no services left.
+ return $this->_current;
+ }
+
+ /**
+ * @access private
+ */
+ function forURL($url)
+ {
+ return in_array($url, array($this->starting_url, $this->yadis_url));
+ }
+
+ /**
+ * @access private
+ */
+ function started()
+ {
+ // Has the first service been returned?
+ return $this->_current !== null;
+ }
+}
+
+/**
+ * State management for discovery.
+ *
+ * High-level usage pattern is to call .getNextService(discover) in
+ * order to find the next available service for this user for this
+ * session. Once a request completes, call .finish() to clean up the
+ * session state.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_Discovery {
+
+ /**
+ * @access private
+ */
+ var $DEFAULT_SUFFIX = 'auth';
+
+ /**
+ * @access private
+ */
+ var $PREFIX = '_yadis_services_';
+
+ /**
+ * Initialize a discovery object.
+ *
+ * @param Services_Yadis_PHPSession $session An object which
+ * implements the Services_Yadis_PHPSession API.
+ * @param string $url The URL on which to attempt discovery.
+ * @param string $session_key_suffix The optional session key
+ * suffix override.
+ */
+ function Services_Yadis_Discovery(&$session, $url,
+ $session_key_suffix = null)
+ {
+ /// Initialize a discovery object
+ $this->session =& $session;
+ $this->url = $url;
+ if ($session_key_suffix === null) {
+ $session_key_suffix = $this->DEFAULT_SUFFIX;
+ }
+
+ $this->session_key_suffix = $session_key_suffix;
+ $this->session_key = $this->PREFIX . $this->session_key_suffix;
+ }
+
+ /**
+ * Return the next authentication service for the pair of
+ * user_input and session. This function handles fallback.
+ */
+ function getNextService($discover_cb, &$fetcher)
+ {
+ $manager = $this->getManager();
+ if ((!$manager) ||
+ $manager->stale) {
+ $this->destroyManager();
+ $http_response = array();
+
+ $services = call_user_func($discover_cb, $this->url,
+ $fetcher);
+
+ $manager = $this->createManager($services, $this->url);
+ }
+
+ if ($manager) {
+ $service = $manager->nextService();
+ $this->session->set($this->session_key, serialize($manager));
+ } else {
+ $service = null;
+ }
+
+ return $service;
+ }
+
+ /**
+ * Clean up Yadis-related services in the session and return the
+ * most-recently-attempted service from the manager, if one
+ * exists.
+ */
+ function cleanup()
+ {
+ $manager = $this->getManager();
+ if ($manager) {
+ $service = $manager->current();
+ $this->destroyManager();
+ } else {
+ $service = null;
+ }
+
+ return $service;
+ }
+
+ /**
+ * @access private
+ */
+ function getSessionKey()
+ {
+ // Get the session key for this starting URL and suffix
+ return $this->PREFIX . $this->session_key_suffix;
+ }
+
+ /**
+ * @access private
+ */
+ function getManager()
+ {
+ // Extract the YadisServiceManager for this object's URL and
+ // suffix from the session.
+
+ $manager_str = $this->session->get($this->getSessionKey());
+ $manager = null;
+
+ if ($manager_str !== null) {
+ $manager = unserialize($manager_str);
+ }
+
+ if ($manager && $manager->forURL($this->url)) {
+ return $manager;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function &createManager($services, $yadis_url = null)
+ {
+ $key = $this->getSessionKey();
+ if ($this->getManager()) {
+ return $this->getManager();
+ }
+
+ if (!$services) {
+ return null;
+ }
+
+ $manager = new Services_Yadis_Manager($this->url, $yadis_url,
+ $services, $key);
+ $this->session->set($this->session_key, serialize($manager));
+ return $manager;
+ }
+
+ /**
+ * @access private
+ */
+ function destroyManager()
+ {
+ if ($this->getManager() !== null) {
+ $key = $this->getSessionKey();
+ $this->session->del($key);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/ParanoidHTTPFetcher.php b/Services/Yadis/ParanoidHTTPFetcher.php
index 1f25785..5ddd129 100644
--- a/Auth/OpenID/ParanoidHTTPFetcher.php
+++ b/Services/Yadis/ParanoidHTTPFetcher.php
@@ -7,7 +7,7 @@
*
* LICENSE: See the COPYING file included in this distribution.
*
- * @package OpenID
+ * @package Yadis
* @author JanRain, Inc. <openid@janrain.com>
* @copyright 2005 Janrain, Inc.
* @license http://www.gnu.org/copyleft/lesser.html LGPL
@@ -16,23 +16,23 @@
/**
* Interface import
*/
-require_once "Auth/OpenID/HTTPFetcher.php";
+require_once "Services/Yadis/HTTPFetcher.php";
/**
* Define this based on whether the CURL extension is available.
*/
-define('Auth_OpenID_CURL_PRESENT', function_exists('curl_init'));
+define('Services_Yadis_CURL_PRESENT', function_exists('curl_init'));
/**
- * A paranoid {@link Auth_OpenID_HTTPFetcher} class which uses CURL
+ * A paranoid {@link Services_Yadis_HTTPFetcher} class which uses CURL
* for fetching.
*
- * @package OpenID
+ * @package Yadis
*/
-class Auth_OpenID_ParanoidHTTPFetcher extends Auth_OpenID_HTTPFetcher {
- function Auth_OpenID_ParanoidHTTPFetcher()
+class Services_Yadis_ParanoidHTTPFetcher extends Services_Yadis_HTTPFetcher {
+ function Services_Yadis_ParanoidHTTPFetcher()
{
- if (!Auth_OpenID_CURL_PRESENT) {
+ if (!Services_Yadis_CURL_PRESENT) {
trigger_error("Cannot use this class; CURL extension not found",
E_USER_ERROR);
}
@@ -125,7 +125,7 @@ class Auth_OpenID_ParanoidHTTPFetcher extends Auth_OpenID_HTTPFetcher {
}
}
- return new Auth_OpenID_HTTPResponse($url, $code,
+ return new Services_Yadis_HTTPResponse($url, $code,
$new_headers, $body);
}
@@ -181,8 +181,8 @@ class Auth_OpenID_ParanoidHTTPFetcher extends Auth_OpenID_HTTPFetcher {
}
- return new Auth_OpenID_HTTPResponse($url, $code,
- $new_headers, $body);
+ return new Services_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
}
}
diff --git a/Services/Yadis/ParseHTML.php b/Services/Yadis/ParseHTML.php
new file mode 100644
index 0000000..fc0df17
--- /dev/null
+++ b/Services/Yadis/ParseHTML.php
@@ -0,0 +1,270 @@
+<?php
+
+/**
+ * This is the HTML pseudo-parser for the Yadis library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * This class is responsible for scanning an HTML string to find META
+ * tags and their attributes. This is used by the Yadis discovery
+ * process. This class must be instantiated to be used.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_ParseHTML {
+
+ /**
+ * @access private
+ */
+ var $_re_flags = "si";
+
+ /**
+ * @access private
+ */
+ var $_tag_expr = "<%s\b(?!:)([^>]*?)(?:\/>|>(.*?)(?:<\/?%s\s*>|\Z))";
+
+ /**
+ * @access private
+ */
+ var $_close_tag_expr = "<\/?%s\s*>";
+
+ /**
+ * @access private
+ */
+ var $_removed_re =
+ "<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
+
+ /**
+ * @access private
+ */
+ var $_attr_find = '\b([-\w]+)=("[^"]*"|\'[^\']*\'|[^\'"\s\/<>]+)';
+
+ function Services_Yadis_ParseHTML()
+ {
+ $this->_meta_find = sprintf("/<meta\b(?!:)([^>]*)(?!<)>/%s",
+ $this->_re_flags);
+
+ $this->_removed_re = sprintf("/%s/%s",
+ $this->_removed_re,
+ $this->_re_flags);
+
+ $this->_attr_find = sprintf("/%s/%s",
+ $this->_attr_find,
+ $this->_re_flags);
+
+ $this->_entity_replacements = array(
+ 'amp' => '&',
+ 'lt' => '<',
+ 'gt' => '>',
+ 'quot' => '"'
+ );
+
+ $this->_ent_replace =
+ sprintf("&(%s);", implode("|",
+ $this->_entity_replacements));
+ }
+
+ /**
+ * Replace HTML entities (amp, lt, gt, and quot) as well as
+ * numeric entities (e.g. #x9f;) with their actual values and
+ * return the new string.
+ *
+ * @access private
+ * @param string $str The string in which to look for entities
+ * @return string $new_str The new string entities decoded
+ */
+ function replaceEntities($str)
+ {
+ foreach ($this->_entity_replacements as $old => $new) {
+ $str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
+ }
+
+ // Replace numeric entities because html_entity_decode doesn't
+ // do it for us.
+ $str = preg_replace('~&#x([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $str);
+ $str = preg_replace('~&#([0-9]+);~e', 'chr(\\1)', $str);
+
+ return $str;
+ }
+
+ /**
+ * Strip single and double quotes off of a string, if they are
+ * present.
+ *
+ * @access private
+ * @param string $str The original string
+ * @return string $new_str The new string with leading and
+ * trailing quotes removed
+ */
+ function removeQuotes($str)
+ {
+ $matches = array();
+ $double = '/^"(.*)"$/';
+ $single = "/^\'(.*)\'$/";
+
+ if (preg_match($double, $str, $matches)) {
+ return $matches[1];
+ } else if (preg_match($single, $str, $matches)) {
+ return $matches[1];
+ } else {
+ return $str;
+ }
+ }
+
+ /**
+ * Create a regular expression that will match an opening (and
+ * optional) closing tag of a given name.
+ *
+ * @access private
+ * @param string $tag_name The tag name to match
+ * @param array $close_tags An array of tag names which also
+ * constitute closing of the original tag
+ * @return string $regex A regular expression string to be used
+ * in, say, preg_match.
+ */
+ function tagMatcher($tag_name, $close_tags = null)
+ {
+ if ($close_tags) {
+ $options = implode("|", array_merge(array($tag_name), $close_tags));
+ $closer = sprintf("(?:%s)", $options);
+ } else {
+ $closer = $tag_name;
+ }
+
+ $expr = sprintf($this->_tag_expr, $tag_name, $closer);
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ /**
+ * @access private
+ */
+ function htmlFind($str)
+ {
+ return $this->tagMatcher('html', array('body'));
+ }
+
+ /**
+ * @access private
+ */
+ function headFind()
+ {
+ return $this->tagMatcher('head', array('body'));
+ }
+
+ /**
+ * Given an HTML document string, this finds all the META tags in
+ * the document, provided they are found in the
+ * <HTML><HEAD>...</HEAD> section of the document. The <HTML> tag
+ * may be missing.
+ *
+ * @access private
+ * @param string $html_string An HTMl document string
+ * @return array $tag_list Array of tags; each tag is an array of
+ * attribute -> value.
+ */
+ function getMetaTags($html_string)
+ {
+ $stripped = preg_replace($this->_removed_re,
+ "",
+ $html_string);
+
+ // Look for the closing body tag.
+ $body_closer = sprintf($this->_close_tag_expr, 'body');
+ $body_matches = array();
+ preg_match($body_closer, $html_string, $body_matches,
+ PREG_OFFSET_CAPTURE);
+ if ($body_matches) {
+ $html_string = substr($html_string, 0, $body_matches[0][1]);
+ }
+
+ // Look for the opening body tag, and discard everything after
+ // that tag.
+ $body_re = $this->tagMatcher('body');
+ $body_matches = array();
+ preg_match($body_re, $html_string, $body_matches, PREG_OFFSET_CAPTURE);
+ if ($body_matches) {
+ $html_string = substr($html_string, 0, $body_matches[0][1]);
+ }
+
+ // If an HTML tag is found at all, it must be in the right
+ // order; else, it may be missing (which is a case we allow
+ // for).
+ $html_re = $this->tagMatcher('html', array('body'));
+ preg_match($html_re, $html_string, $html_matches);
+ if ($html_matches) {
+ $html = $html_matches[0];
+ } else {
+ $html = $html_string;
+ }
+
+ // Try to find the <HEAD> tag.
+ $head_re = $this->headFind();
+ $head_matches = array();
+ if (!preg_match($head_re, $html, $head_matches)) {
+ return array();
+ }
+
+ $link_data = array();
+ $link_matches = array();
+
+ if (!preg_match_all($this->_meta_find, $head_matches[0],
+ $link_matches)) {
+ return array();
+ }
+
+ foreach ($link_matches[0] as $link) {
+ $attr_matches = array();
+ preg_match_all($this->_attr_find, $link, $attr_matches);
+ $link_attrs = array();
+ foreach ($attr_matches[0] as $index => $full_match) {
+ $name = $attr_matches[1][$index];
+ $value = $this->replaceEntities(
+ $this->removeQuotes($attr_matches[2][$index]));
+
+ $link_attrs[strtolower($name)] = $value;
+ }
+ $link_data[] = $link_attrs;
+ }
+
+ return $link_data;
+ }
+
+ /**
+ * Looks for a META tag with an "http-equiv" attribute whose value
+ * is one of ("x-xrds-location", "x-yadis-location"), ignoring
+ * case. If such a META tag is found, its "content" attribute
+ * value is returned.
+ *
+ * @param string $html_string An HTML document in string format
+ * @return mixed $content The "content" attribute value of the
+ * META tag, if found, or null if no such tag was found.
+ */
+ function getHTTPEquiv($html_string)
+ {
+ $meta_tags = $this->getMetaTags($html_string);
+
+ if ($meta_tags) {
+ foreach ($meta_tags as $tag) {
+ if (array_key_exists('http-equiv', $tag) &&
+ (in_array(strtolower($tag['http-equiv']),
+ array('x-xrds-location', 'x-yadis-location'))) &&
+ array_key_exists('content', $tag)) {
+ return $tag['content'];
+ }
+ }
+ }
+
+ return null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Auth/OpenID/PlainHTTPFetcher.php b/Services/Yadis/PlainHTTPFetcher.php
index 9919066..4d8d45c 100644
--- a/Auth/OpenID/PlainHTTPFetcher.php
+++ b/Services/Yadis/PlainHTTPFetcher.php
@@ -8,7 +8,7 @@
*
* LICENSE: See the COPYING file included in this distribution.
*
- * @package OpenID
+ * @package Yadis
* @author JanRain, Inc. <openid@janrain.com>
* @copyright 2005 Janrain, Inc.
* @license http://www.gnu.org/copyleft/lesser.html LGPL
@@ -17,15 +17,15 @@
/**
* Interface import
*/
-require_once "Auth/OpenID/HTTPFetcher.php";
+require_once "Services/Yadis/HTTPFetcher.php";
/**
* This class implements a plain, hand-built socket-based fetcher
* which will be used in the event that CURL is unavailable.
*
- * @package OpenID
+ * @package Yadis
*/
-class Auth_OpenID_PlainHTTPFetcher extends Auth_OpenID_HTTPFetcher {
+class Services_Yadis_PlainHTTPFetcher extends Services_Yadis_HTTPFetcher {
function get($url, $extra_headers = null)
{
if (!$this->allowedURL($url)) {
@@ -67,7 +67,7 @@ class Auth_OpenID_PlainHTTPFetcher extends Auth_OpenID_HTTPFetcher {
$host = 'ssl://' . $host;
}
- $user_agent = "PHP OpenID Library Fetcher";
+ $user_agent = "PHP Yadis Library Fetcher";
$headers = array(
"GET ".$parts['path'].
@@ -134,7 +134,7 @@ class Auth_OpenID_PlainHTTPFetcher extends Auth_OpenID_HTTPFetcher {
}
- return new Auth_OpenID_HTTPResponse($url, $code, $new_headers, $body);
+ return new Services_Yadis_HTTPResponse($url, $code, $new_headers, $body);
}
function post($url, $body)
@@ -229,8 +229,8 @@ class Auth_OpenID_PlainHTTPFetcher extends Auth_OpenID_HTTPFetcher {
}
- return new Auth_OpenID_HTTPResponse($url, $code,
- $headers, $response_body);
+ return new Services_Yadis_HTTPResponse($url, $code,
+ $headers, $response_body);
}
}
diff --git a/Services/Yadis/XML.php b/Services/Yadis/XML.php
new file mode 100644
index 0000000..1f1b18b
--- /dev/null
+++ b/Services/Yadis/XML.php
@@ -0,0 +1,346 @@
+<?php
+
+/**
+ * XML-parsing classes to wrap the domxml and DOM extensions for PHP 4
+ * and 5, respectively.
+ *
+ * @package Yadis
+ */
+
+/**
+ * The base class for wrappers for available PHP XML-parsing
+ * extensions. To work with this Yadis library, subclasses of this
+ * class MUST implement the API as defined in the remarks for this
+ * class. Subclasses of Services_Yadis_XMLParser are used to wrap
+ * particular PHP XML extensions such as 'domxml'. These are used
+ * internally by the library depending on the availability of
+ * supported PHP XML extensions.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_XMLParser {
+ /**
+ * Initialize an instance of Services_Yadis_XMLParser with some
+ * XML and namespaces. This SHOULD NOT be overridden by
+ * subclasses.
+ *
+ * @param string $xml_string A string of XML to be parsed.
+ * @param array $namespace_map An array of ($ns_name => $ns_uri)
+ * to be registered with the XML parser. May be empty.
+ * @return boolean $result True if the initialization and
+ * namespace registration(s) succeeded; false otherwise.
+ */
+ function init($xml_string, $namespace_map)
+ {
+ if (!$this->setXML($xml_string)) {
+ return false;
+ }
+
+ foreach ($namespace_map as $prefix => $uri) {
+ if (!$this->registerNamespace($prefix, $uri)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Register a namespace with the XML parser. This should be
+ * overridden by subclasses.
+ *
+ * @param string $prefix The namespace prefix to appear in XML tag
+ * names.
+ *
+ * @param string $uri The namespace URI to be used to identify the
+ * namespace in the XML.
+ *
+ * @return boolean $result True if the registration succeeded;
+ * false otherwise.
+ */
+ function registerNamespace($prefix, $uri)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Set this parser object's XML payload. This should be
+ * overridden by subclasses.
+ *
+ * @param string $xml_string The XML string to pass to this
+ * object's XML parser.
+ *
+ * @return boolean $result True if the initialization succeeded;
+ * false otherwise.
+ */
+ function setXML($xml_string)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Evaluate an XPath expression and return the resulting node
+ * list. This should be overridden by subclasses.
+ *
+ * @param string $xpath The XPath expression to be evaluated.
+ *
+ * @param mixed $node A node object resulting from a previous
+ * evalXPath call. This node, if specified, provides the context
+ * for the evaluation of this xpath expression.
+ *
+ * @return array $node_list An array of matching opaque node
+ * objects to be used with other methods of this parser class.
+ */
+ function evalXPath($xpath, $node = null)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Return the textual content of a specified node.
+ *
+ * @param mixed $node A node object from a previous call to
+ * $this->evalXPath().
+ *
+ * @return string $content The content of this node.
+ */
+ function content($node)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Return the attributes of a specified node.
+ *
+ * @param mixed $node A node object from a previous call to
+ * $this->evalXPath().
+ *
+ * @return array $attrs An array mapping attribute names to
+ * values.
+ */
+ function attributes($node)
+ {
+ // Not implemented.
+ }
+}
+
+/**
+ * This concrete implementation of Services_Yadis_XMLParser implements
+ * the appropriate API for the 'domxml' extension which is typically
+ * packaged with PHP 4. This class will be used whenever the 'domxml'
+ * extension is detected. See the Services_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_domxml extends Services_Yadis_XMLParser {
+ function Services_Yadis_domxml()
+ {
+ $this->xml = null;
+ $this->doc = null;
+ $this->xpath = null;
+ $this->errors = array();
+ }
+
+ function setXML($xml_string)
+ {
+ $this->xml = $xml_string;
+ $this->doc = @domxml_open_mem($xml_string, DOMXML_LOAD_PARSING,
+ $this->errors);
+
+ if (!$this->doc) {
+ return false;
+ }
+
+ $this->xpath = $this->doc->xpath_new_context();
+
+ return true;
+ }
+
+ function registerNamespace($prefix, $uri)
+ {
+ return xpath_register_ns($this->xpath, $prefix, $uri);
+ }
+
+ function &evalXPath($xpath, $node = null)
+ {
+ if ($node) {
+ $result = @$this->xpath->xpath_eval($xpath, $node);
+ } else {
+ $result = @$this->xpath->xpath_eval($xpath);
+ }
+
+ if (!$result->nodeset) {
+ $n = array();
+ return $n;
+ }
+
+ return $result->nodeset;
+ }
+
+ function content($node)
+ {
+ if ($node) {
+ return $node->get_content();
+ }
+ }
+
+ function attributes($node)
+ {
+ if ($node) {
+ $arr = $node->attributes();
+ $result = array();
+
+ if ($arr) {
+ foreach ($arr as $attrnode) {
+ $result[$attrnode->name] = $attrnode->value;
+ }
+ }
+
+ return $result;
+ }
+ }
+}
+
+/**
+ * This concrete implementation of Services_Yadis_XMLParser implements
+ * the appropriate API for the 'dom' extension which is typically
+ * packaged with PHP 5. This class will be used whenever the 'dom'
+ * extension is detected. See the Services_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_dom extends Services_Yadis_XMLParser {
+ function Services_Yadis_dom()
+ {
+ $this->xml = null;
+ $this->doc = null;
+ $this->xpath = null;
+ $this->errors = array();
+ }
+
+ function setXML($xml_string)
+ {
+ $this->xml = $xml_string;
+ $this->doc = new DOMDocument;
+
+ if (!$this->doc) {
+ return false;
+ }
+
+ if (!@$this->doc->loadXML($xml_string)) {
+ return false;
+ }
+
+ $this->xpath = new DOMXPath($this->doc);
+
+ if ($this->xpath) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function registerNamespace($prefix, $uri)
+ {
+ return $this->xpath->registerNamespace($prefix, $uri);
+ }
+
+ function &evalXPath($xpath, $node = null)
+ {
+ if ($node) {
+ $result = @$this->xpath->evaluate($xpath, $node);
+ } else {
+ $result = @$this->xpath->evaluate($xpath);
+ }
+
+ $n = array();
+
+ for ($i = 0; $i < $result->length; $i++) {
+ $n[] = $result->item($i);
+ }
+
+ return $n;
+ }
+
+ function content($node)
+ {
+ if ($node) {
+ return $node->textContent;
+ }
+ }
+
+ function attributes($node)
+ {
+ if ($node) {
+ $arr = $node->attributes;
+ $result = array();
+
+ if ($arr) {
+ for ($i = 0; $i < $arr->length; $i++) {
+ $node = $arr->item($i);
+ $result[$node->nodeName] = $node->nodeValue;
+ }
+ }
+
+ return $result;
+ }
+ }
+}
+
+$__Services_Yadis_defaultParser = null;
+
+/**
+ * Set a default parser to override the extension-driven selection of
+ * available parser classes. This is helpful in a test environment or
+ * one in which multiple parsers can be used but one is more
+ * desirable.
+ *
+ * @param Services_Yadis_XMLParser $parser An instance of a
+ * Services_Yadis_XMLParser subclass.
+ */
+function Services_Yadis_setDefaultParser(&$parser)
+{
+ global $__Services_Yadis_defaultParser;
+ $__Services_Yadis_defaultParser =& $parser;
+}
+
+$__Services_Yadis_xml_extensions = array(
+ 'dom' => 'Services_Yadis_dom',
+ 'domxml' => 'Services_Yadis_domxml'
+ );
+
+/**
+ * Returns an instance of a Services_Yadis_XMLParser subclass based on
+ * the availability of PHP extensions for XML parsing. If
+ * Services_Yadis_setDefaultParser has been called, the parser used in
+ * that call will be returned instead.
+ */
+function &Services_Yadis_getXMLParser()
+{
+ global $__Services_Yadis_defaultParser,
+ $__Services_Yadis_xml_extensions;
+
+ if ($__Services_Yadis_defaultParser) {
+ return $__Services_Yadis_defaultParser;
+ }
+
+ $p = null;
+
+ // Return a wrapper for the resident implementation, if any.
+ foreach ($__Services_Yadis_xml_extensions as $name => $cls) {
+ if (extension_loaded($name) ||
+ @dl($name . '.so')) {
+ // First create a dummy variable because PHP doesn't let
+ // you return things by reference unless they're
+ // variables. Feh.
+ $p = new $cls();
+ return $p;
+ }
+ }
+
+ return null;
+}
+
+?> \ No newline at end of file
diff --git a/Services/Yadis/XRDS.php b/Services/Yadis/XRDS.php
new file mode 100644
index 0000000..55674c4
--- /dev/null
+++ b/Services/Yadis/XRDS.php
@@ -0,0 +1,418 @@
+<?php
+
+/**
+ * This module contains the XRDS parsing code.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Require the XPath implementation.
+ */
+require_once 'Services/Yadis/XML.php';
+
+/**
+ * This match mode means a given service must match ALL filters passed
+ * to the Services_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ALL', 101);
+
+/**
+ * This match mode means a given service must match ANY filters (at
+ * least one) passed to the Services_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ANY', 102);
+
+global $_Services_Yadis_ns_map;
+$_Services_Yadis_ns_map = array('xrds' => 'xri://$xrds',
+ 'xrd' => 'xri://$xrd*($v*2.0)');
+
+define('SERVICES_YADIS_MAX_PRIORITY', pow(2, 30));
+
+/**
+ * @access private
+ */
+function Services_Yadis_array_scramble($arr)
+{
+ $result = array();
+
+ while (count($arr)) {
+ $index = array_rand($arr, 1);
+ $result[] = $arr[$index];
+ unset($arr[$index]);
+ }
+
+ return $result;
+}
+
+/**
+ * This class represents a <Service> element in an XRDS document.
+ * Objects of this type are returned by
+ * Services_Yadis_XRDS::services() and
+ * Services_Yadis_Yadis::services(). Each object corresponds directly
+ * to a <Service> element in the XRDS and supplies a
+ * getElements($name) method which you should use to inspect the
+ * element's contents. See {@link Services_Yadis_Yadis} for more
+ * information on the role this class plays in Yadis discovery.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_Service {
+
+ /**
+ * Creates an empty service object.
+ */
+ function Services_Yadis_Service()
+ {
+ $this->element = null;
+ $this->parser = null;
+ }
+
+ /**
+ * Return the URIs in the "Type" elements, if any, of this Service
+ * element.
+ *
+ * @return array $type_uris An array of Type URI strings.
+ */
+ function getTypes()
+ {
+ $t = array();
+ foreach ($this->getElements('xrd:Type') as $elem) {
+ $c = $this->parser->content($elem);
+ if ($c) {
+ $t[] = $c;
+ }
+ }
+ return $t;
+ }
+
+ /**
+ * Return the URIs in the "URI" elements, if any, of this Service
+ * element. The URIs are returned sorted in priority order.
+ *
+ * @return array $uris An array of URI strings.
+ */
+ function getURIs()
+ {
+ $uris = array();
+ $last = array();
+
+ foreach ($this->getElements('xrd:URI') as $elem) {
+ $uri_string = $this->parser->content($elem);
+ $attrs = $this->parser->attributes($elem);
+ if ($attrs &&
+ array_key_exists('priority', $attrs)) {
+ $priority = intval($attrs['priority']);
+ if (!array_key_exists($priority, $uris)) {
+ $uris[$priority] = array();
+ }
+
+ $uris[$priority][] = $uri_string;
+ } else {
+ $last[] = $uri_string;
+ }
+ }
+
+ $keys = array_keys($uris);
+ sort($keys);
+
+ // Rebuild array of URIs.
+ $result = array();
+ foreach ($keys as $k) {
+ $new_uris = Services_Yadis_array_scramble($uris[$k]);
+ $result = array_merge($result, $new_uris);
+ }
+
+ $result = array_merge($result,
+ Services_Yadis_array_scramble($last));
+
+ return $result;
+ }
+
+ /**
+ * Returns the "priority" attribute value of this <Service>
+ * element, if the attribute is present. Returns null if not.
+ *
+ * @return mixed $result Null or integer, depending on whether
+ * this Service element has a 'priority' attribute.
+ */
+ function getPriority()
+ {
+ $attributes = $this->parser->attributes($this->element);
+
+ if (array_key_exists('priority', $attributes)) {
+ return intval($attributes['priority']);
+ }
+
+ return null;
+ }
+
+ /**
+ * Used to get XML elements from this object's <Service> element.
+ *
+ * This is what you should use to get all custom information out
+ * of this element. This is used by service filter functions to
+ * determine whether a service element contains specific tags,
+ * etc. NOTE: this only considers elements which are direct
+ * children of the <Service> element for this object.
+ *
+ * @param string $name The name of the element to look for
+ * @return array $list An array of elements with the specified
+ * name which are direct children of the <Service> element. The
+ * nodes returned by this function can be passed to $this->parser
+ * methods (see {@link Services_Yadis_XMLParser}).
+ */
+ function getElements($name)
+ {
+ return $this->parser->evalXPath($name, $this->element);
+ }
+}
+
+/**
+ * This class performs parsing of XRDS documents.
+ *
+ * You should not instantiate this class directly; rather, call
+ * parseXRDS statically:
+ *
+ * <pre> $xrds = Services_Yadis_XRDS::parseXRDS($xml_string);</pre>
+ *
+ * If the XRDS can be parsed and is valid, an instance of
+ * Services_Yadis_XRDS will be returned. Otherwise, null will be
+ * returned. This class is used by the Services_Yadis_Yadis::discover
+ * method.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_XRDS {
+
+ /**
+ * Instantiate a Services_Yadis_XRDS object. Requires an XPath
+ * instance which has been used to parse a valid XRDS document.
+ */
+ function Services_Yadis_XRDS(&$xmlParser, &$xrdNode)
+ {
+ $this->parser =& $xmlParser;
+ $this->xrdNode = $xrdNode;
+ $this->serviceList = array();
+ $this->_parse();
+ }
+
+ /**
+ * Parse an XML string (XRDS document) and return either a
+ * Services_Yadis_XRDS object or null, depending on whether the
+ * XRDS XML is valid.
+ *
+ * @param string $xml_string An XRDS XML string.
+ * @return mixed $xrds An instance of Services_Yadis_XRDS or null,
+ * depending on the validity of $xml_string
+ */
+ function parseXRDS($xml_string, $extra_ns_map = null)
+ {
+ global $_Services_Yadis_ns_map;
+
+ if (!$xml_string) {
+ return null;
+ }
+
+ $parser = Services_Yadis_getXMLParser();
+
+ $ns_map = $_Services_Yadis_ns_map;
+
+ if ($extra_ns_map && is_array($extra_ns_map)) {
+ $ns_map = array_merge($ns_map, $extra_ns_map);
+ }
+
+ if (!($parser && $parser->init($xml_string, $ns_map))) {
+ return null;
+ }
+
+ // Try to get root element.
+ $root = $parser->evalXPath('/xrds:XRDS[1]');
+ if (!$root) {
+ return null;
+ }
+
+ if (is_array($root)) {
+ $root = $root[0];
+ }
+
+ $attrs = $parser->attributes($root);
+
+ if (array_key_exists('xmlns:xrd', $attrs) &&
+ $attrs['xmlns:xrd'] != 'xri://$xrd*($v*2.0)') {
+ return null;
+ } else if (array_key_exists('xmlns', $attrs) &&
+ preg_match('/xri/', $attrs['xmlns']) &&
+ $attrs['xmlns'] != 'xri://$xrd*($v*2.0)') {
+ return null;
+ }
+
+ // Get the last XRD node.
+ $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD[last()]');
+
+ if (!$xrd_nodes) {
+ return null;
+ }
+
+ $xrds = new Services_Yadis_XRDS($parser, $xrd_nodes[0]);
+ return $xrds;
+ }
+
+ /**
+ * @access private
+ */
+ function _addService($priority, $service)
+ {
+ $priority = intval($priority);
+
+ if (!array_key_exists($priority, $this->serviceList)) {
+ $this->serviceList[$priority] = array();
+ }
+
+ $this->serviceList[$priority][] = $service;
+ }
+
+ /**
+ * Creates the service list using nodes from the XRDS XML
+ * document.
+ *
+ * @access private
+ */
+ function _parse()
+ {
+ $this->serviceList = array();
+
+ $services = $this->parser->evalXPath('xrd:Service', $this->xrdNode);
+
+ foreach ($services as $node) {
+ $s =& new Services_Yadis_Service();
+ $s->element = $node;
+ $s->parser =& $this->parser;
+
+ $priority = $s->getPriority();
+
+ if ($priority === null) {
+ $priority = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ $this->_addService($priority, $s);
+ }
+ }
+
+ /**
+ * Returns a list of service objects which correspond to <Service>
+ * elements in the XRDS XML document for this object.
+ *
+ * Optionally, an array of filter callbacks may be given to limit
+ * the list of returned service objects. Furthermore, the default
+ * mode is to return all service objects which match ANY of the
+ * specified filters, but $filter_mode may be
+ * SERVICES_YADIS_MATCH_ALL if you want to be sure that the
+ * returned services match all the given filters. See {@link
+ * Services_Yadis_Yadis} for detailed usage information on filter
+ * functions.
+ *
+ * @param mixed $filters An array of callbacks to filter the
+ * returned services, or null if all services are to be returned.
+ * @param integer $filter_mode SERVICES_YADIS_MATCH_ALL or
+ * SERVICES_YADIS_MATCH_ANY, depending on whether the returned
+ * services should match ALL or ANY of the specified filters,
+ * respectively.
+ * @return mixed $services An array of {@link
+ * Services_Yadis_Service} objects if $filter_mode is a valid
+ * mode; null if $filter_mode is an invalid mode (i.e., not
+ * SERVICES_YADIS_MATCH_ANY or SERVICES_YADIS_MATCH_ALL).
+ */
+ function services($filters = null,
+ $filter_mode = SERVICES_YADIS_MATCH_ANY)
+ {
+
+ $pri_keys = array_keys($this->serviceList);
+ sort($pri_keys, SORT_NUMERIC);
+
+ // If no filters are specified, return the entire service
+ // list, ordered by priority.
+ if (!$filters ||
+ (!is_array($filters))) {
+
+ $result = array();
+ foreach ($pri_keys as $pri) {
+ $result = array_merge($result, $this->serviceList[$pri]);
+ }
+
+ return $result;
+ }
+
+ // If a bad filter mode is specified, return null.
+ if (!in_array($filter_mode, array(SERVICES_YADIS_MATCH_ANY,
+ SERVICES_YADIS_MATCH_ALL))) {
+ return null;
+ }
+
+ // Otherwise, use the callbacks in the filter list to
+ // determine which services are returned.
+ $filtered = array();
+
+ foreach ($pri_keys as $priority_value) {
+ $service_obj_list = $this->serviceList[$priority_value];
+
+ foreach ($service_obj_list as $service) {
+
+ $matches = 0;
+
+ foreach ($filters as $filter) {
+ if (call_user_func_array($filter, array($service))) {
+ $matches++;
+
+ if ($filter_mode == SERVICES_YADIS_MATCH_ANY) {
+ $pri = $service->getPriority();
+ if ($pri === null) {
+ $pri = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ if (!array_key_exists($pri, $filtered)) {
+ $filtered[$pri] = array();
+ }
+
+ $filtered[$pri][] = $service;
+ break;
+ }
+ }
+ }
+
+ if (($filter_mode == SERVICES_YADIS_MATCH_ALL) &&
+ ($matches == count($filters))) {
+
+ $pri = $service->getPriority();
+ if ($pri === null) {
+ $pri = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ if (!array_key_exists($pri, $filtered)) {
+ $filtered[$pri] = array();
+ }
+ $filtered[$pri][] = $service;
+ }
+ }
+ }
+
+ $pri_keys = array_keys($filtered);
+ sort($pri_keys, SORT_NUMERIC);
+
+ $result = array();
+ foreach ($pri_keys as $pri) {
+ $result = array_merge($result, $filtered[$pri]);
+ }
+
+ return $result;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Services/Yadis/Yadis.php b/Services/Yadis/Yadis.php
new file mode 100644
index 0000000..78fb11c
--- /dev/null
+++ b/Services/Yadis/Yadis.php
@@ -0,0 +1,309 @@
+<?php
+
+/**
+ * The core PHP Yadis implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package Yadis
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Need both fetcher types so we can use the right one based on the
+ * presence or absence of CURL.
+ */
+require_once "Services/Yadis/PlainHTTPFetcher.php";
+require_once "Services/Yadis/ParanoidHTTPFetcher.php";
+
+/**
+ * Need this for parsing HTML (looking for META tags).
+ */
+require_once "Services/Yadis/ParseHTML.php";
+
+/**
+ * Need this to parse the XRDS document during Yadis discovery.
+ */
+require_once "Services/Yadis/XRDS.php";
+
+/**
+ * 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:
+ *
+ * <pre> require_once "Services/Yadis/Yadis.php";</pre>
+ *
+ * To perform Yadis discovery, first call the "discover" method
+ * statically with a URI parameter:
+ *
+ * <pre> $http_response = array();
+ * $fetcher = Services_Yadis_Yadis::getHTTPFetcher();
+ * $yadis_object = Services_Yadis_Yadis::discover($uri,
+ * $http_response, $fetcher);</pre>
+ *
+ * If the discovery succeeds, $yadis_object will be an instance of
+ * {@link Services_Yadis_Yadis}. If not, it will be null. The XRDS
+ * document found during discovery should have service descriptions,
+ * which can be accessed by calling
+ *
+ * <pre> $service_list = $yadis_object->services();</pre>
+ *
+ * which returns an array of objects which describe each service.
+ * These objects are instances of Services_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():
+ *
+ * <pre> $service_list = $yadis_object->services(
+ * array("filterByURI",
+ * "filterByExtension"));</pre>
+ *
+ * The filter functions (whose names appear in the array passed to
+ * services()) take the following form:
+ *
+ * <pre> function myFilter(&$service) {
+ * // Query $service object here. Return true if the service
+ * // matches your query; false if not.
+ * }</pre>
+ *
+ * This is an example of a filter which uses a regular expression to
+ * match the content of URI tags (note that the Services_Yadis_Service
+ * class provides a getURIs() method which you should use instead of
+ * this contrived example):
+ *
+ * <pre>
+ * function URIMatcher(&$service) {
+ * foreach ($service->getElements('xrd:URI') as $uri) {
+ * if (preg_match("/some_pattern/",
+ * $service->parser->content($uri))) {
+ * return true;
+ * }
+ * }
+ * return false;
+ * }</pre>
+ *
+ * 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():
+ *
+ * <pre> $yadis_object->services(array("filter1", "filter2"),
+ * SERVICES_YADIS_MATCH_ALL);</pre>
+ *
+ * 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
+ * Services_Yadis_Service}.
+ *
+ * @package Yadis
+ */
+class Services_Yadis_Yadis {
+
+ /**
+ * Returns an HTTP fetcher object. If the CURL extension is
+ * present, an instance of {@link Services_Yadis_ParanoidHTTPFetcher}
+ * is returned. If not, an instance of
+ * {@link Services_Yadis_PlainHTTPFetcher} is returned.
+ */
+ function getHTTPFetcher($timeout = 20)
+ {
+ if (defined('Services_Yadis_CURL_PRESENT') &&
+ Services_Yadis_CURL_PRESENT) {
+ $fetcher = new Services_Yadis_ParanoidHTTPFetcher($timeout);
+ } else {
+ $fetcher = new Services_Yadis_PlainHTTPFetcher($timeout);
+ }
+ return $fetcher;
+ }
+
+ /**
+ * @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
+ * Services_Yadis_HTTPResponse}.
+ *
+ * @param Services_Yadis_HTTPFetcher $fetcher An instance of a
+ * Services_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
+ * Services_Yadis_Yadis, depending on whether the discovery
+ * succeeded.
+ */
+ function discover($uri, &$http_response, &$fetcher,
+ $extra_ns_map = null, $timeout = 20)
+ {
+ if (!$uri) {
+ return null;
+ }
+
+ $request_uri = $uri;
+ $headers = array("Accept: application/xrds+xml");
+
+ if (!$fetcher) {
+ $fetcher = Services_Yadis_Yadis::getHTTPFetcher($timeout);
+ }
+
+ $response = $fetcher->get($uri, $headers);
+ $http_response = $response;
+
+ if (!$response) {
+ return null;
+ }
+
+ if ($response->status != 200) {
+ return null;
+ }
+
+ $xrds_uri = $response->final_url;
+ $uri = $response->final_url;
+ $body = $response->body;
+
+ $xrds_header_uri = Services_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('x-xrds-location',
+ 'x-yadis-location'));
+
+ $content_type = Services_Yadis_Yadis::_getHeader($response->headers,
+ array('content-type'));
+
+ if ($xrds_header_uri) {
+ $xrds_uri = $xrds_header_uri;
+ $response = $fetcher->get($xrds_uri);
+ $http_response = $response;
+ if (!$response) {
+ return null;
+ } else {
+ $body = $response->body;
+ $headers = $response->headers;
+ $content_type = Services_Yadis_Yadis::_getHeader($headers,
+ array('content-type'));
+ }
+ }
+
+ if (Services_Yadis_Yadis::_getContentType($content_type) !=
+ 'application/xrds+xml') {
+ // Treat the body as HTML and look for a META tag.
+ $parser = new Services_Yadis_ParseHTML();
+ $new_uri = $parser->getHTTPEquiv($body);
+ $xrds_uri = null;
+ if ($new_uri) {
+ $response = $fetcher->get($new_uri);
+ if ($response->status != 200) {
+ return null;
+ }
+ $http_response = $response;
+ $body = $response->body;
+ $xrds_uri = $new_uri;
+ $content_type = Services_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('content-type'));
+ }
+ }
+
+ $xrds = Services_Yadis_XRDS::parseXRDS($body, $extra_ns_map);
+
+ if ($xrds !== null) {
+ $y = new Services_Yadis_Yadis();
+
+ $y->request_uri = $request_uri;
+ $y->xrds = $xrds;
+ $y->uri = $uri;
+ $y->xrds_uri = $xrds_uri;
+ $y->body = $body;
+ $y->content_type = $content_type;
+
+ return $y;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Instantiates an empty Services_Yadis_Yadis object. This
+ * constructor should not be used by any user of the library.
+ * This constructor results in a completely useless object which
+ * must be populated with valid discovery information. Instead of
+ * using this constructor, call
+ * Services_Yadis_Yadis::discover($uri).
+ */
+ function Services_Yadis_Yadis()
+ {
+ $this->request_uri = null;
+ $this->uri = null;
+ $this->xrds = null;
+ $this->xrds_uri = null;
+ $this->body = null;
+ $this->content_type = null;
+ }
+
+ /**
+ * 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 Services_Yadis_Service}
+ * objects
+ */
+ function services()
+ {
+ if ($this->xrds) {
+ return $this->xrds->services();
+ }
+
+ return null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/Consumer.php b/Tests/Auth/OpenID/Consumer.php
index 1b07b8d..156fcdf 100644
--- a/Tests/Auth/OpenID/Consumer.php
+++ b/Tests/Auth/OpenID/Consumer.php
@@ -14,12 +14,11 @@
*/
require_once 'Auth/OpenID/CryptUtil.php';
-require_once 'Auth/OpenID/HTTPFetcher.php';
+require_once 'Services/Yadis/HTTPFetcher.php';
require_once 'Auth/OpenID/DiffieHellman.php';
require_once 'Auth/OpenID/FileStore.php';
require_once 'Auth/OpenID/KVForm.php';
require_once 'Auth/OpenID/Consumer.php';
-require_once 'Auth/OpenID/HTTPFetcher.php';
require_once 'Tests/Auth/OpenID/MemStore.php';
require_once 'PHPUnit.php';
@@ -75,15 +74,15 @@ function Auth_OpenID_associate($qs, $assoc_secret, $assoc_handle)
return Auth_OpenID_KVForm::fromArray($reply_dict);
}
-class Auth_OpenID_TestFetcher extends Auth_OpenID_HTTPFetcher {
+class Auth_OpenID_TestFetcher extends Services_Yadis_HTTPFetcher {
function Auth_OpenID_TestFetcher($user_url, $user_page,
$assoc_secret, $assoc_handle)
{
$this->get_responses = array($user_url =>
- new Auth_OpenID_HTTPResponse($user_url,
- 200,
- array(),
- $user_page));
+ new Services_Yadis_HTTPResponse($user_url,
+ 200,
+ array(),
+ $user_page));
$this->assoc_secret = $assoc_secret;
$this->assoc_handle = $assoc_handle;
$this->num_assocs = 0;
@@ -92,9 +91,9 @@ class Auth_OpenID_TestFetcher extends Auth_OpenID_HTTPFetcher {
function response($url, $body)
{
if ($body === null) {
- return new Auth_OpenID_HTTPResponse($url, 404, array(), 'Not found');
+ return new Services_Yadis_HTTPResponse($url, 404, array(), 'Not found');
} else {
- return new Auth_OpenID_HTTPResponse($url, 200, array(), $body);
+ return new Services_Yadis_HTTPResponse($url, 200, array(), $body);
}
}
@@ -118,9 +117,9 @@ class Auth_OpenID_TestFetcher extends Auth_OpenID_HTTPFetcher {
);
if ($query_data == $expected) {
- return new Auth_OpenID_HTTPResponse($url, 200, array(), "is_valid:true\n");
+ return new Services_Yadis_HTTPResponse($url, 200, array(), "is_valid:true\n");
} else {
- return new Auth_OpenID_HTTPResponse($url, 400, array(),
+ return new Services_Yadis_HTTPResponse($url, 400, array(),
"error:bad check_authentication query\n");
}
}
@@ -739,7 +738,7 @@ class Tests_Auth_OpenID_Consumer_TestCheckAuth extends _TestIdRes {
function test_checkauth_error()
{
global $_Auth_OpenID_server_url;
- $this->fetcher->response = new Auth_OpenID_HTTPResponse("http://some_url",
+ $this->fetcher->response = new Services_Yadis_HTTPResponse("http://some_url",
404,
array(),
"blah:blah\n");
@@ -772,10 +771,10 @@ class Tests_Auth_OpenID_Consumer_TestFetchAssoc extends PHPUnit_TestCase {
function test_kvpost_error()
{
- $this->fetcher->response = new Auth_OpenID_HTTPResponse("http://some_url",
- 404,
- array(),
- "blah:blah\n");
+ $this->fetcher->response = new Services_Yadis_HTTPResponse("http://some_url",
+ 404,
+ array(),
+ "blah:blah\n");
$r = $this->consumer->_makeKVPost(array('openid.mode' => 'associate'),
"http://server_url");
if ($r !== null) {
diff --git a/Tests/Auth/OpenID/Discover.php b/Tests/Auth/OpenID/Discover.php
index bf37655..43161a5 100644
--- a/Tests/Auth/OpenID/Discover.php
+++ b/Tests/Auth/OpenID/Discover.php
@@ -148,8 +148,8 @@ class _DiscoveryMockFetcher {
$body = '';
}
- return new Auth_OpenID_HTTPResponse($final_url, $status,
- array('content-type' => $ctype), $body);
+ return new Services_Yadis_HTTPResponse($final_url, $status,
+ array('content-type' => $ctype), $body);
}
}
diff --git a/Tests/TestDriver.php b/Tests/TestDriver.php
index 0fa94d9..8fe9a7c 100644
--- a/Tests/TestDriver.php
+++ b/Tests/TestDriver.php
@@ -68,7 +68,9 @@ function loadTests($test_dir, $test_names)
$filename = $test_dir . $filename . '.php';
$class_name = str_replace(DIRECTORY_SEPARATOR, '_', $filename);
$class_name = basename($class_name, '.php');
- global_require_once($filename);
+ if (!global_require_once($filename)) {
+ continue;
+ }
$test = new $class_name($class_name);
if (is_a($test, 'PHPUnit_TestCase')) {
@@ -94,52 +96,73 @@ function loadTests($test_dir, $test_names)
function global_require_once($name)
{
- require_once $name;
+ $f = @include_once $name;
+ if (!$f) {
+ return false;
+ }
foreach (get_defined_vars() as $k => $v) {
if (!in_array($k, array('name', 'GLOBALS'))) {
$GLOBALS[$k] = $v;
}
}
+ return true;
}
-$_test_dir = 'Tests/Auth/OpenID/';
-$_test_names = array(
- 'Association',
- 'BigMath',
- 'Consumer',
- 'CryptUtil',
- 'DiffieHellman',
- 'HMACSHA1',
- 'KVForm',
- 'Util',
- 'Parse',
- 'StoreTest',
- 'Server',
- 'TrustRoot',
- 'Discover',
- 'OpenID_Yadis',
- 'URINorm'
- );
+$_tests = array(
+ array(
+ 'dir' => 'Tests/Auth/OpenID/',
+ 'files' => array(
+ 'Association',
+ 'BigMath',
+ 'Consumer',
+ 'CryptUtil',
+ 'DiffieHellman',
+ 'HMACSHA1',
+ 'KVForm',
+ 'Util',
+ 'Parse',
+ 'StoreTest',
+ 'Server',
+ 'TrustRoot',
+ 'Discover',
+ 'OpenID_Yadis',
+ 'URINorm'),
+ ),
+ array(
+ 'dir' => 'Tests/Services/Yadis/',
+ 'files' => array(
+ 'ParseHTML',
+ 'XRDS',
+ 'Yadis',
+ 'Discover'
+ )
+ )
+ );
function selectTests($names)
{
- global $_test_names;
+ global $_tests;
$lnames = array_map('strtolower', $names);
$include = array();
$exclude = array();
- foreach ($_test_names as $t) {
- $l = strtolower($t);
- if (in_array($l, $lnames)) {
- $include[] = $t;
- }
+ foreach ($_tests as $package) {
+ foreach ($package['files'] as $t) {
+ $l = strtolower($t);
+ if (in_array($l, $lnames)) {
+ $include[] = $t;
+ }
- if (in_array("/$l", $lnames)) {
- $exclude[] = $t;
+ if (in_array("/$l", $lnames)) {
+ $exclude[] = $t;
+ }
}
}
if (!count($include)) {
- $include = $_test_names;
+ $include = array();
+ foreach ($_tests as $package) {
+ $include = array_merge($include, $package['files']);
+ }
}
return array_diff($include, $exclude);
@@ -148,12 +171,18 @@ function selectTests($names)
// Load OpenID library tests
function loadSuite($names=null)
{
- global $_test_names;
- global $_test_dir;
+ global $_tests;
if ($names === null) {
- $names = $_test_names;
+ $names = array();
+ foreach ($_tests as $package) {
+ $names = array_merge($names, $package['files']);
+ }
}
$selected = selectTests($names);
- return loadTests($_test_dir, $selected);
+ $result = array();
+ foreach ($_tests as $package) {
+ $result = array_merge($result, loadTests($package['dir'], $selected));
+ }
+ return $result;
}
?>
diff --git a/admin/makedoc.sh b/admin/makedoc.sh
index d6fe8ad..7c0e77d 100644
--- a/admin/makedoc.sh
+++ b/admin/makedoc.sh
@@ -1,5 +1,5 @@
#!/bin/sh
set -v
-phpdoc -p -t doc -d Auth,admin/tutorials -ti "JanRain OpenID Library" \
- --ignore \*~,BigMath.php,Discover.php,CryptUtil.php,DiffieHellman.php,HMACSHA1.php,KVForm.php,Parse.php,TrustRoot.php \
+phpdoc -p -t doc -d Auth,admin/tutorials,Services -ti "JanRain OpenID Library" \
+ --ignore \*~,BigMath.php,Discover.php,CryptUtil.php,DiffieHellman.php,HMACSHA1.php,KVForm.php,Parse.php,TrustRoot.php,HTTPFetcher.php,ParanoidHTTPFetcher.php,PlainHTTPFetcher.php,ParseHTML.php \
-dn "OpenID" -o "HTML:frames:phphtmllib"