code:
<?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.
     */
    static 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.
     */
    static 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
     */
    static 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) && ($ax_args['count.' . $alias] !== Auth_OpenID_AX_UNLIMITED_VALUES)) {

                $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
     */
    static 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;
    }
}


code:
<?php

/**
 * This module contains code for dealing with associations between
 * consumers and servers.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

/**
 * @access private
 */
require_once 'Auth/OpenID/CryptUtil.php';

/**
 * @access private
 */
require_once 'Auth/OpenID/KVForm.php';

/**
 * @access private
 */
require_once 'Auth/OpenID/HMAC.php';

/**
 * This class represents an association between a server and a
 * consumer.  In general, users of this library will never see
 * instances of this object.  The only exception is if you implement a
 * custom {@link Auth_OpenID_OpenIDStore}.
 *
 * If you do implement such a store, it will need to store the values
 * of the handle, secret, issued, lifetime, and assoc_type instance
 * variables.
 *
 * @package OpenID
 */
class Auth_OpenID_Association {

    /**
     * This is a HMAC-SHA1 specific value.
     *
     * @access private
     */
    var $SIG_LENGTH = 20;

    /**
     * The ordering and name of keys as stored by serialize.
     *
     * @access private
     */
    var $assoc_keys = array(
                            'version',
                            'handle',
                            'secret',
                            'issued',
                            'lifetime',
                            '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
     * implementations shouldn't use this constructor.
     *
     * @access private
     *
     * @param integer $expires_in This is the amount of time this
     * association is good for, measured in seconds since the
     * association was issued.
     *
     * @param string $handle This is the handle the server gave this
     * association.
     *
     * @param string secret This is the shared secret the server
     * generated for this association.
     *
     * @param assoc_type This is the type of association this
     * 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.
     */
    static function fromExpiresIn($expires_in, $handle, $secret, $assoc_type)
    {
        $issued = time();
        $lifetime = $expires_in;
        return new Auth_OpenID_Association($handle, $secret,
                                           $issued, $lifetime, $assoc_type);
    }

    /**
     * This is the standard constructor for creating an association.
     * The library should create all of the necessary associations, so
     * this constructor is not part of the external API.
     *
     * @access private
     *
     * @param string $handle This is the handle the server gave this
     * association.
     *
     * @param string $secret This is the shared secret the server
     * generated for this association.
     *
     * @param integer $issued This is the time this association was
     * issued, in seconds since 00:00 GMT, January 1, 1970.  (ie, a
     * unix timestamp)
     *
     * @param integer $lifetime This is the amount of time this
     * association is good for, measured in seconds since the
     * association was issued.
     *
     * @param string $assoc_type This is the type of association this
     * 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)
    {
        if (!in_array($assoc_type,
                      Auth_OpenID_getSupportedAssociationTypes(), true)) {
            $fmt = 'Unsupported association type (%s)';
            trigger_error(sprintf($fmt, $assoc_type), E_USER_ERROR);
        }

        $this->handle = $handle;
        $this->secret = $secret;
        $this->issued = $issued;
        $this->lifetime = $lifetime;
        $this->assoc_type = $assoc_type;
    }

    /**
     * This returns the number of seconds this association is still
     * valid for, or 0 if the association is no longer valid.
     *
     * @return integer $seconds The number of seconds this association
     * is still valid for, or 0 if the association is no longer valid.
     */
    function getExpiresIn($now = null)
    {
        if ($now == null) {
            $now = time();
        }

        return max(0, $this->issued + $this->lifetime - $now);
    }

    /**
     * This checks to see if two {@link Auth_OpenID_Association}
     * instances represent the same association.
     *
     * @return bool $result true if the two instances represent the
     * same association, false otherwise.
     */
    function equal($other)
    {
        return ((gettype($this) == gettype($other))
                && ($this->handle == $other->handle)
                && ($this->secret == $other->secret)
                && ($this->issued == $other->issued)
                && ($this->lifetime == $other->lifetime)
                && ($this->assoc_type == $other->assoc_type));
    }

    /**
     * Convert an association to KV form.
     *
     * @return string $result String in KV form suitable for
     * deserialization by deserialize.
     */
    function serialize()
    {
        $data = array(
                     'version' => '2',
                     'handle' => $this->handle,
                     'secret' => base64_encode($this->secret),
                     'issued' => strval(intval($this->issued)),
                     'lifetime' => strval(intval($this->lifetime)),
                     'assoc_type' => $this->assoc_type
                     );

        assert(array_keys($data) == $this->assoc_keys);

        return Auth_OpenID_KVForm::fromArray($data, $strict = true);
    }

    /**
     * Parse an association as stored by serialize().  This is the
     * inverse of serialize.
     *
     * @param string $assoc_s Association as serialized by serialize()
     * @return Auth_OpenID_Association $result instance of this class
     */
    static function deserialize($class_name, $assoc_s)
    {
        $pairs = Auth_OpenID_KVForm::toArray($assoc_s, $strict = true);
        $keys = array();
        $values = array();
        foreach ($pairs as $key => $value) {
            if (is_array($value)) {
                list($key, $value) = $value;
            }
            $keys[] = $key;
            $values[] = $value;
        }

        $class_vars = get_class_vars($class_name);
        $class_assoc_keys = $class_vars['assoc_keys'];

        sort($keys);
        sort($class_assoc_keys);

        if ($keys != $class_assoc_keys) {
            trigger_error('Unexpected key values: ' . var_export($keys, true),
                          E_USER_WARNING);
            return null;
        }

        $version = $pairs['version'];
        $handle = $pairs['handle'];
        $secret = $pairs['secret'];
        $issued = $pairs['issued'];
        $lifetime = $pairs['lifetime'];
        $assoc_type = $pairs['assoc_type'];

        if ($version != '2') {
            trigger_error('Unknown version: ' . $version, E_USER_WARNING);
            return null;
        }

        $issued = intval($issued);
        $lifetime = intval($lifetime);
        $secret = base64_decode($secret);

        return new $class_name(
            $handle, $secret, $issued, $lifetime, $assoc_type);
    }

    /**
     * Generate a signature for a sequence of (key, value) pairs
     *
     * @access private
     * @param array $pairs The pairs to sign, in order.  This is an
     * array of two-tuples.
     * @return string $signature The binary signature of this sequence
     * of pairs
     */
    function sign($pairs)
    {
        $kv = Auth_OpenID_KVForm::fromArray($pairs);

        /* Invalid association types should be caught at constructor */
        $callback = $this->_macs[$this->assoc_type];

        return call_user_func_array($callback, array($this->secret, $kv));
    }

    /**
     * Generate a signature for some fields in a dictionary
     *
     * @access private
     * @param array $fields The fields to sign, in order; this is an
     * array of strings.
     * @param array $data Dictionary of values to sign (an array of
     * string => string pairs).
     * @return string $signature The signature, base64 encoded
     */
    function signMessage($message)
    {
        if ($message->hasKey(Auth_OpenID_OPENID_NS, 'sig') ||
            $message->hasKey(Auth_OpenID_OPENID_NS, 'signed')) {
            // Already has a sig
            return null;
        }

        $extant_handle = $message->getArg(Auth_OpenID_OPENID_NS,
                                          'assoc_handle');

        if ($extant_handle && ($extant_handle != $this->handle)) {
            // raise ValueError("Message has a different association handle")
            return null;
        }

        $signed_message = $message;
        $signed_message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
                                $this->handle);

        $message_keys = array_keys($signed_message->toPostArgs());
        $signed_list = array();
        $signed_prefix = 'openid.';

        foreach ($message_keys as $k) {
            if (strpos($k, $signed_prefix) === 0) {
                $signed_list[] = substr($k, strlen($signed_prefix));
            }
        }

        $signed_list[] = 'signed';
        sort($signed_list);

        $signed_message->setArg(Auth_OpenID_OPENID_NS, 'signed',
                                implode(',', $signed_list));
        $sig = $this->getMessageSignature($signed_message);
        $signed_message->setArg(Auth_OpenID_OPENID_NS, 'sig', $sig);
        return $signed_message;
    }

