diff --git a/lib/Auth/OpenID.php b/lib/Auth/OpenID.php
index 551dee69b8d22bed9a37176af094b424d0b069b5..6556b5b01e8bb43ef315991932b370ce30535bdf 100644
--- a/lib/Auth/OpenID.php
+++ b/lib/Auth/OpenID.php
@@ -13,16 +13,22 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
+/**
+ * The library version string
+ */
+define('Auth_OpenID_VERSION', '2.1.2');
+
 /**
  * Require the fetcher code.
  */
 require_once "Auth/Yadis/PlainHTTPFetcher.php";
 require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
 require_once "Auth/OpenID/BigMath.php";
+require_once "Auth/OpenID/URINorm.php";
 
 /**
  * Status code returned by the server when the only option is to show
@@ -97,7 +103,7 @@ define('Auth_OpenID_punct',
        "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
 
 if (Auth_OpenID_getMathLib() === null) {
-    define('Auth_OpenID_NO_MATH_SUPPORT', true);
+    Auth_OpenID_setNoMathSupport();
 }
 
 /**
@@ -137,20 +143,42 @@ class Auth_OpenID {
      */
     function getQuery($query_str=null)
     {
+        $data = array();
+
         if ($query_str !== null) {
-            $str = $query_str;
-        } else if ($_SERVER['REQUEST_METHOD'] == 'GET') {
-            $str = $_SERVER['QUERY_STRING'];
-        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $data = Auth_OpenID::params_from_string($query_str);
+        } else if (!array_key_exists('REQUEST_METHOD', $_SERVER)) {
+            // Do nothing.
+        } else {
+          // XXX HACK FIXME HORRIBLE.
+          //
+          // POSTing to a URL with query parameters is acceptable, but
+          // we don't have a clean way to distinguish those parameters
+          // when we need to do things like return_to verification
+          // which only want to look at one kind of parameter.  We're
+          // going to emulate the behavior of some other environments
+          // by defaulting to GET and overwriting with POST if POST
+          // data is available.
+          $data = Auth_OpenID::params_from_string($_SERVER['QUERY_STRING']);
+
+          if ($_SERVER['REQUEST_METHOD'] == 'POST') {
             $str = file_get_contents('php://input');
 
             if ($str === false) {
-                return array();
+              $post = array();
+            } else {
+              $post = Auth_OpenID::params_from_string($str);
             }
-        } else {
-            return array();
+
+            $data = array_merge($data, $post);
+          }
         }
 
+        return $data;
+    }
+
+    function params_from_string($str)
+    {
         $chunks = explode("&", $str);
 
         $data = array();
@@ -180,11 +208,14 @@ class Auth_OpenID {
         if (is_dir($dir_name) || @mkdir($dir_name)) {
             return true;
         } else {
-            if (Auth_OpenID::ensureDir(dirname($dir_name))) {
-                return is_dir($dir_name) || @mkdir($dir_name);
-            } else {
-                return false;
+            $parent_dir = dirname($dir_name);
+
+            // Terminal case; there is no parent directory to create.
+            if ($parent_dir == $dir_name) {
+                return true;
             }
+
+            return (Auth_OpenID::ensureDir($parent_dir) && @mkdir($dir_name));
         }
     }
 
@@ -321,38 +352,6 @@ class Auth_OpenID {
         return $url . $sep . Auth_OpenID::httpBuildQuery($args);
     }
 
-    /**
-     * Turn a string into an ASCII string.
-     *
-     * Replace non-ascii characters with a %-encoded, UTF-8
-     * encoding. This function will fail if the input is a string and
-     * there are non-7-bit-safe characters. It is assumed that the
-     * caller will have already translated the input into a Unicode
-     * character sequence, according to the encoding of the HTTP POST
-     * or GET.
-     *
-     * Do not escape anything that is already 7-bit safe, so we do the
-     * minimal transform on the identity URL
-     *
-     * @access private
-     */
-    function quoteMinimal($s)
-    {
-        $res = array();
-        for ($i = 0; $i < strlen($s); $i++) {
-            $c = $s[$i];
-            if ($c >= "\x80") {
-                for ($j = 0; $j < count(utf8_encode($c)); $j++) {
-                    array_push($res, sprintf("%02X", ord($c[$j])));
-                }
-            } else {
-                array_push($res, $c);
-            }
-        }
-    
-        return implode('', $res);
-    }
-
     /**
      * Implements python's urlunparse, which is not available in PHP.
      * Given the specified components of a URL, this function rebuilds
@@ -381,7 +380,7 @@ class Auth_OpenID {
         }
 
         if (!$path) {
-            $path = '/';
+            $path = '';
         }
 
         $result = $scheme . "://" . $host;
@@ -415,65 +414,28 @@ class Auth_OpenID {
      */
     function normalizeUrl($url)
     {
-        if ($url === null) {
-            return null;
-        }
-
-        assert(is_string($url));
-
-        $old_url = $url;
-        $url = trim($url);
-
-        if (strpos($url, "://") === false) {
-            $url = "http://" . $url;
-        }
-
-        $parsed = @parse_url($url);
+        @$parsed = parse_url($url);
 
-        if ($parsed === false) {
+        if (!$parsed) {
             return null;
         }
 
-        $defaults = array(
-                          'scheme' => '',
-                          'host' => '',
-                          'path' => '',
-                          'query' => '',
-                          'fragment' => '',
-                          'port' => ''
-                          );
-
-        $parsed = array_merge($defaults, $parsed);
-
-        if (($parsed['scheme'] == '') ||
-            ($parsed['host'] == '')) {
-            if ($parsed['path'] == '' &&
-                $parsed['query'] == '' &&
-                $parsed['fragment'] == '') {
+        if (isset($parsed['scheme']) &&
+            isset($parsed['host'])) {
+            $scheme = strtolower($parsed['scheme']);
+            if (!in_array($scheme, array('http', 'https'))) {
                 return null;
             }
-
-            $url = 'http://' + $url;
-            $parsed = parse_url($url);
-
-            $parsed = array_merge($defaults, $parsed);
+        } else {
+            $url = 'http://' . $url;
         }
 
-        $tail = array_map(array('Auth_OpenID', 'quoteMinimal'),
-                          array($parsed['path'],
-                                $parsed['query'],
-                                $parsed['fragment']));
-        if ($tail[0] == '') {
-            $tail[0] = '/';
+        $normalized = Auth_OpenID_urinorm($url);
+        if ($normalized === null) {
+            return null;
         }
-
-        $url = Auth_OpenID::urlunparse($parsed['scheme'], $parsed['host'],
-                                       $parsed['port'], $tail[0], $tail[1],
-                                       $tail[2]);
-
-        assert(is_string($url));
-
-        return $url;
+        list($defragged, $frag) = Auth_OpenID::urldefrag($normalized);
+        return $defragged;
     }
 
     /**
@@ -523,6 +485,68 @@ class Auth_OpenID {
 
         return $b;
     }
-}
 
-?>
\ No newline at end of file
+    function urldefrag($url)
+    {
+        $parts = explode("#", $url, 2);
+
+        if (count($parts) == 1) {
+            return array($parts[0], "");
+        } else {
+            return $parts;
+        }
+    }
+
+    function filter($callback, &$sequence)
+    {
+        $result = array();
+
+        foreach ($sequence as $item) {
+            if (call_user_func_array($callback, array($item))) {
+                $result[] = $item;
+            }
+        }
+
+        return $result;
+    }
+
+    function update(&$dest, &$src)
+    {
+        foreach ($src as $k => $v) {
+            $dest[$k] = $v;
+        }
+    }
+
+    /**
+     * Wrap PHP's standard error_log functionality.  Use this to
+     * perform all logging. It will interpolate any additional
+     * arguments into the format string before logging.
+     *
+     * @param string $format_string The sprintf format for the message
+     */
+    function log($format_string)
+    {
+        $args = func_get_args();
+        $message = call_user_func_array('sprintf', $args);
+        error_log($message);
+    }
+
+    function autoSubmitHTML($form, $title="OpenId transaction in progress")
+    {
+        return("<html>".
+               "<head><title>".
+               $title .
+               "</title></head>".
+               "<body onload='document.forms[0].submit();'>".
+               $form .
+               "<script>".
+               "var elements = document.forms[0].elements;".
+               "for (var i = 0; i < elements.length; i++) {".
+               "  elements[i].style.display = \"none\";".
+               "}".
+               "</script>".
+               "</body>".
+               "</html>");
+    }
+}
+?>
diff --git a/lib/Auth/OpenID/AX.php b/lib/Auth/OpenID/AX.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a617ae30c313a9abbf8edbe2adf8d199d158396
--- /dev/null
+++ b/lib/Auth/OpenID/AX.php
@@ -0,0 +1,1023 @@
+<?php
+
+/**
+ * Implements the OpenID attribute exchange specification, version 1.0
+ * as of svn revision 370 from openid.net svn.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require utility classes and functions for the consumer.
+ */
+require_once "Auth/OpenID/Extension.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/TrustRoot.php";
+
+define('Auth_OpenID_AX_NS_URI',
+       'http://openid.net/srv/ax/1.0');
+
+// Use this as the 'count' value for an attribute in a FetchRequest to
+// ask for as many values as the OP can provide.
+define('Auth_OpenID_AX_UNLIMITED_VALUES', 'unlimited');
+
+// Minimum supported alias length in characters.  Here for
+// completeness.
+define('Auth_OpenID_AX_MINIMUM_SUPPORTED_ALIAS_LENGTH', 32);
+
+/**
+ * AX utility class.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX {
+    /**
+     * @param mixed $thing Any object which may be an
+     * Auth_OpenID_AX_Error object.
+     *
+     * @return bool true if $thing is an Auth_OpenID_AX_Error; false
+     * if not.
+     */
+    function isError($thing)
+    {
+        return is_a($thing, 'Auth_OpenID_AX_Error');
+    }
+}
+
+/**
+ * Check an alias for invalid characters; raise AXError if any are
+ * found.  Return None if the alias is valid.
+ */
+function Auth_OpenID_AX_checkAlias($alias)
+{
+  if (strpos($alias, ',') !== false) {
+      return new Auth_OpenID_AX_Error(sprintf(
+                   "Alias %s must not contain comma", $alias));
+  }
+  if (strpos($alias, '.') !== false) {
+      return new Auth_OpenID_AX_Error(sprintf(
+                   "Alias %s must not contain period", $alias));
+  }
+
+  return true;
+}
+
+/**
+ * Results from data that does not meet the attribute exchange 1.0
+ * specification
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_Error {
+    function Auth_OpenID_AX_Error($message=null)
+    {
+        $this->message = $message;
+    }
+}
+
+/**
+ * Abstract class containing common code for attribute exchange
+ * messages.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_Message extends Auth_OpenID_Extension {
+    /**
+     * ns_alias: The preferred namespace alias for attribute exchange
+     * messages
+     */
+    var $ns_alias = 'ax';
+
+    /**
+     * mode: The type of this attribute exchange message. This must be
+     * overridden in subclasses.
+     */
+    var $mode = null;
+
+    var $ns_uri = Auth_OpenID_AX_NS_URI;
+
+    /**
+     * Return Auth_OpenID_AX_Error if the mode in the attribute
+     * exchange arguments does not match what is expected for this
+     * class; true otherwise.
+     *
+     * @access private
+     */
+    function _checkMode($ax_args)
+    {
+        $mode = Auth_OpenID::arrayGet($ax_args, 'mode');
+        if ($mode != $this->mode) {
+            return new Auth_OpenID_AX_Error(
+                            sprintf(
+                                    "Expected mode '%s'; got '%s'",
+                                    $this->mode, $mode));
+        }
+
+        return true;
+    }
+
+    /**
+     * Return a set of attribute exchange arguments containing the
+     * basic information that must be in every attribute exchange
+     * message.
+     *
+     * @access private
+     */
+    function _newArgs()
+    {
+        return array('mode' => $this->mode);
+    }
+}
+
+/**
+ * Represents a single attribute in an attribute exchange
+ * request. This should be added to an AXRequest object in order to
+ * request the attribute.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_AttrInfo {
+    /**
+     * Construct an attribute information object.  Do not call this
+     * directly; call make(...) instead.
+     *
+     * @param string $type_uri The type URI for this attribute.
+     *
+     * @param int $count The number of values of this type to request.
+     *
+     * @param bool $required Whether the attribute will be marked as
+     * required in the request.
+     *
+     * @param string $alias The name that should be given to this
+     * attribute in the request.
+     */
+    function Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
+                                     $alias)
+    {
+        /**
+         * required: Whether the attribute will be marked as required
+         * when presented to the subject of the attribute exchange
+         * request.
+         */
+        $this->required = $required;
+
+        /**
+         * count: How many values of this type to request from the
+         * subject. Defaults to one.
+         */
+        $this->count = $count;
+
+        /**
+         * type_uri: The identifier that determines what the attribute
+         * represents and how it is serialized. For example, one type
+         * URI representing dates could represent a Unix timestamp in
+         * base 10 and another could represent a human-readable
+         * string.
+         */
+        $this->type_uri = $type_uri;
+
+        /**
+         * alias: The name that should be given to this attribute in
+         * the request. If it is not supplied, a generic name will be
+         * assigned. For example, if you want to call a Unix timestamp
+         * value 'tstamp', set its alias to that value. If two
+         * attributes in the same message request to use the same
+         * alias, the request will fail to be generated.
+         */
+        $this->alias = $alias;
+    }
+
+    /**
+     * Construct an attribute information object.  For parameter
+     * details, see the constructor.
+     */
+    function make($type_uri, $count=1, $required=false,
+                  $alias=null)
+    {
+        if ($alias !== null) {
+            $result = Auth_OpenID_AX_checkAlias($alias);
+
+            if (Auth_OpenID_AX::isError($result)) {
+                return $result;
+            }
+        }
+
+        return new Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
+                                           $alias);
+    }
+
+    /**
+     * When processing a request for this attribute, the OP should
+     * call this method to determine whether all available attribute
+     * values were requested.  If self.count == UNLIMITED_VALUES, this
+     * returns True.  Otherwise this returns False, in which case
+     * self.count is an integer.
+    */
+    function wantsUnlimitedValues()
+    {
+        return $this->count === Auth_OpenID_AX_UNLIMITED_VALUES;
+    }
+}
+
+/**
+ * Given a namespace mapping and a string containing a comma-separated
+ * list of namespace aliases, return a list of type URIs that
+ * correspond to those aliases.
+ *
+ * @param $namespace_map The mapping from namespace URI to alias
+ * @param $alias_list_s The string containing the comma-separated
+ * list of aliases. May also be None for convenience.
+ *
+ * @return $seq The list of namespace URIs that corresponds to the
+ * supplied list of aliases. If the string was zero-length or None, an
+ * empty list will be returned.
+ *
+ * return null If an alias is present in the list of aliases but
+ * is not present in the namespace map.
+ */
+function Auth_OpenID_AX_toTypeURIs(&$namespace_map, $alias_list_s)
+{
+    $uris = array();
+
+    if ($alias_list_s) {
+        foreach (explode(',', $alias_list_s) as $alias) {
+            $type_uri = $namespace_map->getNamespaceURI($alias);
+            if ($type_uri === null) {
+                // raise KeyError(
+                // 'No type is defined for attribute name %r' % (alias,))
+                return new Auth_OpenID_AX_Error(
+                  sprintf('No type is defined for attribute name %s',
+                          $alias)
+                  );
+            } else {
+                $uris[] = $type_uri;
+            }
+        }
+    }
+
+    return $uris;
+}
+
+/**
+ * An attribute exchange 'fetch_request' message. This message is sent
+ * by a relying party when it wishes to obtain attributes about the
+ * subject of an OpenID authentication request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message {
+
+    var $mode = 'fetch_request';
+
+    function Auth_OpenID_AX_FetchRequest($update_url=null)
+    {
+        /**
+         * requested_attributes: The attributes that have been
+         * requested thus far, indexed by the type URI.
+         */
+        $this->requested_attributes = array();
+
+        /**
+         * update_url: A URL that will accept responses for this
+         * attribute exchange request, even in the absence of the user
+         * who made this request.
+        */
+        $this->update_url = $update_url;
+    }
+
+    /**
+     * Add an attribute to this attribute exchange request.
+     *
+     * @param attribute: The attribute that is being requested
+     * @return true on success, false when the requested attribute is
+     * already present in this fetch request.
+     */
+    function add($attribute)
+    {
+        if ($this->contains($attribute->type_uri)) {
+            return new Auth_OpenID_AX_Error(
+              sprintf("The attribute %s has already been requested",
+                      $attribute->type_uri));
+        }
+
+        $this->requested_attributes[$attribute->type_uri] = $attribute;
+
+        return true;
+    }
+
+    /**
+     * Get the serialized form of this attribute fetch request.
+     *
+     * @returns Auth_OpenID_AX_FetchRequest The fetch request message parameters
+     */
+    function getExtensionArgs()
+    {
+        $aliases = new Auth_OpenID_NamespaceMap();
+
+        $required = array();
+        $if_available = array();
+
+        $ax_args = $this->_newArgs();
+
+        foreach ($this->requested_attributes as $type_uri => $attribute) {
+            if ($attribute->alias === null) {
+                $alias = $aliases->add($type_uri);
+            } else {
+                $alias = $aliases->addAlias($type_uri, $attribute->alias);
+
+                if ($alias === null) {
+                    return new Auth_OpenID_AX_Error(
+                      sprintf("Could not add alias %s for URI %s",
+                              $attribute->alias, $type_uri
+                      ));
+                }
+            }
+
+            if ($attribute->required) {
+                $required[] = $alias;
+            } else {
+                $if_available[] = $alias;
+            }
+
+            if ($attribute->count != 1) {
+                $ax_args['count.' . $alias] = strval($attribute->count);
+            }
+
+            $ax_args['type.' . $alias] = $type_uri;
+        }
+
+        if ($required) {
+            $ax_args['required'] = implode(',', $required);
+        }
+
+        if ($if_available) {
+            $ax_args['if_available'] = implode(',', $if_available);
+        }
+
+        return $ax_args;
+    }
+
+    /**
+     * Get the type URIs for all attributes that have been marked as
+     * required.
+     *
+     * @return A list of the type URIs for attributes that have been
+     * marked as required.
+     */
+    function getRequiredAttrs()
+    {
+        $required = array();
+        foreach ($this->requested_attributes as $type_uri => $attribute) {
+            if ($attribute->required) {
+                $required[] = $type_uri;
+            }
+        }
+
+        return $required;
+    }
+
+    /**
+     * Extract a FetchRequest from an OpenID message
+     *
+     * @param request: The OpenID request containing the attribute
+     * fetch request
+     *
+     * @returns mixed An Auth_OpenID_AX_Error or the
+     * Auth_OpenID_AX_FetchRequest extracted from the request message if
+     * successful
+     */
+    function &fromOpenIDRequest($request)
+    {
+        $m = $request->message;
+        $obj = new Auth_OpenID_AX_FetchRequest();
+        $ax_args = $m->getArgs($obj->ns_uri);
+
+        $result = $obj->parseExtensionArgs($ax_args);
+
+        if (Auth_OpenID_AX::isError($result)) {
+            return $result;
+        }
+
+        if ($obj->update_url) {
+            // Update URL must match the openid.realm of the
+            // underlying OpenID 2 message.
+            $realm = $m->getArg(Auth_OpenID_OPENID_NS, 'realm',
+                        $m->getArg(
+                                  Auth_OpenID_OPENID_NS,
+                                  'return_to'));
+
+            if (!$realm) {
+                $obj = new Auth_OpenID_AX_Error(
+                  sprintf("Cannot validate update_url %s " .
+                          "against absent realm", $obj->update_url));
+            } else if (!Auth_OpenID_TrustRoot::match($realm,
+                                                     $obj->update_url)) {
+                $obj = new Auth_OpenID_AX_Error(
+                  sprintf("Update URL %s failed validation against realm %s",
+                          $obj->update_url, $realm));
+            }
+        }
+
+        return $obj;
+    }
+
+    /**
+     * Given attribute exchange arguments, populate this FetchRequest.
+     *
+     * @return $result Auth_OpenID_AX_Error if the data to be parsed
+     * does not follow the attribute exchange specification. At least
+     * when 'if_available' or 'required' is not specified for a
+     * particular attribute type.  Returns true otherwise.
+    */
+    function parseExtensionArgs($ax_args)
+    {
+        $result = $this->_checkMode($ax_args);
+        if (Auth_OpenID_AX::isError($result)) {
+            return $result;
+        }
+
+        $aliases = new Auth_OpenID_NamespaceMap();
+
+        foreach ($ax_args as $key => $value) {
+            if (strpos($key, 'type.') === 0) {
+                $alias = substr($key, 5);
+                $type_uri = $value;
+
+                $alias = $aliases->addAlias($type_uri, $alias);
+
+                if ($alias === null) {
+                    return new Auth_OpenID_AX_Error(
+                      sprintf("Could not add alias %s for URI %s",
+                              $alias, $type_uri)
+                      );
+                }
+
+                $count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias);
+                if ($count_s) {
+                    $count = Auth_OpenID::intval($count_s);
+                    if (($count === false) &&
+                        ($count_s === Auth_OpenID_AX_UNLIMITED_VALUES)) {
+                        $count = $count_s;
+                    }
+                } else {
+                    $count = 1;
+                }
+
+                if ($count === false) {
+                    return new Auth_OpenID_AX_Error(
+                      sprintf("Integer value expected for %s, got %s",
+                              'count.' . $alias, $count_s));
+                }
+
+                $attrinfo = Auth_OpenID_AX_AttrInfo::make($type_uri, $count,
+                                                          false, $alias);
+
+                if (Auth_OpenID_AX::isError($attrinfo)) {
+                    return $attrinfo;
+                }
+
+                $this->add($attrinfo);
+            }
+        }
+
+        $required = Auth_OpenID_AX_toTypeURIs($aliases,
+                         Auth_OpenID::arrayGet($ax_args, 'required'));
+
+        foreach ($required as $type_uri) {
+            $attrib =& $this->requested_attributes[$type_uri];
+            $attrib->required = true;
+        }
+
+        $if_available = Auth_OpenID_AX_toTypeURIs($aliases,
+                             Auth_OpenID::arrayGet($ax_args, 'if_available'));
+
+        $all_type_uris = array_merge($required, $if_available);
+
+        foreach ($aliases->iterNamespaceURIs() as $type_uri) {
+            if (!in_array($type_uri, $all_type_uris)) {
+                return new Auth_OpenID_AX_Error(
+                  sprintf('Type URI %s was in the request but not ' .
+                          'present in "required" or "if_available"',
+                          $type_uri));
+
+            }
+        }
+
+        $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
+
+        return true;
+    }
+
+    /**
+     * Iterate over the AttrInfo objects that are contained in this
+     * fetch_request.
+     */
+    function iterAttrs()
+    {
+        return array_values($this->requested_attributes);
+    }
+
+    function iterTypes()
+    {
+        return array_keys($this->requested_attributes);
+    }
+
+    /**
+     * Is the given type URI present in this fetch_request?
+     */
+    function contains($type_uri)
+    {
+        return in_array($type_uri, $this->iterTypes());
+    }
+}
+
+/**
+ * An abstract class that implements a message that has attribute keys
+ * and values. It contains the common code between fetch_response and
+ * store_request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message {
+
+    function Auth_OpenID_AX_KeyValueMessage()
+    {
+        $this->data = array();
+    }
+
+    /**
+     * Add a single value for the given attribute type to the
+     * message. If there are already values specified for this type,
+     * this value will be sent in addition to the values already
+     * specified.
+     *
+     * @param type_uri: The URI for the attribute
+     * @param value: The value to add to the response to the relying
+     * party for this attribute
+     * @return null
+     */
+    function addValue($type_uri, $value)
+    {
+        if (!array_key_exists($type_uri, $this->data)) {
+            $this->data[$type_uri] = array();
+        }
+
+        $values =& $this->data[$type_uri];
+        $values[] = $value;
+    }
+
+    /**
+     * Set the values for the given attribute type. This replaces any
+     * values that have already been set for this attribute.
+     *
+     * @param type_uri: The URI for the attribute
+     * @param values: A list of values to send for this attribute.
+     */
+    function setValues($type_uri, &$values)
+    {
+        $this->data[$type_uri] =& $values;
+    }
+
+    /**
+     * Get the extension arguments for the key/value pairs contained
+     * in this message.
+     *
+     * @param aliases: An alias mapping. Set to None if you don't care
+     * about the aliases for this request.
+     *
+     * @access private
+     */
+    function _getExtensionKVArgs(&$aliases)
+    {
+        if ($aliases === null) {
+            $aliases = new Auth_OpenID_NamespaceMap();
+        }
+
+        $ax_args = array();
+
+        foreach ($this->data as $type_uri => $values) {
+            $alias = $aliases->add($type_uri);
+
+            $ax_args['type.' . $alias] = $type_uri;
+            $ax_args['count.' . $alias] = strval(count($values));
+
+            foreach ($values as $i => $value) {
+              $key = sprintf('value.%s.%d', $alias, $i + 1);
+              $ax_args[$key] = $value;
+            }
+        }
+
+        return $ax_args;
+    }
+
+    /**
+     * Parse attribute exchange key/value arguments into this object.
+     *
+     * @param ax_args: The attribute exchange fetch_response
+     * arguments, with namespacing removed.
+     *
+     * @return Auth_OpenID_AX_Error or true
+     */
+    function parseExtensionArgs($ax_args)
+    {
+        $result = $this->_checkMode($ax_args);
+        if (Auth_OpenID_AX::isError($result)) {
+            return $result;
+        }
+
+        $aliases = new Auth_OpenID_NamespaceMap();
+
+        foreach ($ax_args as $key => $value) {
+            if (strpos($key, 'type.') === 0) {
+                $type_uri = $value;
+                $alias = substr($key, 5);
+
+                $result = Auth_OpenID_AX_checkAlias($alias);
+
+                if (Auth_OpenID_AX::isError($result)) {
+                    return $result;
+                }
+
+                $alias = $aliases->addAlias($type_uri, $alias);
+
+                if ($alias === null) {
+                    return new Auth_OpenID_AX_Error(
+                      sprintf("Could not add alias %s for URI %s",
+                              $alias, $type_uri)
+                      );
+                }
+            }
+        }
+
+        foreach ($aliases->iteritems() as $pair) {
+            list($type_uri, $alias) = $pair;
+
+            if (array_key_exists('count.' . $alias, $ax_args)) {
+
+                $count_key = 'count.' . $alias;
+                $count_s = $ax_args[$count_key];
+
+                $count = Auth_OpenID::intval($count_s);
+
+                if ($count === false) {
+                    return new Auth_OpenID_AX_Error(
+                      sprintf("Integer value expected for %s, got %s",
+                              'count. %s' . $alias, $count_s,
+                              Auth_OpenID_AX_UNLIMITED_VALUES)
+                                                    );
+                }
+
+                $values = array();
+                for ($i = 1; $i < $count + 1; $i++) {
+                    $value_key = sprintf('value.%s.%d', $alias, $i);
+
+                    if (!array_key_exists($value_key, $ax_args)) {
+                      return new Auth_OpenID_AX_Error(
+                        sprintf(
+                                "No value found for key %s",
+                                $value_key));
+                    }
+
+                    $value = $ax_args[$value_key];
+                    $values[] = $value;
+                }
+            } else {
+                $key = 'value.' . $alias;
+
+                if (!array_key_exists($key, $ax_args)) {
+                  return new Auth_OpenID_AX_Error(
+                    sprintf(
+                            "No value found for key %s",
+                            $key));
+                }
+
+                $value = $ax_args['value.' . $alias];
+
+                if ($value == '') {
+                    $values = array();
+                } else {
+                    $values = array($value);
+                }
+            }
+
+            $this->data[$type_uri] = $values;
+        }
+
+        return true;
+    }
+
+    /**
+     * Get a single value for an attribute. If no value was sent for
+     * this attribute, use the supplied default. If there is more than
+     * one value for this attribute, this method will fail.
+     *
+     * @param type_uri: The URI for the attribute
+     * @param default: The value to return if the attribute was not
+     * sent in the fetch_response.
+     *
+     * @return $value Auth_OpenID_AX_Error on failure or the value of
+     * the attribute in the fetch_response message, or the default
+     * supplied
+     */
+    function getSingle($type_uri, $default=null)
+    {
+        $values = Auth_OpenID::arrayGet($this->data, $type_uri);
+        if (!$values) {
+            return $default;
+        } else if (count($values) == 1) {
+            return $values[0];
+        } else {
+            return new Auth_OpenID_AX_Error(
+              sprintf('More than one value present for %s',
+                      $type_uri)
+              );
+        }
+    }
+
+    /**
+     * Get the list of values for this attribute in the
+     * fetch_response.
+     *
+     * XXX: what to do if the values are not present? default
+     * parameter? this is funny because it's always supposed to return
+     * a list, so the default may break that, though it's provided by
+     * the user's code, so it might be okay. If no default is
+     * supplied, should the return be None or []?
+     *
+     * @param type_uri: The URI of the attribute
+     *
+     * @return $values The list of values for this attribute in the
+     * response. May be an empty list.  If the attribute was not sent
+     * in the response, returns Auth_OpenID_AX_Error.
+     */
+    function get($type_uri)
+    {
+        if (array_key_exists($type_uri, $this->data)) {
+            return $this->data[$type_uri];
+        } else {
+            return new Auth_OpenID_AX_Error(
+              sprintf("Type URI %s not found in response",
+                      $type_uri)
+              );
+        }
+    }
+
+    /**
+     * Get the number of responses for a particular attribute in this
+     * fetch_response message.
+     *
+     * @param type_uri: The URI of the attribute
+     *
+     * @returns int The number of values sent for this attribute.  If
+     * the attribute was not sent in the response, returns
+     * Auth_OpenID_AX_Error.
+     */
+    function count($type_uri)
+    {
+        if (array_key_exists($type_uri, $this->data)) {
+            return count($this->get($type_uri));
+        } else {
+            return new Auth_OpenID_AX_Error(
+              sprintf("Type URI %s not found in response",
+                      $type_uri)
+              );
+        }
+    }
+}
+
+/**
+ * A fetch_response attribute exchange message.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage {
+    var $mode = 'fetch_response';
+
+    function Auth_OpenID_AX_FetchResponse($update_url=null)
+    {
+        $this->Auth_OpenID_AX_KeyValueMessage();
+        $this->update_url = $update_url;
+    }
+
+    /**
+     * Serialize this object into arguments in the attribute exchange
+     * namespace
+     *
+     * @return $args The dictionary of unqualified attribute exchange
+     * arguments that represent this fetch_response, or
+     * Auth_OpenID_AX_Error on error.
+     */
+    function getExtensionArgs($request=null)
+    {
+        $aliases = new Auth_OpenID_NamespaceMap();
+
+        $zero_value_types = array();
+
+        if ($request !== null) {
+            // Validate the data in the context of the request (the
+            // same attributes should be present in each, and the
+            // counts in the response must be no more than the counts
+            // in the request)
+
+            foreach ($this->data as $type_uri => $unused) {
+                if (!$request->contains($type_uri)) {
+                    return new Auth_OpenID_AX_Error(
+                      sprintf("Response attribute not present in request: %s",
+                              $type_uri)
+                      );
+                }
+            }
+
+            foreach ($request->iterAttrs() as $attr_info) {
+                // Copy the aliases from the request so that reading
+                // the response in light of the request is easier
+                if ($attr_info->alias === null) {
+                    $aliases->add($attr_info->type_uri);
+                } else {
+                    $alias = $aliases->addAlias($attr_info->type_uri,
+                                                $attr_info->alias);
+
+                    if ($alias === null) {
+                        return new Auth_OpenID_AX_Error(
+                          sprintf("Could not add alias %s for URI %s",
+                                  $attr_info->alias, $attr_info->type_uri)
+                          );
+                    }
+                }
+
+                if (array_key_exists($attr_info->type_uri, $this->data)) {
+                    $values = $this->data[$attr_info->type_uri];
+                } else {
+                    $values = array();
+                    $zero_value_types[] = $attr_info;
+                }
+
+                if (($attr_info->count != Auth_OpenID_AX_UNLIMITED_VALUES) &&
+                    ($attr_info->count < count($values))) {
+                    return new Auth_OpenID_AX_Error(
+                      sprintf("More than the number of requested values " .
+                              "were specified for %s",
+                              $attr_info->type_uri)
+                      );
+                }
+            }
+        }
+
+        $kv_args = $this->_getExtensionKVArgs($aliases);
+
+        // Add the KV args into the response with the args that are
+        // unique to the fetch_response
+        $ax_args = $this->_newArgs();
+
+        // For each requested attribute, put its type/alias and count
+        // into the response even if no data were returned.
+        foreach ($zero_value_types as $attr_info) {
+            $alias = $aliases->getAlias($attr_info->type_uri);
+            $kv_args['type.' . $alias] = $attr_info->type_uri;
+            $kv_args['count.' . $alias] = '0';
+        }
+
+        $update_url = null;
+        if ($request) {
+            $update_url = $request->update_url;
+        } else {
+            $update_url = $this->update_url;
+        }
+
+        if ($update_url) {
+            $ax_args['update_url'] = $update_url;
+        }
+
+        Auth_OpenID::update(&$ax_args, $kv_args);
+
+        return $ax_args;
+    }
+
+    /**
+     * @return $result Auth_OpenID_AX_Error on failure or true on
+     * success.
+     */
+    function parseExtensionArgs($ax_args)
+    {
+        $result = parent::parseExtensionArgs($ax_args);
+
+        if (Auth_OpenID_AX::isError($result)) {
+            return $result;
+        }
+
+        $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
+
+        return true;
+    }
+
+    /**
+     * Construct a FetchResponse object from an OpenID library
+     * SuccessResponse object.
+     *
+     * @param success_response: A successful id_res response object
+     *
+     * @param signed: Whether non-signed args should be processsed. If
+     * True (the default), only signed arguments will be processsed.
+     *
+     * @return $response A FetchResponse containing the data from the
+     * OpenID message
+     */
+    function fromSuccessResponse($success_response, $signed=true)
+    {
+        $obj = new Auth_OpenID_AX_FetchResponse();
+        if ($signed) {
+            $ax_args = $success_response->getSignedNS($obj->ns_uri);
+        } else {
+            $ax_args = $success_response->message->getArgs($obj->ns_uri);
+        }
+        if ($ax_args === null || Auth_OpenID::isFailure($ax_args) ||
+              sizeof($ax_args) == 0) {
+            return null;
+        }
+
+        $result = $obj->parseExtensionArgs($ax_args);
+        if (Auth_OpenID_AX::isError($result)) {
+            #XXX log me
+            return null;
+        }
+        return $obj;
+    }
+}
+
+/**
+ * A store request attribute exchange message representation.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage {
+    var $mode = 'store_request';
+
+    /**
+     * @param array $aliases The namespace aliases to use when making
+     * this store response. Leave as None to use defaults.
+     */
+    function getExtensionArgs($aliases=null)
+    {
+        $ax_args = $this->_newArgs();
+        $kv_args = $this->_getExtensionKVArgs($aliases);
+        Auth_OpenID::update(&$ax_args, $kv_args);
+        return $ax_args;
+    }
+}
+
+/**
+ * An indication that the store request was processed along with this
+ * OpenID transaction.  Use make(), NOT the constructor, to create
+ * response objects.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message {
+    var $SUCCESS_MODE = 'store_response_success';
+    var $FAILURE_MODE = 'store_response_failure';
+
+    /**
+     * Returns Auth_OpenID_AX_Error on error or an
+     * Auth_OpenID_AX_StoreResponse object on success.
+     */
+    function &make($succeeded=true, $error_message=null)
+    {
+        if (($succeeded) && ($error_message !== null)) {
+            return new Auth_OpenID_AX_Error('An error message may only be '.
+                                    'included in a failing fetch response');
+        }
+
+        return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message);
+    }
+
+    function Auth_OpenID_AX_StoreResponse($succeeded=true, $error_message=null)
+    {
+        if ($succeeded) {
+            $this->mode = $this->SUCCESS_MODE;
+        } else {
+            $this->mode = $this->FAILURE_MODE;
+        }
+
+        $this->error_message = $error_message;
+    }
+
+    /**
+     * Was this response a success response?
+     */
+    function succeeded()
+    {
+        return $this->mode == $this->SUCCESS_MODE;
+    }
+
+    function getExtensionArgs()
+    {
+        $ax_args = $this->_newArgs();
+        if ((!$this->succeeded()) && $this->error_message) {
+            $ax_args['error'] = $this->error_message;
+        }
+
+        return $ax_args;
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/lib/Auth/OpenID/Association.php b/lib/Auth/OpenID/Association.php
index 16c83bf3df26acc8f439ee3d8c8f3412c28cf052..37ce0cbf4545c8a19e066c4218e520bd4b8d7426 100644
--- a/lib/Auth/OpenID/Association.php
+++ b/lib/Auth/OpenID/Association.php
@@ -10,8 +10,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -27,7 +27,7 @@ require_once 'Auth/OpenID/KVForm.php';
 /**
  * @access private
  */
-require_once 'Auth/OpenID/HMACSHA1.php';
+require_once 'Auth/OpenID/HMAC.php';
 
 /**
  * This class represents an association between a server and a
@@ -64,6 +64,11 @@ class Auth_OpenID_Association {
                             'assoc_type'
                             );
 
+    var $_macs = array(
+                       'HMAC-SHA1' => 'Auth_OpenID_HMACSHA1',
+                       'HMAC-SHA256' => 'Auth_OpenID_HMACSHA256'
+                       );
+
     /**
      * This is an alternate constructor (factory method) used by the
      * OpenID consumer library to create associations.  OpenID store
@@ -82,9 +87,9 @@ class Auth_OpenID_Association {
      * generated for this association.
      *
      * @param assoc_type This is the type of association this
-     * instance represents.  The only valid value of this field at
-     * this time is 'HMAC-SHA1', but new types may be defined in the
-     * future.
+     * instance represents.  The only valid values of this field at
+     * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may
+     * be defined in the future.
      *
      * @return association An {@link Auth_OpenID_Association}
      * instance.
@@ -119,9 +124,9 @@ class Auth_OpenID_Association {
      * association was issued.
      *
      * @param string $assoc_type This is the type of association this
-     * instance represents.  The only valid value of this field at
-     * this time is 'HMAC-SHA1', but new types may be defined in the
-     * future.
+     * instance represents.  The only valid values of this field at
+     * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may
+     * be defined in the future.
      */
     function Auth_OpenID_Association(
         $handle, $secret, $issued, $lifetime, $assoc_type)
@@ -258,7 +263,11 @@ class Auth_OpenID_Association {
     function sign($pairs)
     {
         $kv = Auth_OpenID_KVForm::fromArray($pairs);
-        return Auth_OpenID_HMACSHA1($this->secret, $kv);
+
+        /* Invalid association types should be caught at constructor */
+        $callback = $this->_macs[$this->assoc_type];
+
+        return call_user_func_array($callback, array($this->secret, $kv));
     }
 
     /**
@@ -321,7 +330,7 @@ class Auth_OpenID_Association {
     function _makePairs(&$message)
     {
         $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
-        if (!$signed) {
+        if (!$signed || Auth_OpenID::isFailure($signed)) {
             // raise ValueError('Message has no signed list: %s' % (message,))
             return null;
         }
@@ -360,7 +369,7 @@ class Auth_OpenID_Association {
         $sig = $message->getArg(Auth_OpenID_OPENID_NS,
                                 'sig');
 
-        if (!$sig) {
+        if (!$sig || Auth_OpenID::isFailure($sig)) {
             return false;
         }
 
@@ -423,7 +432,7 @@ function Auth_OpenID_getDefaultAssociationOrder()
 {
     $order = array();
 
-    if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+    if (!Auth_OpenID_noMathSupport()) {
         $order[] = array('HMAC-SHA1', 'DH-SHA1');
 
         if (Auth_OpenID_HMACSHA256_SUPPORTED) {
@@ -518,7 +527,8 @@ function &Auth_OpenID_getEncryptedNegotiator()
 class Auth_OpenID_SessionNegotiator {
     function Auth_OpenID_SessionNegotiator($allowed_types)
     {
-        $this->allowed_types = $allowed_types;
+        $this->allowed_types = array();
+        $this->setAllowedTypes($allowed_types);
     }
 
     /**
diff --git a/lib/Auth/OpenID/BigMath.php b/lib/Auth/OpenID/BigMath.php
index cfa0b3541d3a49a93421f91ef42067dec7368046..45104947d6da44412671f6e319f0dcf817131893 100644
--- a/lib/Auth/OpenID/BigMath.php
+++ b/lib/Auth/OpenID/BigMath.php
@@ -11,8 +11,8 @@
  * @access private
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -427,7 +427,7 @@ function &Auth_OpenID_getMathLib()
         return $lib;
     }
 
-    if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+    if (Auth_OpenID_noMathSupport()) {
         $null = null;
         return $null;
     }
@@ -443,8 +443,10 @@ function &Auth_OpenID_getMathLib()
         }
         $triedstr = implode(", ", $tried);
 
-        define('Auth_OpenID_NO_MATH_SUPPORT', true);
-        return null;
+        Auth_OpenID_setNoMathSupport();
+
+        $result = null;
+        return $result;
     }
 
     // Instantiate a new wrapper
@@ -454,4 +456,16 @@ function &Auth_OpenID_getMathLib()
     return $lib;
 }
 
+function Auth_OpenID_setNoMathSupport()
+{
+    if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+        define('Auth_OpenID_NO_MATH_SUPPORT', true);
+    }
+}
+
+function Auth_OpenID_noMathSupport()
+{
+    return defined('Auth_OpenID_NO_MATH_SUPPORT');
+}
+
 ?>
diff --git a/lib/Auth/OpenID/Consumer.php b/lib/Auth/OpenID/Consumer.php
index c1a05eeb13c8c91a27de12cf384c418d2bae7186..500890b6568def082e94b3afdc28ac893cd3592c 100644
--- a/lib/Auth/OpenID/Consumer.php
+++ b/lib/Auth/OpenID/Consumer.php
@@ -153,8 +153,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -162,13 +162,14 @@
  */
 require_once "Auth/OpenID.php";
 require_once "Auth/OpenID/Message.php";
-require_once "Auth/OpenID/HMACSHA1.php";
+require_once "Auth/OpenID/HMAC.php";
 require_once "Auth/OpenID/Association.php";
 require_once "Auth/OpenID/CryptUtil.php";
 require_once "Auth/OpenID/DiffieHellman.php";
 require_once "Auth/OpenID/KVForm.php";
 require_once "Auth/OpenID/Nonce.php";
 require_once "Auth/OpenID/Discover.php";
+require_once "Auth/OpenID/URINorm.php";
 require_once "Auth/Yadis/Manager.php";
 require_once "Auth/Yadis/XRI.php";
 
@@ -388,6 +389,13 @@ class Auth_OpenID_Consumer {
      * request. It is called in step 4 of the flow described in the
      * consumer overview.
      *
+     * @param string $current_url The URL used to invoke the application.
+     * Extract the URL from your application's web
+     * request framework and specify it here to have it checked
+     * against the openid.current_url value in the response.  If
+     * the current_url URL check fails, the status of the
+     * completion will be FAILURE.
+     *
      * @param array $query An array of the query parameters (key =>
      * value pairs) for this HTTP request.  Defaults to null.  If
      * null, the GET or POST data are automatically gotten from the
@@ -399,8 +407,16 @@ class Auth_OpenID_Consumer {
      * indicated by the status attribute, which will be one of
      * SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
      */
-    function complete($query=null)
+    function complete($current_url, $query=null)
     {
+        if ($current_url && !is_string($current_url)) {
+            // This is ugly, but we need to complain loudly when
+            // someone uses the API incorrectly.
+            trigger_error("current_url must be a string; see NEWS file " .
+                          "for upgrading notes.",
+                          E_USER_ERROR);
+        }
+
         if ($query === null) {
             $query = Auth_OpenID::getQuery();
         }
@@ -410,14 +426,10 @@ class Auth_OpenID_Consumer {
         $endpoint =
             $loader->fromSession($endpoint_data);
 
-        if ($endpoint === null) {
-            $response = new Auth_OpenID_FailureResponse(null,
-                                                   'No session state found');
-        } else {
-            $message = Auth_OpenID_Message::fromPostArgs($query);
-            $response = $this->consumer->complete($message, $endpoint);
-            $this->session->del($this->_token_key);
-        }
+        $message = Auth_OpenID_Message::fromPostArgs($query);
+        $response = $this->consumer->complete($message, $endpoint, 
+                                              $current_url);
+        $this->session->del($this->_token_key);
 
         if (in_array($response->status, array(Auth_OpenID_SUCCESS,
                                               Auth_OpenID_CANCEL))) {
@@ -425,7 +437,7 @@ class Auth_OpenID_Consumer {
                 $disco = $this->getDiscoveryObject($this->session,
                                                    $response->identity_url,
                                                    $this->session_key_prefix);
-                $disco->cleanup();
+                $disco->cleanup(true);
             }
         }
 
@@ -515,7 +527,7 @@ class Auth_OpenID_DiffieHellmanSHA256ConsumerSession extends
  */
 class Auth_OpenID_PlainTextConsumerSession {
     var $session_type = 'no-encryption';
-    var $allowed_assoc_types =  array('HMAC-SHA1');
+    var $allowed_assoc_types =  array('HMAC-SHA1', 'HMAC-SHA256');
 
     function getRequest()
     {
@@ -574,6 +586,13 @@ class Auth_OpenID_GenericConsumer {
      */
     var $openid1_nonce_query_arg_name = 'janrain_nonce';
 
+    /**
+     * Another query parameter that gets added to the return_to for
+     * OpenID 1; if the user's session state is lost, use this claimed
+     * identifier to do discovery when verifying the response.
+     */
+    var $openid1_return_to_identifier_name = 'openid1_claimed_id';
+
     /**
      * This method initializes a new {@link Auth_OpenID_Consumer}
      * instance to access the library.
@@ -615,6 +634,12 @@ class Auth_OpenID_GenericConsumer {
         $r = new Auth_OpenID_AuthRequest($service_endpoint, $assoc);
         $r->return_to_args[$this->openid1_nonce_query_arg_name] =
             Auth_OpenID_mkNonce();
+
+        if ($r->message->isOpenID1()) {
+            $r->return_to_args[$this->openid1_return_to_identifier_name] =
+                $r->endpoint->claimed_id;
+        }
+
         return $r;
     }
 
@@ -625,41 +650,85 @@ class Auth_OpenID_GenericConsumer {
      *
      * @access private
      */
-    function complete($message, $endpoint, $return_to = null)
+    function complete($message, $endpoint, $return_to)
     {
         $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
                                  '<no mode set>');
 
-        if ($return_to !== null) {
-            if (!$this->_checkReturnTo($message, $return_to)) {
-                return new Auth_OpenID_FailureResponse($endpoint,
-                             "openid.return_to does not match return URL");
-            }
+        $mode_methods = array(
+                              'cancel' => '_complete_cancel',
+                              'error' => '_complete_error',
+                              'setup_needed' => '_complete_setup_needed',
+                              'id_res' => '_complete_id_res',
+                              );
+
+        $method = Auth_OpenID::arrayGet($mode_methods, $mode,
+                                        '_completeInvalid');
+
+        return call_user_func_array(array(&$this, $method),
+                                    array($message, $endpoint, $return_to));
+    }
+
+    /**
+     * @access private
+     */
+    function _completeInvalid($message, &$endpoint, $unused)
+    {
+        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
+                                 '<No mode set>');
+
+        return new Auth_OpenID_FailureResponse($endpoint,
+                    sprintf("Invalid openid.mode '%s'", $mode));
+    }
+
+    /**
+     * @access private
+     */
+    function _complete_cancel($message, &$endpoint, $unused)
+    {
+        return new Auth_OpenID_CancelResponse($endpoint);
+    }
+
+    /**
+     * @access private
+     */
+    function _complete_error($message, &$endpoint, $unused)
+    {
+        $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error');
+        $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact');
+        $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference');
+
+        return new Auth_OpenID_FailureResponse($endpoint, $error,
+                                               $contact, $reference);
+    }
+
+    /**
+     * @access private
+     */
+    function _complete_setup_needed($message, &$endpoint, $unused)
+    {
+        if (!$message->isOpenID2()) {
+            return $this->_completeInvalid($message, $endpoint);
         }
 
-        if ($mode == 'cancel') {
-            return new Auth_OpenID_CancelResponse($endpoint);
-        } else if ($mode == 'error') {
-            $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error');
-            $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact');
-            $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference');
+        $user_setup_url = $message->getArg(Auth_OpenID_OPENID2_NS,
+                                           'user_setup_url');
+        return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url);
+    }
 
-            return new Auth_OpenID_FailureResponse($endpoint, $error,
-                                                   $contact, $reference);
-        } else if ($message->isOpenID2() && ($mode == 'setup_needed')) {
-            return new Auth_OpenID_SetupNeededResponse($endpoint);
+    /**
+     * @access private
+     */
+    function _complete_id_res($message, &$endpoint, $return_to)
+    {
+        $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
+                                           'user_setup_url');
 
-        } else if ($mode == 'id_res') {
-            if ($this->_checkSetupNeeded($message)) {
-                return SetupNeededResponse($endpoint,
-                                           $result->user_setup_url);
-            } else {
-                return $this->_doIdRes($message, $endpoint);
-            }
+        if ($this->_checkSetupNeeded($message)) {
+            return new Auth_OpenID_SetupNeededResponse(
+                $endpoint, $user_setup_url);
         } else {
-            return new Auth_OpenID_FailureResponse($endpoint,
-                                           sprintf("Invalid openid.mode '%s'",
-                                                   $mode));
+            return $this->_doIdRes($message, $endpoint, $return_to);
         }
     }
 
@@ -685,26 +754,23 @@ class Auth_OpenID_GenericConsumer {
     /**
      * @access private
      */
-    function _doIdRes($message, $endpoint)
+    function _doIdRes($message, $endpoint, $return_to)
     {
-        $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS,
-                                            'signed');
-
-        if ($signed_list_str === null) {
-            return new Auth_OpenID_FailureResponse($endpoint,
-                                   "Response missing signed list");
-        }
-
-        $signed_list = explode(',', $signed_list_str);
-
         // Checks for presence of appropriate fields (and checks
         // signed list fields)
-        $result = $this->_idResCheckForFields($message, $signed_list);
+        $result = $this->_idResCheckForFields($message);
 
         if (Auth_OpenID::isFailure($result)) {
             return $result;
         }
 
+        if (!$this->_checkReturnTo($message, $return_to)) {
+            return new Auth_OpenID_FailureResponse(null,
+            sprintf("return_to does not match return URL. Expected %s, got %s",
+                    $return_to,
+                    $message->getArg(Auth_OpenID_OPENID_NS, 'return_to')));
+        }
+
         // Verify discovery information:
         $result = $this->_verifyDiscoveryResults($message, $endpoint);
 
@@ -721,15 +787,19 @@ class Auth_OpenID_GenericConsumer {
             return $result;
         }
 
-        $response_identity = $message->getArg(Auth_OpenID_OPENID_NS,
-                                              'identity');
-
         $result = $this->_idResCheckNonce($message, $endpoint);
 
         if (Auth_OpenID::isFailure($result)) {
             return $result;
         }
 
+        $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 'signed',
+                                            Auth_OpenID_NO_DEFAULT);
+        if (Auth_OpenID::isFailure($signed_list_str)) {
+            return $signed_list_str;
+        }
+        $signed_list = explode(',', $signed_list_str);
+
         $signed_fields = Auth_OpenID::addPrefix($signed_list, "openid.");
 
         return new Auth_OpenID_SuccessResponse($endpoint, $message,
@@ -758,9 +828,13 @@ class Auth_OpenID_GenericConsumer {
         // message.
         $msg_return_to = $message->getArg(Auth_OpenID_OPENID_NS,
                                           'return_to');
+        if (Auth_OpenID::isFailure($return_to)) {
+            // XXX log me
+            return false;
+        }
 
-        $return_to_parts = parse_url($return_to);
-        $msg_return_to_parts = parse_url($msg_return_to);
+        $return_to_parts = parse_url(Auth_OpenID_urinorm($return_to));
+        $msg_return_to_parts = parse_url(Auth_OpenID_urinorm($msg_return_to));
 
         // If port is absent from both, add it so it's equal in the
         // check below.
@@ -811,10 +885,13 @@ class Auth_OpenID_GenericConsumer {
         $message = Auth_OpenID_Message::fromPostArgs($query);
         $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to');
 
+        if (Auth_OpenID::isFailure($return_to)) {
+            return $return_to;
+        }
         // XXX: this should be checked by _idResCheckForFields
         if (!$return_to) {
             return new Auth_OpenID_FailureResponse(null,
-                         "no openid.return_to in query");
+                           "Response has no return_to");
         }
 
         $parsed_url = parse_url($return_to);
@@ -840,6 +917,17 @@ class Auth_OpenID_GenericConsumer {
             }
         }
 
+        // Make sure all non-OpenID arguments in the response are also
+        // in the signed return_to.
+        $bare_args = $message->getArgs(Auth_OpenID_BARE_NS);
+        foreach ($bare_args as $key => $value) {
+            if (Auth_OpenID::arrayGet($q, $key) != $value) {
+                return new Auth_OpenID_FailureResponse(null,
+                  sprintf("Parameter %s = %s not in return_to URL",
+                          $key, $value));
+            }
+        }
+
         return true;
     }
 
@@ -850,6 +938,9 @@ class Auth_OpenID_GenericConsumer {
     {
         $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
                                          'assoc_handle');
+        if (Auth_OpenID::isFailure($assoc_handle)) {
+            return $assoc_handle;
+        }
 
         $assoc = $this->store->getAssociation($server_url, $assoc_handle);
 
@@ -902,12 +993,17 @@ class Auth_OpenID_GenericConsumer {
      */
     function _verifyDiscoveryResultsOpenID1($message, $endpoint)
     {
-        if ($endpoint === null) {
+        $claimed_id = $message->getArg(Auth_OpenID_BARE_NS,
+                                $this->openid1_return_to_identifier_name);
+
+        if (($endpoint === null) && ($claimed_id === null)) {
             return new Auth_OpenID_FailureResponse($endpoint,
               'When using OpenID 1, the claimed ID must be supplied, ' .
               'either by passing it through as a return_to parameter ' .
               'or by using a session, and supplied to the GenericConsumer ' .
               'as the argument to complete()');
+        } else if (($endpoint !== null) && ($claimed_id === null)) {
+            $claimed_id = $endpoint->claimed_id;
         }
 
         $to_match = new Auth_OpenID_ServiceEndpoint();
@@ -916,7 +1012,7 @@ class Auth_OpenID_GenericConsumer {
                                                'identity');
 
         // Restore delegate information from the initiation phase
-        $to_match->claimed_id = $endpoint->claimed_id;
+        $to_match->claimed_id = $claimed_id;
 
         if ($to_match->local_id === null) {
             return new Auth_OpenID_FailureResponse($endpoint,
@@ -926,17 +1022,27 @@ class Auth_OpenID_GenericConsumer {
         $to_match_1_0 = $to_match->copy();
         $to_match_1_0->type_uris = array(Auth_OpenID_TYPE_1_0);
 
-        $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
+        if ($endpoint !== null) {
+            $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
 
-        if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) {
-            $result = $this->_verifyDiscoverySingle($endpoint, $to_match_1_0);
-        }
+            if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) {
+                $result = $this->_verifyDiscoverySingle($endpoint,
+                                                        $to_match_1_0);
+            }
 
-        if (Auth_OpenID::isFailure($result)) {
-            return $result;
-        } else {
-            return $endpoint;
+            if (Auth_OpenID::isFailure($result)) {
+                // oidutil.log("Error attempting to use stored
+                //             discovery information: " + str(e))
+                //             oidutil.log("Attempting discovery to
+                //             verify endpoint")
+            } else {
+                return $endpoint;
+            }
         }
+
+        // Endpoint is either bad (failed verification) or None
+        return $this->_discoverAndVerify($to_match->claimed_id,
+                                         array($to_match, $to_match_1_0));
     }
 
     /**
@@ -953,10 +1059,16 @@ class Auth_OpenID_GenericConsumer {
             }
         }
 
-        if ($to_match->claimed_id != $endpoint->claimed_id) {
+        // Fragments do not influence discovery, so we can't compare a
+        // claimed identifier with a fragment to discovered
+        // information.
+        list($defragged_claimed_id, $_) =
+            Auth_OpenID::urldefrag($to_match->claimed_id);
+
+        if ($defragged_claimed_id != $endpoint->claimed_id) {
             return new Auth_OpenID_FailureResponse($endpoint,
               sprintf('Claimed ID does not match (different subjects!), ' .
-                      'Expected %s, got %s', $to_match->claimed_id,
+                      'Expected %s, got %s', $defragged_claimed_id,
                       $endpoint->claimed_id));
         }
 
@@ -1012,111 +1124,117 @@ class Auth_OpenID_GenericConsumer {
             ($to_match->local_id !== null)) {
             return new Auth_OpenID_FailureResponse($endpoint,
               'openid.identity is present without openid.claimed_id');
-        } else if (($to_match->claimed_id !== null) &&
-                   ($to_match->local_id === null)) {
+        }
+
+        if (($to_match->claimed_id !== null) &&
+            ($to_match->local_id === null)) {
             return new Auth_OpenID_FailureResponse($endpoint,
               'openid.claimed_id is present without openid.identity');
-        } else if ($to_match->claimed_id === null) {
+        }
+
+        if ($to_match->claimed_id === null) {
             // This is a response without identifiers, so there's
             // really no checking that we can do, so return an
             // endpoint that's for the specified `openid.op_endpoint'
             return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL(
                                                 $to_match->server_url);
-        } else if (!$endpoint) {
+        }
+
+        if (!$endpoint) {
             // The claimed ID doesn't match, so we have to do
             // discovery again. This covers not using sessions, OP
             // identifier endpoints and responses that didn't match
             // the original request.
             // oidutil.log('No pre-discovered information supplied.')
-            return $this->_discoverAndVerify($to_match);
-        } else if ($to_match->claimed_id != $endpoint->claimed_id) {
-            // oidutil.log('Mismatched pre-discovered session data. '
-            //             'Claimed ID in session=%s, in assertion=%s' %
-            //             (endpoint.claimed_id, to_match.claimed_id))
-            return $this->_discoverAndVerify($to_match);
+            return $this->_discoverAndVerify($to_match->claimed_id,
+                                             array($to_match));
         } else {
+
             // The claimed ID matches, so we use the endpoint that we
             // discovered in initiation. This should be the most
             // common case.
             $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
 
             if (Auth_OpenID::isFailure($result)) {
-                return $result;
+                $endpoint = $this->_discoverAndVerify($to_match->claimed_id,
+                                                      array($to_match));
+                if (Auth_OpenID::isFailure($endpoint)) {
+                    return $endpoint;
+                }
             }
+        }
 
-            return $endpoint;
+        // The endpoint we return should have the claimed ID from the
+        // message we just verified, fragment and all.
+        if ($endpoint->claimed_id != $to_match->claimed_id) {
+            $endpoint->claimed_id = $to_match->claimed_id;
         }
 
-        // Never reached.
+        return $endpoint;
     }
 
     /**
      * @access private
      */
-    function _discoverAndVerify($to_match)
+    function _discoverAndVerify($claimed_id, $to_match_endpoints)
     {
-        // oidutil.log('Performing discovery on %s' % (to_match.claimed_id,))
+        // oidutil.log('Performing discovery on %s' % (claimed_id,))
         list($unused, $services) = call_user_func($this->discoverMethod,
-                                                  $to_match->claimed_id,
+                                                  $claimed_id,
                                                   $this->fetcher);
+
         if (!$services) {
             return new Auth_OpenID_FailureResponse(null,
               sprintf("No OpenID information found at %s",
-                      $to_match->claimed_id));
+                      $claimed_id));
         }
 
+        return $this->_verifyDiscoveryServices($claimed_id, $services,
+                                               $to_match_endpoints);
+    }
+
+    /**
+     * @access private
+     */
+    function _verifyDiscoveryServices($claimed_id, 
+                                      &$services, &$to_match_endpoints)
+    {
         // Search the services resulting from discovery to find one
         // that matches the information from the assertion
-        $failure_messages = array();
 
         foreach ($services as $endpoint) {
-            $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
-
-            if (Auth_OpenID::isFailure($result)) {
-                $failure_messages[] = $result;
-            } else {
-                // It matches, so discover verification has
-                // succeeded. Return this endpoint.
-                return $endpoint;
+            foreach ($to_match_endpoints as $to_match_endpoint) {
+                $result = $this->_verifyDiscoverySingle($endpoint, 
+                                                        $to_match_endpoint);
+
+                if (!Auth_OpenID::isFailure($result)) {
+                    // It matches, so discover verification has
+                    // succeeded. Return this endpoint.
+                    return $endpoint;
+                }
             }
         }
 
         return new Auth_OpenID_FailureResponse(null,
           sprintf('No matching endpoint found after discovering %s',
-                  $to_match->claimed_id));
+                  $claimed_id));
     }
 
     /**
+     * Extract the nonce from an OpenID 1 response.  Return the nonce
+     * from the BARE_NS since we independently check the return_to
+     * arguments are the same as those in the response message.
+     *
+     * See the openid1_nonce_query_arg_name class variable
+     *
+     * @returns $nonce The nonce as a string or null
+     *
      * @access private
      */
     function _idResGetNonceOpenID1($message, $endpoint)
     {
-        $return_to = $message->getArg(Auth_OpenID_OPENID1_NS,
-                                      'return_to');
-        if ($return_to === null) {
-            return null;
-        }
-
-        $parsed_url = parse_url($return_to);
-
-        if (!array_key_exists('query', $parsed_url)) {
-            return null;
-        }
-
-        $query = $parsed_url['query'];
-        $pairs = Auth_OpenID::parse_str($query);
-
-        if ($pairs === null) {
-            return null;
-        }
-
-        foreach ($pairs as $k => $v) {
-            if ($k == $this->openid1_nonce_query_arg_name) {
-                return $v;
-            }
-        }
-
-        return null;
+        return $message->getArg(Auth_OpenID_BARE_NS,
+                                $this->openid1_nonce_query_arg_name);
     }
 
     /**
@@ -1160,9 +1278,9 @@ class Auth_OpenID_GenericConsumer {
     /**
      * @access private
      */
-    function _idResCheckForFields($message, $signed_list)
+    function _idResCheckForFields($message)
     {
-        $basic_fields = array('return_to', 'assoc_handle', 'sig');
+        $basic_fields = array('return_to', 'assoc_handle', 'sig', 'signed');
         $basic_sig_fields = array('return_to', 'identity');
 
         $require_fields = array(
@@ -1177,7 +1295,8 @@ class Auth_OpenID_GenericConsumer {
             Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
                                                   array('response_nonce',
                                                         'claimed_id',
-                                                        'assoc_handle')),
+                                                        'assoc_handle',
+                                                        'op_endpoint')),
             Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
                                                   array('nonce'))
             );
@@ -1189,6 +1308,14 @@ class Auth_OpenID_GenericConsumer {
             }
         }
 
+        $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS,
+                                            'signed',
+                                            Auth_OpenID_NO_DEFAULT);
+        if (Auth_OpenID::isFailure($signed_list_str)) {
+            return $signed_list_str;
+        }
+        $signed_list = explode(',', $signed_list_str);
+
         foreach ($require_sigs[$message->getOpenIDNamespace()] as $field) {
             // Field is present and not in signed list
             if ($message->hasKey(Auth_OpenID_OPENID_NS, $field) &&
@@ -1226,44 +1353,18 @@ class Auth_OpenID_GenericConsumer {
     function _createCheckAuthRequest($message)
     {
         $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
-        if ($signed === null) {
-            return null;
-        }
-
-        $whitelist = array('assoc_handle', 'sig',
-                           'signed', 'invalidate_handle');
-
-        $check_args = array();
-
-        foreach ($whitelist as $k) {
-            $val = $message->getArg(Auth_OpenID_OPENID_NS, $k);
-            if ($val !== null) {
-                $check_args[$k] = $val;
-            }
-        }
-
-        $signed = $message->getArg(Auth_OpenID_OPENID_NS,
-                                   'signed');
-
         if ($signed) {
             foreach (explode(',', $signed) as $k) {
-                if ($k == 'ns') {
-                    $check_args['ns'] = $message->getOpenIDNamespace();
-                    continue;
-                }
-
-                if (!$message->hasKey(Auth_OpenID_OPENID_NS,
-                                      $k)) {
+                $value = $message->getAliasedArg($k);
+                if ($value === null) {
                     return null;
                 }
-
-                $val = $message->getAliasedArg($k);
-                $check_args[$k] = $val;
             }
         }
-
-        $check_args['mode'] = 'check_authentication';
-        return Auth_OpenID_Message::fromOpenIDArgs($check_args);
+        $ca_message = $message->copy();
+        $ca_message->setArg(Auth_OpenID_OPENID_NS, 'mode', 
+                            'check_authentication');
+        return $ca_message;
     }
 
     /**
@@ -1289,6 +1390,28 @@ class Auth_OpenID_GenericConsumer {
         return false;
     }
 
+    /**
+     * Adapt a POST response to a Message.
+     *
+     * @param $response Result of a POST to an OpenID endpoint.
+     *
+     * @access private
+     */
+    function _httpResponseToMessage($response, $server_url)
+    {
+        // Should this function be named Message.fromHTTPResponse instead?
+        $response_message = Auth_OpenID_Message::fromKVForm($response->body);
+
+        if ($response->status == 400) {
+            return Auth_OpenID_ServerErrorContainer::fromMessage(
+                        $response_message);
+        } else if ($response->status != 200 and $response->status != 206) {
+            return null;
+        }
+
+        return $response_message;
+    }
+
     /**
      * @access private
      */
@@ -1298,19 +1421,10 @@ class Auth_OpenID_GenericConsumer {
         $resp = $this->fetcher->post($server_url, $body);
 
         if ($resp === null) {
-            return Auth_OpenID_ServerErrorContainer::fromMessage('');
-        }
-
-        $response_message = Auth_OpenID_Message::fromKVForm($resp->body);
-
-        if ($resp->status == 400) {
-            return Auth_OpenID_ServerErrorContainer::fromMessage(
-                                                     $response_message);
-        } else if ($resp->status != 200) {
             return null;
         }
 
-        return $response_message;
+        return $this->_httpResponseToMessage($resp, $server_url);
     }
 
     /**
@@ -1338,6 +1452,47 @@ class Auth_OpenID_GenericConsumer {
         return $assoc;
     }
 
+    /**
+     * Handle ServerErrors resulting from association requests.
+     *
+     * @return $result If server replied with an C{unsupported-type}
+     * error, return a tuple of supported C{association_type},
+     * C{session_type}.  Otherwise logs the error and returns null.
+     *
+     * @access private
+     */
+    function _extractSupportedAssociationType(&$server_error, &$endpoint,
+                                              $assoc_type)
+    {
+        // Any error message whose code is not 'unsupported-type'
+        // should be considered a total failure.
+        if (($server_error->error_code != 'unsupported-type') ||
+            ($server_error->message->isOpenID1())) {
+            return null;
+        }
+
+        // The server didn't like the association/session type that we
+        // sent, and it sent us back a message that might tell us how
+        // to handle it.
+
+        // Extract the session_type and assoc_type from the error
+        // message
+        $assoc_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
+                                                     'assoc_type');
+
+        $session_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
+                                                       'session_type');
+
+        if (($assoc_type === null) || ($session_type === null)) {
+            return null;
+        } else if (!$this->negotiator->isAllowed($assoc_type,
+                                                 $session_type)) {
+            return null;
+        } else {
+          return array($assoc_type, $session_type);
+        }
+    }
+
     /**
      * @access private
      */
@@ -1356,42 +1511,12 @@ class Auth_OpenID_GenericConsumer {
         if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
             $why = $assoc;
 
-            // Any error message whose code is not 'unsupported-type'
-            // should be considered a total failure.
-            if (($why->error_code != 'unsupported-type') ||
-                ($why->message->isOpenID1())) {
-                // oidutil.log(
-                //    'Server error when requesting an association from %r: %s'
-                //     % (endpoint.server_url, why.error_text))
-                return null;
-            }
+            $supportedTypes = $this->_extractSupportedAssociationType(
+                                     $why, $endpoint, $assoc_type);
 
-            // The server didn't like the association/session type
-            // that we sent, and it sent us back a message that
-            // might tell us how to handle it.
-            // oidutil.log(
-            //     'Unsupported association type %s: %s' % (assoc_type,
-            //                                              why.error_text,))
+            if ($supportedTypes !== null) {
+                list($assoc_type, $session_type) = $supportedTypes;
 
-            // Extract the session_type and assoc_type from the
-            // error message
-            $assoc_type = $why->message->getArg(Auth_OpenID_OPENID_NS,
-                                                'assoc_type');
-
-            $session_type = $why->message->getArg(Auth_OpenID_OPENID_NS,
-                                                  'session_type');
-
-            if (($assoc_type === null) || ($session_type === null)) {
-                // oidutil.log('Server responded with unsupported association '
-                //             'session but did not supply a fallback.')
-                return null;
-            } else if (!$this->negotiator->isAllowed($assoc_type,
-                                                     $session_type)) {
-                // fmt = ('Server sent unsupported session/association type: '
-                //        'session_type=%s, assoc_type=%s')
-                // oidutil.log(fmt % (session_type, assoc_type))
-                return null;
-            } else {
                 // Attempt to create an association from the assoc_type
                 // and session_type that the server told us it
                 // supported.
@@ -1409,10 +1534,12 @@ class Auth_OpenID_GenericConsumer {
                 } else {
                     return $assoc;
                 }
+            } else {
+                return null;
             }
+        } else {
+            return $assoc;
         }
-
-        return $assoc;
     }
 
     /**
@@ -1447,18 +1574,16 @@ class Auth_OpenID_GenericConsumer {
                          Auth_OpenID_OPENID_NS, 'assoc_type',
                          Auth_OpenID_NO_DEFAULT);
 
-        if ($assoc_type === null) {
-            return new Auth_OpenID_FailureResponse(null,
-              'assoc_type missing from association response');
+        if (Auth_OpenID::isFailure($assoc_type)) {
+            return $assoc_type;
         }
 
         $assoc_handle = $assoc_response->getArg(
                            Auth_OpenID_OPENID_NS, 'assoc_handle',
                            Auth_OpenID_NO_DEFAULT);
 
-        if ($assoc_handle === null) {
-            return new Auth_OpenID_FailureResponse(null,
-              'assoc_handle missing from association response');
+        if (Auth_OpenID::isFailure($assoc_handle)) {
+            return $assoc_handle;
         }
 
         // expires_in is a base-10 string. The Python parsing will
@@ -1469,14 +1594,16 @@ class Auth_OpenID_GenericConsumer {
                              Auth_OpenID_OPENID_NS, 'expires_in',
                              Auth_OpenID_NO_DEFAULT);
 
-        if ($expires_in_str === null) {
-            return new Auth_OpenID_FailureResponse(null,
-              'expires_in missing from association response');
+        if (Auth_OpenID::isFailure($expires_in_str)) {
+            return $expires_in_str;
         }
 
         $expires_in = Auth_OpenID::intval($expires_in_str);
         if ($expires_in === false) {
-            return null;
+            
+            $err = sprintf("Could not parse expires_in from association ".
+                           "response %s", print_r($assoc_response, true));
+            return new Auth_OpenID_FailureResponse(null, $err);
         }
 
         // OpenID 1 has funny association session behaviour.
@@ -1487,9 +1614,8 @@ class Auth_OpenID_GenericConsumer {
                                Auth_OpenID_OPENID2_NS, 'session_type',
                                Auth_OpenID_NO_DEFAULT);
 
-            if ($session_type === null) {
-                return new Auth_OpenID_FailureResponse(null,
-                  'session_type missing from association response');
+            if (Auth_OpenID::isFailure($session_type)) {
+                return $session_type;
             }
         }
 
@@ -1627,8 +1753,7 @@ class Auth_OpenID_AuthRequest {
         $this->assoc = $assoc;
         $this->endpoint =& $endpoint;
         $this->return_to_args = array();
-        $this->message = new Auth_OpenID_Message();
-        $this->message->setOpenIDNamespace(
+        $this->message = new Auth_OpenID_Message(
             $endpoint->preferredNamespace());
         $this->_anonymous = false;
     }
@@ -1665,7 +1790,7 @@ class Auth_OpenID_AuthRequest {
      */
     function addExtensionArg($namespace, $key, $value)
     {
-        $this->message->setArg($namespace, $key, $value);
+        return $this->message->setArg($namespace, $key, $value);
     }
 
     /**
@@ -1813,6 +1938,24 @@ class Auth_OpenID_AuthRequest {
                                       $form_tag_attrs);
     }
 
+    /**
+     * Get a complete html document that will autosubmit the request
+     * to the IDP.
+     *
+     * Wraps formMarkup.  See the documentation for that function.
+     */
+    function htmlMarkup($realm, $return_to=null, $immediate=false,
+                        $form_tag_attrs=null)
+    {
+        $form = $this->formMarkup($realm, $return_to, $immediate, 
+                                  $form_tag_attrs);
+
+        if (Auth_OpenID::isFailure($form)) {
+            return $form;
+        }
+        return Auth_OpenID::autoSubmitHTML($form);
+    }
+
     function shouldSendRedirect()
     {
         return $this->endpoint->compatibilityMode();
@@ -1836,6 +1979,31 @@ class Auth_OpenID_ConsumerResponse {
             $this->identity_url = $endpoint->claimed_id;
         }
     }
+
+    /**
+     * Return the display identifier for this response.
+     *
+     * The display identifier is related to the Claimed Identifier, but the
+     * two are not always identical.  The display identifier is something the
+     * user should recognize as what they entered, whereas the response's
+     * claimed identifier (in the identity_url attribute) may have extra
+     * information for better persistence.
+     *
+     * URLs will be stripped of their fragments for display.  XRIs will
+     * display the human-readable identifier (i-name) instead of the
+     * persistent identifier (i-number).
+     *
+     * Use the display identifier in your user interface.  Use
+     * identity_url for querying your database or authorization server.
+     *
+     */
+    function getDisplayIdentifier()
+    {
+        if ($this->endpoint !== null) {
+            return $this->endpoint->getDisplayIdentifier();
+        }
+        return null;
+    }
 }
 
 /**
@@ -1915,6 +2083,9 @@ class Auth_OpenID_SuccessResponse extends Auth_OpenID_ConsumerResponse {
         $args = array();
 
         $msg_args = $this->message->getArgs($ns_uri);
+        if (Auth_OpenID::isFailure($msg_args)) {
+            return null;
+        }
 
         foreach ($msg_args as $key => $value) {
             if (!$this->isSigned($ns_uri, $key)) {
diff --git a/lib/Auth/OpenID/CryptUtil.php b/lib/Auth/OpenID/CryptUtil.php
index 8d7e06983442ff383eedaff04a6d5c63ea050ebd..aacc3cd3974857b0bce24e468e702d600dd71ab4 100644
--- a/lib/Auth/OpenID/CryptUtil.php
+++ b/lib/Auth/OpenID/CryptUtil.php
@@ -11,8 +11,8 @@
  * @access private
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 if (!defined('Auth_OpenID_RAND_SOURCE')) {
diff --git a/lib/Auth/OpenID/DatabaseConnection.php b/lib/Auth/OpenID/DatabaseConnection.php
index 3f4515fa59bce89b3f5442d378dd85c501540001..9db6e0eb3f3c3cd0a771d97e108b04e19bb40341 100644
--- a/lib/Auth/OpenID/DatabaseConnection.php
+++ b/lib/Auth/OpenID/DatabaseConnection.php
@@ -6,8 +6,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
diff --git a/lib/Auth/OpenID/DiffieHellman.php b/lib/Auth/OpenID/DiffieHellman.php
index 9b999096aa38034faebc6618aa3976ea0533fd49..f4ded7eba57191bc665d78844a1f206e10cf2f67 100644
--- a/lib/Auth/OpenID/DiffieHellman.php
+++ b/lib/Auth/OpenID/DiffieHellman.php
@@ -10,13 +10,12 @@
  * @access private
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 require_once 'Auth/OpenID.php';
 require_once 'Auth/OpenID/BigMath.php';
-require_once 'Auth/OpenID/HMACSHA1.php';
 
 function Auth_OpenID_getDefaultMod()
 {
@@ -90,27 +89,6 @@ class Auth_OpenID_DiffieHellman {
         return $this->public;
     }
 
-    /**
-     * Generate the arguments for an OpenID Diffie-Hellman association
-     * request
-     */
-    function getAssocArgs()
-    {
-        $cpub = $this->lib->longToBase64($this->getPublicKey());
-        $args = array(
-                      'openid.dh_consumer_public' => $cpub,
-                      'openid.session_type' => 'DH-SHA1'
-                      );
-
-        if ($this->lib->cmp($this->mod, Auth_OpenID_getDefaultMod()) ||
-            $this->lib->cmp($this->gen, Auth_OpenID_getDefaultGen())) {
-            $args['openid.dh_modulus'] = $this->lib->longToBase64($this->mod);
-            $args['openid.dh_gen'] = $this->lib->longToBase64($this->gen);
-        }
-
-        return $args;
-    }
-
     function usingDefaultValues()
     {
         return ($this->mod == Auth_OpenID_getDefaultMod() &&
@@ -131,3 +109,5 @@ class Auth_OpenID_DiffieHellman {
         return $xsecret;
     }
 }
+
+?>
diff --git a/lib/Auth/OpenID/Discover.php b/lib/Auth/OpenID/Discover.php
index bf20b90f7b403e6068a4d21ac8f8ecbc3d77e665..62aeb1d2bc550db88247e1c22502cf912781737e 100644
--- a/lib/Auth/OpenID/Discover.php
+++ b/lib/Auth/OpenID/Discover.php
@@ -19,6 +19,8 @@ define('Auth_OpenID_TYPE_1_1', 'http://openid.net/signon/1.1');
 define('Auth_OpenID_TYPE_1_0', 'http://openid.net/signon/1.0');
 define('Auth_OpenID_TYPE_2_0_IDP', 'http://specs.openid.net/auth/2.0/server');
 define('Auth_OpenID_TYPE_2_0', 'http://specs.openid.net/auth/2.0/signon');
+define('Auth_OpenID_RP_RETURN_TO_URL_TYPE',
+       'http://specs.openid.net/auth/2.0/return_to');
 
 function Auth_OpenID_getOpenIDTypeURIs()
 {
@@ -26,7 +28,8 @@ function Auth_OpenID_getOpenIDTypeURIs()
                  Auth_OpenID_TYPE_2_0,
                  Auth_OpenID_TYPE_1_2,
                  Auth_OpenID_TYPE_1_1,
-                 Auth_OpenID_TYPE_1_0);
+                 Auth_OpenID_TYPE_1_0,
+                 Auth_OpenID_RP_RETURN_TO_URL_TYPE);
 }
 
 /**
@@ -41,6 +44,28 @@ class Auth_OpenID_ServiceEndpoint {
         $this->local_id = null;
         $this->canonicalID = null;
         $this->used_yadis = false; // whether this came from an XRDS
+        $this->display_identifier = null;
+    }
+
+    function getDisplayIdentifier()
+    {
+        if ($this->display_identifier) {
+            return $this->display_identifier;
+        }
+        if (! $this->claimed_id) {
+          return $this->claimed_id;
+        }
+        $parsed = parse_url($this->claimed_id);
+        $scheme = $parsed['scheme'];
+        $host = $parsed['host'];
+        $path = $parsed['path'];
+        if (array_key_exists('query', $parsed)) {
+            $query = $parsed['query'];
+            $no_frag = "$scheme://$host$path?$query";
+        } else {
+            $no_frag = "$scheme://$host$path";
+        }
+        return $no_frag;
     }
 
     function usesExtension($extension_uri)
@@ -58,6 +83,29 @@ class Auth_OpenID_ServiceEndpoint {
         }
     }
 
+    /*
+     * Query this endpoint to see if it has any of the given type
+     * URIs. This is useful for implementing other endpoint classes
+     * that e.g. need to check for the presence of multiple versions
+     * of a single protocol.
+     *
+     * @param $type_uris The URIs that you wish to check
+     *
+     * @return all types that are in both in type_uris and
+     * $this->type_uris
+     */
+    function matchTypes($type_uris)
+    {
+        $result = array();
+        foreach ($type_uris as $test_uri) {
+            if ($this->supportsType($test_uri)) {
+                $result[] = $test_uri;
+            }
+        }
+
+        return $result;
+    }
+
     function supportsType($type_uri)
     {
         // Does this endpoint support this type?
@@ -123,6 +171,45 @@ class Auth_OpenID_ServiceEndpoint {
         }
     }
 
+    /*
+     * Parse the given document as XRDS looking for OpenID services.
+     *
+     * @return array of Auth_OpenID_ServiceEndpoint or null if the
+     * document cannot be parsed.
+     */
+    function fromXRDS($uri, $xrds_text)
+    {
+        $xrds =& Auth_Yadis_XRDS::parseXRDS($xrds_text);
+
+        if ($xrds) {
+            $yadis_services =
+              $xrds->services(array('filter_MatchesAnyOpenIDType'));
+            return Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services);
+        }
+
+        return null;
+    }
+
+    /*
+     * Create endpoints from a DiscoveryResult.
+     *
+     * @param discoveryResult Auth_Yadis_DiscoveryResult
+     * @return array of Auth_OpenID_ServiceEndpoint or null if
+     * endpoints cannot be created.
+     */
+    function fromDiscoveryResult($discoveryResult)
+    {
+        if ($discoveryResult->isXRDS()) {
+            return Auth_OpenID_ServiceEndpoint::fromXRDS(
+                                     $discoveryResult->normalized_uri,
+                                     $discoveryResult->response_text);
+        } else {
+            return Auth_OpenID_ServiceEndpoint::fromHTML(
+                                     $discoveryResult->normalized_uri,
+                                     $discoveryResult->response_text);
+        }
+    }
+
     function fromHTML($uri, $html)
     {
         $discovery_types = array(
@@ -328,7 +415,9 @@ function Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services)
     return $s;
 }
 
-function Auth_OpenID_discoverWithYadis($uri, &$fetcher)
+function Auth_OpenID_discoverWithYadis($uri, &$fetcher,
+              $endpoint_filter='Auth_OpenID_getOPOrUserServices',
+              $discover_function=null)
 {
     // Discover OpenID services for a URI. Tries Yadis and falls back
     // on old-style <link rel='...'> discovery if Yadis fails.
@@ -337,8 +426,15 @@ function Auth_OpenID_discoverWithYadis($uri, &$fetcher)
     // came back for that URI at all.  I don't think falling back to
     // OpenID 1.0 discovery on the same URL will help, so don't bother
     // to catch it.
+    if ($discover_function === null) {
+        $discover_function = array('Auth_Yadis_Yadis', 'discover');
+    }
+
     $openid_services = array();
-    $response = Auth_Yadis_Yadis::discover($uri, $fetcher);
+
+    $response = call_user_func_array($discover_function,
+                                     array($uri, &$fetcher));
+
     $yadis_url = $response->normalized_uri;
     $yadis_services = array();
 
@@ -346,14 +442,11 @@ function Auth_OpenID_discoverWithYadis($uri, &$fetcher)
         return array($uri, array());
     }
 
-    $xrds =& Auth_Yadis_XRDS::parseXRDS($response->response_text);
-
-    if ($xrds) {
-        $yadis_services =
-            $xrds->services(array('filter_MatchesAnyOpenIDType'));
-    }
+    $openid_services = Auth_OpenID_ServiceEndpoint::fromXRDS(
+                                         $yadis_url,
+                                         $response->response_text);
 
-    if (!$yadis_services) {
+    if (!$openid_services) {
         if ($response->isXRDS()) {
             return Auth_OpenID_discoverWithoutYadis($uri,
                                                     $fetcher);
@@ -364,39 +457,25 @@ function Auth_OpenID_discoverWithYadis($uri, &$fetcher)
         $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
                                         $yadis_url,
                                         $response->response_text);
-    } else {
-        $openid_services = Auth_OpenID_makeOpenIDEndpoints($yadis_url,
-                                                           $yadis_services);
     }
 
-    $openid_services = Auth_OpenID_getOPOrUserServices($openid_services);
+    $openid_services = call_user_func_array($endpoint_filter,
+                                            array(&$openid_services));
+
     return array($yadis_url, $openid_services);
 }
 
 function Auth_OpenID_discoverURI($uri, &$fetcher)
 {
-    $parsed = parse_url($uri);
-
-    if ($parsed && isset($parsed['scheme']) &&
-        isset($parsed['host'])) {
-        if (!in_array($parsed['scheme'], array('http', 'https'))) {
-            // raise DiscoveryFailure('URI scheme is not HTTP or HTTPS', None)
-            return array($uri, array());
-        }
-    } else {
-        $uri = 'http://' . $uri;
-    }
-
     $uri = Auth_OpenID::normalizeUrl($uri);
-    return Auth_OpenID_discoverWithYadis($uri,
-                                         $fetcher);
+    return Auth_OpenID_discoverWithYadis($uri, $fetcher);
 }
 
 function Auth_OpenID_discoverWithoutYadis($uri, &$fetcher)
 {
     $http_resp = @$fetcher->get($uri);
 
-    if ($http_resp->status != 200) {
+    if ($http_resp->status != 200 and $http_resp->status != 206) {
         return array($uri, array());
     }
 
@@ -427,6 +506,7 @@ function Auth_OpenID_discoverXRI($iname, &$fetcher)
     for ($i = 0; $i < count($openid_services); $i++) {
         $openid_services[$i]->canonicalID = $canonicalID;
         $openid_services[$i]->claimed_id = $canonicalID;
+        $openid_services[$i]->display_identifier = $iname;
     }
 
     // FIXME: returned xri should probably be in some normal form
@@ -465,4 +545,4 @@ function Auth_OpenID_discover($uri, &$fetcher)
     return $result;
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/lib/Auth/OpenID/DumbStore.php b/lib/Auth/OpenID/DumbStore.php
index ef1a37f828b0d0a12a04484ba664560a312dc46f..22fd2d36610bca79a0feb1d6d3dd6944aaf23c0b 100644
--- a/lib/Auth/OpenID/DumbStore.php
+++ b/lib/Auth/OpenID/DumbStore.php
@@ -10,15 +10,15 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
  * Import the interface for creating a new store class.
  */
 require_once 'Auth/OpenID/Interface.php';
-require_once 'Auth/OpenID/HMACSHA1.php';
+require_once 'Auth/OpenID/HMAC.php';
 
 /**
  * This is a store for use in the worst case, when you have no way of
diff --git a/lib/Auth/OpenID/Extension.php b/lib/Auth/OpenID/Extension.php
index 73b5d502eacba77d97bb136bb2e00c2d4a7c5119..f362a4b389e51e572bd225daba7e860aebe5050a 100644
--- a/lib/Auth/OpenID/Extension.php
+++ b/lib/Auth/OpenID/Extension.php
@@ -41,8 +41,12 @@ class Auth_OpenID_Extension {
      */
     function toMessage(&$message)
     {
-        if ($message->namespaces->addAlias($this->ns_uri,
-                                           $this->ns_alias) === null) {
+        $implicit = $message->isOpenID1();
+        $added = $message->namespaces->addAlias($this->ns_uri,
+                                                $this->ns_alias,
+                                                $implicit);
+
+        if ($added === null) {
             if ($message->namespaces->getAlias($this->ns_uri) !=
                 $this->ns_alias) {
                 return null;
diff --git a/lib/Auth/OpenID/FileStore.php b/lib/Auth/OpenID/FileStore.php
index dba0e4d5093a558423e2e3142649148ae864a370..29d8d20e76bec41b14d33f383a468013174f7f2d 100644
--- a/lib/Auth/OpenID/FileStore.php
+++ b/lib/Auth/OpenID/FileStore.php
@@ -10,8 +10,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -19,7 +19,8 @@
  */
 require_once 'Auth/OpenID.php';
 require_once 'Auth/OpenID/Interface.php';
-require_once 'Auth/OpenID/HMACSHA1.php';
+require_once 'Auth/OpenID/HMAC.php';
+require_once 'Auth/OpenID/Nonce.php';
 
 /**
  * This is a filesystem-based store for OpenID associations and
@@ -115,6 +116,28 @@ class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
         }
     }
 
+    function cleanupNonces()
+    {
+        global $Auth_OpenID_SKEW;
+
+        $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir);
+        $now = time();
+
+        $removed = 0;
+        // Check all nonces for expiry
+        foreach ($nonces as $nonce_fname) {
+            $base = basename($nonce_fname);
+            $parts = explode('-', $base, 2);
+            $timestamp = $parts[0];
+            $timestamp = intval($timestamp, 16);
+            if (abs($timestamp - $now) > $Auth_OpenID_SKEW) {
+                Auth_OpenID_FileStore::_removeIfPresent($nonce_fname);
+                $removed += 1;
+            }
+        }
+        return $removed;
+    }
+
     /**
      * Create a unique filename for a given server url and
      * handle. This implementation does not assume anything about the
@@ -231,16 +254,15 @@ class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
             // strip off the path to do the comparison
             $name = basename($filename);
             foreach ($association_files as $association_file) {
-                if (strpos($association_file, $name) === 0) {
+                $base = basename($association_file);
+                if (strpos($base, $name) === 0) {
                     $matching_files[] = $association_file;
                 }
             }
 
             $matching_associations = array();
             // read the matching files and sort by time issued
-            foreach ($matching_files as $name) {
-                $full_name = $this->association_dir . DIRECTORY_SEPARATOR .
-                    $name;
+            foreach ($matching_files as $full_name) {
                 $association = $this->_getAssociation($full_name);
                 if ($association !== null) {
                     $matching_associations[] = array($association->issued,
@@ -337,11 +359,17 @@ class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
      */
     function useNonce($server_url, $timestamp, $salt)
     {
+        global $Auth_OpenID_SKEW;
+
         if (!$this->active) {
             trigger_error("FileStore no longer active", E_USER_ERROR);
             return null;
         }
 
+        if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
+            return False;
+        }
+
         if ($server_url) {
             list($proto, $rest) = explode('://', $server_url, 2);
         } else {
@@ -435,18 +463,6 @@ class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
         }
     }
 
-    function getExpired()
-    {
-        $urls = array();
-        foreach ($this->_allAssocs() as $pair) {
-            list($_, $assoc) = $pair;
-            if ($assoc->getExpiresIn() <= 0) {
-                $urls[] = $assoc->server_url;
-            }
-        }
-        return $urls;
-    }
-
     /**
      * @access private
      */
@@ -526,7 +542,7 @@ class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
         $files = array();
         while (false !== ($filename = readdir($handle))) {
             if (!in_array($filename, array('.', '..'))) {
-                $files[] = $filename;
+                $files[] = $dir . DIRECTORY_SEPARATOR . $filename;
             }
         }
         return $files;
@@ -584,6 +600,19 @@ class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
     {
         return @unlink($filename);
     }
+
+    function cleanupAssociations()
+    {
+        $removed = 0;
+        foreach ($this->_allAssocs() as $pair) {
+            list($assoc_filename, $assoc) = $pair;
+            if ($assoc->getExpiresIn() == 0) {
+                $this->_removeIfPresent($assoc_filename);
+                $removed += 1;
+            }
+        }
+        return $removed;
+    }
 }
 
 ?>
diff --git a/lib/Auth/OpenID/HMACSHA1.php b/lib/Auth/OpenID/HMAC.php
similarity index 94%
rename from lib/Auth/OpenID/HMACSHA1.php
rename to lib/Auth/OpenID/HMAC.php
index 9fc293e7fb7d4b5777ca1b6e2a817c2d791b85c0..ec42db8dfc6f0aa60ed2f23b1d46d8dac9948358 100644
--- a/lib/Auth/OpenID/HMACSHA1.php
+++ b/lib/Auth/OpenID/HMAC.php
@@ -10,8 +10,8 @@
  * @access private
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 require_once 'Auth/OpenID.php';
@@ -88,7 +88,7 @@ if (function_exists('hash_hmac') &&
     function Auth_OpenID_HMACSHA256($key, $text)
     {
         // Return raw MAC (not hex string).
-        return hash_hmac('sha256', $key, $text, true);
+        return hash_hmac('sha256', $text, $key, true);
     }
 
     define('Auth_OpenID_HMACSHA256_SUPPORTED', true);
diff --git a/lib/Auth/OpenID/Interface.php b/lib/Auth/OpenID/Interface.php
index 9e7d496ec75ac80689ed5ed2d4468d65c51995f5..f4c6062f8c7fcfc12b87242b9e9f03f29991d430 100644
--- a/lib/Auth/OpenID/Interface.php
+++ b/lib/Auth/OpenID/Interface.php
@@ -9,8 +9,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -20,9 +20,9 @@
  * consumers.  If you want to create an SQL-driven store, please see
  * then {@link Auth_OpenID_SQLStore} class.
  *
- * Change: Version 2.0 removed the storeNonce and getAuthKey methods,
- * and changed the behavior of the useNonce method to support one-way
- * nonces.
+ * Change: Version 2.0 removed the storeNonce, getAuthKey, and isDumb
+ * methods, and changed the behavior of the useNonce method to support
+ * one-way nonces.
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
@@ -47,6 +47,60 @@ class Auth_OpenID_OpenIDStore {
                       "not implemented", E_USER_ERROR);
     }
 
+    /*
+     * Remove expired nonces from the store.
+     *
+     * Discards any nonce from storage that is old enough that its
+     * timestamp would not pass useNonce().
+     *
+     * This method is not called in the normal operation of the
+     * library.  It provides a way for store admins to keep their
+     * storage from filling up with expired data.
+     *
+     * @return the number of nonces expired
+     */
+    function cleanupNonces()
+    {
+        trigger_error("Auth_OpenID_OpenIDStore::cleanupNonces ".
+                      "not implemented", E_USER_ERROR);
+    }
+
+    /*
+     * Remove expired associations from the store.
+     *
+     * This method is not called in the normal operation of the
+     * library.  It provides a way for store admins to keep their
+     * storage from filling up with expired data.
+     *
+     * @return the number of associations expired.
+     */
+    function cleanupAssociations()
+    {
+        trigger_error("Auth_OpenID_OpenIDStore::cleanupAssociations ".
+                      "not implemented", E_USER_ERROR);
+    }
+
+    /*
+     * Shortcut for cleanupNonces(), cleanupAssociations().
+     *
+     * This method is not called in the normal operation of the
+     * library.  It provides a way for store admins to keep their
+     * storage from filling up with expired data.
+     */
+    function cleanup()
+    {
+        return array($this->cleanupNonces(),
+                     $this->cleanupAssociations());
+    }
+
+    /**
+     * Report whether this storage supports cleanup
+     */
+    function supportsCleanup()
+    {
+        return true;
+    }
+
     /**
      * This method returns an Association object from storage that
      * matches the server URL and, if specified, handle. It returns
@@ -56,7 +110,7 @@ class Auth_OpenID_OpenIDStore {
      * If no handle is specified, the store may return any association
      * which matches the server URL. If multiple associations are
      * valid, the recommended return value for this method is the one
-     * that will remain valid for the longest duration.
+     * most recently issued.
      *
      * This method is allowed (and encouraged) to garbage collect
      * expired associations when found. This method must not return
@@ -132,15 +186,6 @@ class Auth_OpenID_OpenIDStore {
                       "not implemented", E_USER_ERROR);
     }
 
-    /**
-     * Return all server URLs that have expired associations.
-     */
-    function getExpired()
-    {
-        trigger_error("Auth_OpenID_OpenIDStore::getExpired ".
-                      "not implemented", E_USER_ERROR);
-    }
-
     /**
      * Removes all entries from the store; implementation is optional.
      */
diff --git a/lib/Auth/OpenID/KVForm.php b/lib/Auth/OpenID/KVForm.php
index 6075c44f00518a434cca4489a082f52e2c54f2d8..fb342a00136606534306ae62dba9f02828ab3aa1 100644
--- a/lib/Auth/OpenID/KVForm.php
+++ b/lib/Auth/OpenID/KVForm.php
@@ -11,8 +11,8 @@
  * @access private
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
diff --git a/lib/Auth/OpenID/MemcachedStore.php b/lib/Auth/OpenID/MemcachedStore.php
new file mode 100644
index 0000000000000000000000000000000000000000..d357c6b11d7bb7ca2d92e76367be1a61f99a5a4e
--- /dev/null
+++ b/lib/Auth/OpenID/MemcachedStore.php
@@ -0,0 +1,208 @@
+<?php
+
+/**
+ * This file supplies a memcached store backend for OpenID servers and
+ * consumers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author Artemy Tregubenko <me@arty.name>
+ * @copyright 2008 JanRain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ * Contributed by Open Web Technologies <http://openwebtech.ru/>
+ */
+
+/**
+ * Import the interface for creating a new store class.
+ */
+require_once 'Auth/OpenID/Interface.php';
+
+/**
+ * This is a memcached-based store for OpenID associations and
+ * nonces. 
+ * 
+ * As memcache has limit of 250 chars for key length, 
+ * server_url, handle and salt are hashed with sha1(). 
+ *
+ * Most of the methods of this class are implementation details.
+ * People wishing to just use this store need only pay attention to
+ * the constructor.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MemcachedStore extends Auth_OpenID_OpenIDStore {
+
+    /**
+     * Initializes a new {@link Auth_OpenID_MemcachedStore} instance.
+     * Just saves memcached object as property.
+     *
+     * @param resource connection Memcache connection resourse
+     */
+    function Auth_OpenID_MemcachedStore($connection, $compress = false)
+    {
+        $this->connection = $connection;
+        $this->compress = $compress ? MEMCACHE_COMPRESSED : 0;
+    }
+
+    /**
+     * Store association until its expiration time in memcached. 
+     * Overwrites any existing association with same server_url and 
+     * handle. Handles list of associations for every server. 
+     */
+    function storeAssociation($server_url, $association)
+    {
+        // create memcached keys for association itself 
+        // and list of associations for this server
+        $associationKey = $this->associationKey($server_url, 
+            $association->handle);
+        $serverKey = $this->associationServerKey($server_url);
+        
+        // get list of associations 
+        $serverAssociations = $this->connection->get($serverKey);
+        
+        // if no such list, initialize it with empty array
+        if (!$serverAssociations) {
+            $serverAssociations = array();
+        }
+        // and store given association key in it
+        $serverAssociations[$association->issued] = $associationKey;
+        
+        // save associations' keys list 
+        $this->connection->set(
+            $serverKey,
+            $serverAssociations,
+            $this->compress
+        );
+        // save association itself
+        $this->connection->set(
+            $associationKey,
+            $association, 
+            $this->compress, 
+            $association->issued + $association->lifetime);
+    }
+
+    /**
+     * Read association from memcached. If no handle given 
+     * and multiple associations found, returns latest issued
+     */
+    function getAssociation($server_url, $handle = null)
+    {
+        // simple case: handle given
+        if ($handle !== null) {
+            // get association, return null if failed
+            $association = $this->connection->get(
+                $this->associationKey($server_url, $handle));
+            return $association ? $association : null;
+        }
+        
+        // no handle given, working with list
+        // create key for list of associations
+        $serverKey = $this->associationServerKey($server_url);
+        
+        // get list of associations
+        $serverAssociations = $this->connection->get($serverKey);
+        // return null if failed or got empty list
+        if (!$serverAssociations) {
+            return null;
+        }
+        
+        // get key of most recently issued association
+        $keys = array_keys($serverAssociations);
+        sort($keys);
+        $lastKey = $serverAssociations[array_pop($keys)];
+        
+        // get association, return null if failed
+        $association = $this->connection->get($lastKey);
+        return $association ? $association : null;
+    }
+
+    /**
+     * Immediately delete association from memcache.
+     */
+    function removeAssociation($server_url, $handle)
+    {
+        // create memcached keys for association itself 
+        // and list of associations for this server
+        $serverKey = $this->associationServerKey($server_url);
+        $associationKey = $this->associationKey($server_url, 
+            $handle);
+        
+        // get list of associations
+        $serverAssociations = $this->connection->get($serverKey);
+        // return null if failed or got empty list
+        if (!$serverAssociations) {
+            return false;
+        }
+        
+        // ensure that given association key exists in list
+        $serverAssociations = array_flip($serverAssociations);
+        if (!array_key_exists($associationKey, $serverAssociations)) {
+            return false;
+        }
+        
+        // remove given association key from list
+        unset($serverAssociations[$associationKey]);
+        $serverAssociations = array_flip($serverAssociations);
+        
+        // save updated list
+        $this->connection->set(
+            $serverKey,
+            $serverAssociations,
+            $this->compress
+        );
+
+        // delete association 
+        return $this->connection->delete($associationKey);
+    }
+
+    /**
+     * Create nonce for server and salt, expiring after 
+     * $Auth_OpenID_SKEW seconds.
+     */
+    function useNonce($server_url, $timestamp, $salt)
+    {
+        global $Auth_OpenID_SKEW;
+        
+        // save one request to memcache when nonce obviously expired 
+        if (abs($timestamp - time()) > $Auth_OpenID_SKEW) {
+            return false;
+        }
+        
+        // returns false when nonce already exists
+        // otherwise adds nonce
+        return $this->connection->add(
+            'openid_nonce_' . sha1($server_url) . '_' . sha1($salt), 
+            1, // any value here 
+            $this->compress, 
+            $Auth_OpenID_SKEW);
+    }
+    
+    /**
+     * Memcache key is prefixed with 'openid_association_' string. 
+     */
+    function associationKey($server_url, $handle = null) 
+    {
+        return 'openid_association_' . sha1($server_url) . '_' . sha1($handle);
+    }
+    
+    /**
+     * Memcache key is prefixed with 'openid_association_' string. 
+     */
+    function associationServerKey($server_url) 
+    {
+        return 'openid_association_server_' . sha1($server_url);
+    }
+    
+    /**
+     * Report that this storage doesn't support cleanup
+     */
+    function supportsCleanup()
+    {
+        return false;
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/lib/Auth/OpenID/Message.php b/lib/Auth/OpenID/Message.php
index 95d1103d41828073475ec9331fab43364c97d88d..5ab115a86e0670d15dd32a9c75be6ef907309565 100644
--- a/lib/Auth/OpenID/Message.php
+++ b/lib/Auth/OpenID/Message.php
@@ -12,6 +12,7 @@
 require_once 'Auth/OpenID.php';
 require_once 'Auth/OpenID/KVForm.php';
 require_once 'Auth/Yadis/XML.php';
+require_once 'Auth/OpenID/Consumer.php'; // For Auth_OpenID_FailureResponse
 
 // This doesn't REALLY belong here, but where is better?
 define('Auth_OpenID_IDENTIFIER_SELECT',
@@ -23,6 +24,13 @@ define('Auth_OpenID_SREG_URI', 'http://openid.net/sreg/1.0');
 
 // The OpenID 1.X namespace URI
 define('Auth_OpenID_OPENID1_NS', 'http://openid.net/signon/1.0');
+define('Auth_OpenID_THE_OTHER_OPENID1_NS', 'http://openid.net/signon/1.1');
+
+function Auth_OpenID_isOpenID1($ns)
+{
+    return ($ns == Auth_OpenID_THE_OTHER_OPENID1_NS) ||
+        ($ns == Auth_OpenID_OPENID1_NS);
+}
 
 // The OpenID 2.0 namespace URI
 define('Auth_OpenID_OPENID2_NS', 'http://specs.openid.net/auth/2.0');
@@ -42,6 +50,10 @@ define('Auth_OpenID_BARE_NS', 'Bare namespace');
 // return null instead of returning a default.
 define('Auth_OpenID_NO_DEFAULT', 'NO DEFAULT ALLOWED');
 
+// Limit, in bytes, of identity provider and return_to URLs, including
+// response payload.  See OpenID 1.1 specification, Appendix D.
+define('Auth_OpenID_OPENID1_URL_LIMIT', 2047);
+
 // All OpenID protocol fields.  Used to check namespace aliases.
 global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
 $Auth_OpenID_OPENID_PROTOCOL_FIELDS = array(
@@ -266,6 +278,7 @@ class Auth_OpenID_NamespaceMap {
     {
         $this->alias_to_namespace = new Auth_OpenID_Mapping();
         $this->namespace_to_alias = new Auth_OpenID_Mapping();
+        $this->implicit_namespaces = array();
     }
 
     function getAlias($namespace_uri)
@@ -295,7 +308,12 @@ class Auth_OpenID_NamespaceMap {
         return $this->namespace_to_alias->items();
     }
 
-    function addAlias($namespace_uri, $desired_alias)
+    function isImplicit($namespace_uri)
+    {
+        return in_array($namespace_uri, $this->implicit_namespaces);
+    }
+
+    function addAlias($namespace_uri, $desired_alias, $implicit=false)
     {
         // Add an alias from this namespace URI to the desired alias
         global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
@@ -303,14 +321,15 @@ class Auth_OpenID_NamespaceMap {
         // Check that desired_alias is not an openid protocol field as
         // per the spec.
         if (in_array($desired_alias, $Auth_OpenID_OPENID_PROTOCOL_FIELDS)) {
-            // "%r is not an allowed namespace alias" % (desired_alias,);
+            Auth_OpenID::log("\"%s\" is not an allowed namespace alias",
+                            $desired_alias);
             return null;
         }
 
         // Check that desired_alias does not contain a period as per
         // the spec.
         if (strpos($desired_alias, '.') !== false) {
-            // "%r must not contain a dot" % (desired_alias,)
+            Auth_OpenID::log('"%s" must not contain a dot', $desired_alias);
             return null;
         }
 
@@ -321,7 +340,8 @@ class Auth_OpenID_NamespaceMap {
 
         if (($current_namespace_uri !== null) &&
             ($current_namespace_uri != $namespace_uri)) {
-            // Cannot map because previous mapping exists
+            Auth_OpenID::log('Cannot map "%s" because previous mapping exists',
+                            $namespace_uri);
             return null;
         }
 
@@ -330,9 +350,9 @@ class Auth_OpenID_NamespaceMap {
         $alias = $this->namespace_to_alias->get($namespace_uri);
 
         if (($alias !== null) && ($alias != $desired_alias)) {
-            // fmt = ('Cannot map %r to alias %r. '
-            //        'It is already mapped to alias %r')
-            // raise KeyError(fmt % (namespace_uri, desired_alias, alias))
+            Auth_OpenID::log('Cannot map %s to alias %s. ' .
+                            'It is already mapped to alias %s',
+                            $namespace_uri, $desired_alias, $alias);
             return null;
         }
 
@@ -341,6 +361,9 @@ class Auth_OpenID_NamespaceMap {
 
         $this->alias_to_namespace->set($desired_alias, $namespace_uri);
         $this->namespace_to_alias->set($namespace_uri, $desired_alias);
+        if ($implicit) {
+            array_push($this->implicit_namespaces, $namespace_uri);
+        }
 
         return $desired_alias;
     }
@@ -396,6 +419,7 @@ class Auth_OpenID_Message {
         // Create an empty Message
         $this->allowed_openid_namespaces = array(
                                Auth_OpenID_OPENID1_NS,
+                               Auth_OpenID_THE_OTHER_OPENID1_NS,
                                Auth_OpenID_OPENID2_NS);
 
         $this->args = new Auth_OpenID_Mapping();
@@ -403,13 +427,14 @@ class Auth_OpenID_Message {
         if ($openid_namespace === null) {
             $this->_openid_ns_uri = null;
         } else {
-            $this->setOpenIDNamespace($openid_namespace);
+            $implicit = Auth_OpenID_isOpenID1($openid_namespace);
+            $this->setOpenIDNamespace($openid_namespace, $implicit);
         }
     }
 
     function isOpenID1()
     {
-        return $this->getOpenIDNamespace() == Auth_OpenID_OPENID1_NS;
+        return Auth_OpenID_isOpenID1($this->getOpenIDNamespace());
     }
 
     function isOpenID2()
@@ -500,8 +525,7 @@ class Auth_OpenID_Message {
             } else if (($ns_alias == Auth_OpenID_NULL_NAMESPACE) &&
                        ($ns_key == 'ns')) {
                 // null namespace
-                if ($this->namespaces->addAlias($value,
-                                     Auth_OpenID_NULL_NAMESPACE) === null) {
+                if ($this->setOpenIDNamespace($value, false) === false) {
                     return false;
                 }
             } else {
@@ -509,38 +533,25 @@ class Auth_OpenID_Message {
             }
         }
 
-        // Ensure that there is an OpenID namespace definition
-        $openid_ns_uri =
-            $this->namespaces->getNamespaceURI(Auth_OpenID_NULL_NAMESPACE);
-
-        if ($openid_ns_uri === null) {
-            $openid_ns_uri = Auth_OpenID_OPENID1_NS;
+        if (!$this->getOpenIDNamespace()) {
+            if ($this->setOpenIDNamespace(Auth_OpenID_OPENID1_NS, true) ===
+                false) {
+                return false;
+            }
         }
 
-        $this->setOpenIDNamespace($openid_ns_uri);
-
         // Actually put the pairs into the appropriate namespaces
         foreach ($ns_args as $triple) {
             list($ns_alias, $ns_key, $value) = $triple;
             $ns_uri = $this->namespaces->getNamespaceURI($ns_alias);
             if ($ns_uri === null) {
-                // Only try to map an alias to a default if it's an
-                // OpenID 1.x message.
-                if ($openid_ns_uri == Auth_OpenID_OPENID1_NS) {
-                    foreach ($Auth_OpenID_registered_aliases
-                             as $alias => $uri) {
-                        if ($alias == $ns_alias) {
-                            $ns_uri = $uri;
-                            break;
-                        }
-                    }
-                }
-
+                $ns_uri = $this->_getDefaultNamespace($ns_alias);
                 if ($ns_uri === null) {
-                    $ns_uri = $openid_ns_uri;
+
+                    $ns_uri = Auth_OpenID_OPENID_NS;
                     $ns_key = sprintf('%s.%s', $ns_alias, $ns_key);
                 } else {
-                    $this->namespaces->addAlias($ns_uri, $ns_alias);
+                    $this->namespaces->addAlias($ns_uri, $ns_alias, true);
                 }
             }
 
@@ -550,16 +561,32 @@ class Auth_OpenID_Message {
         return true;
     }
 
-    function setOpenIDNamespace($openid_ns_uri)
+    function _getDefaultNamespace($mystery_alias)
+    {
+        global $Auth_OpenID_registered_aliases;
+        if ($this->isOpenID1()) {
+            return @$Auth_OpenID_registered_aliases[$mystery_alias];
+        }
+        return null;
+    }
+
+    function setOpenIDNamespace($openid_ns_uri, $implicit)
     {
         if (!in_array($openid_ns_uri, $this->allowed_openid_namespaces)) {
-            // raise ValueError('Invalid null namespace: %r' % (openid_ns_uri,))
+            Auth_OpenID::log('Invalid null namespace: "%s"', $openid_ns_uri);
+            return false;
+        }
+
+        $succeeded = $this->namespaces->addAlias($openid_ns_uri,
+                                                 Auth_OpenID_NULL_NAMESPACE,
+                                                 $implicit);
+        if ($succeeded === false) {
             return false;
         }
 
-        $this->namespaces->addAlias($openid_ns_uri,
-                                    Auth_OpenID_NULL_NAMESPACE);
         $this->_openid_ns_uri = $openid_ns_uri;
+
+        return true;
     }
 
     function getOpenIDNamespace()
@@ -589,25 +616,15 @@ class Auth_OpenID_Message {
         // Add namespace definitions to the output
         foreach ($this->namespaces->iteritems() as $pair) {
             list($ns_uri, $alias) = $pair;
-
+            if ($this->namespaces->isImplicit($ns_uri)) {
+                continue;
+            }
             if ($alias == Auth_OpenID_NULL_NAMESPACE) {
-                if ($ns_uri != Auth_OpenID_OPENID1_NS) {
-                    $args['openid.ns'] = $ns_uri;
-                } else {
-                    // drop the default null namespace
-                    // definition. This potentially changes a message
-                    // since we have no way of knowing whether it was
-                    // explicitly specified at the time the message
-                    // was parsed. The vast majority of the time, this
-                    // will be the right thing to do. Possibly this
-                    // could look in the signed list.
-                }
+                $ns_key = 'openid.ns';
             } else {
-                if ($this->getOpenIDNamespace() != Auth_OpenID_OPENID1_NS) {
-                    $ns_key = 'openid.ns.' . $alias;
-                    $args[$ns_key] = $ns_uri;
-                }
+                $ns_key = 'openid.ns.' . $alias;
             }
+            $args[$ns_key] = $ns_uri;
         }
 
         foreach ($this->args->items() as $pair) {
@@ -716,19 +733,20 @@ class Auth_OpenID_Message {
 
         if ($namespace == Auth_OpenID_OPENID_NS) {
             if ($this->_openid_ns_uri === null) {
-                // raise UndefinedOpenIDNamespace('OpenID namespace not set')
-                return null;
+                return new Auth_OpenID_FailureResponse(null,
+                    'OpenID namespace not set');
             } else {
                 $namespace = $this->_openid_ns_uri;
             }
         }
 
         if (($namespace != Auth_OpenID_BARE_NS) &&
-            (!is_string($namespace))) {
-            // raise TypeError(
-            //     "Namespace must be BARE_NS, OPENID_NS or a string. got %r"
-            //     % (namespace,))
-            return null;
+              (!is_string($namespace))) {
+            //TypeError
+            $err_msg = sprintf("Namespace must be Auth_OpenID_BARE_NS, ".
+                              "Auth_OpenID_OPENID_NS or a string. got %s",
+                              print_r($namespace, true));
+            return new Auth_OpenID_FailureResponse(null, $err_msg);
         }
 
         if (($namespace != Auth_OpenID_BARE_NS) &&
@@ -749,10 +767,11 @@ class Auth_OpenID_Message {
     function hasKey($namespace, $ns_key)
     {
         $namespace = $this->_fixNS($namespace);
-        if ($namespace !== null) {
-            return $this->args->contains(array($namespace, $ns_key));
-        } else {
+        if (Auth_OpenID::isFailure($namespace)) {
+            // XXX log me
             return false;
+        } else {
+            return $this->args->contains(array($namespace, $ns_key));
         }
     }
 
@@ -760,6 +779,9 @@ class Auth_OpenID_Message {
     {
         // Get the key for a particular namespaced argument
         $namespace = $this->_fixNS($namespace);
+        if (Auth_OpenID::isFailure($namespace)) {
+            return $namespace;
+        }
         if ($namespace == Auth_OpenID_BARE_NS) {
             return $ns_key;
         }
@@ -785,15 +807,17 @@ class Auth_OpenID_Message {
         // Get a value for a namespaced key.
         $namespace = $this->_fixNS($namespace);
 
-        if ($namespace !== null) {
+        if (Auth_OpenID::isFailure($namespace)) {
+            return $namespace;
+        } else {
             if ((!$this->args->contains(array($namespace, $key))) &&
-                ($default == Auth_OpenID_NO_DEFAULT)) {
-                return null;
+              ($default == Auth_OpenID_NO_DEFAULT)) {
+                $err_msg = sprintf("Namespace %s missing required field %s",
+                                   $namespace, $key);
+                return new Auth_OpenID_FailureResponse(null, $err_msg);
             } else {
                 return $this->args->get(array($namespace, $key), $default);
             }
-        } else {
-            return null;
         }
     }
 
@@ -802,7 +826,9 @@ class Auth_OpenID_Message {
         // Get the arguments that are defined for this namespace URI
 
         $namespace = $this->_fixNS($namespace);
-        if ($namespace !== null) {
+        if (Auth_OpenID::isFailure($namespace)) {
+            return $namespace;
+        } else {
             $stuff = array();
             foreach ($this->args->items() as $pair) {
                 list($key, $value) = $pair;
@@ -814,8 +840,6 @@ class Auth_OpenID_Message {
 
             return $stuff;
         }
-
-        return array();
     }
 
     function updateArgs($namespace, $updates)
@@ -824,13 +848,13 @@ class Auth_OpenID_Message {
 
         $namespace = $this->_fixNS($namespace);
 
-        if ($namespace !== null) {
+        if (Auth_OpenID::isFailure($namespace)) {
+            return $namespace;
+        } else {
             foreach ($updates as $k => $v) {
                 $this->setArg($namespace, $k, $v);
             }
             return true;
-        } else {
-            return false;
         }
     }
 
@@ -839,14 +863,14 @@ class Auth_OpenID_Message {
         // Set a single argument in this namespace
         $namespace = $this->_fixNS($namespace);
 
-        if ($namespace !== null) {
+        if (Auth_OpenID::isFailure($namespace)) {
+            return $namespace;
+        } else {
             $this->args->set(array($namespace, $key), $value);
             if ($namespace !== Auth_OpenID_BARE_NS) {
                 $this->namespaces->add($namespace);
             }
             return true;
-        } else {
-            return false;
         }
     }
 
@@ -854,22 +878,34 @@ class Auth_OpenID_Message {
     {
         $namespace = $this->_fixNS($namespace);
 
-        if ($namespace !== null) {
-            return $this->args->del(array($namespace, $key));
+        if (Auth_OpenID::isFailure($namespace)) {
+            return $namespace;
         } else {
-            return false;
+            return $this->args->del(array($namespace, $key));
         }
     }
 
     function getAliasedArg($aliased_key, $default = null)
     {
+        if ($aliased_key == 'ns') {
+            // Return the namespace URI for the OpenID namespace
+            return $this->getOpenIDNamespace();
+        }
+
         $parts = explode('.', $aliased_key, 2);
 
         if (count($parts) != 2) {
             $ns = null;
         } else {
             list($alias, $key) = $parts;
-            $ns = $this->namespaces->getNamespaceURI($alias);
+
+            if ($alias == 'ns') {
+              // Return the namespace URI for a namespace alias
+              // parameter.
+              return $this->namespaces->getNamespaceURI($key);
+            } else {
+              $ns = $this->namespaces->getNamespaceURI($alias);
+            }
         }
 
         if ($ns === null) {
@@ -881,4 +917,4 @@ class Auth_OpenID_Message {
     }
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/lib/Auth/OpenID/MySQLStore.php b/lib/Auth/OpenID/MySQLStore.php
index 4b2d29d0f5350b746ca6872dd90676d63e633ae9..eb08af01626644e6b591d61805488ebb944c67ed 100644
--- a/lib/Auth/OpenID/MySQLStore.php
+++ b/lib/Auth/OpenID/MySQLStore.php
@@ -24,25 +24,26 @@ class Auth_OpenID_MySQLStore extends Auth_OpenID_SQLStore {
     {
         $this->sql['nonce_table'] =
             "CREATE TABLE %s (\n".
-            "  server_url VARCHAR(2047),\n".
-            "  timestamp INTEGER,\n".
-            "  salt CHAR(40),\n".
+            "  server_url VARCHAR(2047) NOT NULL,\n".
+            "  timestamp INTEGER NOT NULL,\n".
+            "  salt CHAR(40) NOT NULL,\n".
             "  UNIQUE (server_url(255), timestamp, salt)\n".
-            ") TYPE=InnoDB";
+            ") ENGINE=InnoDB";
 
         $this->sql['assoc_table'] =
             "CREATE TABLE %s (\n".
-            "  server_url BLOB,\n".
-            "  handle VARCHAR(255),\n".
-            "  secret BLOB,\n".
-            "  issued INTEGER,\n".
-            "  lifetime INTEGER,\n".
-            "  assoc_type VARCHAR(64),\n".
+            "  server_url BLOB NOT NULL,\n".
+            "  handle VARCHAR(255) NOT NULL,\n".
+            "  secret BLOB NOT NULL,\n".
+            "  issued INTEGER NOT NULL,\n".
+            "  lifetime INTEGER NOT NULL,\n".
+            "  assoc_type VARCHAR(64) NOT NULL,\n".
             "  PRIMARY KEY (server_url(255), handle)\n".
-            ") TYPE=InnoDB";
+            ") ENGINE=InnoDB";
 
         $this->sql['set_assoc'] =
-            "REPLACE INTO %s VALUES (?, ?, !, ?, ?, ?)";
+            "REPLACE INTO %s (server_url, handle, secret, issued,\n".
+            "  lifetime, assoc_type) VALUES (?, ?, !, ?, ?, ?)";
 
         $this->sql['get_assocs'] =
             "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
@@ -58,8 +59,11 @@ class Auth_OpenID_MySQLStore extends Auth_OpenID_SQLStore {
         $this->sql['add_nonce'] =
             "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";
 
-        $this->sql['get_expired'] =
-            "SELECT server_url FROM %s WHERE issued + lifetime < ?";
+        $this->sql['clean_nonce'] =
+            "DELETE FROM %s WHERE timestamp < ?";
+
+        $this->sql['clean_assoc'] =
+            "DELETE FROM %s WHERE issued + lifetime < ?";
     }
 
     /**
diff --git a/lib/Auth/OpenID/Nonce.php b/lib/Auth/OpenID/Nonce.php
index 71a33e1a6e076c2a18a7de143f83cbb37f1fcfdf..effecac38521647c46e8902cd1a99b6b01b71536 100644
--- a/lib/Auth/OpenID/Nonce.php
+++ b/lib/Auth/OpenID/Nonce.php
@@ -96,7 +96,11 @@ function Auth_OpenID_mkNonce($when = null)
     $salt = Auth_OpenID_CryptUtil::randomString(
         6, Auth_OpenID_Nonce_CHRS);
     if ($when === null) {
-        $when = gmmktime();
+        // It's safe to call time() with no arguments; it returns a
+        // GMT unix timestamp on PHP 4 and PHP 5.  gmmktime() with no
+        // args returns a local unix timestamp on PHP 4, so don't use
+        // that.
+        $when = time();
     }
     $time_str = gmstrftime(Auth_OpenID_Nonce_TIME_FMT, $when);
     return $time_str . $salt;
diff --git a/lib/Auth/OpenID/PAPE.php b/lib/Auth/OpenID/PAPE.php
new file mode 100644
index 0000000000000000000000000000000000000000..62cba8a912d46cf6691d3920bab55966d9cde10b
--- /dev/null
+++ b/lib/Auth/OpenID/PAPE.php
@@ -0,0 +1,301 @@
+<?php
+
+/**
+ * An implementation of the OpenID Provider Authentication Policy
+ *  Extension 1.0
+ *
+ * See:
+ * http://openid.net/developers/specs/
+ */
+
+require_once "Auth/OpenID/Extension.php";
+
+define('Auth_OpenID_PAPE_NS_URI',
+       "http://specs.openid.net/extensions/pape/1.0");
+
+define('PAPE_AUTH_MULTI_FACTOR_PHYSICAL',
+       'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical');
+define('PAPE_AUTH_MULTI_FACTOR',
+       'http://schemas.openid.net/pape/policies/2007/06/multi-factor');
+define('PAPE_AUTH_PHISHING_RESISTANT',
+       'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant');
+
+define('PAPE_TIME_VALIDATOR',
+       '^[0-9]{4,4}-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z$');
+/**
+ * A Provider Authentication Policy request, sent from a relying party
+ * to a provider
+ *
+ * preferred_auth_policies: The authentication policies that
+ * the relying party prefers
+ *
+ * max_auth_age: The maximum time, in seconds, that the relying party
+ * wants to allow to have elapsed before the user must re-authenticate
+ */
+class Auth_OpenID_PAPE_Request extends Auth_OpenID_Extension {
+
+    var $ns_alias = 'pape';
+    var $ns_uri = Auth_OpenID_PAPE_NS_URI;
+
+    function Auth_OpenID_PAPE_Request($preferred_auth_policies=null,
+                                      $max_auth_age=null)
+    {
+        if ($preferred_auth_policies === null) {
+            $preferred_auth_policies = array();
+        }
+
+        $this->preferred_auth_policies = $preferred_auth_policies;
+        $this->max_auth_age = $max_auth_age;
+    }
+
+    /**
+     * Add an acceptable authentication policy URI to this request
+     *
+     * This method is intended to be used by the relying party to add
+     * acceptable authentication types to the request.
+     *
+     * policy_uri: The identifier for the preferred type of
+     * authentication.
+     */
+    function addPolicyURI($policy_uri)
+    {
+        if (!in_array($policy_uri, $this->preferred_auth_policies)) {
+            $this->preferred_auth_policies[] = $policy_uri;
+        }
+    }
+
+    function getExtensionArgs()
+    {
+        $ns_args = array(
+                         'preferred_auth_policies' =>
+                           implode(' ', $this->preferred_auth_policies)
+                         );
+
+        if ($this->max_auth_age !== null) {
+            $ns_args['max_auth_age'] = strval($this->max_auth_age);
+        }
+
+        return $ns_args;
+    }
+
+    /**
+     * Instantiate a Request object from the arguments in a checkid_*
+     * OpenID message
+     */
+    function fromOpenIDRequest($request)
+    {
+        $obj = new Auth_OpenID_PAPE_Request();
+        $args = $request->message->getArgs(Auth_OpenID_PAPE_NS_URI);
+
+        if ($args === null || $args === array()) {
+            return null;
+        }
+
+        $obj->parseExtensionArgs($args);
+        return $obj;
+    }
+
+    /**
+     * Set the state of this request to be that expressed in these
+     * PAPE arguments
+     *
+     * @param args: The PAPE arguments without a namespace
+     */
+    function parseExtensionArgs($args)
+    {
+        // preferred_auth_policies is a space-separated list of policy
+        // URIs
+        $this->preferred_auth_policies = array();
+
+        $policies_str = Auth_OpenID::arrayGet($args, 'preferred_auth_policies');
+        if ($policies_str) {
+            foreach (explode(' ', $policies_str) as $uri) {
+                if (!in_array($uri, $this->preferred_auth_policies)) {
+                    $this->preferred_auth_policies[] = $uri;
+                }
+            }
+        }
+
+        // max_auth_age is base-10 integer number of seconds
+        $max_auth_age_str = Auth_OpenID::arrayGet($args, 'max_auth_age');
+        if ($max_auth_age_str) {
+            $this->max_auth_age = Auth_OpenID::intval($max_auth_age_str);
+        } else {
+            $this->max_auth_age = null;
+        }
+    }
+
+    /**
+     * Given a list of authentication policy URIs that a provider
+     * supports, this method returns the subsequence of those types
+     * that are preferred by the relying party.
+     *
+     * @param supported_types: A sequence of authentication policy
+     * type URIs that are supported by a provider
+     *
+     * @return array The sub-sequence of the supported types that are
+     * preferred by the relying party. This list will be ordered in
+     * the order that the types appear in the supported_types
+     * sequence, and may be empty if the provider does not prefer any
+     * of the supported authentication types.
+     */
+    function preferredTypes($supported_types)
+    {
+        $result = array();
+
+        foreach ($supported_types as $st) {
+            if (in_array($st, $this->preferred_auth_policies)) {
+                $result[] = $st;
+            }
+        }
+        return $result;
+    }
+}
+
+/**
+ * A Provider Authentication Policy response, sent from a provider to
+ * a relying party
+ */
+class Auth_OpenID_PAPE_Response extends Auth_OpenID_Extension {
+
+    var $ns_alias = 'pape';
+    var $ns_uri = Auth_OpenID_PAPE_NS_URI;
+
+    function Auth_OpenID_PAPE_Response($auth_policies=null, $auth_time=null,
+                                       $nist_auth_level=null)
+    {
+        if ($auth_policies) {
+            $this->auth_policies = $auth_policies;
+        } else {
+            $this->auth_policies = array();
+        }
+
+        $this->auth_time = $auth_time;
+        $this->nist_auth_level = $nist_auth_level;
+    }
+
+    /**
+     * Add a authentication policy to this response
+     *
+     * This method is intended to be used by the provider to add a
+     * policy that the provider conformed to when authenticating the
+     * user.
+     *
+     * @param policy_uri: The identifier for the preferred type of
+     * authentication.
+     */
+    function addPolicyURI($policy_uri)
+    {
+        if (!in_array($policy_uri, $this->auth_policies)) {
+            $this->auth_policies[] = $policy_uri;
+        }
+    }
+
+    /**
+     * Create an Auth_OpenID_PAPE_Response object from a successful
+     * OpenID library response.
+     *
+     * @param success_response $success_response A SuccessResponse
+     * from Auth_OpenID_Consumer::complete()
+     *
+     * @returns: A provider authentication policy response from the
+     * data that was supplied with the id_res response.
+     */
+    function fromSuccessResponse($success_response)
+    {
+        $obj = new Auth_OpenID_PAPE_Response();
+
+        // PAPE requires that the args be signed.
+        $args = $success_response->getSignedNS(Auth_OpenID_PAPE_NS_URI);
+
+        if ($args === null || $args === array()) {
+            return null;
+        }
+
+        $result = $obj->parseExtensionArgs($args);
+
+        if ($result === false) {
+            return null;
+        } else {
+            return $obj;
+        }
+    }
+
+    /**
+     * Parse the provider authentication policy arguments into the
+     *  internal state of this object
+     *
+     * @param args: unqualified provider authentication policy
+     * arguments
+     *
+     * @param strict: Whether to return false when bad data is
+     * encountered
+     *
+     * @return null The data is parsed into the internal fields of
+     * this object.
+    */
+    function parseExtensionArgs($args, $strict=false)
+    {
+        $policies_str = Auth_OpenID::arrayGet($args, 'auth_policies');
+        if ($policies_str && $policies_str != "none") {
+            $this->auth_policies = explode(" ", $policies_str);
+        }
+
+        $nist_level_str = Auth_OpenID::arrayGet($args, 'nist_auth_level');
+        if ($nist_level_str !== null) {
+            $nist_level = Auth_OpenID::intval($nist_level_str);
+
+            if ($nist_level === false) {
+                if ($strict) {
+                    return false;
+                } else {
+                    $nist_level = null;
+                }
+            }
+
+            if (0 <= $nist_level && $nist_level < 5) {
+                $this->nist_auth_level = $nist_level;
+            } else if ($strict) {
+                return false;
+            }
+        }
+
+        $auth_time = Auth_OpenID::arrayGet($args, 'auth_time');
+        if ($auth_time !== null) {
+            if (ereg(PAPE_TIME_VALIDATOR, $auth_time)) {
+                $this->auth_time = $auth_time;
+            } else if ($strict) {
+                return false;
+            }
+        }
+    }
+
+    function getExtensionArgs()
+    {
+        $ns_args = array();
+        if (count($this->auth_policies) > 0) {
+            $ns_args['auth_policies'] = implode(' ', $this->auth_policies);
+        } else {
+            $ns_args['auth_policies'] = 'none';
+        }
+
+        if ($this->nist_auth_level !== null) {
+            if (!in_array($this->nist_auth_level, range(0, 4), true)) {
+                return false;
+            }
+            $ns_args['nist_auth_level'] = strval($this->nist_auth_level);
+        }
+
+        if ($this->auth_time !== null) {
+            if (!ereg(PAPE_TIME_VALIDATOR, $this->auth_time)) {
+                return false;
+            }
+
+            $ns_args['auth_time'] = $this->auth_time;
+        }
+
+        return $ns_args;
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/lib/Auth/OpenID/Parse.php b/lib/Auth/OpenID/Parse.php
index 7d485155ef21cc93a1484ebd6ba27c7be135056f..546f34f6be717eb05ca807d42fddcd9a89b944b9 100644
--- a/lib/Auth/OpenID/Parse.php
+++ b/lib/Auth/OpenID/Parse.php
@@ -75,8 +75,8 @@
  * @access private
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -105,6 +105,9 @@ class Auth_OpenID_Parse {
 
     var $_attr_find = '\b(\w+)=("[^"]*"|\'[^\']*\'|[^\'"\s\/<>]+)';
 
+    var $_open_tag_expr = "<%s\b";
+    var $_close_tag_expr = "<((\/%s\b)|(%s[^>\/]*\/))>";
+
     function Auth_OpenID_Parse()
     {
         $this->_link_find = sprintf("/<link\b(?!:)([^>]*)(?!<)>/%s",
@@ -136,6 +139,8 @@ class Auth_OpenID_Parse {
      */
     function tagMatcher($tag_name, $close_tags = null)
     {
+        $expr = $this->_tag_expr;
+
         if ($close_tags) {
             $options = implode("|", array_merge(array($tag_name), $close_tags));
             $closer = sprintf("(?:%s)", $options);
@@ -143,18 +148,49 @@ class Auth_OpenID_Parse {
             $closer = $tag_name;
         }
 
-        $expr = sprintf($this->_tag_expr, $tag_name, $closer);
+        $expr = sprintf($expr, $tag_name, $closer);
         return sprintf("/%s/%s", $expr, $this->_re_flags);
     }
 
-    function htmlFind()
+    function openTag($tag_name)
     {
-        return $this->tagMatcher('html');
+        $expr = sprintf($this->_open_tag_expr, $tag_name);
+        return sprintf("/%s/%s", $expr, $this->_re_flags);
+    }
+
+    function closeTag($tag_name)
+    {
+        $expr = sprintf($this->_close_tag_expr, $tag_name, $tag_name);
+        return sprintf("/%s/%s", $expr, $this->_re_flags);
+    }
+
+    function htmlBegin($s)
+    {
+        $matches = array();
+        $result = preg_match($this->openTag('html'), $s,
+                             $matches, PREG_OFFSET_CAPTURE);
+        if ($result === false || !$matches) {
+            return false;
+        }
+        // Return the offset of the first match.
+        return $matches[0][1];
+    }
+
+    function htmlEnd($s)
+    {
+        $matches = array();
+        $result = preg_match($this->closeTag('html'), $s,
+                             $matches, PREG_OFFSET_CAPTURE);
+        if ($result === false || !$matches) {
+            return false;
+        }
+        // Return the offset of the first match.
+        return $matches[count($matches) - 1][1];
     }
 
     function headFind()
     {
-        return $this->tagMatcher('head', array('body'));
+        return $this->tagMatcher('head', array('body', 'html'));
     }
 
     function replaceEntities($str)
@@ -194,17 +230,24 @@ class Auth_OpenID_Parse {
                                  "",
                                  $html);
 
-        // Try to find the <HTML> tag.
-        $html_re = $this->htmlFind();
-        $html_matches = array();
-        if (!preg_match($html_re, $stripped, $html_matches)) {
+        $html_begin = $this->htmlBegin($stripped);
+        $html_end = $this->htmlEnd($stripped);
+
+        if ($html_begin === false) {
             return array();
         }
 
+        if ($html_end === false) {
+            $html_end = strlen($stripped);
+        }
+
+        $stripped = substr($stripped, $html_begin,
+                           $html_end - $html_begin);
+
         // Try to find the <HEAD> tag.
         $head_re = $this->headFind();
         $head_matches = array();
-        if (!preg_match($head_re, $html_matches[0], $head_matches)) {
+        if (!preg_match($head_re, $stripped, $head_matches)) {
             return array();
         }
 
diff --git a/lib/Auth/OpenID/PostgreSQLStore.php b/lib/Auth/OpenID/PostgreSQLStore.php
index ffbbc69b67c6a81da6ef3a9b297df6eb5972ed94..69d95e7b8dca452a131dad23001e56a2098784fb 100644
--- a/lib/Auth/OpenID/PostgreSQLStore.php
+++ b/lib/Auth/OpenID/PostgreSQLStore.php
@@ -23,13 +23,19 @@ class Auth_OpenID_PostgreSQLStore extends Auth_OpenID_SQLStore {
     function setSQL()
     {
         $this->sql['nonce_table'] =
-            "CREATE TABLE %s (server_url VARCHAR(2047), timestamp INTEGER, ".
-            "salt CHAR(40), UNIQUE (server_url, timestamp, salt))";
+            "CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
+                             "timestamp INTEGER NOT NULL, ".
+                             "salt CHAR(40) NOT NULL, ".
+                "UNIQUE (server_url, timestamp, salt))";
 
         $this->sql['assoc_table'] =
-            "CREATE TABLE %s (server_url VARCHAR(2047), handle VARCHAR(255), ".
-            "secret BYTEA, issued INTEGER, lifetime INTEGER, ".
-            "assoc_type VARCHAR(64), PRIMARY KEY (server_url, handle), ".
+            "CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ". 
+                             "handle VARCHAR(255) NOT NULL, ".
+                             "secret BYTEA NOT NULL, ".
+                             "issued INTEGER NOT NULL, ".
+                             "lifetime INTEGER NOT NULL, ".
+                             "assoc_type VARCHAR(64) NOT NULL, ".
+            "PRIMARY KEY (server_url, handle), ".
             "CONSTRAINT secret_length_constraint CHECK ".
             "(LENGTH(secret) <= 128))";
 
@@ -54,13 +60,16 @@ class Auth_OpenID_PostgreSQLStore extends Auth_OpenID_SQLStore {
         $this->sql['remove_assoc'] =
             "DELETE FROM %s WHERE server_url = ? AND handle = ?";
 
-        $this->sql['get_expired'] =
-            "SELECT server_url FROM %s WHERE issued + lifetime < ?";
-
         $this->sql['add_nonce'] =
                   "INSERT INTO %s (server_url, timestamp, salt) VALUES ".
                   "(?, ?, ?)"
                   ;
+
+        $this->sql['clean_nonce'] =
+            "DELETE FROM %s WHERE timestamp < ?";
+
+        $this->sql['clean_assoc'] =
+            "DELETE FROM %s WHERE issued + lifetime < ?";
     }
 
     /**
diff --git a/lib/Auth/OpenID/SQLStore.php b/lib/Auth/OpenID/SQLStore.php
index b71729dcac595fbcf7db3cce9986e8d533419f66..da93c6aa25c4ade331613962433eb5e863f03ddb 100644
--- a/lib/Auth/OpenID/SQLStore.php
+++ b/lib/Auth/OpenID/SQLStore.php
@@ -9,8 +9,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -27,12 +27,18 @@ $__Auth_OpenID_PEAR_AVAILABLE = @include_once 'DB.php';
  * @access private
  */
 require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/Nonce.php';
 
 /**
  * @access private
  */
 require_once 'Auth/OpenID.php';
 
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/Nonce.php';
+
 /**
  * This is the parent class for the SQL stores, which contains the
  * logic common to all of the SQL stores.
@@ -161,8 +167,9 @@ class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
     function tableExists($table_name)
     {
         return !$this->isError(
-                      $this->connection->query("SELECT * FROM %s LIMIT 0",
-                                               $table_name));
+                      $this->connection->query(
+                          sprintf("SELECT * FROM %s LIMIT 0",
+                                  $table_name)));
     }
 
     /**
@@ -224,8 +231,7 @@ class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
                                    'set_assoc',
                                    'get_assoc',
                                    'get_assocs',
-                                   'remove_assoc',
-                                   'get_expired',
+                                   'remove_assoc'
                                    );
 
         foreach ($required_sql_keys as $key) {
@@ -248,7 +254,8 @@ class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
                               array(
                                     'value' => $this->nonces_table_name,
                                     'keys' => array('nonce_table',
-                                                    'add_nonce')
+                                                    'add_nonce',
+                                                    'clean_nonce')
                                     ),
                               array(
                                     'value' => $this->associations_table_name,
@@ -257,7 +264,7 @@ class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
                                                     'get_assoc',
                                                     'get_assocs',
                                                     'remove_assoc',
-                                                    'get_expired')
+                                                    'clean_assoc')
                                     )
                               );
 
@@ -399,20 +406,6 @@ class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
         return true;
     }
 
-    function getExpired()
-    {
-        $sql = $this->sql['get_expired'];
-        $result = $this->connection->getAll($sql, array(time()));
-
-        $expired = array();
-
-        foreach ($result as $row) {
-            $expired[] = $row['server_url'];
-        }
-
-        return $expired;
-    }
-
     function getAssociation($server_url, $handle = null)
     {
         if ($handle !== null) {
@@ -486,6 +479,12 @@ class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
 
     function useNonce($server_url, $timestamp, $salt)
     {
+        global $Auth_OpenID_SKEW;
+
+        if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
+            return False;
+        }
+
         return $this->_add_nonce($server_url, $timestamp, $salt);
     }
 
@@ -545,6 +544,26 @@ class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
 
         return $result;
     }
+
+    function cleanupNonces()
+    {
+        global $Auth_OpenID_SKEW;
+        $v = time() - $Auth_OpenID_SKEW;
+
+        $this->connection->query($this->sql['clean_nonce'], array($v));
+        $num = $this->connection->affectedRows();
+        $this->connection->commit();
+        return $num;
+    }
+
+    function cleanupAssociations()
+    {
+        $this->connection->query($this->sql['clean_assoc'],
+                                 array(time()));
+        $num = $this->connection->affectedRows();
+        $this->connection->commit();
+        return $num;
+    }
 }
 
 ?>
diff --git a/lib/Auth/OpenID/SQLiteStore.php b/lib/Auth/OpenID/SQLiteStore.php
index debb5fe125e84ee2cce9e9b41d061d0c77168f3a..ec2bf58e46ce436d93cc8272433d792f4d6c93d2 100644
--- a/lib/Auth/OpenID/SQLiteStore.php
+++ b/lib/Auth/OpenID/SQLiteStore.php
@@ -35,9 +35,6 @@ class Auth_OpenID_SQLiteStore extends Auth_OpenID_SQLStore {
             "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
             "WHERE server_url = ?";
 
-        $this->sql['get_expired'] =
-            "SELECT server_url FROM %s WHERE issued + lifetime < ?";
-
         $this->sql['get_assoc'] =
             "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
             "WHERE server_url = ? AND handle = ?";
@@ -47,6 +44,12 @@ class Auth_OpenID_SQLiteStore extends Auth_OpenID_SQLStore {
 
         $this->sql['add_nonce'] =
             "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";
+
+        $this->sql['clean_nonce'] =
+            "DELETE FROM %s WHERE timestamp < ?";
+
+        $this->sql['clean_assoc'] =
+            "DELETE FROM %s WHERE issued + lifetime < ?";
     }
 
     /**
diff --git a/lib/Auth/OpenID/SReg.php b/lib/Auth/OpenID/SReg.php
index 208a6aba9fd0b451f6588e9548fcc19d42c72b2e..63280769fd6befbfce9cc512e70bf0b445772bfd 100644
--- a/lib/Auth/OpenID/SReg.php
+++ b/lib/Auth/OpenID/SReg.php
@@ -22,7 +22,7 @@
  * object and adds it to the id_res response:
  *
  *   $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest(
- *                                  $checkid_request->message);
+ *                                  $checkid_request);
  *   // [ get the user's approval and data, informing the user that
  *   //   the fields in sreg_response were requested ]
  *   $sreg_resp = Auth_OpenID_SRegResponse::extractResponse(
@@ -175,9 +175,10 @@ class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {
      */
     function build($required=null, $optional=null,
                    $policy_url=null,
-                   $sreg_ns_uri=Auth_OpenID_SREG_NS_URI)
+                   $sreg_ns_uri=Auth_OpenID_SREG_NS_URI,
+                   $cls='Auth_OpenID_SRegRequest')
     {
-        $obj = new Auth_OpenID_SRegRequest();
+        $obj = new $cls();
 
         $obj->required = array();
         $obj->optional = array();
@@ -204,23 +205,28 @@ class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {
      * that were requested in the OpenID request with the given
      * arguments
      *
-     * $message: The arguments that were given for this OpenID
-     * authentication request
+     * $request: The OpenID authentication request from which to
+     * extract an sreg request.
+     *
+     * $cls: name of class to use when creating sreg request object.
+     * Used for testing.
      *
      * Returns the newly created simple registration request
      */
-    function fromOpenIDRequest($message)
+    function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest')
     {
-        $obj = Auth_OpenID_SRegRequest::build();
+
+        $obj = call_user_func_array(array($cls, 'build'),
+                 array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls));
 
         // Since we're going to mess with namespace URI mapping, don't
         // mutate the object that was passed in.
-        $m = $message;
+        $m = $request->message;
 
         $obj->ns_uri = $obj->_getSRegNS($m);
         $args = $m->getArgs($obj->ns_uri);
 
-        if ($args === null) {
+        if ($args === null || Auth_OpenID::isFailure($args)) {
             return null;
         }
 
@@ -478,7 +484,7 @@ class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {
             $args = $success_response->message->getArgs($obj->ns_uri);
         }
 
-        if ($args === null) {
+        if ($args === null || Auth_OpenID::isFailure($args)) {
             return null;
         }
 
@@ -512,30 +518,4 @@ class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {
     }
 }
 
-/**
- * Convenience function for copying all the sreg data that was
- * requested from a supplied set of sreg data into the response
- * message. If no data were requested, no data will be sent.
- *
- * openid_request: The OpenID (checkid_*) request that may be
- * requesting sreg data.
- *
- * data: The simple registration data to send. All requested fields
- * that are present in this dictionary will be added to the response
- * message.
- *
- * openid_response: The OpenID C{id_res} response to which the simple
- * registration data should be added
- *
- * Does not return a value; updates the openid_response instead.
- */
-function Auth_OpenID_sendSRegFields(&$openid_request, $data, &$openid_response)
-{
-    $sreg_request = Auth_OpenID_SRegRequest::fromOpenIDRequest(
-                                                   $openid_request->message);
-    $sreg_response = Auth_OpenID_SRegResponse::extractResponse(
-                                                   $sreg_request, $data);
-    $sreg_response->toMessage($openid_response->fields);
-}
-
-?>
\ No newline at end of file
+?>
diff --git a/lib/Auth/OpenID/Server.php b/lib/Auth/OpenID/Server.php
index 013892c5c8ee88244d647382d157289e2abee29f..f1db4d872567ec6f311b150fde193f0a29926119 100644
--- a/lib/Auth/OpenID/Server.php
+++ b/lib/Auth/OpenID/Server.php
@@ -85,8 +85,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -107,25 +107,27 @@ define('AUTH_OPENID_HTTP_OK', 200);
 define('AUTH_OPENID_HTTP_REDIRECT', 302);
 define('AUTH_OPENID_HTTP_ERROR', 400);
 
-global $_Auth_OpenID_Request_Modes,
-    $_Auth_OpenID_Encode_Kvform,
-    $_Auth_OpenID_Encode_Url;
-
 /**
  * @access private
  */
+global $_Auth_OpenID_Request_Modes;
 $_Auth_OpenID_Request_Modes = array('checkid_setup',
                                     'checkid_immediate');
 
 /**
  * @access private
  */
-$_Auth_OpenID_Encode_Kvform = array('kfvorm');
+define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_URL', 'URL/redirect');
 
 /**
  * @access private
  */
-$_Auth_OpenID_Encode_Url = array('URL/redirect');
+define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');
 
 /**
  * @access private
@@ -155,18 +157,24 @@ class Auth_OpenID_ServerError {
         $this->reference = $reference;
     }
 
+    function getReturnTo()
+    {
+        if ($this->message &&
+            $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) {
+            return $this->message->getArg(Auth_OpenID_OPENID_NS,
+                                          'return_to');
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Returns the return_to URL for the request which caused this
      * error.
      */
     function hasReturnTo()
     {
-        if ($this->message) {
-            return $this->message->hasKey(Auth_OpenID_OPENID_NS,
-                                          'return_to');
-        } else {
-            return false;
-        }
+        return $this->getReturnTo() !== null;
     }
 
     /**
@@ -180,15 +188,8 @@ class Auth_OpenID_ServerError {
             return null;
         }
 
-        $return_to = $this->message->getArg(Auth_OpenID_OPENID_NS,
-                                            'return_to');
-        if (!$return_to) {
-            return null;
-        }
-
-        return Auth_OpenID::appendArgs($return_to,
-                            array('openid.mode' => 'error',
-                                  'openid.error' => $this->toString()));
+        $msg = $this->toMessage();
+        return $msg->toURL($this->getReturnTo());
     }
 
     /**
@@ -204,6 +205,18 @@ class Auth_OpenID_ServerError {
                                             'error' => $this->toString()));
     }
 
+    function toFormMarkup($form_tag_attrs=null)
+    {
+        $msg = $this->toMessage();
+        return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs);
+    }
+
+    function toHTML($form_tag_attrs=null)
+    {
+        return Auth_OpenID::autoSubmitHTML(
+                      $this->toFormMarkup($form_tag_attrs));
+    }
+
     function toMessage()
     {
         // Generate a Message object for sending to the relying party,
@@ -226,18 +239,22 @@ class Auth_OpenID_ServerError {
     }
 
     /**
-     * Returns one of $_Auth_OpenID_Encode_Url,
-     * $_Auth_OpenID_Encode_Kvform, or null, depending on the type of
+     * Returns one of Auth_OpenID_ENCODE_URL,
+     * Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of
      * encoding expected for this error's payload.
      */
     function whichEncoding()
     {
-        global $_Auth_OpenID_Encode_Url,
-            $_Auth_OpenID_Encode_Kvform,
-            $_Auth_OpenID_Request_Modes;
+        global $_Auth_OpenID_Request_Modes;
 
         if ($this->hasReturnTo()) {
-            return $_Auth_OpenID_Encode_Url;
+            if ($this->message->isOpenID2() &&
+                (strlen($this->encodeToURL()) >
+                   Auth_OpenID_OPENID1_URL_LIMIT)) {
+                return Auth_OpenID_ENCODE_HTML_FORM;
+            } else {
+                return Auth_OpenID_ENCODE_URL;
+            }
         }
 
         if (!$this->message) {
@@ -249,7 +266,7 @@ class Auth_OpenID_ServerError {
 
         if ($mode) {
             if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
-                return $_Auth_OpenID_Encode_Kvform;
+                return Auth_OpenID_ENCODE_KVFORM;
             }
         }
         return null;
@@ -366,14 +383,6 @@ class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
         $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
         $signed_list = explode(",", $signed_list);
 
-        foreach ($signed_list as $field) {
-            if (!$message->hasKey(Auth_OpenID_OPENID_NS, $field)) {
-                return new Auth_OpenID_ServerError($message,
-                             sprintf("Couldn't find signed field %s in query",
-                                     $field));
-            }
-        }
-
         $signed = $message;
         if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) {
             $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
@@ -591,7 +600,7 @@ class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
     function fromMessage($message, $server=null)
     {
         if ($message->isOpenID1()) {
-            $session_type = $message->getArg(Auth_OpenID_OPENID1_NS,
+            $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
                                              'session_type');
 
             if ($session_type == 'no-encryption') {
@@ -601,7 +610,7 @@ class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
                 $session_type = 'no-encryption';
             }
         } else {
-            $session_type = $message->getArg(Auth_OpenID_OPENID2_NS,
+            $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
                                              'session_type');
             if ($session_type === null) {
                 return new Auth_OpenID_ServerError($message,
@@ -652,7 +661,8 @@ class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
         $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
            $this->session->answer($assoc->secret));
 
-        if ($this->session->session_type != 'no-encryption') {
+        if (! ($this->session->session_type == 'no-encryption' 
+               && $this->message->isOpenID1())) {
             $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                       'session_type',
                                       $this->session->session_type);
@@ -697,6 +707,12 @@ class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
  * @package OpenID
  */
 class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
+    /**
+     * Return-to verification callback.  Default is
+     * Auth_OpenID_verifyReturnTo from TrustRoot.php.
+     */
+    var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo';
+
     /**
      * The mode of this request.
      */
@@ -712,6 +728,12 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
      */
     var $trust_root = null;
 
+    /**
+     * The OpenID namespace for this request.
+     * deprecated since version 2.0.2
+     */
+    var $namespace;
+    
     function make(&$message, $identity, $return_to, $trust_root = null,
                   $immediate = false, $assoc_handle = null, $server = null)
     {
@@ -743,12 +765,17 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
 
     function Auth_OpenID_CheckIDRequest($identity, $return_to,
                                         $trust_root = null, $immediate = false,
-                                        $assoc_handle = null, $server = null)
+                                        $assoc_handle = null, $server = null,
+                                        $claimed_id = null)
     {
         $this->namespace = Auth_OpenID_OPENID2_NS;
         $this->assoc_handle = $assoc_handle;
         $this->identity = $identity;
-        $this->claimed_id = $identity;
+        if ($claimed_id === null) {
+            $this->claimed_id = $identity;
+        } else {
+            $this->claimed_id = $claimed_id;
+        }
         $this->return_to = $return_to;
         $this->trust_root = $trust_root;
         $this->server =& $server;
@@ -774,6 +801,26 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
                 ($this->trust_root == $other->trust_root));
     }
 
+    /*
+     * Does the relying party publish the return_to URL for this
+     * response under the realm? It is up to the provider to set a
+     * policy for what kinds of realms should be allowed. This
+     * return_to URL verification reduces vulnerability to data-theft
+     * attacks based on open proxies, corss-site-scripting, or open
+     * redirectors.
+     *
+     * This check should only be performed after making sure that the
+     * return_to URL matches the realm.
+     *
+     * @return true if the realm publishes a document with the
+     * return_to URL listed, false if not or if discovery fails
+     */
+    function returnToVerified()
+    {
+        return call_user_func_array($this->verifyReturnTo,
+                                    array($this->trust_root, $this->return_to));
+    }
+
     function fromMessage(&$message, $server)
     {
         $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
@@ -790,9 +837,7 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
         $return_to = $message->getArg(Auth_OpenID_OPENID_NS,
                                       'return_to');
 
-        $namespace = $message->getOpenIDNamespace();
-
-        if (($namespace == Auth_OpenID_OPENID1_NS) &&
+        if (($message->isOpenID1()) &&
             (!$return_to)) {
             $fmt = "Missing required field 'return_to' from checkid request";
             return new Auth_OpenID_ServerError($message, $fmt);
@@ -800,42 +845,43 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
 
         $identity = $message->getArg(Auth_OpenID_OPENID_NS,
                                      'identity');
-
-        if ($identity && $message->isOpenID2()) {
-            $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS,
-                                           'claimed_id');
-            if (!$claimed_id) {
-                return new Auth_OpenID_ServerError($message,
-                  "OpenID 2.0 message contained openid.identity " .
-                  "but not claimed_id");
+        $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id');
+        if ($message->isOpenID1()) {
+            if ($identity === null) {
+                $s = "OpenID 1 message did not contain openid.identity";
+                return new Auth_OpenID_ServerError($message, $s);
             }
         } else {
-            $claimed_id = null;
-        }
-
-        if (($identity === null) &&
-            ($namespace == Auth_OpenID_OPENID1_NS)) {
-            return new Auth_OpenID_ServerError($message,
-              "OpenID 1 message did not contain openid.identity");
+            if ($identity && !$claimed_id) {
+                $s = "OpenID 2.0 message contained openid.identity but not " .
+                  "claimed_id";
+                return new Auth_OpenID_ServerError($message, $s);
+            } else if ($claimed_id && !$identity) {
+                $s = "OpenID 2.0 message contained openid.claimed_id " .
+                  "but not identity";
+                return new Auth_OpenID_ServerError($message, $s);
+            }
         }
 
         // There's a case for making self.trust_root be a TrustRoot
         // here.  But if TrustRoot isn't currently part of the
         // "public" API, I'm not sure it's worth doing.
-        if ($namespace == Auth_OpenID_OPENID1_NS) {
-            $trust_root = $message->getArg(Auth_OpenID_OPENID_NS,
-                                           'trust_root',
-                                           $return_to);
+        if ($message->isOpenID1()) {
+            $trust_root_param = 'trust_root';
         } else {
-            $trust_root = $message->getArg(Auth_OpenID_OPENID_NS,
-                                           'realm',
-                                           $return_to);
+            $trust_root_param = 'realm';
+        }
+        $trust_root = $message->getArg(Auth_OpenID_OPENID_NS, 
+                                       $trust_root_param);
+        if (! $trust_root) {
+            $trust_root = $return_to;
+        }
 
-            if (($return_to === null) &&
-                ($trust_root === null)) {
-                return new Auth_OpenID_ServerError($message,
-                  "openid.realm required when openid.return_to absent");
-            }
+        if (! $message->isOpenID1() && 
+            ($return_to === null) &&
+            ($trust_root === null)) {
+            return new Auth_OpenID_ServerError($message,
+              "openid.realm required when openid.return_to absent");
         }
 
         $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
@@ -873,7 +919,8 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
 
         $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
         if ($tr === false) {
-            return new Auth_OpenID_MalformedTrustRoot(null, $this->trust_root);
+            return new Auth_OpenID_MalformedTrustRoot($this->message,
+                                                      $this->trust_root);
         }
 
         if ($this->return_to !== null) {
@@ -931,7 +978,7 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
         }
 
         if (!$server_url) {
-            if (($this->namespace != Auth_OpenID_OPENID1_NS) &&
+            if ((!$this->message->isOpenID1()) &&
                 (!$this->server->op_endpoint)) {
                 return new Auth_OpenID_ServerError(null,
                   "server should be constructed with op_endpoint to " .
@@ -943,7 +990,7 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
 
         if ($allow) {
             $mode = 'id_res';
-        } else if ($this->namespace == Auth_OpenID_OPENID1_NS) {
+        } else if ($this->message->isOpenID1()) {
             if ($this->immediate) {
                 $mode = 'id_res';
             } else {
@@ -966,7 +1013,7 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
         $response = new Auth_OpenID_ServerResponse($this);
 
         if ($claimed_id &&
-            ($this->namespace == Auth_OpenID_OPENID1_NS)) {
+            ($this->message->isOpenID1())) {
             return new Auth_OpenID_ServerError(null,
               "claimed_id is new in OpenID 2.0 and not " .
               "available for ".$this->namespace);
@@ -1008,7 +1055,7 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
                 $response_identity = null;
             }
 
-            if (($this->namespace == Auth_OpenID_OPENID1_NS) &&
+            if (($this->message->isOpenID1()) &&
                 ($response_identity === null)) {
                 return new Auth_OpenID_ServerError(null,
                   "Request was an OpenID 1 request, so response must " .
@@ -1017,16 +1064,20 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
 
             $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
                    array('mode' => $mode,
-                         'op_endpoint' => $server_url,
                          'return_to' => $this->return_to,
                          'response_nonce' => Auth_OpenID_mkNonce()));
 
+            if (!$this->message->isOpenID1()) {
+                $response->fields->setArg(Auth_OpenID_OPENID_NS,
+                                          'op_endpoint', $server_url);
+            }
+
             if ($response_identity !== null) {
                 $response->fields->setArg(
                                           Auth_OpenID_OPENID_NS,
                                           'identity',
                                           $response_identity);
-                if ($this->namespace == Auth_OpenID_OPENID2_NS) {
+                if ($this->message->isOpenID2()) {
                     $response->fields->setArg(
                                               Auth_OpenID_OPENID_NS,
                                               'claimed_id',
@@ -1039,7 +1090,7 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
                                       'mode', $mode);
 
             if ($this->immediate) {
-                if (($this->namespace == Auth_OpenID_OPENID1_NS) &&
+                if (($this->message->isOpenID1()) &&
                     (!$server_url)) {
                     return new Auth_OpenID_ServerError(null,
                                  'setup_url is required for $allow=false \
@@ -1052,7 +1103,9 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
                                                 $this->trust_root,
                                                 false,
                                                 $this->assoc_handle,
-                                                $this->server);
+                                                $this->server,
+                                                $this->claimed_id);
+                $setup_request->message = $this->message;
 
                 $setup_url = $setup_request->encodeToURL($server_url);
 
@@ -1086,7 +1139,7 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
                    'return_to' => $this->return_to);
 
         if ($this->trust_root) {
-            if ($this->namespace == Auth_OpenID_OPENID1_NS) {
+            if ($this->message->isOpenID1()) {
                 $q['trust_root'] = $this->trust_root;
             } else {
                 $q['realm'] = $this->trust_root;
@@ -1097,9 +1150,9 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
             $q['assoc_handle'] = $this->assoc_handle;
         }
 
-        $response = new Auth_OpenID_Message($this->namespace);
-        $response->updateArgs($this->namespace, $q);
-
+        $response = new Auth_OpenID_Message(
+            $this->message->getOpenIDNamespace());
+        $response->updateArgs(Auth_OpenID_OPENID_NS, $q);
         return $response->toURL($server_url);
     }
 
@@ -1116,7 +1169,8 @@ class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
                                                requests.");
         }
 
-        $response = new Auth_OpenID_Message($this->namespace);
+        $response = new Auth_OpenID_Message(
+            $this->message->getOpenIDNamespace());
         $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
         return $response->toURL($this->return_to);
     }
@@ -1137,22 +1191,63 @@ class Auth_OpenID_ServerResponse {
 
     function whichEncoding()
     {
-        global $_Auth_OpenID_Encode_Kvform,
-            $_Auth_OpenID_Request_Modes,
-            $_Auth_OpenID_Encode_Url;
+      global $_Auth_OpenID_Request_Modes;
 
         if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
-            return $_Auth_OpenID_Encode_Url;
+            if ($this->fields->isOpenID2() &&
+                (strlen($this->encodeToURL()) >
+                   Auth_OpenID_OPENID1_URL_LIMIT)) {
+                return Auth_OpenID_ENCODE_HTML_FORM;
+            } else {
+                return Auth_OpenID_ENCODE_URL;
+            }
         } else {
-            return $_Auth_OpenID_Encode_Kvform;
+            return Auth_OpenID_ENCODE_KVFORM;
         }
     }
 
+    /*
+     * Returns the form markup for this response.
+     *
+     * @return str
+     */
+    function toFormMarkup($form_tag_attrs=null)
+    {
+        return $this->fields->toFormMarkup($this->request->return_to,
+                                           $form_tag_attrs);
+    }
+
+    /*
+     * Returns an HTML document containing the form markup for this
+     * response that autosubmits with javascript.
+     */
+    function toHTML()
+    {
+        return Auth_OpenID::autoSubmitHTML($this->toFormMarkup());
+    }
+
+    /*
+     * Returns True if this response's encoding is ENCODE_HTML_FORM.
+     * Convenience method for server authors.
+     *
+     * @return bool
+     */
+    function renderAsForm()
+    {
+        return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM;
+    }
+
+
     function encodeToURL()
     {
         return $this->fields->toURL($this->request->return_to);
     }
 
+    function addExtension($extension_response)
+    {
+        $extension_response->toMessage($this->fields);
+    }
+
     function needsSigning()
     {
         return $this->fields->getArg(Auth_OpenID_OPENID_NS,
@@ -1354,21 +1449,21 @@ class Auth_OpenID_Encoder {
      */
     function encode(&$response)
     {
-        global $_Auth_OpenID_Encode_Kvform,
-            $_Auth_OpenID_Encode_Url;
-
         $cls = $this->responseFactory;
 
         $encode_as = $response->whichEncoding();
-        if ($encode_as == $_Auth_OpenID_Encode_Kvform) {
+        if ($encode_as == Auth_OpenID_ENCODE_KVFORM) {
             $wr = new $cls(null, null, $response->encodeToKVForm());
             if (is_a($response, 'Auth_OpenID_ServerError')) {
                 $wr->code = AUTH_OPENID_HTTP_ERROR;
             }
-        } else if ($encode_as == $_Auth_OpenID_Encode_Url) {
+        } else if ($encode_as == Auth_OpenID_ENCODE_URL) {
             $location = $response->encodeToURL();
             $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT,
                            array('location' => $location));
+        } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) {
+          $wr = new $cls(AUTH_OPENID_HTTP_OK, array(),
+                         $response->toFormMarkup());
         } else {
             return new Auth_OpenID_EncodingError($response);
         }
@@ -1392,20 +1487,8 @@ class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {
      * Sign an {@link Auth_OpenID_ServerResponse} and return an
      * {@link Auth_OpenID_WebResponse}.
      */
-    function encode(&$response)    {
-
-
-// 
-// 		$trace = debug_backtrace();
-//     	foreach ($trace AS $t) {
-//     		$str = '';
-//     		foreach ($t AS $te) {
-//     			if (is_string($te)) $str .= $te . ' ';
-//     		}
-//     		error_log($str);
-//     	}
-    	
-    	
+    function encode(&$response)
+    {
         // the isinstance is a bit of a kludge... it means there isn't
         // really an adapter to make the interfaces quite match.
         if (!is_a($response, 'Auth_OpenID_ServerError') &&
@@ -1457,12 +1540,34 @@ class Auth_OpenID_Decoder {
 
         $message = Auth_OpenID_Message::fromPostArgs($query);
 
+        if ($message === null) {
+            /*
+             * It's useful to have a Message attached to a
+             * ProtocolError, so we override the bad ns value to build
+             * a Message out of it.  Kinda kludgy, since it's made of
+             * lies, but the parts that aren't lies are more useful
+             * than a 'None'.
+             */
+            $old_ns = $query['openid.ns'];
+
+            $query['openid.ns'] = Auth_OpenID_OPENID2_NS;
+            $message = Auth_OpenID_Message::fromPostArgs($query);
+            return new Auth_OpenID_ServerError(
+                  $message,
+                  sprintf("Invalid OpenID namespace URI: %s", $old_ns));
+        }
+
         $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
         if (!$mode) {
             return new Auth_OpenID_ServerError($message,
                                                "No mode value in message");
         }
 
+        if (Auth_OpenID::isFailure($mode)) {
+            return new Auth_OpenID_ServerError($message,
+                                               $mode->message);
+        }
+
         $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
                                             $this->defaultDecoder($message));
 
@@ -1477,8 +1582,14 @@ class Auth_OpenID_Decoder {
     function defaultDecoder($message)
     {
         $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+
+        if (Auth_OpenID::isFailure($mode)) {
+            return new Auth_OpenID_ServerError($message,
+                                               $mode->message);
+        }
+
         return new Auth_OpenID_ServerError($message,
-                       sprintf("No decoder for mode %s", $mode));
+                       sprintf("Unrecognized OpenID mode %s", $mode));
     }
 }
 
@@ -1590,7 +1701,6 @@ class Auth_OpenID_Server {
             $handler = array($this, "openid_" . $request->mode);
             return call_user_func($handler, $request);
         }
-        error_log('Method did not exist ' . "openid_" . $request->mode);
         return null;
     }
 
diff --git a/lib/Auth/OpenID/ServerRequest.php b/lib/Auth/OpenID/ServerRequest.php
index 00728941a5a60f23d38c6dd116c6c34b0a1d44e4..33a8556ceaacd102602d3f1ab6c893c9f4c586ca 100644
--- a/lib/Auth/OpenID/ServerRequest.php
+++ b/lib/Auth/OpenID/ServerRequest.php
@@ -10,8 +10,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
diff --git a/lib/Auth/OpenID/TrustRoot.php b/lib/Auth/OpenID/TrustRoot.php
index 88eff295d7300fe5ae540759c5a9d3e90d175659..4919a60651d3c76c83e5366325b9d3568bfe97a6 100644
--- a/lib/Auth/OpenID/TrustRoot.php
+++ b/lib/Auth/OpenID/TrustRoot.php
@@ -8,10 +8,12 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
+require_once 'Auth/OpenID/Discover.php';
+
 /**
  * A regular expression that matches a domain ending in a top-level domains.
  * Used in checking trust roots for sanity.
@@ -19,23 +21,66 @@
  * @access private
  */
 define('Auth_OpenID___TLDs',
-       '/\.(com|edu|gov|int|mil|net|org|biz|info|name|museum|coop|aero|ac|' .
-       'ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|' .
-       'bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|' .
-       'cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|' .
-       'fi|fj|fk|fm|fo|fr|ga|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|' .
-       'gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|' .
-       'ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|' .
-       'ma|mc|md|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|' .
-       'nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|' .
-       'ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|' .
-       'so|sr|st|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|' .
-       'ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)$/');
+       '/\.(ac|ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|asia' .
+       '|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br' .
+       '|bs|bt|bv|bw|by|bz|ca|cat|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co' .
+       '|com|coop|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg' .
+       '|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl' .
+       '|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie' .
+       '|il|im|in|info|int|io|iq|ir|is|it|je|jm|jo|jobs|jp|ke|kg|kh' .
+       '|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly' .
+       '|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mo|mobi|mp|mq|mr|ms|mt' .
+       '|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no' .
+       '|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt' .
+       '|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl' .
+       '|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm' .
+       '|tn|to|tp|tr|travel|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve' .
+       '|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g' .
+       '|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d' .
+       '|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp' .
+       '|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)\.?$/');
+
+define('Auth_OpenID___HostSegmentRe',
+       "/^(?:[-a-zA-Z0-9!$&'\\(\\)\\*+,;=._~]|%[a-zA-Z0-9]{2})*$/");
 
 /**
  * A wrapper for trust-root related functions
  */
 class Auth_OpenID_TrustRoot {
+    /*
+     * Return a discovery URL for this realm.
+     *
+     * Return null if the realm could not be parsed or was not valid.
+     *
+     * @param return_to The relying party return URL of the OpenID
+     * authentication request
+     *
+     * @return The URL upon which relying party discovery should be
+     * run in order to verify the return_to URL
+     */
+    function buildDiscoveryURL($realm)
+    {
+        $parsed = Auth_OpenID_TrustRoot::_parse($realm);
+
+        if ($parsed === false) {
+            return false;
+        }
+
+        if ($parsed['wildcard']) {
+            // Use "www." in place of the star
+            if ($parsed['host'][0] != '.') {
+                return false;
+            }
+
+            $www_domain = 'www' . $parsed['host'];
+
+            return sprintf('%s://%s%s', $parsed['scheme'],
+                           $www_domain, $parsed['path']);
+        } else {
+            return $parsed['unparsed'];
+        }
+    }
+
     /**
      * Parse a URL into its trust_root parts.
      *
@@ -50,10 +95,20 @@ class Auth_OpenID_TrustRoot {
      */
     function _parse($trust_root)
     {
+        $trust_root = Auth_OpenID_urinorm($trust_root);
+        if ($trust_root === null) {
+            return false;
+        }
+
+        if (preg_match("/:\/\/[^:]+(:\d+){2,}(\/|$)/", $trust_root)) {
+            return false;
+        }
+
         $parts = @parse_url($trust_root);
         if ($parts === false) {
             return false;
         }
+
         $required_parts = array('scheme', 'host');
         $forbidden_parts = array('user', 'pass', 'fragment');
         $keys = array_keys($parts);
@@ -65,9 +120,7 @@ class Auth_OpenID_TrustRoot {
             return false;
         }
 
-        // Return false if the original trust root value has more than
-        // one port specification.
-        if (preg_match("/:\/\/[^:]+(:\d+){2,}(\/|$)/", $trust_root)) {
+        if (!preg_match(Auth_OpenID___HostSegmentRe, $parts['host'])) {
             return false;
         }
 
@@ -103,16 +156,21 @@ class Auth_OpenID_TrustRoot {
 
         if (isset($parts['path'])) {
             $path = strtolower($parts['path']);
-            if (substr($path, -1) != '/') {
-                $path .= '/';
+            if (substr($path, 0, 1) != '/') {
+                return false;
             }
         } else {
             $path = '/';
         }
+
         $parts['path'] = $path;
         if (!isset($parts['port'])) {
             $parts['port'] = false;
         }
+
+
+        $parts['unparsed'] = $trust_root;
+
         return $parts;
     }
 
@@ -152,6 +210,25 @@ class Auth_OpenID_TrustRoot {
         if ($parts['host'] == 'localhost') {
             return true;
         }
+        
+        $host_parts = explode('.', $parts['host']);
+        if ($parts['wildcard']) {
+            // Remove the empty string from the beginning of the array
+            array_shift($host_parts);
+        }
+
+        if ($host_parts && !$host_parts[count($host_parts) - 1]) {
+            array_pop($host_parts);
+        }
+
+        if (!$host_parts) {
+            return false;
+        }
+
+        // Don't allow adjacent dots
+        if (in_array('', $host_parts, true)) {
+            return false;
+        }
 
         // Get the top-level domain of the host. If it is not a valid TLD,
         // it's not sane.
@@ -161,19 +238,20 @@ class Auth_OpenID_TrustRoot {
         }
         $tld = $matches[1];
 
-        // Require at least two levels of specificity for non-country
-        // tlds and three levels for country tlds.
-        $elements = explode('.', $parts['host']);
-        $n = count($elements);
-        if ($parts['wildcard']) {
-            $n -= 1;
-        }
-        if (strlen($tld) == 2) {
-            $n -= 1;
-        }
-        if ($n <= 1) {
+        if (count($host_parts) == 1) {
             return false;
         }
+
+        if ($parts['wildcard']) {
+            // It's a 2-letter tld with a short second to last segment
+            // so there needs to be more than two segments specified
+            // (e.g. *.co.uk is insane)
+            $second_level = $host_parts[count($host_parts) - 2];
+            if (strlen($tld) == 2 && strlen($second_level) <= 3) {
+                return count($host_parts) > 2;
+            }
+        }
+
         return true;
     }
 
@@ -221,8 +299,14 @@ class Auth_OpenID_TrustRoot {
         $base_path = $trust_root_parsed['path'];
         $path = $url_parsed['path'];
         if (!isset($trust_root_parsed['query'])) {
-            if (substr($path, 0, strlen($base_path)) != $base_path) {
-                return false;
+            if ($base_path != $path) {
+                if (substr($path, 0, strlen($base_path)) != $base_path) {
+                    return false;
+                }
+                if (substr($base_path, strlen($base_path) - 1, 1) != '/' &&
+                    substr($path, strlen($base_path), 1) != '/') {
+                    return false;
+                }
             }
         } else {
             $base_query = $trust_root_parsed['query'];
@@ -240,4 +324,139 @@ class Auth_OpenID_TrustRoot {
                 $url_parsed['port'] === $trust_root_parsed['port']);
     }
 }
+
+/*
+ * If the endpoint is a relying party OpenID return_to endpoint,
+ * return the endpoint URL. Otherwise, return None.
+ *
+ * This function is intended to be used as a filter for the Yadis
+ * filtering interface.
+ *
+ * @see: C{L{openid.yadis.services}}
+ * @see: C{L{openid.yadis.filters}}
+ *
+ * @param endpoint: An XRDS BasicServiceEndpoint, as returned by
+ * performing Yadis dicovery.
+ *
+ * @returns: The endpoint URL or None if the endpoint is not a
+ * relying party endpoint.
+ */
+function filter_extractReturnURL(&$endpoint)
+{
+    if ($endpoint->matchTypes(array(Auth_OpenID_RP_RETURN_TO_URL_TYPE))) {
+        return $endpoint;
+    } else {
+        return null;
+    }
+}
+
+function &Auth_OpenID_extractReturnURL(&$endpoint_list)
+{
+    $result = array();
+
+    foreach ($endpoint_list as $endpoint) {
+        if (filter_extractReturnURL($endpoint)) {
+            $result[] = $endpoint;
+        }
+    }
+
+    return $result;
+}
+
+/*
+ * Is the return_to URL under one of the supplied allowed return_to
+ * URLs?
+ */
+function Auth_OpenID_returnToMatches($allowed_return_to_urls, $return_to)
+{
+    foreach ($allowed_return_to_urls as $allowed_return_to) {
+        // A return_to pattern works the same as a realm, except that
+        // it's not allowed to use a wildcard. We'll model this by
+        // parsing it as a realm, and not trying to match it if it has
+        // a wildcard.
+
+        $return_realm = Auth_OpenID_TrustRoot::_parse($allowed_return_to);
+        if (// Parses as a trust root
+            ($return_realm !== false) &&
+            // Does not have a wildcard
+            (!$return_realm['wildcard']) &&
+            // Matches the return_to that we passed in with it
+            (Auth_OpenID_TrustRoot::match($allowed_return_to, $return_to))) {
+            return true;
+        }
+    }
+
+    // No URL in the list matched
+    return false;
+}
+
+/*
+ * Given a relying party discovery URL return a list of return_to
+ * URLs.
+ */
+function Auth_OpenID_getAllowedReturnURLs($relying_party_url, &$fetcher,
+              $discover_function=null)
+{
+    if ($discover_function === null) {
+        $discover_function = array('Auth_Yadis_Yadis', 'discover');
+    }
+
+    $xrds_parse_cb = array('Auth_OpenID_ServiceEndpoint', 'fromXRDS');
+
+    list($rp_url_after_redirects, $endpoints) =
+        Auth_Yadis_getServiceEndpoints($relying_party_url, $xrds_parse_cb,
+                                       $discover_function, $fetcher);
+
+    if ($rp_url_after_redirects != $relying_party_url) {
+        // Verification caused a redirect
+        return false;
+    }
+
+    call_user_func_array($discover_function,
+                         array($relying_party_url, $fetcher));
+
+    $return_to_urls = array();
+    $matching_endpoints = Auth_OpenID_extractReturnURL($endpoints);
+
+    foreach ($matching_endpoints as $e) {
+        $return_to_urls[] = $e->server_url;
+    }
+
+    return $return_to_urls;
+}
+
+/*
+ * Verify that a return_to URL is valid for the given realm.
+ *
+ * This function builds a discovery URL, performs Yadis discovery on
+ * it, makes sure that the URL does not redirect, parses out the
+ * return_to URLs, and finally checks to see if the current return_to
+ * URL matches the return_to.
+ *
+ * @return true if the return_to URL is valid for the realm
+ */
+function Auth_OpenID_verifyReturnTo($realm_str, $return_to, &$fetcher,
+              $_vrfy='Auth_OpenID_getAllowedReturnURLs')
+{
+    $disco_url = Auth_OpenID_TrustRoot::buildDiscoveryURL($realm_str);
+
+    if ($disco_url === false) {
+        return false;
+    }
+
+    $allowable_urls = call_user_func_array($_vrfy,
+                           array($disco_url, &$fetcher));
+
+    // The realm_str could not be parsed.
+    if ($allowable_urls === false) {
+        return false;
+    }
+
+    if (Auth_OpenID_returnToMatches($allowable_urls, $return_to)) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
 ?>
\ No newline at end of file
diff --git a/lib/Auth/OpenID/URINorm.php b/lib/Auth/OpenID/URINorm.php
index 60e900a4c1737fb0967e396c40ccd418e3f4a5ef..f821d836a90b2256bb06727d45c629a408535813 100644
--- a/lib/Auth/OpenID/URINorm.php
+++ b/lib/Auth/OpenID/URINorm.php
@@ -5,8 +5,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 require_once 'Auth/Yadis/Misc.php';
@@ -27,6 +27,17 @@ function Auth_OpenID_getEncodedPattern()
     return '/%([0-9A-Fa-f]{2})/';
 }
 
+# gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+#
+# sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
+#                  / "*" / "+" / "," / ";" / "="
+#
+# unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
+function Auth_OpenID_getURLIllegalCharRE()
+{
+    return "/([^-A-Za-z0-9:\/\?#\[\]@\!\$&'\(\)\*\+,;=\._~\%])/";
+}
+
 function Auth_OpenID_getUnreserved()
 {
     $_unreserved = array();
@@ -88,7 +99,7 @@ function Auth_OpenID_pct_encoded_replace($mo)
 function Auth_OpenID_remove_dot_segments($path)
 {
     $result_segments = array();
-    
+
     while ($path) {
         if (Auth_Yadis_startswith($path, '../')) {
             $path = substr($path, 3);
@@ -139,6 +150,13 @@ function Auth_OpenID_urinorm($uri)
         }
     }
 
+    $illegal_matches = array();
+    preg_match(Auth_OpenID_getURLIllegalCharRE(),
+               $uri, $illegal_matches);
+    if ($illegal_matches) {
+        return null;
+    }
+
     $scheme = $uri_matches[2];
     if ($scheme) {
         $scheme = strtolower($scheme);
diff --git a/lib/Auth/Yadis/HTTPFetcher.php b/lib/Auth/Yadis/HTTPFetcher.php
index 4b461404fce27c7396635c79aafb19357d83e133..963b9a49a48f50495e561be4863957b61aa377a3 100644
--- a/lib/Auth/Yadis/HTTPFetcher.php
+++ b/lib/Auth/Yadis/HTTPFetcher.php
@@ -9,10 +9,19 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @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)
@@ -36,6 +45,30 @@ 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.
@@ -85,7 +118,7 @@ class Auth_Yadis_HTTPFetcher {
     function _findRedirect($headers)
     {
         foreach ($headers as $line) {
-            if (strpos($line, "Location: ") === 0) {
+            if (strpos(strtolower($line), "location: ") === 0) {
                 $parts = explode(" ", $line, 2);
                 return $parts[1];
             }
@@ -105,7 +138,7 @@ class Auth_Yadis_HTTPFetcher {
      * pass the URLHasAllowedScheme check or if the server's response
      * is malformed.
      */
-    function get($url, $headers)
+    function get($url, $headers = null)
     {
         trigger_error("not implemented", E_USER_ERROR);
     }
diff --git a/lib/Auth/Yadis/Manager.php b/lib/Auth/Yadis/Manager.php
index bb60b8ae244452d40c3db7cda343c791c4c9de1c..d50cf7ad65eb697f1ab8ef745433338bc1c04f92 100644
--- a/lib/Auth/Yadis/Manager.php
+++ b/lib/Auth/Yadis/Manager.php
@@ -361,7 +361,7 @@ class Auth_Yadis_Manager {
  *
  * 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. Once a request completes, call .cleanup() to clean up the
  * session state.
  *
  * @package OpenID
@@ -410,7 +410,6 @@ class Auth_Yadis_Discovery {
         $manager = $this->getManager();
         if (!$manager || (!$manager->services)) {
             $this->destroyManager();
-            $http_response = array();
 
             list($yadis_url, $services) = call_user_func($discover_cb,
                                                          $this->url,
@@ -435,13 +434,16 @@ class Auth_Yadis_Discovery {
      * 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()
+    function cleanup($force=false)
     {
-        $manager = $this->getManager();
+        $manager = $this->getManager($force);
         if ($manager) {
             $service = $manager->current();
-            $this->destroyManager();
+            $this->destroyManager($force);
         } else {
             $service = null;
         }
@@ -460,8 +462,11 @@ class Auth_Yadis_Discovery {
 
     /**
      * @access private
+     *
+     * @param $force True if the manager should be returned regardless
+     * of whether it's a manager for $this->url.
      */
-    function &getManager()
+    function &getManager($force=false)
     {
         // Extract the YadisServiceManager for this object's URL and
         // suffix from the session.
@@ -474,7 +479,7 @@ class Auth_Yadis_Discovery {
             $manager = $loader->fromSession(unserialize($manager_str));
         }
 
-        if ($manager && $manager->forURL($this->url)) {
+        if ($manager && ($manager->forURL($this->url) || $force)) {
             return $manager;
         } else {
             $unused = null;
@@ -508,10 +513,13 @@ class Auth_Yadis_Discovery {
 
     /**
      * @access private
+     *
+     * @param $force True if the manager should be deleted regardless
+     * of whether it's a manager for $this->url.
      */
-    function destroyManager()
+    function destroyManager($force=false)
     {
-        if ($this->getManager() !== null) {
+        if ($this->getManager($force) !== null) {
             $key = $this->getSessionKey();
             $this->session->del($key);
         }
diff --git a/lib/Auth/Yadis/Misc.php b/lib/Auth/Yadis/Misc.php
index a29ce4a00b5e13b1a3db5d92f8249d991fe5d672..1134a4ff4bc729f4ab2172848c4d212d80a12438 100644
--- a/lib/Auth/Yadis/Misc.php
+++ b/lib/Auth/Yadis/Misc.php
@@ -5,8 +5,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 function Auth_Yadis_getUCSChars()
diff --git a/lib/Auth/Yadis/ParanoidHTTPFetcher.php b/lib/Auth/Yadis/ParanoidHTTPFetcher.php
index 08aced69218a2d220021db86c9ac26535d2bbebb..6a418260eefebfa409768d5d750e1026457ff4dd 100644
--- a/lib/Auth/Yadis/ParanoidHTTPFetcher.php
+++ b/lib/Auth/Yadis/ParanoidHTTPFetcher.php
@@ -9,8 +9,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -18,6 +18,8 @@
  */
 require_once "Auth/Yadis/HTTPFetcher.php";
 
+require_once "Auth/OpenID.php";
+
 /**
  * A paranoid {@link Auth_Yadis_HTTPFetcher} class which uses CURL
  * for fetching.
@@ -50,8 +52,12 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
      */
     function _writeData($ch, $data)
     {
-        $this->data .= $data;
-        return strlen($data);
+        if (strlen($this->data) > 1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB) {
+            return 0;
+        } else {
+            $this->data .= $data;
+            return strlen($data);
+        }
     }
 
     /**
@@ -60,12 +66,18 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
     function supportsSSL()
     {
         $v = curl_version();
-        return in_array('https', $v['protocols']);
+        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->isHTTPS($url) && !$this->supportsSSL()) {
+        if (!$this->canFetchURL($url)) {
             return null;
         }
 
@@ -78,11 +90,21 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
             $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;
             }
 
@@ -95,6 +117,14 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
                 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);
 
@@ -105,6 +135,9 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
             $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;
             }
 
@@ -118,12 +151,16 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
                 $new_headers = array();
 
                 foreach ($headers as $header) {
-                    if (preg_match("/:/", $header)) {
-                        list($name, $value) = explode(": ", $header, 2);
+                    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);
             }
@@ -136,19 +173,18 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
 
     function post($url, $body, $extra_headers = null)
     {
-        $this->reset();
-
-        if ($this->isHTTPS($url) && !$this->supportsSSL()) {
+        if (!$this->canFetchURL($url)) {
             return null;
         }
 
-        if (!$this->allowedURL($url)) {
-            return null;
-        }
+        $this->reset();
 
         $c = curl_init();
 
-        curl_setopt($c, CURLOPT_NOSIGNAL, true);
+        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);
@@ -161,6 +197,7 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
         $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
 
         if (!$code) {
+            Auth_OpenID::log("Got no response code when fetching %s", $url);
             return null;
         }
 
@@ -168,20 +205,19 @@ class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
 
         curl_close($c);
 
-        if ($extra_headers === null) {
-            $new_headers = null;
-        } else {
-            $new_headers = $extra_headers;
-        }
+        $new_headers = $extra_headers;
 
         foreach ($this->headers as $header) {
-            if (preg_match("/:/", $header)) {
-                list($name, $value) = explode(": ", $header, 2);
+            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);
     }
diff --git a/lib/Auth/Yadis/ParseHTML.php b/lib/Auth/Yadis/ParseHTML.php
index 1922917ae681ad5f277a355c47ef858bdf1aefc5..297ccbd2c34d536a75f7bbc9fd6a1af09c1c624f 100644
--- a/lib/Auth/Yadis/ParseHTML.php
+++ b/lib/Auth/Yadis/ParseHTML.php
@@ -9,8 +9,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -41,7 +41,7 @@ class Auth_Yadis_ParseHTML {
     /**
      * @access private
      */
-    var $_attr_find = '\b([-\w]+)=(".*?"|\'.*?\'|.+?)[\s>]';
+    var $_attr_find = '\b([-\w]+)=(".*?"|\'.*?\'|.+?)[\/\s>]';
 
     function Auth_Yadis_ParseHTML()
     {
diff --git a/lib/Auth/Yadis/PlainHTTPFetcher.php b/lib/Auth/Yadis/PlainHTTPFetcher.php
index 15dc3a7385c22bd65251ad0843eb2c161b2d5329..3e0ca2bb0c7302f5496244f4d9b36bd3fc1ce56a 100644
--- a/lib/Auth/Yadis/PlainHTTPFetcher.php
+++ b/lib/Auth/Yadis/PlainHTTPFetcher.php
@@ -10,8 +10,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -36,11 +36,7 @@ class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
 
     function get($url, $extra_headers = null)
     {
-        if ($this->isHTTPS($url) && !$this->supportsSSL()) {
-            return null;
-        }
-
-        if (!$this->allowedURL($url)) {
+        if (!$this->canFetchURL($url)) {
             return null;
         }
 
@@ -67,13 +63,17 @@ class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
                 }
             }
 
+            if (!array_key_exists('path', $parts)) {
+                $parts['path'] = '/';
+            }
+
             $host = $parts['host'];
 
             if ($parts['scheme'] == 'https') {
                 $host = 'ssl://' . $host;
             }
 
-            $user_agent = "PHP Yadis Library Fetcher";
+            $user_agent = Auth_OpenID_USER_AGENT;
 
             $headers = array(
                              "GET ".$parts['path'].
@@ -105,8 +105,11 @@ class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
             fputs($sock, implode("\r\n", $headers) . "\r\n\r\n");
 
             $data = "";
-            while (!feof($sock)) {
+            $kilobytes = 0;
+            while (!feof($sock) &&
+                   $kilobytes < Auth_OpenID_FETCHER_MAX_RESPONSE_KB ) {
                 $data .= fgets($sock, 1024);
+                $kilobytes += 1;
             }
 
             fclose($sock);
@@ -132,8 +135,12 @@ class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
 
         foreach ($headers as $header) {
             if (preg_match("/:/", $header)) {
-                list($name, $value) = explode(": ", $header, 2);
-                $new_headers[$name] = $value;
+                $parts = explode(": ", $header, 2);
+
+                if (count($parts) == 2) {
+                    list($name, $value) = $parts;
+                    $new_headers[$name] = $value;
+                }
             }
 
         }
@@ -143,11 +150,7 @@ class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
 
     function post($url, $body, $extra_headers = null)
     {
-        if ($this->isHTTPS($url) && !$this->supportsSSL()) {
-            return null;
-        }
-
-        if (!$this->allowedURL($url)) {
+        if (!$this->canFetchURL($url)) {
             return null;
         }
 
diff --git a/lib/Auth/Yadis/XML.php b/lib/Auth/Yadis/XML.php
index 4854f12bbcd2e3ca5d4f382a16e9491da0ee611b..81b2ce2210a2332a7a82b202b7e3c244542ab3f9 100644
--- a/lib/Auth/Yadis/XML.php
+++ b/lib/Auth/Yadis/XML.php
@@ -91,7 +91,7 @@ class Auth_Yadis_XMLParser {
      * @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)
+    function &evalXPath($xpath, $node = null)
     {
         // Not implemented.
     }
diff --git a/lib/Auth/Yadis/XRDS.php b/lib/Auth/Yadis/XRDS.php
index 127bc961ce5e8a08cf6a4856e8db341cd6f3eec4..f14a7948e1a4379e61da6b1be9b3493182129d48 100644
--- a/lib/Auth/Yadis/XRDS.php
+++ b/lib/Auth/Yadis/XRDS.php
@@ -9,8 +9,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -204,6 +204,36 @@ class Auth_Yadis_Service {
     }
 }
 
+/*
+ * 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.
  *
diff --git a/lib/Auth/Yadis/XRI.php b/lib/Auth/Yadis/XRI.php
index d9d0726342697d668d17d9caac84ec46ea90e8c2..4e346231767b47e13f54642fd8a315b93a5681ee 100644
--- a/lib/Auth/Yadis/XRI.php
+++ b/lib/Auth/Yadis/XRI.php
@@ -5,8 +5,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 require_once 'Auth/Yadis/Misc.php';
@@ -15,7 +15,7 @@ require_once 'Auth/OpenID.php';
 
 function Auth_Yadis_getDefaultProxy()
 {
-    return 'http://proxy.xri.net/';
+    return 'http://xri.net/';
 }
 
 function Auth_Yadis_getXRIAuthorities()
@@ -43,7 +43,8 @@ function Auth_Yadis_getXrefRE()
 function Auth_Yadis_identifierScheme($identifier)
 {
     if (Auth_Yadis_startswith($identifier, 'xri://') ||
-        (in_array($identifier[0], Auth_Yadis_getXRIAuthorities()))) {
+        ($identifier &&
+          in_array($identifier[0], Auth_Yadis_getXRIAuthorities()))) {
         return "XRI";
     } else {
         return "URI";
@@ -198,7 +199,7 @@ function Auth_Yadis_getCanonicalID($iname, $xrds)
         return false;
     }
 
-    $canonicalID = $canonicalID_nodes[count($canonicalID_nodes) - 1];
+    $canonicalID = $canonicalID_nodes[0];
     $canonicalID = Auth_Yadis_XRI($parser->content($canonicalID));
 
     $childID = $canonicalID;
@@ -207,13 +208,13 @@ function Auth_Yadis_getCanonicalID($iname, $xrds)
         $xrd = $xrd_list[$i];
 
         $parent_sought = substr($childID, 0, strrpos($childID, '!'));
-        $parent_list = array();
-
-        foreach ($parser->evalXPath('xrd:CanonicalID', $xrd) as $c) {
-            $parent_list[] = Auth_Yadis_XRI($parser->content($c));
+        $parentCID = $parser->evalXPath('xrd:CanonicalID', $xrd);
+        if (!$parentCID) {
+            return false;
         }
+        $parentCID = Auth_Yadis_XRI($parser->content($parentCID[0]));
 
-        if (!in_array($parent_sought, $parent_list)) {
+        if (strcasecmp($parent_sought, $parentCID)) {
             // raise XRDSFraud.
             return false;
         }
@@ -230,4 +231,4 @@ function Auth_Yadis_getCanonicalID($iname, $xrds)
     return $canonicalID;
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/lib/Auth/Yadis/XRIRes.php b/lib/Auth/Yadis/XRIRes.php
index b90591fe2a88ec39cb539f327a01e51388106a21..4e8e8d0372deb1bd308a2db4ca7dc8101dcfb3dd 100644
--- a/lib/Auth/Yadis/XRIRes.php
+++ b/lib/Auth/Yadis/XRIRes.php
@@ -44,7 +44,7 @@ class Auth_Yadis_ProxyResolver {
         foreach ($service_types as $service_type) {
             $url = $this->queryURL($xri, $service_type);
             $response = $this->fetcher->get($url);
-            if ($response->status != 200) {
+            if ($response->status != 200 and $response->status != 206) {
                 continue;
             }
             $xrds = Auth_Yadis_XRDS::parseXRDS($response->body);
@@ -69,4 +69,4 @@ class Auth_Yadis_ProxyResolver {
     }
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/lib/Auth/Yadis/Yadis.php b/lib/Auth/Yadis/Yadis.php
index 514703cd0ed7a716ddaabe61e88b657f6c4f29fa..d89f77c6d7c5f4cbf647c45661f91f8325941b17 100644
--- a/lib/Auth/Yadis/Yadis.php
+++ b/lib/Auth/Yadis/Yadis.php
@@ -9,8 +9,8 @@
  *
  * @package OpenID
  * @author JanRain, Inc. <openid@janrain.com>
- * @copyright 2005 Janrain, Inc.
- * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  */
 
 /**
@@ -105,7 +105,7 @@ class Auth_Yadis_DiscoveryResult {
     function usedYadisLocation()
     {
         // Was the Yadis protocol's indirection used?
-        return $this->normalized_uri == $this->xrds_uri;
+        return $this->normalized_uri != $this->xrds_uri;
     }
 
     function isXRDS()
@@ -116,6 +116,48 @@ class Auth_Yadis_DiscoveryResult {
     }
 }
 
+/**
+ *
+ * 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
@@ -281,15 +323,17 @@ class Auth_Yadis_Yadis {
         $result = new Auth_Yadis_DiscoveryResult($uri);
 
         $request_uri = $uri;
-        $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE);
+        $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
+                         ', text/html; q=0.3, application/xhtml+xml; q=0.5');
 
-        if (!$fetcher) {
+        if ($fetcher === null) {
             $fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
         }
 
         $response = $fetcher->get($uri, $headers);
 
-        if (!$response || ($response->status != 200)) {
+        if (!$response || ($response->status != 200 and
+                           $response->status != 206)) {
             $result->fail();
             return $result;
         }
@@ -318,7 +362,8 @@ class Auth_Yadis_Yadis {
 
                 $response = $fetcher->get($yadis_location);
 
-                if ($response->status != 200) {
+                if ((!$response) || ($response->status != 200 and
+                                     $response->status != 206)) {
                     $result->fail();
                     return $result;
                 }
@@ -334,4 +379,4 @@ class Auth_Yadis_Yadis {
     }
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/modules/openid/www/consumer.php b/modules/openid/www/consumer.php
index c697cdedc4f67bca9fd991ac46d198d9aa915a96..f0ca314f939ef1d745e424359a48c7f05f78cd70 100644
--- a/modules/openid/www/consumer.php
+++ b/modules/openid/www/consumer.php
@@ -146,9 +146,11 @@ function run_finish_auth() {
 	
 		$consumer = getConsumer();
 	
+		$return_to = SimpleSAML_Utilities::selfURL();
+
 		// Complete the authentication process using the server's
 		// response.
-		$response = $consumer->complete();
+		$response = $consumer->complete($return_to);
 	
 		// Check the response status.
 		if ($response->status == Auth_OpenID_CANCEL) {