diff options
-rw-r--r-- | Auth/OpenID.php | 12 | ||||
-rw-r--r-- | Services/Yadis/HTTPFetcher.php | 92 | ||||
-rw-r--r-- | Services/Yadis/Manager.php | 303 | ||||
-rw-r--r-- | Services/Yadis/ParanoidHTTPFetcher.php (renamed from Auth/OpenID/ParanoidHTTPFetcher.php) | 22 | ||||
-rw-r--r-- | Services/Yadis/ParseHTML.php | 270 | ||||
-rw-r--r-- | Services/Yadis/PlainHTTPFetcher.php (renamed from Auth/OpenID/PlainHTTPFetcher.php) | 16 | ||||
-rw-r--r-- | Services/Yadis/XML.php | 346 | ||||
-rw-r--r-- | Services/Yadis/XRDS.php | 418 | ||||
-rw-r--r-- | Services/Yadis/Yadis.php | 309 | ||||
-rw-r--r-- | Tests/Auth/OpenID/Consumer.php | 31 | ||||
-rw-r--r-- | Tests/Auth/OpenID/Discover.php | 4 | ||||
-rw-r--r-- | Tests/TestDriver.php | 95 | ||||
-rw-r--r-- | admin/makedoc.sh | 4 |
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" |