    /**
     * Given a {@link Auth_OpenID_Message}, return the key/value pairs
     * to be signed according to the signed list in the message.  If
     * the message lacks a signed list, return null.
     *
     * @access private
     */
    function _makePairs($message)
    {
        $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
        if (!$signed || Auth_OpenID::isFailure($signed)) {
            // raise ValueError('Message has no signed list: %s' % (message,))
            return null;
        }

        $signed_list = explode(',', $signed);
        $pairs = array();
        $data = $message->toPostArgs();
        foreach ($signed_list as $field) {
            $pairs[] = array($field, Auth_OpenID::arrayGet($data,
                                                           'openid.' .
                                                           $field, ''));
        }
        return $pairs;
    }

    /**
     * Given an {@link Auth_OpenID_Message}, return the signature for
     * the signed list in the message.
     *
     * @access private
     */
    function getMessageSignature($message)
    {
        $pairs = $this->_makePairs($message);
        return base64_encode($this->sign($pairs));
    }

    /**
     * Confirm that the signature of these fields matches the
     * signature contained in the data.
     *
     * @access private
     */
    function checkMessageSignature($message)
    {
        $sig = $message->getArg(Auth_OpenID_OPENID_NS,
                                'sig');

        if (!$sig || Auth_OpenID::isFailure($sig)) {
            return false;
        }

        $calculated_sig = $this->getMessageSignature($message);
        return Auth_OpenID_CryptUtil::constEq($calculated_sig, $sig);
    }
}

