summaryrefslogtreecommitdiff
path: root/extlib/Auth/Yadis
diff options
context:
space:
mode:
Diffstat (limited to 'extlib/Auth/Yadis')
-rw-r--r--extlib/Auth/Yadis/HTTPFetcher.php147
-rw-r--r--extlib/Auth/Yadis/Manager.php529
-rw-r--r--extlib/Auth/Yadis/Misc.php59
-rw-r--r--extlib/Auth/Yadis/ParanoidHTTPFetcher.php228
-rw-r--r--extlib/Auth/Yadis/ParseHTML.php259
-rw-r--r--extlib/Auth/Yadis/PlainHTTPFetcher.php251
-rw-r--r--extlib/Auth/Yadis/XML.php374
-rw-r--r--extlib/Auth/Yadis/XRDS.php478
-rw-r--r--extlib/Auth/Yadis/XRI.php234
-rw-r--r--extlib/Auth/Yadis/XRIRes.php72
-rw-r--r--extlib/Auth/Yadis/Yadis.php382
11 files changed, 3013 insertions, 0 deletions
diff --git a/extlib/Auth/Yadis/HTTPFetcher.php b/extlib/Auth/Yadis/HTTPFetcher.php
new file mode 100644
index 000000000..a1825403d
--- /dev/null
+++ b/extlib/Auth/Yadis/HTTPFetcher.php
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * This module contains the HTTP fetcher interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require logging functionality
+ */
+require_once "Auth/OpenID.php";
+
+define('Auth_OpenID_FETCHER_MAX_RESPONSE_KB', 1024);
+define('Auth_OpenID_USER_AGENT',
+ 'php-openid/'.Auth_OpenID_VERSION.' (php/'.phpversion().')');
+
+class Auth_Yadis_HTTPResponse {
+ function Auth_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 OpenID
+ */
+class Auth_Yadis_HTTPFetcher {
+
+ var $timeout = 20; // timeout in seconds.
+
+ /**
+ * Return whether a URL can be fetched. Returns false if the URL
+ * scheme is not allowed or is not supported by this fetcher
+ * implementation; returns true otherwise.
+ *
+ * @return bool
+ */
+ function canFetchURL($url)
+ {
+ if ($this->isHTTPS($url) && !$this->supportsSSL()) {
+ Auth_OpenID::log("HTTPS URL unsupported fetching %s",
+ $url);
+ return false;
+ }
+
+ if (!$this->allowedURL($url)) {
+ Auth_OpenID::log("URL fetching not allowed for '%s'",
+ $url);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Does this fetcher implementation (and runtime) support fetching
+ * HTTPS URLs? May inspect the runtime environment.
+ *
+ * @return bool $support True if this fetcher supports HTTPS
+ * fetching; false if not.
+ */
+ function supportsSSL()
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * Is this an https URL?
+ *
+ * @access private
+ */
+ function isHTTPS($url)
+ {
+ return (bool)preg_match('/^https:\/\//i', $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(strtolower($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/extlib/Auth/Yadis/Manager.php b/extlib/Auth/Yadis/Manager.php
new file mode 100644
index 000000000..d50cf7ad6
--- /dev/null
+++ b/extlib/Auth/Yadis/Manager.php
@@ -0,0 +1,529 @@
+<?php
+
+/**
+ * Yadis service manager to be used during yadis-driven authentication
+ * attempts.
+ *
+ * @package OpenID
+ */
+
+/**
+ * The base session class used by the Auth_Yadis_Manager. This
+ * class wraps the default PHP session machinery and should be
+ * subclassed if your application doesn't use PHP sessioning.
+ *
+ * @package OpenID
+ */
+class Auth_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]);
+ }
+
+ /**
+ * Return the contents of the session in array form.
+ */
+ function contents()
+ {
+ return $_SESSION;
+ }
+}
+
+/**
+ * A session helper class designed to translate between arrays and
+ * objects. Note that the class used must have a constructor that
+ * takes no parameters. This is not a general solution, but it works
+ * for dumb objects that just need to have attributes set. The idea
+ * is that you'll subclass this and override $this->check($data) ->
+ * bool to implement your own session data validation.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_SessionLoader {
+ /**
+ * Override this.
+ *
+ * @access private
+ */
+ function check($data)
+ {
+ return true;
+ }
+
+ /**
+ * Given a session data value (an array), this creates an object
+ * (returned by $this->newObject()) whose attributes and values
+ * are those in $data. Returns null if $data lacks keys found in
+ * $this->requiredKeys(). Returns null if $this->check($data)
+ * evaluates to false. Returns null if $this->newObject()
+ * evaluates to false.
+ *
+ * @access private
+ */
+ function fromSession($data)
+ {
+ if (!$data) {
+ return null;
+ }
+
+ $required = $this->requiredKeys();
+
+ foreach ($required as $k) {
+ if (!array_key_exists($k, $data)) {
+ return null;
+ }
+ }
+
+ if (!$this->check($data)) {
+ return null;
+ }
+
+ $data = array_merge($data, $this->prepareForLoad($data));
+ $obj = $this->newObject($data);
+
+ if (!$obj) {
+ return null;
+ }
+
+ foreach ($required as $k) {
+ $obj->$k = $data[$k];
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Prepares the data array by making any necessary changes.
+ * Returns an array whose keys and values will be used to update
+ * the original data array before calling $this->newObject($data).
+ *
+ * @access private
+ */
+ function prepareForLoad($data)
+ {
+ return array();
+ }
+
+ /**
+ * Returns a new instance of this loader's class, using the
+ * session data to construct it if necessary. The object need
+ * only be created; $this->fromSession() will take care of setting
+ * the object's attributes.
+ *
+ * @access private
+ */
+ function newObject($data)
+ {
+ return null;
+ }
+
+ /**
+ * Returns an array of keys and values built from the attributes
+ * of $obj. If $this->prepareForSave($obj) returns an array, its keys
+ * and values are used to update the $data array of attributes
+ * from $obj.
+ *
+ * @access private
+ */
+ function toSession($obj)
+ {
+ $data = array();
+ foreach ($obj as $k => $v) {
+ $data[$k] = $v;
+ }
+
+ $extra = $this->prepareForSave($obj);
+
+ if ($extra && is_array($extra)) {
+ foreach ($extra as $k => $v) {
+ $data[$k] = $v;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Override this.
+ *
+ * @access private
+ */
+ function prepareForSave($obj)
+ {
+ return array();
+ }
+}
+
+/**
+ * A concrete loader implementation for Auth_OpenID_ServiceEndpoints.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServiceEndpointLoader extends Auth_Yadis_SessionLoader {
+ function newObject($data)
+ {
+ return new Auth_OpenID_ServiceEndpoint();
+ }
+
+ function requiredKeys()
+ {
+ $obj = new Auth_OpenID_ServiceEndpoint();
+ $data = array();
+ foreach ($obj as $k => $v) {
+ $data[] = $k;
+ }
+ return $data;
+ }
+
+ function check($data)
+ {
+ return is_array($data['type_uris']);
+ }
+}
+
+/**
+ * A concrete loader implementation for Auth_Yadis_Managers.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ManagerLoader extends Auth_Yadis_SessionLoader {
+ function requiredKeys()
+ {
+ return array('starting_url',
+ 'yadis_url',
+ 'services',
+ 'session_key',
+ '_current',
+ 'stale');
+ }
+
+ function newObject($data)
+ {
+ return new Auth_Yadis_Manager($data['starting_url'],
+ $data['yadis_url'],
+ $data['services'],
+ $data['session_key']);
+ }
+
+ function check($data)
+ {
+ return is_array($data['services']);
+ }
+
+ function prepareForLoad($data)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $services = array();
+ foreach ($data['services'] as $s) {
+ $services[] = $loader->fromSession($s);
+ }
+ return array('services' => $services);
+ }
+
+ function prepareForSave($obj)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $services = array();
+ foreach ($obj->services as $s) {
+ $services[] = $loader->toSession($s);
+ }
+ return array('services' => $services);
+ }
+}
+
+/**
+ * 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 OpenID
+ */
+class Auth_Yadis_Manager {
+
+ /**
+ * Intialize a new yadis service manager.
+ *
+ * @access private
+ */
+ function Auth_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 .cleanup() to clean up the
+ * session state.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Discovery {
+
+ /**
+ * @access private
+ */
+ var $DEFAULT_SUFFIX = 'auth';
+
+ /**
+ * @access private
+ */
+ var $PREFIX = '_yadis_services_';
+
+ /**
+ * Initialize a discovery object.
+ *
+ * @param Auth_Yadis_PHPSession $session An object which
+ * implements the Auth_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 Auth_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->services)) {
+ $this->destroyManager();
+
+ list($yadis_url, $services) = call_user_func($discover_cb,
+ $this->url,
+ $fetcher);
+
+ $manager = $this->createManager($services, $yadis_url);
+ }
+
+ if ($manager) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $service = $manager->nextService();
+ $this->session->set($this->session_key,
+ serialize($loader->toSession($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.
+ *
+ * @param $force True if the manager should be deleted regardless
+ * of whether it's a manager for $this->url.
+ */
+ function cleanup($force=false)
+ {
+ $manager = $this->getManager($force);
+ if ($manager) {
+ $service = $manager->current();
+ $this->destroyManager($force);
+ } 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
+ *
+ * @param $force True if the manager should be returned regardless
+ * of whether it's a manager for $this->url.
+ */
+ function &getManager($force=false)
+ {
+ // 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) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $manager = $loader->fromSession(unserialize($manager_str));
+ }
+
+ if ($manager && ($manager->forURL($this->url) || $force)) {
+ return $manager;
+ } else {
+ $unused = null;
+ return $unused;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function &createManager($services, $yadis_url = null)
+ {
+ $key = $this->getSessionKey();
+ if ($this->getManager()) {
+ return $this->getManager();
+ }
+
+ if ($services) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $manager = new Auth_Yadis_Manager($this->url, $yadis_url,
+ $services, $key);
+ $this->session->set($this->session_key,
+ serialize($loader->toSession($manager)));
+ return $manager;
+ } else {
+ // Oh, PHP.
+ $unused = null;
+ return $unused;
+ }
+ }
+
+ /**
+ * @access private
+ *
+ * @param $force True if the manager should be deleted regardless
+ * of whether it's a manager for $this->url.
+ */
+ function destroyManager($force=false)
+ {
+ if ($this->getManager($force) !== null) {
+ $key = $this->getSessionKey();
+ $this->session->del($key);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/Misc.php b/extlib/Auth/Yadis/Misc.php
new file mode 100644
index 000000000..1134a4ff4
--- /dev/null
+++ b/extlib/Auth/Yadis/Misc.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Miscellaneous utility values and functions for OpenID and Yadis.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+function Auth_Yadis_getUCSChars()
+{
+ return array(
+ array(0xA0, 0xD7FF),
+ array(0xF900, 0xFDCF),
+ array(0xFDF0, 0xFFEF),
+ array(0x10000, 0x1FFFD),
+ array(0x20000, 0x2FFFD),
+ array(0x30000, 0x3FFFD),
+ array(0x40000, 0x4FFFD),
+ array(0x50000, 0x5FFFD),
+ array(0x60000, 0x6FFFD),
+ array(0x70000, 0x7FFFD),
+ array(0x80000, 0x8FFFD),
+ array(0x90000, 0x9FFFD),
+ array(0xA0000, 0xAFFFD),
+ array(0xB0000, 0xBFFFD),
+ array(0xC0000, 0xCFFFD),
+ array(0xD0000, 0xDFFFD),
+ array(0xE1000, 0xEFFFD)
+ );
+}
+
+function Auth_Yadis_getIPrivateChars()
+{
+ return array(
+ array(0xE000, 0xF8FF),
+ array(0xF0000, 0xFFFFD),
+ array(0x100000, 0x10FFFD)
+ );
+}
+
+function Auth_Yadis_pct_escape_unicode($char_match)
+{
+ $c = $char_match[0];
+ $result = "";
+ for ($i = 0; $i < strlen($c); $i++) {
+ $result .= "%".sprintf("%X", ord($c[$i]));
+ }
+ return $result;
+}
+
+function Auth_Yadis_startswith($s, $stuff)
+{
+ return strpos($s, $stuff) === 0;
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/ParanoidHTTPFetcher.php b/extlib/Auth/Yadis/ParanoidHTTPFetcher.php
new file mode 100644
index 000000000..8975d7f89
--- /dev/null
+++ b/extlib/Auth/Yadis/ParanoidHTTPFetcher.php
@@ -0,0 +1,228 @@
+<?php
+
+/**
+ * This module contains the CURL-based HTTP fetcher implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Interface import
+ */
+require_once "Auth/Yadis/HTTPFetcher.php";
+
+require_once "Auth/OpenID.php";
+
+/**
+ * A paranoid {@link Auth_Yadis_HTTPFetcher} class which uses CURL
+ * for fetching.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
+ function Auth_Yadis_ParanoidHTTPFetcher()
+ {
+ $this->reset();
+ }
+
+ function reset()
+ {
+ $this->headers = array();
+ $this->data = "";
+ }
+
+ /**
+ * @access private
+ */
+ function _writeHeader($ch, $header)
+ {
+ array_push($this->headers, rtrim($header));
+ return strlen($header);
+ }
+
+ /**
+ * @access private
+ */
+ function _writeData($ch, $data)
+ {
+ if (strlen($this->data) > 1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB) {
+ return 0;
+ } else {
+ $this->data .= $data;
+ return strlen($data);
+ }
+ }
+
+ /**
+ * Does this fetcher support SSL URLs?
+ */
+ function supportsSSL()
+ {
+ $v = curl_version();
+ if(is_array($v)) {
+ return in_array('https', $v['protocols']);
+ } elseif (is_string($v)) {
+ return preg_match('/OpenSSL/i', $v);
+ } else {
+ return 0;
+ }
+ }
+
+ function get($url, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $stop = time() + $this->timeout;
+ $off = $this->timeout;
+
+ $redir = true;
+
+ while ($redir && ($off > 0)) {
+ $this->reset();
+
+ $c = curl_init();
+
+ if ($c === false) {
+ Auth_OpenID::log(
+ "curl_init returned false; could not " .
+ "initialize for URL '%s'", $url);
+ return null;
+ }
+
+ if (defined('CURLOPT_NOSIGNAL')) {
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ }
+
+ if (!$this->allowedURL($url)) {
+ Auth_OpenID::log("Fetching URL not allowed: %s",
+ $url);
+ return null;
+ }
+
+ curl_setopt($c, CURLOPT_WRITEFUNCTION,
+ array(&$this, "_writeData"));
+ curl_setopt($c, CURLOPT_HEADERFUNCTION,
+ array(&$this, "_writeHeader"));
+
+ if ($extra_headers) {
+ curl_setopt($c, CURLOPT_HTTPHEADER, $extra_headers);
+ }
+
+ $cv = curl_version();
+ if(is_array($cv)) {
+ $curl_user_agent = 'curl/'.$cv['version'];
+ } else {
+ $curl_user_agent = $cv;
+ }
+ curl_setopt($c, CURLOPT_USERAGENT,
+ Auth_OpenID_USER_AGENT.' '.$curl_user_agent);
+ curl_setopt($c, CURLOPT_TIMEOUT, $off);
+ curl_setopt($c, CURLOPT_URL, $url);
+ curl_setopt($c, CURLOPT_RANGE,
+ "0-".(1024 * Auth_OpenID_FETCHER_MAX_RESPONSE_KB));
+
+ curl_exec($c);
+
+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+ $body = $this->data;
+ $headers = $this->headers;
+
+ if (!$code) {
+ Auth_OpenID::log("Got no response code when fetching %s", $url);
+ Auth_OpenID::log("CURL error (%s): %s",
+ curl_errno($c), curl_error($c));
+ return null;
+ }
+
+ if (in_array($code, array(301, 302, 303, 307))) {
+ $url = $this->_findRedirect($headers);
+ $redir = true;
+ } else {
+ $redir = false;
+ curl_close($c);
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (strpos($header, ': ')) {
+ list($name, $value) = explode(': ', $header, 2);
+ $new_headers[$name] = $value;
+ }
+ }
+
+ Auth_OpenID::log(
+ "Successfully fetched '%s': GET response code %s",
+ $url, $code);
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
+ }
+
+ $off = $stop - time();
+ }
+
+ return null;
+ }
+
+ function post($url, $body, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $this->reset();
+
+ $c = curl_init();
+
+ if (defined('CURLOPT_NOSIGNAL')) {
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ }
+
+ curl_setopt($c, CURLOPT_POST, true);
+ curl_setopt($c, CURLOPT_POSTFIELDS, $body);
+ curl_setopt($c, CURLOPT_TIMEOUT, $this->timeout);
+ curl_setopt($c, CURLOPT_URL, $url);
+ curl_setopt($c, CURLOPT_WRITEFUNCTION,
+ array(&$this, "_writeData"));
+
+ curl_exec($c);
+
+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+
+ if (!$code) {
+ Auth_OpenID::log("Got no response code when fetching %s", $url);
+ return null;
+ }
+
+ $body = $this->data;
+
+ curl_close($c);
+
+ $new_headers = $extra_headers;
+
+ foreach ($this->headers as $header) {
+ if (strpos($header, ': ')) {
+ list($name, $value) = explode(': ', $header, 2);
+ $new_headers[$name] = $value;
+ }
+
+ }
+
+ Auth_OpenID::log("Successfully fetched '%s': POST response code %s",
+ $url, $code);
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/ParseHTML.php b/extlib/Auth/Yadis/ParseHTML.php
new file mode 100644
index 000000000..297ccbd2c
--- /dev/null
+++ b/extlib/Auth/Yadis/ParseHTML.php
@@ -0,0 +1,259 @@
+<?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 OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * 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 OpenID
+ */
+class Auth_Yadis_ParseHTML {
+
+ /**
+ * @access private
+ */
+ var $_re_flags = "si";
+
+ /**
+ * @access private
+ */
+ var $_removed_re =
+ "<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
+
+ /**
+ * @access private
+ */
+ var $_tag_expr = "<%s%s(?:\s.*?)?%s>";
+
+ /**
+ * @access private
+ */
+ var $_attr_find = '\b([-\w]+)=(".*?"|\'.*?\'|.+?)[\/\s>]';
+
+ function Auth_Yadis_ParseHTML()
+ {
+ $this->_attr_find = sprintf("/%s/%s",
+ $this->_attr_find,
+ $this->_re_flags);
+
+ $this->_removed_re = sprintf("/%s/%s",
+ $this->_removed_re,
+ $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
+ * or closing tag from a set of names.
+ *
+ * @access private
+ * @param mixed $tag_names Tag names to match
+ * @param mixed $close false/0 = no, true/1 = yes, other = maybe
+ * @param mixed $self_close false/0 = no, true/1 = yes, other = maybe
+ * @return string $regex A regular expression string to be used
+ * in, say, preg_match.
+ */
+ function tagPattern($tag_names, $close, $self_close)
+ {
+ if (is_array($tag_names)) {
+ $tag_names = '(?:'.implode('|',$tag_names).')';
+ }
+ if ($close) {
+ $close = '\/' . (($close == 1)? '' : '?');
+ } else {
+ $close = '';
+ }
+ if ($self_close) {
+ $self_close = '(?:\/\s*)' . (($self_close == 1)? '' : '?');
+ } else {
+ $self_close = '';
+ }
+ $expr = sprintf($this->_tag_expr, $close, $tag_names, $self_close);
+
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ /**
+ * 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)
+ {
+ $html_string = preg_replace($this->_removed_re,
+ "",
+ $html_string);
+
+ $key_tags = array($this->tagPattern('html', false, false),
+ $this->tagPattern('head', false, false),
+ $this->tagPattern('head', true, false),
+ $this->tagPattern('html', true, false),
+ $this->tagPattern(array(
+ 'body', 'frameset', 'frame', 'p', 'div',
+ 'table','span','a'), 'maybe', 'maybe'));
+ $key_tags_pos = array();
+ foreach ($key_tags as $pat) {
+ $matches = array();
+ preg_match($pat, $html_string, $matches, PREG_OFFSET_CAPTURE);
+ if($matches) {
+ $key_tags_pos[] = $matches[0][1];
+ } else {
+ $key_tags_pos[] = null;
+ }
+ }
+ // no opening head tag
+ if (is_null($key_tags_pos[1])) {
+ return array();
+ }
+ // the effective </head> is the min of the following
+ if (is_null($key_tags_pos[2])) {
+ $key_tags_pos[2] = strlen($html_string);
+ }
+ foreach (array($key_tags_pos[3], $key_tags_pos[4]) as $pos) {
+ if (!is_null($pos) && $pos < $key_tags_pos[2]) {
+ $key_tags_pos[2] = $pos;
+ }
+ }
+ // closing head tag comes before opening head tag
+ if ($key_tags_pos[1] > $key_tags_pos[2]) {
+ return array();
+ }
+ // if there is an opening html tag, make sure the opening head tag
+ // comes after it
+ if (!is_null($key_tags_pos[0]) && $key_tags_pos[1] < $key_tags_pos[0]) {
+ return array();
+ }
+ $html_string = substr($html_string, $key_tags_pos[1],
+ ($key_tags_pos[2]-$key_tags_pos[1]));
+
+ $link_data = array();
+ $link_matches = array();
+
+ if (!preg_match_all($this->tagPattern('meta', false, 'maybe'),
+ $html_string, $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/extlib/Auth/Yadis/PlainHTTPFetcher.php b/extlib/Auth/Yadis/PlainHTTPFetcher.php
new file mode 100644
index 000000000..8882e3cef
--- /dev/null
+++ b/extlib/Auth/Yadis/PlainHTTPFetcher.php
@@ -0,0 +1,251 @@
+<?php
+
+/**
+ * This module contains the plain non-curl HTTP fetcher
+ * implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Interface import
+ */
+require_once "Auth/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
+ */
+class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
+ /**
+ * Does this fetcher support SSL URLs?
+ */
+ function supportsSSL()
+ {
+ return function_exists('openssl_open');
+ }
+
+ function get($url, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $redir = true;
+
+ $stop = time() + $this->timeout;
+ $off = $this->timeout;
+
+ while ($redir && ($off > 0)) {
+
+ $parts = parse_url($url);
+
+ $specify_port = true;
+
+ // Set a default port.
+ if (!array_key_exists('port', $parts)) {
+ $specify_port = false;
+ if ($parts['scheme'] == 'http') {
+ $parts['port'] = 80;
+ } elseif ($parts['scheme'] == 'https') {
+ $parts['port'] = 443;
+ } else {
+ return null;
+ }
+ }
+
+ if (!array_key_exists('path', $parts)) {
+ $parts['path'] = '/';
+ }
+
+ $host = $parts['host'];
+
+ if ($parts['scheme'] == 'https') {
+ $host = 'ssl://' . $host;
+ }
+
+ $user_agent = Auth_OpenID_USER_AGENT;
+
+ $headers = array(
+ "GET ".$parts['path'].
+ (array_key_exists('query', $parts) ?
+ "?".$parts['query'] : "").
+ " HTTP/1.0",
+ "User-Agent: $user_agent",
+ "Host: ".$parts['host'].
+ ($specify_port ? ":".$parts['port'] : ""),
+ "Range: 0-".
+ (1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB),
+ "Port: ".$parts['port']);
+
+ $errno = 0;
+ $errstr = '';
+
+ if ($extra_headers) {
+ foreach ($extra_headers as $h) {
+ $headers[] = $h;
+ }
+ }
+
+ @$sock = fsockopen($host, $parts['port'], $errno, $errstr,
+ $this->timeout);
+ if ($sock === false) {
+ return false;
+ }
+
+ stream_set_timeout($sock, $this->timeout);
+
+ fputs($sock, implode("\r\n", $headers) . "\r\n\r\n");
+
+ $data = "";
+ $kilobytes = 0;
+ while (!feof($sock) &&
+ $kilobytes < Auth_OpenID_FETCHER_MAX_RESPONSE_KB ) {
+ $data .= fgets($sock, 1024);
+ $kilobytes += 1;
+ }
+
+ fclose($sock);
+
+ // Split response into header and body sections
+ list($headers, $body) = explode("\r\n\r\n", $data, 2);
+ $headers = explode("\r\n", $headers);
+
+ $http_code = explode(" ", $headers[0]);
+ $code = $http_code[1];
+
+ if (in_array($code, array('301', '302'))) {
+ $url = $this->_findRedirect($headers);
+ $redir = true;
+ } else {
+ $redir = false;
+ }
+
+ $off = $stop - time();
+ }
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (preg_match("/:/", $header)) {
+ $parts = explode(": ", $header, 2);
+
+ if (count($parts) == 2) {
+ list($name, $value) = $parts;
+ $new_headers[$name] = $value;
+ }
+ }
+
+ }
+
+ return new Auth_Yadis_HTTPResponse($url, $code, $new_headers, $body);
+ }
+
+ function post($url, $body, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $parts = parse_url($url);
+
+ $headers = array();
+
+ $post_path = $parts['path'];
+ if (isset($parts['query'])) {
+ $post_path .= '?' . $parts['query'];
+ }
+
+ $headers[] = "POST ".$post_path." HTTP/1.0";
+ $headers[] = "Host: " . $parts['host'];
+ $headers[] = "Content-type: application/x-www-form-urlencoded";
+ $headers[] = "Content-length: " . strval(strlen($body));
+
+ if ($extra_headers &&
+ is_array($extra_headers)) {
+ $headers = array_merge($headers, $extra_headers);
+ }
+
+ // Join all headers together.
+ $all_headers = implode("\r\n", $headers);
+
+ // Add headers, two newlines, and request body.
+ $request = $all_headers . "\r\n\r\n" . $body;
+
+ // Set a default port.
+ if (!array_key_exists('port', $parts)) {
+ if ($parts['scheme'] == 'http') {
+ $parts['port'] = 80;
+ } elseif ($parts['scheme'] == 'https') {
+ $parts['port'] = 443;
+ } else {
+ return null;
+ }
+ }
+
+ if ($parts['scheme'] == 'https') {
+ $parts['host'] = sprintf("ssl://%s", $parts['host']);
+ }
+
+ // Connect to the remote server.
+ $errno = 0;
+ $errstr = '';
+
+ $sock = fsockopen($parts['host'], $parts['port'], $errno, $errstr,
+ $this->timeout);
+
+ if ($sock === false) {
+ return null;
+ }
+
+ stream_set_timeout($sock, $this->timeout);
+
+ // Write the POST request.
+ fputs($sock, $request);
+
+ // Get the response from the server.
+ $response = "";
+ while (!feof($sock)) {
+ if ($data = fgets($sock, 128)) {
+ $response .= $data;
+ } else {
+ break;
+ }
+ }
+
+ // Split the request into headers and body.
+ list($headers, $response_body) = explode("\r\n\r\n", $response, 2);
+
+ $headers = explode("\r\n", $headers);
+
+ // Expect the first line of the headers data to be something
+ // like HTTP/1.1 200 OK. Split the line on spaces and take
+ // the second token, which should be the return code.
+ $http_code = explode(" ", $headers[0]);
+ $code = $http_code[1];
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (preg_match("/:/", $header)) {
+ list($name, $value) = explode(": ", $header, 2);
+ $new_headers[$name] = $value;
+ }
+
+ }
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $response_body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/XML.php b/extlib/Auth/Yadis/XML.php
new file mode 100644
index 000000000..4854f12bb
--- /dev/null
+++ b/extlib/Auth/Yadis/XML.php
@@ -0,0 +1,374 @@
+<?php
+
+/**
+ * XML-parsing classes to wrap the domxml and DOM extensions for PHP 4
+ * and 5, respectively.
+ *
+ * @package OpenID
+ */
+
+/**
+ * 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 Auth_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 OpenID
+ */
+class Auth_Yadis_XMLParser {
+ /**
+ * Initialize an instance of Auth_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 Auth_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 Auth_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_domxml extends Auth_Yadis_XMLParser {
+ function Auth_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) {
+ $n = array();
+ return $n;
+ }
+
+ 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 Auth_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 Auth_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_dom extends Auth_Yadis_XMLParser {
+ function Auth_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->query($xpath, $node);
+ } else {
+ $result = @$this->xpath->query($xpath);
+ }
+
+ $n = array();
+
+ if (!$result) {
+ return $n;
+ }
+
+ 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;
+ }
+ }
+}
+
+global $__Auth_Yadis_defaultParser;
+$__Auth_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 Auth_Yadis_XMLParser $parser An instance of a
+ * Auth_Yadis_XMLParser subclass.
+ */
+function Auth_Yadis_setDefaultParser(&$parser)
+{
+ global $__Auth_Yadis_defaultParser;
+ $__Auth_Yadis_defaultParser =& $parser;
+}
+
+function Auth_Yadis_getSupportedExtensions()
+{
+ return array(
+ 'dom' => array('classname' => 'Auth_Yadis_dom',
+ 'libname' => array('dom.so', 'dom.dll')),
+ 'domxml' => array('classname' => 'Auth_Yadis_domxml',
+ 'libname' => array('domxml.so', 'php_domxml.dll')),
+ );
+}
+
+/**
+ * Returns an instance of a Auth_Yadis_XMLParser subclass based on
+ * the availability of PHP extensions for XML parsing. If
+ * Auth_Yadis_setDefaultParser has been called, the parser used in
+ * that call will be returned instead.
+ */
+function &Auth_Yadis_getXMLParser()
+{
+ global $__Auth_Yadis_defaultParser;
+
+ if (isset($__Auth_Yadis_defaultParser)) {
+ return $__Auth_Yadis_defaultParser;
+ }
+
+ $p = null;
+ $classname = null;
+
+ $extensions = Auth_Yadis_getSupportedExtensions();
+
+ // Return a wrapper for the resident implementation, if any.
+ foreach ($extensions as $name => $params) {
+ if (!extension_loaded($name)) {
+ foreach ($params['libname'] as $libname) {
+ if (@dl($libname)) {
+ $classname = $params['classname'];
+ }
+ }
+ } else {
+ $classname = $params['classname'];
+ }
+ if (isset($classname)) {
+ $p = new $classname();
+ return $p;
+ }
+ }
+
+ if (!isset($p)) {
+ trigger_error('No XML parser was found', E_USER_ERROR);
+ } else {
+ Auth_Yadis_setDefaultParser($p);
+ }
+
+ return $p;
+}
+
+?>
diff --git a/extlib/Auth/Yadis/XRDS.php b/extlib/Auth/Yadis/XRDS.php
new file mode 100644
index 000000000..f14a7948e
--- /dev/null
+++ b/extlib/Auth/Yadis/XRDS.php
@@ -0,0 +1,478 @@
+<?php
+
+/**
+ * This module contains the XRDS parsing code.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require the XPath implementation.
+ */
+require_once 'Auth/Yadis/XML.php';
+
+/**
+ * This match mode means a given service must match ALL filters passed
+ * to the Auth_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 Auth_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ANY', 102);
+
+/**
+ * The priority value used for service elements with no priority
+ * specified.
+ */
+define('SERVICES_YADIS_MAX_PRIORITY', pow(2, 30));
+
+/**
+ * XRD XML namespace
+ */
+define('Auth_Yadis_XMLNS_XRD_2_0', 'xri://$xrd*($v*2.0)');
+
+/**
+ * XRDS XML namespace
+ */
+define('Auth_Yadis_XMLNS_XRDS', 'xri://$xrds');
+
+function Auth_Yadis_getNSMap()
+{
+ return array('xrds' => Auth_Yadis_XMLNS_XRDS,
+ 'xrd' => Auth_Yadis_XMLNS_XRD_2_0);
+}
+
+/**
+ * @access private
+ */
+function Auth_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
+ * Auth_Yadis_XRDS::services() and
+ * Auth_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 Auth_Yadis_Yadis} for more
+ * information on the role this class plays in Yadis discovery.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Service {
+
+ /**
+ * Creates an empty service object.
+ */
+ function Auth_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;
+ }
+
+ function matchTypes($type_uris)
+ {
+ $result = array();
+
+ foreach ($this->getTypes() as $typ) {
+ if (in_array($typ, $type_uris)) {
+ $result[] = $typ;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 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 = Auth_Yadis_array_scramble($uris[$k]);
+ $result = array_merge($result, $new_uris);
+ }
+
+ $result = array_merge($result,
+ Auth_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 Auth_Yadis_XMLParser}).
+ */
+ function getElements($name)
+ {
+ return $this->parser->evalXPath($name, $this->element);
+ }
+}
+
+/*
+ * Return the expiration date of this XRD element, or None if no
+ * expiration was specified.
+ *
+ * @param $default The value to use as the expiration if no expiration
+ * was specified in the XRD.
+ */
+function Auth_Yadis_getXRDExpiration($xrd_element, $default=null)
+{
+ $expires_element = $xrd_element->$parser->evalXPath('/xrd:Expires');
+ if ($expires_element === null) {
+ return $default;
+ } else {
+ $expires_string = $expires_element->text;
+
+ // Will raise ValueError if the string is not the expected
+ // format
+ $t = strptime($expires_string, "%Y-%m-%dT%H:%M:%SZ");
+
+ if ($t === false) {
+ return false;
+ }
+
+ // [int $hour [, int $minute [, int $second [,
+ // int $month [, int $day [, int $year ]]]]]]
+ return mktime($t['tm_hour'], $t['tm_min'], $t['tm_sec'],
+ $t['tm_mon'], $t['tm_day'], $t['tm_year']);
+ }
+}
+
+/**
+ * This class performs parsing of XRDS documents.
+ *
+ * You should not instantiate this class directly; rather, call
+ * parseXRDS statically:
+ *
+ * <pre> $xrds = Auth_Yadis_XRDS::parseXRDS($xml_string);</pre>
+ *
+ * If the XRDS can be parsed and is valid, an instance of
+ * Auth_Yadis_XRDS will be returned. Otherwise, null will be
+ * returned. This class is used by the Auth_Yadis_Yadis::discover
+ * method.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_XRDS {
+
+ /**
+ * Instantiate a Auth_Yadis_XRDS object. Requires an XPath
+ * instance which has been used to parse a valid XRDS document.
+ */
+ function Auth_Yadis_XRDS(&$xmlParser, &$xrdNodes)
+ {
+ $this->parser =& $xmlParser;
+ $this->xrdNode = $xrdNodes[count($xrdNodes) - 1];
+ $this->allXrdNodes =& $xrdNodes;
+ $this->serviceList = array();
+ $this->_parse();
+ }
+
+ /**
+ * Parse an XML string (XRDS document) and return either a
+ * Auth_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 Auth_Yadis_XRDS or null,
+ * depending on the validity of $xml_string
+ */
+ function &parseXRDS($xml_string, $extra_ns_map = null)
+ {
+ $_null = null;
+
+ if (!$xml_string) {
+ return $_null;
+ }
+
+ $parser = Auth_Yadis_getXMLParser();
+
+ $ns_map = Auth_Yadis_getNSMap();
+
+ 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'] != Auth_Yadis_XMLNS_XRDS) {
+ return $_null;
+ } else if (array_key_exists('xmlns', $attrs) &&
+ preg_match('/xri/', $attrs['xmlns']) &&
+ $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
+ return $_null;
+ }
+
+ // Get the last XRD node.
+ $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
+
+ if (!$xrd_nodes) {
+ return $_null;
+ }
+
+ $xrds = new Auth_Yadis_XRDS($parser, $xrd_nodes);
+ 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 Auth_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
+ * Auth_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
+ * Auth_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/extlib/Auth/Yadis/XRI.php b/extlib/Auth/Yadis/XRI.php
new file mode 100644
index 000000000..4e3462317
--- /dev/null
+++ b/extlib/Auth/Yadis/XRI.php
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * Routines for XRI resolution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/Yadis/Misc.php';
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'Auth/OpenID.php';
+
+function Auth_Yadis_getDefaultProxy()
+{
+ return 'http://xri.net/';
+}
+
+function Auth_Yadis_getXRIAuthorities()
+{
+ return array('!', '=', '@', '+', '$', '(');
+}
+
+function Auth_Yadis_getEscapeRE()
+{
+ $parts = array();
+ foreach (array_merge(Auth_Yadis_getUCSChars(),
+ Auth_Yadis_getIPrivateChars()) as $pair) {
+ list($m, $n) = $pair;
+ $parts[] = sprintf("%s-%s", chr($m), chr($n));
+ }
+
+ return sprintf('/[%s]/', implode('', $parts));
+}
+
+function Auth_Yadis_getXrefRE()
+{
+ return '/\((.*?)\)/';
+}
+
+function Auth_Yadis_identifierScheme($identifier)
+{
+ if (Auth_Yadis_startswith($identifier, 'xri://') ||
+ ($identifier &&
+ in_array($identifier[0], Auth_Yadis_getXRIAuthorities()))) {
+ return "XRI";
+ } else {
+ return "URI";
+ }
+}
+
+function Auth_Yadis_toIRINormal($xri)
+{
+ if (!Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = 'xri://' . $xri;
+ }
+
+ return Auth_Yadis_escapeForIRI($xri);
+}
+
+function _escape_xref($xref_match)
+{
+ $xref = $xref_match[0];
+ $xref = str_replace('/', '%2F', $xref);
+ $xref = str_replace('?', '%3F', $xref);
+ $xref = str_replace('#', '%23', $xref);
+ return $xref;
+}
+
+function Auth_Yadis_escapeForIRI($xri)
+{
+ $xri = str_replace('%', '%25', $xri);
+ $xri = preg_replace_callback(Auth_Yadis_getXrefRE(),
+ '_escape_xref', $xri);
+ return $xri;
+}
+
+function Auth_Yadis_toURINormal($xri)
+{
+ return Auth_Yadis_iriToURI(Auth_Yadis_toIRINormal($xri));
+}
+
+function Auth_Yadis_iriToURI($iri)
+{
+ if (1) {
+ return $iri;
+ } else {
+ // According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
+ return preg_replace_callback(Auth_Yadis_getEscapeRE(),
+ 'Auth_Yadis_pct_escape_unicode', $iri);
+ }
+}
+
+
+function Auth_Yadis_XRIAppendArgs($url, $args)
+{
+ // Append some arguments to an HTTP query. Yes, this is just like
+ // OpenID's appendArgs, but with special seasoning for XRI
+ // queries.
+
+ if (count($args) == 0) {
+ return $url;
+ }
+
+ // Non-empty array; if it is an array of arrays, use multisort;
+ // otherwise use sort.
+ if (array_key_exists(0, $args) &&
+ is_array($args[0])) {
+ // Do nothing here.
+ } else {
+ $keys = array_keys($args);
+ sort($keys);
+ $new_args = array();
+ foreach ($keys as $key) {
+ $new_args[] = array($key, $args[$key]);
+ }
+ $args = $new_args;
+ }
+
+ // According to XRI Resolution section "QXRI query parameters":
+ //
+ // "If the original QXRI had a null query component (only a
+ // leading question mark), or a query component consisting of
+ // only question marks, one additional leading question mark MUST
+ // be added when adding any XRI resolution parameters."
+ if (strpos(rtrim($url, '?'), '?') !== false) {
+ $sep = '&';
+ } else {
+ $sep = '?';
+ }
+
+ return $url . $sep . Auth_OpenID::httpBuildQuery($args);
+}
+
+function Auth_Yadis_providerIsAuthoritative($providerID, $canonicalID)
+{
+ $lastbang = strrpos($canonicalID, '!');
+ $p = substr($canonicalID, 0, $lastbang);
+ return $p == $providerID;
+}
+
+function Auth_Yadis_rootAuthority($xri)
+{
+ // Return the root authority for an XRI.
+
+ $root = null;
+
+ if (Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = substr($xri, 6);
+ }
+
+ $authority = explode('/', $xri, 2);
+ $authority = $authority[0];
+ if ($authority[0] == '(') {
+ // Cross-reference.
+ // XXX: This is incorrect if someone nests cross-references so
+ // there is another close-paren in there. Hopefully nobody
+ // does that before we have a real xriparse function.
+ // Hopefully nobody does that *ever*.
+ $root = substr($authority, 0, strpos($authority, ')') + 1);
+ } else if (in_array($authority[0], Auth_Yadis_getXRIAuthorities())) {
+ // Other XRI reference.
+ $root = $authority[0];
+ } else {
+ // IRI reference.
+ $_segments = explode("!", $authority);
+ $segments = array();
+ foreach ($_segments as $s) {
+ $segments = array_merge($segments, explode("*", $s));
+ }
+ $root = $segments[0];
+ }
+
+ return Auth_Yadis_XRI($root);
+}
+
+function Auth_Yadis_XRI($xri)
+{
+ if (!Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = 'xri://' . $xri;
+ }
+ return $xri;
+}
+
+function Auth_Yadis_getCanonicalID($iname, $xrds)
+{
+ // Returns false or a canonical ID value.
+
+ // Now nodes are in reverse order.
+ $xrd_list = array_reverse($xrds->allXrdNodes);
+ $parser =& $xrds->parser;
+ $node = $xrd_list[0];
+
+ $canonicalID_nodes = $parser->evalXPath('xrd:CanonicalID', $node);
+
+ if (!$canonicalID_nodes) {
+ return false;
+ }
+
+ $canonicalID = $canonicalID_nodes[0];
+ $canonicalID = Auth_Yadis_XRI($parser->content($canonicalID));
+
+ $childID = $canonicalID;
+
+ for ($i = 1; $i < count($xrd_list); $i++) {
+ $xrd = $xrd_list[$i];
+
+ $parent_sought = substr($childID, 0, strrpos($childID, '!'));
+ $parentCID = $parser->evalXPath('xrd:CanonicalID', $xrd);
+ if (!$parentCID) {
+ return false;
+ }
+ $parentCID = Auth_Yadis_XRI($parser->content($parentCID[0]));
+
+ if (strcasecmp($parent_sought, $parentCID)) {
+ // raise XRDSFraud.
+ return false;
+ }
+
+ $childID = $parent_sought;
+ }
+
+ $root = Auth_Yadis_rootAuthority($iname);
+ if (!Auth_Yadis_providerIsAuthoritative($root, $childID)) {
+ // raise XRDSFraud.
+ return false;
+ }
+
+ return $canonicalID;
+}
+
+?>
diff --git a/extlib/Auth/Yadis/XRIRes.php b/extlib/Auth/Yadis/XRIRes.php
new file mode 100644
index 000000000..4e8e8d037
--- /dev/null
+++ b/extlib/Auth/Yadis/XRIRes.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Code for using a proxy XRI resolver.
+ */
+
+require_once 'Auth/Yadis/XRDS.php';
+require_once 'Auth/Yadis/XRI.php';
+
+class Auth_Yadis_ProxyResolver {
+ function Auth_Yadis_ProxyResolver(&$fetcher, $proxy_url = null)
+ {
+ $this->fetcher =& $fetcher;
+ $this->proxy_url = $proxy_url;
+ if (!$this->proxy_url) {
+ $this->proxy_url = Auth_Yadis_getDefaultProxy();
+ }
+ }
+
+ function queryURL($xri, $service_type = null)
+ {
+ // trim off the xri:// prefix
+ $qxri = substr(Auth_Yadis_toURINormal($xri), 6);
+ $hxri = $this->proxy_url . $qxri;
+ $args = array(
+ '_xrd_r' => 'application/xrds+xml'
+ );
+
+ if ($service_type) {
+ $args['_xrd_t'] = $service_type;
+ } else {
+ // Don't perform service endpoint selection.
+ $args['_xrd_r'] .= ';sep=false';
+ }
+
+ $query = Auth_Yadis_XRIAppendArgs($hxri, $args);
+ return $query;
+ }
+
+ function query($xri, $service_types, $filters = array())
+ {
+ $services = array();
+ $canonicalID = null;
+ foreach ($service_types as $service_type) {
+ $url = $this->queryURL($xri, $service_type);
+ $response = $this->fetcher->get($url);
+ if ($response->status != 200 and $response->status != 206) {
+ continue;
+ }
+ $xrds = Auth_Yadis_XRDS::parseXRDS($response->body);
+ if (!$xrds) {
+ continue;
+ }
+ $canonicalID = Auth_Yadis_getCanonicalID($xri,
+ $xrds);
+
+ if ($canonicalID === false) {
+ return null;
+ }
+
+ $some_services = $xrds->services($filters);
+ $services = array_merge($services, $some_services);
+ // TODO:
+ // * If we do get hits for multiple service_types, we're
+ // almost certainly going to have duplicated service
+ // entries and broken priority ordering.
+ }
+ return array($canonicalID, $services);
+ }
+}
+
+?>
diff --git a/extlib/Auth/Yadis/Yadis.php b/extlib/Auth/Yadis/Yadis.php
new file mode 100644
index 000000000..d89f77c6d
--- /dev/null
+++ b/extlib/Auth/Yadis/Yadis.php
@@ -0,0 +1,382 @@
+<?php
+
+/**
+ * The core PHP Yadis implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Need both fetcher types so we can use the right one based on the
+ * presence or absence of CURL.
+ */
+require_once "Auth/Yadis/PlainHTTPFetcher.php";
+require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
+
+/**
+ * Need this for parsing HTML (looking for META tags).
+ */
+require_once "Auth/Yadis/ParseHTML.php";
+
+/**
+ * Need this to parse the XRDS document during Yadis discovery.
+ */
+require_once "Auth/Yadis/XRDS.php";
+
+/**
+ * XRDS (yadis) content type
+ */
+define('Auth_Yadis_CONTENT_TYPE', 'application/xrds+xml');
+
+/**
+ * Yadis header
+ */
+define('Auth_Yadis_HEADER_NAME', 'X-XRDS-Location');
+
+/**
+ * Contains the result of performing Yadis discovery on a URI.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_DiscoveryResult {
+
+ // The URI that was passed to the fetcher
+ var $request_uri = null;
+
+ // The result of following redirects from the request_uri
+ var $normalized_uri = null;
+
+ // The URI from which the response text was returned (set to
+ // None if there was no XRDS document found)
+ var $xrds_uri = null;
+
+ var $xrds = null;
+
+ // The content-type returned with the response_text
+ var $content_type = null;
+
+ // The document returned from the xrds_uri
+ var $response_text = null;
+
+ // Did the discovery fail miserably?
+ var $failed = false;
+
+ function Auth_Yadis_DiscoveryResult($request_uri)
+ {
+ // Initialize the state of the object
+ // sets all attributes to None except the request_uri
+ $this->request_uri = $request_uri;
+ }
+
+ function fail()
+ {
+ $this->failed = true;
+ }
+
+ function isFailure()
+ {
+ return $this->failed;
+ }
+
+ /**
+ * Returns the list of service objects as described by the XRDS
+ * document, if this yadis object represents a successful Yadis
+ * discovery.
+ *
+ * @return array $services An array of {@link Auth_Yadis_Service}
+ * objects
+ */
+ function services()
+ {
+ if ($this->xrds) {
+ return $this->xrds->services();
+ }
+
+ return null;
+ }
+
+ function usedYadisLocation()
+ {
+ // Was the Yadis protocol's indirection used?
+ return $this->normalized_uri != $this->xrds_uri;
+ }
+
+ function isXRDS()
+ {
+ // Is the response text supposed to be an XRDS document?
+ return ($this->usedYadisLocation() ||
+ $this->content_type == Auth_Yadis_CONTENT_TYPE);
+ }
+}
+
+/**
+ *
+ * Perform the Yadis protocol on the input URL and return an iterable
+ * of resulting endpoint objects.
+ *
+ * input_url: The URL on which to perform the Yadis protocol
+ *
+ * @return: The normalized identity URL and an iterable of endpoint
+ * objects generated by the filter function.
+ *
+ * xrds_parse_func: a callback which will take (uri, xrds_text) and
+ * return an array of service endpoint objects or null. Usually
+ * array('Auth_OpenID_ServiceEndpoint', 'fromXRDS').
+ *
+ * discover_func: if not null, a callback which should take (uri) and
+ * return an Auth_Yadis_Yadis object or null.
+ */
+function Auth_Yadis_getServiceEndpoints($input_url, $xrds_parse_func,
+ $discover_func=null, $fetcher=null)
+{
+ if ($discover_func === null) {
+ $discover_function = array('Auth_Yadis_Yadis', 'discover');
+ }
+
+ $yadis_result = call_user_func_array($discover_func,
+ array($input_url, $fetcher));
+
+ if ($yadis_result === null) {
+ return array($input_url, array());
+ }
+
+ $endpoints = call_user_func_array($xrds_parse_func,
+ array($yadis_result->normalized_uri,
+ $yadis_result->response_text));
+
+ if ($endpoints === null) {
+ $endpoints = array();
+ }
+
+ return array($yadis_result->normalized_uri, $endpoints);
+}
+
+/**
+ * This is the core of the PHP Yadis library. This is the only class
+ * a user needs to use to perform Yadis discovery. This class
+ * performs the discovery AND stores the result of the discovery.
+ *
+ * First, require this library into your program source:
+ *
+ * <pre> require_once "Auth/Yadis/Yadis.php";</pre>
+ *
+ * To perform Yadis discovery, first call the "discover" method
+ * statically with a URI parameter:
+ *
+ * <pre> $http_response = array();
+ * $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ * $yadis_object = Auth_Yadis_Yadis::discover($uri,
+ * $http_response, $fetcher);</pre>
+ *
+ * If the discovery succeeds, $yadis_object will be an instance of
+ * {@link Auth_Yadis_Yadis}. If not, it will be null. The XRDS
+ * document found during discovery should have service descriptions,
+ * which can be accessed by calling
+ *
+ * <pre> $service_list = $yadis_object->services();</pre>
+ *
+ * which returns an array of objects which describe each service.
+ * These objects are instances of Auth_Yadis_Service. Each object
+ * describes exactly one whole Service element, complete with all of
+ * its Types and URIs (no expansion is performed). The common use
+ * case for using the service objects returned by services() is to
+ * write one or more filter functions and pass those to services():
+ *
+ * <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 Auth_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
+ * Auth_Yadis_Service}.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Yadis {
+
+ /**
+ * Returns an HTTP fetcher object. If the CURL extension is
+ * present, an instance of {@link Auth_Yadis_ParanoidHTTPFetcher}
+ * is returned. If not, an instance of
+ * {@link Auth_Yadis_PlainHTTPFetcher} is returned.
+ *
+ * If Auth_Yadis_CURL_OVERRIDE is defined, this method will always
+ * return a {@link Auth_Yadis_PlainHTTPFetcher}.
+ */
+ function getHTTPFetcher($timeout = 20)
+ {
+ if (Auth_Yadis_Yadis::curlPresent() &&
+ (!defined('Auth_Yadis_CURL_OVERRIDE'))) {
+ $fetcher = new Auth_Yadis_ParanoidHTTPFetcher($timeout);
+ } else {
+ $fetcher = new Auth_Yadis_PlainHTTPFetcher($timeout);
+ }
+ return $fetcher;
+ }
+
+ function curlPresent()
+ {
+ return function_exists('curl_init');
+ }
+
+ /**
+ * @access private
+ */
+ function _getHeader($header_list, $names)
+ {
+ foreach ($header_list as $name => $value) {
+ foreach ($names as $n) {
+ if (strtolower($name) == strtolower($n)) {
+ return $value;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _getContentType($content_type_header)
+ {
+ if ($content_type_header) {
+ $parts = explode(";", $content_type_header);
+ return strtolower($parts[0]);
+ }
+ }
+
+ /**
+ * This should be called statically and will build a Yadis
+ * instance if the discovery process succeeds. This implements
+ * Yadis discovery as specified in the Yadis specification.
+ *
+ * @param string $uri The URI on which to perform Yadis discovery.
+ *
+ * @param array $http_response An array reference where the HTTP
+ * response object will be stored (see {@link
+ * Auth_Yadis_HTTPResponse}.
+ *
+ * @param Auth_Yadis_HTTPFetcher $fetcher An instance of a
+ * Auth_Yadis_HTTPFetcher subclass.
+ *
+ * @param array $extra_ns_map An array which maps namespace names
+ * to namespace URIs to be used when parsing the Yadis XRDS
+ * document.
+ *
+ * @param integer $timeout An optional fetcher timeout, in seconds.
+ *
+ * @return mixed $obj Either null or an instance of
+ * Auth_Yadis_Yadis, depending on whether the discovery
+ * succeeded.
+ */
+ function discover($uri, &$fetcher,
+ $extra_ns_map = null, $timeout = 20)
+ {
+ $result = new Auth_Yadis_DiscoveryResult($uri);
+
+ $request_uri = $uri;
+ $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
+ ', text/html; q=0.3, application/xhtml+xml; q=0.5');
+
+ if ($fetcher === null) {
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
+ }
+
+ $response = $fetcher->get($uri, $headers);
+
+ if (!$response || ($response->status != 200 and
+ $response->status != 206)) {
+ $result->fail();
+ return $result;
+ }
+
+ $result->normalized_uri = $response->final_url;
+ $result->content_type = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('content-type'));
+
+ if ($result->content_type &&
+ (Auth_Yadis_Yadis::_getContentType($result->content_type) ==
+ Auth_Yadis_CONTENT_TYPE)) {
+ $result->xrds_uri = $result->normalized_uri;
+ } else {
+ $yadis_location = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array(Auth_Yadis_HEADER_NAME));
+
+ if (!$yadis_location) {
+ $parser = new Auth_Yadis_ParseHTML();
+ $yadis_location = $parser->getHTTPEquiv($response->body);
+ }
+
+ if ($yadis_location) {
+ $result->xrds_uri = $yadis_location;
+
+ $response = $fetcher->get($yadis_location);
+
+ if ((!$response) || ($response->status != 200 and
+ $response->status != 206)) {
+ $result->fail();
+ return $result;
+ }
+
+ $result->content_type = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('content-type'));
+ }
+ }
+
+ $result->response_text = $response->body;
+ return $result;
+ }
+}
+
+?>