function Auth_OpenID_getSecretSize($assoc_type)
{
    if ($assoc_type == 'HMAC-SHA1') {
        return 20;
    } else if ($assoc_type == 'HMAC-SHA256') {
        return 32;
    } else {
        return null;
    }
}

function Auth_OpenID_getAllAssociationTypes()
{
    return array('HMAC-SHA1', 'HMAC-SHA256');
}

function Auth_OpenID_getSupportedAssociationTypes()
{
    $a = array('HMAC-SHA1');

    if (Auth_OpenID_HMACSHA256_SUPPORTED) {
        $a[] = 'HMAC-SHA256';
    }

    return $a;
}

function Auth_OpenID_getSessionTypes($assoc_type)
{
    $assoc_to_session = array(
       'HMAC-SHA1' => array('DH-SHA1', 'no-encryption'));

    if (Auth_OpenID_HMACSHA256_SUPPORTED) {
        $assoc_to_session['HMAC-SHA256'] =
            array('DH-SHA256', 'no-encryption');
    }

    return Auth_OpenID::arrayGet($assoc_to_session, $assoc_type, array());
}

function Auth_OpenID_checkSessionType($assoc_type, $session_type)
{
    if (!in_array($session_type,
                  Auth_OpenID_getSessionTypes($assoc_type))) {
        return false;
    }

    return true;
}

function Auth_OpenID_getDefaultAssociationOrder()
{
    $order = array();

    if (!Auth_OpenID_noMathSupport()) {
        $order[] = array('HMAC-SHA1', 'DH-SHA1');

        if (Auth_OpenID_HMACSHA256_SUPPORTED) {
            $order[] = array('HMAC-SHA256', 'DH-SHA256');
        }
    }

    $order[] = array('HMAC-SHA1', 'no-encryption');

    if (Auth_OpenID_HMACSHA256_SUPPORTED) {
        $order[] = array('HMAC-SHA256', 'no-encryption');
    }

    return $order;
}

function Auth_OpenID_getOnlyEncryptedOrder()
{
    $result = array();

    foreach (Auth_OpenID_getDefaultAssociationOrder() as $pair) {
        list($assoc, $session) = $pair;

        if ($session != 'no-encryption') {
            if (Auth_OpenID_HMACSHA256_SUPPORTED &&
                ($assoc == 'HMAC-SHA256')) {
                $result[] = $pair;
            } else if ($assoc != 'HMAC-SHA256') {
                $result[] = $pair;
            }
        }
    }

    return $result;
}

function Auth_OpenID_getDefaultNegotiator()
{
    return new Auth_OpenID_SessionNegotiator(
                 Auth_OpenID_getDefaultAssociationOrder());
}

function Auth_OpenID_getEncryptedNegotiator()
{
    return new Auth_OpenID_SessionNegotiator(
                 Auth_OpenID_getOnlyEncryptedOrder());
}

/**
 * A session negotiator controls the allowed and preferred association
 * types and association session types. Both the {@link
 * Auth_OpenID_Consumer} and {@link Auth_OpenID_Server} use
 * negotiators when creating associations.
 *
 * You can create and use negotiators if you:

 * - Do not want to do Diffie-Hellman key exchange because you use
 * transport-layer encryption (e.g. SSL)
 *
 * - Want to use only SHA-256 associations
 *
 * - Do not want to support plain-text associations over a non-secure
 * channel
 *
 * It is up to you to set a policy for what kinds of associations to
 * accept. By default, the library will make any kind of association
 * that is allowed in the OpenID 2.0 specification.
 *
 * Use of negotiators in the library
 * =================================
 *
 * When a consumer makes an association request, it calls {@link
 * getAllowedType} to get the preferred association type and
 * association session type.
 *
 * The server gets a request for a particular association/session type
 * and calls {@link isAllowed} to determine if it should create an
 * association. If it is supported, negotiation is complete. If it is
 * not, the server calls {@link getAllowedType} to get an allowed
 * association type to return to the consumer.
 *
 * If the consumer gets an error response indicating that the
 * requested association/session type is not supported by the server
 * that contains an assocation/session type to try, it calls {@link
 * isAllowed} to determine if it should try again with the given
 * combination of association/session type.
 *
 * @package OpenID
 */
class Auth_OpenID_SessionNegotiator {
    function Auth_OpenID_SessionNegotiator($allowed_types)
    {
        $this->allowed_types = array();
        $this->setAllowedTypes($allowed_types);
    }

    /**
     * Set the allowed association types, checking to make sure each
     * combination is valid.
     *
     * @access private
     */
    function setAllowedTypes($allowed_types)
    {
        foreach ($allowed_types as $pair) {
            list($assoc_type, $session_type) = $pair;
            if (!Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
                return false;
            }
        }

        $this->allowed_types = $allowed_types;
        return true;
    }

    /**
     * Add an association type and session type to the allowed types
     * list. The assocation/session pairs are tried in the order that
     * they are added.
     *
     * @access private
     */
    function addAllowedType($assoc_type, $session_type = null)
    {
        if ($this->allowed_types === null) {
            $this->allowed_types = array();
        }

        if ($session_type === null) {
            $available = Auth_OpenID_getSessionTypes($assoc_type);

            if (!$available) {
                return false;
            }

            foreach ($available as $session_type) {
                $this->addAllowedType($assoc_type, $session_type);
            }
        } else {
            if (Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
                $this->allowed_types[] = array($assoc_type, $session_type);
            } else {
                return false;
            }
        }

        return true;
    }

    // Is this combination of association type and session type allowed?
    function isAllowed($assoc_type, $session_type)
    {
        $assoc_good = in_array(array($assoc_type, $session_type),
                               $this->allowed_types);

        $matches = in_array($session_type,
                            Auth_OpenID_getSessionTypes($assoc_type));

        return ($assoc_good && $matches);
    }

    /**
     * Get a pair of assocation type and session type that are
     * supported.
     */
    function getAllowedType()
    {
        if (!$this->allowed_types) {
            return array(null, null);
        }

        return $this->allowed_types[0];
    }
}

repo_name: spookie/gnu

license: mit


code:
<?php

/**
 * BigMath: A math library wrapper that abstracts out the underlying
 * long integer library.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @access private
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

/**
 * Needed for random number generation
 */
require_once 'Auth/OpenID/CryptUtil.php';

/**
 * Need Auth_OpenID::bytes().
 */
require_once 'Auth/OpenID.php';

/**
 * The superclass of all big-integer math implementations
 * @access private
 * @package OpenID
 */
class Auth_OpenID_MathLibrary {
    /**
     * Given a long integer, returns the number converted to a binary
     * string.  This function accepts long integer values of arbitrary
     * magnitude and uses the local large-number math library when
     * available.
     *
     * @param integer $long The long number (can be a normal PHP
     * integer or a number created by one of the available long number
     * libraries)
     * @return string $binary The binary version of $long
     */
    function longToBinary($long)
    {
        $cmp = $this->cmp($long, 0);
        if ($cmp < 0) {
            $msg = __FUNCTION__ . " takes only positive integers.";
            trigger_error($msg, E_USER_ERROR);
            return null;
        }

        if ($cmp == 0) {
            return "\x00";
        }

        $bytes = array();

        while ($this->cmp($long, 0) > 0) {
            array_unshift($bytes, $this->mod($long, 256));
            $long = $this->div($long, pow(2, 8));
        }

        if ($bytes && ($bytes[0] > 127)) {
            array_unshift($bytes, 0);
        }

        $string = '';
        foreach ($bytes as $byte) {
            $string .= pack('C', $byte);
        }

        return $string;
    }

    /**
     * Given a binary string, returns the binary string converted to a
     * long number.
     *
     * @param string $binary The binary version of a long number,
     * probably as a result of calling longToBinary
     * @return integer $long The long number equivalent of the binary
     * string $str
     */
    function binaryToLong($str)
    {
        if ($str === null) {
            return null;
        }

        // Use array_merge to return a zero-indexed array instead of a
        // one-indexed array.
        $bytes = array_merge(unpack('C*', $str));

        $n = $this->init(0);

        if ($bytes && ($bytes[0] > 127)) {
            trigger_error("bytesToNum works only for positive integers.",
                          E_USER_WARNING);
            return null;
        }

        foreach ($bytes as $byte) {
            $n = $this->mul($n, pow(2, 8));
            $n = $this->add($n, $byte);
        }

        return $n;
    }

    function base64ToLong($str)
    {
        $b64 = base64_decode($str);

        if ($b64 === false) {
            return false;
        }

        return $this->binaryToLong($b64);
    }

    function longToBase64($str)
    {
        return base64_encode($this->longToBinary($str));
    }

    /**
     * Returns a random number in the specified range.  This function
     * accepts $start, $stop, and $step values of arbitrary magnitude
     * and will utilize the local large-number math library when
     * available.
     *
     * @param integer $start The start of the range, or the minimum
     * random number to return
     * @param integer $stop The end of the range, or the maximum
     * random number to return
     * @param integer $step The step size, such that $result - ($step
     * * N) = $start for some N
     * @return integer $result The resulting randomly-generated number
     */
    function rand($stop)
    {
        static $duplicate_cache = array();

        // Used as the key for the duplicate cache
        $rbytes = $this->longToBinary($stop);

        if (array_key_exists($rbytes, $duplicate_cache)) {
            list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
        } else {
            if ($rbytes[0] == "\x00") {
                $nbytes = Auth_OpenID::bytes($rbytes) - 1;
            } else {
                $nbytes = Auth_OpenID::bytes($rbytes);
            }

            $mxrand = $this->pow(256, $nbytes);

            // If we get a number less than this, then it is in the
            // duplicated range.
            $duplicate = $this->mod($mxrand, $stop);

            if (count($duplicate_cache) > 10) {
                $duplicate_cache = array();
            }

            $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
        }

        do {
            $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes);
            $n = $this->binaryToLong($bytes);
            // Keep looping if this value is in the low duplicated range
        } while ($this->cmp($n, $duplicate) < 0);

        return $this->mod($n, $stop);
    }
}

/**
 * Exposes BCmath math library functionality.
 *
 * {@link Auth_OpenID_BcMathWrapper} wraps the functionality provided
 * by the BCMath extension.
 *
 * @access private
 * @package OpenID
 */
class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathLibrary{
    var $type = 'bcmath';

    function add($x, $y)
    {
        return bcadd($x, $y);
    }

    function sub($x, $y)
    {
        return bcsub($x, $y);
    }

    function pow($base, $exponent)
    {
        return bcpow($base, $exponent);
    }

    function cmp($x, $y)
    {
        return bccomp($x, $y);
    }

    function init($number, $base = 10)
    {
        return $number;
    }

    function mod($base, $modulus)
    {
        return bcmod($base, $modulus);
    }

    function mul($x, $y)
    {
        return bcmul($x, $y);
    }

    function div($x, $y)
    {
        return bcdiv($x, $y);
    }

    /**
     * Same as bcpowmod when bcpowmod is missing
     *
     * @access private
     */
    function _powmod($base, $exponent, $modulus)
    {
        $square = $this->mod($base, $modulus);
        $result = 1;
        while($this->cmp($exponent, 0) > 0) {
            if ($this->mod($exponent, 2)) {
                $result = $this->mod($this->mul($result, $square), $modulus);
            }
            $square = $this->mod($this->mul($square, $square), $modulus);
            $exponent = $this->div($exponent, 2);
        }
        return $result;
    }

    function powmod($base, $exponent, $modulus)
    {
        if (function_exists('bcpowmod')) {
            return bcpowmod($base, $exponent, $modulus);
        } else {
            return $this->_powmod($base, $exponent, $modulus);
        }
    }

    function toString($num)
    {
        return $num;
    }
}

/**
 * Exposes GMP math library functionality.
 *
 * {@link Auth_OpenID_GmpMathWrapper} wraps the functionality provided
 * by the GMP extension.
 *
 * @access private
 * @package OpenID
 */
class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathLibrary{
    var $type = 'gmp';

    function add($x, $y)
    {
        return gmp_add($x, $y);
    }

    function sub($x, $y)
    {
        return gmp_sub($x, $y);
    }

    function pow($base, $exponent)
    {
        return gmp_pow($base, $exponent);
    }

    function cmp($x, $y)
    {
        return gmp_cmp($x, $y);
    }

    function init($number, $base = 10)
    {
        return gmp_init($number, $base);
    }

    function mod($base, $modulus)
    {
        return gmp_mod($base, $modulus);
    }

    function mul($x, $y)
    {
        return gmp_mul($x, $y);
    }

    function div($x, $y)
    {
        return gmp_div_q($x, $y);
    }

    function powmod($base, $exponent, $modulus)
    {
        return gmp_powm($base, $exponent, $modulus);
    }

    function toString($num)
    {
        return gmp_strval($num);
    }
}

/**
 * Define the supported extensions.  An extension array has keys
 * 'modules', 'extension', and 'class'.  'modules' is an array of PHP
 * module names which the loading code will attempt to load.  These
 * values will be suffixed with a library file extension (e.g. ".so").
 * 'extension' is the name of a PHP extension which will be tested
 * before 'modules' are loaded.  'class' is the string name of a
 * {@link Auth_OpenID_MathWrapper} subclass which should be
 * instantiated if a given extension is present.
 *
 * You can define new math library implementations and add them to
 * this array.
 */
function Auth_OpenID_math_extensions()
{
    $result = array();

    if (!defined('Auth_OpenID_BUGGY_GMP')) {
        $result[] =
            array('modules' => array('gmp', 'php_gmp'),
                  'extension' => 'gmp',
                  'class' => 'Auth_OpenID_GmpMathWrapper');
    }

    $result[] = array('modules' => array('bcmath', 'php_bcmath'),
                      'extension' => 'bcmath',
                      'class' => 'Auth_OpenID_BcMathWrapper');

    return $result;
}

/**
 * Detect which (if any) math library is available
 */
function Auth_OpenID_detectMathLibrary($exts)
{
    $loaded = false;

    foreach ($exts as $extension) {
        if (extension_loaded($extension['extension'])) {
            return $extension;
        }
    }

    return false;
}

/**
 * {@link Auth_OpenID_getMathLib} checks for the presence of long
 * number extension modules and returns an instance of
 * {@link Auth_OpenID_MathWrapper} which exposes the module's
 * functionality.
 *
 * Checks for the existence of an extension module described by the
 * result of {@link Auth_OpenID_math_extensions()} and returns an
 * instance of a wrapper for that extension module.  If no extension
 * module is found, an instance of {@link Auth_OpenID_MathWrapper} is
 * returned, which wraps the native PHP integer implementation.  The
 * proper calling convention for this method is $lib =
 * Auth_OpenID_getMathLib().
 *
 * This function checks for the existence of specific long number
 * implementations in the following order: GMP followed by BCmath.
 *
 * @return Auth_OpenID_MathWrapper $instance An instance of
 * {@link Auth_OpenID_MathWrapper} or one of its subclasses
 *
 * @package OpenID
 */
function Auth_OpenID_getMathLib()
{
    // The instance of Auth_OpenID_MathWrapper that we choose to
    // supply will be stored here, so that subseqent calls to this
    // method will return a reference to the same object.
    static $lib = null;

    if (isset($lib)) {
        return $lib;
    }

    if (Auth_OpenID_noMathSupport()) {
        $null = null;
        return $null;
    }

    // If this method has not been called before, look at
    // Auth_OpenID_math_extensions and try to find an extension that
    // works.
    $ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions());
    if ($ext === false) {
        $tried = array();
        foreach (Auth_OpenID_math_extensions() as $extinfo) {
            $tried[] = $extinfo['extension'];
        }
        $triedstr = implode(", ", $tried);

        Auth_OpenID_setNoMathSupport();

        $result = null;
        return $result;
    }

    // Instantiate a new wrapper
    $class = $ext['class'];
    $lib = new $class();

    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');
}